partial implementation of community profile page - next, straighten out left menus
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Amsterdam Web Communities System
|
||||
* Copyright (c) 2025 Erbosoft Metaverse Design Solutions, All Rights Reserved
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
// The database package contains database management and storage logic.
|
||||
package database
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Category is the structure defining a category.
|
||||
type Category struct {
|
||||
CatId int32 `db:"catid"`
|
||||
Parent int32 `db:"parent"`
|
||||
SymLink int32 `db:"symlink"`
|
||||
HideDirectory int32 `db:"hide_dir"`
|
||||
HideSearch int32 `db:"hide_search"`
|
||||
Name string `db:"name"`
|
||||
}
|
||||
|
||||
// allCategories is the list of all categories loaded from the database.
|
||||
var allCategories []Category
|
||||
|
||||
// categoryIdMap maps IDs to categories.
|
||||
var categoryIdMap map[int32]*Category = make(map[int32]*Category)
|
||||
|
||||
// categoryMutex syncs the loading of the categories.
|
||||
var categoryMutex sync.Mutex
|
||||
|
||||
// loadCategories loads the categories list from the database.
|
||||
func loadCategories() error {
|
||||
categoryMutex.Lock()
|
||||
defer categoryMutex.Unlock()
|
||||
if allCategories == nil {
|
||||
rs, err := amdb.Query("SELECT COUNT(*) FROM refcategory")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !rs.Next() {
|
||||
return errors.New("internal error loading categories")
|
||||
}
|
||||
var ncats int32
|
||||
rs.Scan(&ncats)
|
||||
allCategories = make([]Category, 0, ncats)
|
||||
err = amdb.Select(&allCategories, "SELECT * FROM refcategory ORDER BY parent, name")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, c := range allCategories {
|
||||
categoryIdMap[c.CatId] = &(allCategories[i])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/* AmGetCategory returns the category for the given name.
|
||||
* Parameters:
|
||||
* catid - The ID of the category to get.
|
||||
* Returns:
|
||||
* Pointer to the appropriate Category, or nil.
|
||||
* Standard Go error status.
|
||||
*/
|
||||
func AmGetCategory(catid int32) (*Category, error) {
|
||||
err := loadCategories()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := categoryIdMap[catid]
|
||||
d := 5
|
||||
for c.SymLink != -1 {
|
||||
d--
|
||||
if d == 0 {
|
||||
return nil, errors.New("symlink resolution error")
|
||||
}
|
||||
c = categoryIdMap[c.SymLink]
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
/* AmGetCategoryHierarchy returns the category hierarchy for the given ID.
|
||||
* Parameters:
|
||||
* catid - The ID of the category to get.
|
||||
* Returns:
|
||||
* Array of pointers to the categories in hierarchical order, or nil.
|
||||
* Standard Go error status.
|
||||
*/
|
||||
func AmGetCategoryHierarchy(catid int32) ([]*Category, error) {
|
||||
err := loadCategories()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// walk all the way to the "root" (parent = -1)
|
||||
p := catid
|
||||
ia := make([]*Category, 0, 3)
|
||||
for p != -1 {
|
||||
c := categoryIdMap[p]
|
||||
for c.SymLink != -1 {
|
||||
c = categoryIdMap[c.SymLink]
|
||||
}
|
||||
ia = append(ia, c)
|
||||
p = c.Parent
|
||||
}
|
||||
// reverse the array for return
|
||||
rc := make([]*Category, 0, len(ia))
|
||||
for i := range ia {
|
||||
rc = append(rc, ia[len(ia)-(i+1)])
|
||||
}
|
||||
return rc, nil
|
||||
}
|
||||
+151
-1
@@ -12,10 +12,13 @@ package database
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.erbosoft.com/amy/amsterdam/util"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
// Community struct contains the high level data for a community.
|
||||
@@ -52,6 +55,27 @@ var communityCache *lru.TwoQueueCache = nil
|
||||
// getCommunityMutex is a mutex on AmGetCommunity.
|
||||
var getCommunityMutex sync.Mutex
|
||||
|
||||
// memberCacheData caches membership information for communities.
|
||||
type memberCacheData struct {
|
||||
isMember bool
|
||||
locked bool
|
||||
level uint16
|
||||
}
|
||||
|
||||
// memberCache contains the memberCacheData entries.
|
||||
var memberCache *lru.Cache = nil
|
||||
|
||||
// memberMutex syncs access to the memberCache.
|
||||
var memberMutex sync.Mutex
|
||||
|
||||
// stuffMembership stuffs a membership record into the cache.
|
||||
func stuffMembership(cid int32, uid int32, member bool, locked bool, level uint16) {
|
||||
key := fmt.Sprintf("%d:%d", cid, uid)
|
||||
memberMutex.Lock()
|
||||
memberCache.Add(key, &memberCacheData{isMember: member, locked: locked, level: level})
|
||||
memberMutex.Unlock()
|
||||
}
|
||||
|
||||
// init initializes the community cache.
|
||||
func init() {
|
||||
var err error
|
||||
@@ -59,6 +83,98 @@ func init() {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
memberCache, err = lru.New(250)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Public returns true if the community is public.
|
||||
func (c *Community) Public() bool {
|
||||
return c.JoinKey == nil || *c.JoinKey == ""
|
||||
}
|
||||
|
||||
// ContactInfo returns the contact info structure for the community.
|
||||
func (c *Community) ContactInfo() (*ContactInfo, error) {
|
||||
if c.ContactId < 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return AmGetContactInfo(c.ContactId)
|
||||
}
|
||||
|
||||
// Host returns the reference to the host of the community.
|
||||
func (c *Community) Host() (*User, error) {
|
||||
if c.HostUid == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return AmGetUser(*c.HostUid)
|
||||
}
|
||||
|
||||
func (c *Community) LanguageTag() (*language.Tag, error) {
|
||||
if c.Language == nil {
|
||||
return nil, nil
|
||||
}
|
||||
t, err := language.Parse(*c.Language)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
/* Membership returns the details of the specified user's membership in the community.
|
||||
* Parameters:
|
||||
* u - The user to check the membership of.
|
||||
* Returns:
|
||||
* true if the user is a member, false if not.
|
||||
* true if the user's membership is "locked" (cannot unjoin), false if not.
|
||||
* User's access level in the community, or 0 if the user is not a member.
|
||||
* Standard Go error status.
|
||||
*/
|
||||
func (c *Community) Membership(u *User) (bool, bool, uint16, error) {
|
||||
key := fmt.Sprintf("%d:%d", c.Id, u.Uid)
|
||||
memberMutex.Lock()
|
||||
defer memberMutex.Unlock()
|
||||
mbr, ok := memberCache.Get(key)
|
||||
if ok {
|
||||
m := mbr.(*memberCacheData)
|
||||
return m.isMember, m.locked, m.level, nil
|
||||
}
|
||||
if AmTestPermission("Community.NoJoinRequired", u.BaseLevel) {
|
||||
// "no join required" - they are effectively a member, but don't cache that
|
||||
return true, false, u.BaseLevel, nil
|
||||
}
|
||||
rs, err := amdb.Query("SELECT locked, granted_lvl FROM commmember WHERE commid = ? AND uid = ?", c.Id, u.Uid)
|
||||
if err == nil {
|
||||
if rs.Next() {
|
||||
var locked bool
|
||||
var level uint16
|
||||
rs.Scan(&locked, &level)
|
||||
memberCache.Add(key, &memberCacheData{isMember: true, locked: locked, level: level})
|
||||
return true, locked, level, nil
|
||||
}
|
||||
memberCache.Add(key, &memberCacheData{isMember: false, locked: false, level: uint16(0)})
|
||||
}
|
||||
return false, false, uint16(0), err
|
||||
}
|
||||
|
||||
/* TestPermission is shorthand that tests if a user has a permission with respect to the community.
|
||||
* Parameters:
|
||||
* user - The user to be checked.
|
||||
* perm - The permission to be tested.
|
||||
* Returns:
|
||||
* true if the user has the permission, false if not.
|
||||
* Standard Go error status.
|
||||
*/
|
||||
func (c *Community) TestPermission(user *User, perm string) (bool, error) {
|
||||
member, _, level, err := c.Membership(user)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
effectiveLevel := user.BaseLevel
|
||||
if member && level > effectiveLevel {
|
||||
effectiveLevel = level
|
||||
}
|
||||
return AmTestPermission(perm, effectiveLevel), nil
|
||||
}
|
||||
|
||||
/* AmGetCommunity returns a reference to the specified community.
|
||||
@@ -79,7 +195,9 @@ func AmGetCommunity(id int32) (*Community, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(dbdata) > 1 {
|
||||
if len(dbdata) == 0 {
|
||||
return nil, fmt.Errorf("community with ID %d not found", id)
|
||||
} else if len(dbdata) > 1 {
|
||||
return nil, fmt.Errorf("AmGetCommunity(%d): too many responses(%d)", id, len(dbdata))
|
||||
}
|
||||
rc = &(dbdata[0])
|
||||
@@ -88,6 +206,37 @@ func AmGetCommunity(id int32) (*Community, error) {
|
||||
return rc.(*Community), err
|
||||
}
|
||||
|
||||
/* AmGetCommunityFromParam returns a reference to the specified community based on the parameter.
|
||||
* If the parameter is numeric, it's interpreted as a community ID. Otherwise, it's interpreted
|
||||
* as a community alias.
|
||||
* Parameters:
|
||||
* id - The ID of the community.
|
||||
* Returns:
|
||||
* Pointer to Community containing community data, or nil
|
||||
* Standard Go error status
|
||||
*/
|
||||
func AmGetCommunityFromParam(param string) (*Community, error) {
|
||||
if util.IsNumeric(param) {
|
||||
v, _ := strconv.Atoi(param)
|
||||
c, err := AmGetCommunity(int32(v))
|
||||
if err == nil {
|
||||
return c, nil
|
||||
}
|
||||
// else fall through to trying as alias
|
||||
}
|
||||
rs, err := amdb.Query("SELECT commid FROM communities WHERE alias = ?", param)
|
||||
if err == nil {
|
||||
if rs.Next() {
|
||||
var cid int32
|
||||
rs.Scan(&cid)
|
||||
return AmGetCommunity(cid)
|
||||
} else {
|
||||
return nil, fmt.Errorf("community with alias \"%s\" not found", param)
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
/* AmGetCommunitiesForUser returns a list of communities the user is a member of.
|
||||
* Parameters:
|
||||
* uid - The ID of the user.
|
||||
@@ -164,6 +313,7 @@ func AmAutoJoinCommunities(user *User) error {
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
stuffMembership(cid, user.Uid, true, lock, grantLevel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Amsterdam Web Communities System
|
||||
* Copyright (c) 2025 Erbosoft Metaverse Design Solutions, All Rights Reserved
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
// The database package contains database management and storage logic.
|
||||
package database
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"git.erbosoft.com/amy/amsterdam/util"
|
||||
)
|
||||
|
||||
// Globals contains the global data.
|
||||
type Globals struct {
|
||||
Mutex sync.Mutex
|
||||
PostsPerPage int32 `db:"posts_per_page"`
|
||||
OldPostsAtTop int32 `db:"old_posts_at_top"`
|
||||
MaxSearchPage int32 `db:"max_search_page"`
|
||||
MaxCommunityMemberPage int32 `db:"max_comm_mbr_page"`
|
||||
MaxConferenceMemberPage int32 `db:"max_conf_mbr_page"`
|
||||
FrontPagePosts int32 `db:"fp_posts"`
|
||||
NumAuditPage int32 `db:"num_audit_page"`
|
||||
CommunityCreateLevel int32 `db:"comm_create_lvl"`
|
||||
flags *util.OptionSet
|
||||
}
|
||||
|
||||
// GlobalProperties contains global property entries.
|
||||
type GlobalProperties struct {
|
||||
Index int32 `db:"ndx"`
|
||||
Data string `db:"data"`
|
||||
}
|
||||
|
||||
// Global property indexes defined.
|
||||
const (
|
||||
GlobalPropFlags = int32(0)
|
||||
)
|
||||
|
||||
// Global flag indexes defined.
|
||||
const (
|
||||
GlobalFlagPicturesInPosts = uint(0)
|
||||
GlobalFlagNoCategories = uint(1)
|
||||
)
|
||||
|
||||
// theGlobals contains the singleton instance of Globals.
|
||||
var theGlobals *Globals = nil
|
||||
|
||||
// globalsMutex controls access to theGlobals.
|
||||
var globalsMutex sync.Mutex
|
||||
|
||||
// globalProps is the global properties store.
|
||||
var globalProps map[int32]string = make(map[int32]string)
|
||||
|
||||
// globalPropMutex controls access to globalProps.
|
||||
var globalPropMutex sync.Mutex
|
||||
|
||||
// Flags returns the global flags.
|
||||
func (g *Globals) Flags() (*util.OptionSet, error) {
|
||||
g.Mutex.Lock()
|
||||
defer g.Mutex.Unlock()
|
||||
if g.flags == nil {
|
||||
s, err := AmGetGlobalProperty(GlobalPropFlags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.flags = util.OptionSetFromString(s)
|
||||
}
|
||||
return g.flags, nil
|
||||
}
|
||||
|
||||
// SaveFlags saves off the global flags.
|
||||
func (g *Globals) SaveFlags(f *util.OptionSet) error {
|
||||
s := f.AsString()
|
||||
g.Mutex.Lock()
|
||||
defer g.Mutex.Unlock()
|
||||
err := AmSetGlobalProperty(GlobalPropFlags, s)
|
||||
if err == nil {
|
||||
g.flags = f
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// AmGlobals returns trhe pointer to the singleton Globals instance.
|
||||
func AmGlobals() (*Globals, error) {
|
||||
globalsMutex.Lock()
|
||||
defer globalsMutex.Unlock()
|
||||
if theGlobals == nil {
|
||||
var dbdata []Globals
|
||||
err := amdb.Select(&dbdata, "SELECT * FROM globals")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(dbdata) > 1 {
|
||||
return nil, errors.New("should only be one globals record")
|
||||
}
|
||||
theGlobals = &(dbdata[0])
|
||||
}
|
||||
return theGlobals, nil
|
||||
}
|
||||
|
||||
/* AmGetGlobalProperty returns the value of a global property.
|
||||
* Parameters:
|
||||
* index - The index of the property to retrieve.
|
||||
* Returns:
|
||||
* Value of the property, or empty string.
|
||||
* Standard Go error status.
|
||||
*/
|
||||
func AmGetGlobalProperty(index int32) (string, error) {
|
||||
globalPropMutex.Lock()
|
||||
defer globalPropMutex.Unlock()
|
||||
rc, ok := globalProps[index]
|
||||
if !ok {
|
||||
rs, err := amdb.Query("SELECT data FROM propglobal WHERE ndx = ?", index)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if rs.Next() {
|
||||
rs.Scan(&rc)
|
||||
globalProps[index] = rc
|
||||
return rc, nil
|
||||
}
|
||||
rc = ""
|
||||
}
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
/* AmSetGlobalProperty sets the value of a global property.
|
||||
* Parameters:
|
||||
* index - The index of the property to set.
|
||||
* value - The value of the property to set.
|
||||
* Returns:
|
||||
* Standard Go error status.
|
||||
*/
|
||||
func AmSetGlobalProperty(index int32, value string) error {
|
||||
globalPropMutex.Lock()
|
||||
defer globalPropMutex.Unlock()
|
||||
_, updateMode := globalProps[index]
|
||||
if !updateMode {
|
||||
rs, err := amdb.Query("SELECT data FROM propglobal WHERE ndx = ?", index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updateMode = rs.Next()
|
||||
}
|
||||
var err error = nil
|
||||
if updateMode {
|
||||
_, err = amdb.Exec("UPDATE propglobal SET data = ? WHERE ndx = ?", value, index)
|
||||
} else {
|
||||
_, err = amdb.Exec("INSERT INTO propglobal (ndx, data) VALUES (?, ?)", index, value)
|
||||
}
|
||||
if err == nil {
|
||||
globalProps[index] = value
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#
|
||||
# Amsterdam Web Communities System
|
||||
# Copyright (c) 2025 Erbosoft Metaverse Design Solutions, All Rights Reserved
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
#
|
||||
domains:
|
||||
- domain: "community"
|
||||
services:
|
||||
- id: "Profile"
|
||||
index: 0
|
||||
default: true
|
||||
locked: true
|
||||
requirePermission: ""
|
||||
requireRole: ""
|
||||
linkSequence: 4900
|
||||
link: "/comm/[CID]/profile"
|
||||
title: "Profile"
|
||||
- id: "Admin"
|
||||
index: 1
|
||||
default: true
|
||||
locked: true
|
||||
requirePermission: "Community.Read"
|
||||
requireRole: "Community.AnyAdmin"
|
||||
linkSequence: 5000
|
||||
link: "/TODO/comm/[CID]/admin"
|
||||
title: "Administration"
|
||||
- id: "Conference"
|
||||
index: 3
|
||||
default: true
|
||||
locked: false
|
||||
requirePermission: "Community.Read"
|
||||
requireRole: ""
|
||||
linkSequence: 500
|
||||
link: "/TODO/comm/[CID]/conf"
|
||||
title: "Conferences"
|
||||
- id: "Members"
|
||||
index: 4
|
||||
default: true
|
||||
locked: true
|
||||
requirePermission: "Community.Read"
|
||||
requireRole: "Community.Member"
|
||||
linkSequence: 4800
|
||||
link: "/TODO/comm/[CID]/members"
|
||||
title: "Members"
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Amsterdam Web Communities System
|
||||
* Copyright (c) 2025 Erbosoft Metaverse Design Solutions, All Rights Reserved
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
// The database package contains database management and storage logic.
|
||||
package database
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// ServiceDef holds the definition for an individual service.
|
||||
type ServiceDef struct {
|
||||
Id string `yaml:"id"`
|
||||
Index int16 `yaml:"index"`
|
||||
Default bool `yaml:"default"`
|
||||
Locked bool `yaml:"locked"`
|
||||
RequirePermission string `yaml:"requirePermission"`
|
||||
RequireRole string `yaml:"requireRole"`
|
||||
LinkSequence int `yaml:"linkSequence"`
|
||||
Link int `yaml:"link"`
|
||||
Title string `yaml:"title"`
|
||||
}
|
||||
|
||||
// ServiceDomain holds each individual configured service domain.
|
||||
type ServiceDomain struct {
|
||||
DomainName string `yaml:"domain"`
|
||||
Services []ServiceDef `yaml:"services"`
|
||||
byId map[string]*ServiceDef
|
||||
byIndex map[int16]*ServiceDef
|
||||
seqOrder []*ServiceDef
|
||||
}
|
||||
|
||||
// ServiceConfiguration holds the service configuration.
|
||||
type ServiceConfiguration struct {
|
||||
Domains []ServiceDomain `yaml:"domains"`
|
||||
byName map[string]*ServiceDomain
|
||||
}
|
||||
|
||||
//go:embed servicedefs.yaml
|
||||
var initServiceData []byte
|
||||
|
||||
// The service configuration loaded from YAML.
|
||||
var serviceRoot ServiceConfiguration
|
||||
|
||||
// The services cache for communities.
|
||||
var servicesCache *lru.TwoQueueCache
|
||||
|
||||
// Mutex on the services cache.
|
||||
var servicesCacheMutex sync.Mutex
|
||||
|
||||
// init loads the service configuration and builds all the internal indexes.
|
||||
func init() {
|
||||
var err error
|
||||
if err = yaml.Unmarshal(initServiceData, &serviceRoot); err != nil {
|
||||
panic(err) // can't happen
|
||||
}
|
||||
serviceRoot.byName = make(map[string]*ServiceDomain)
|
||||
for i, dom := range serviceRoot.Domains {
|
||||
serviceRoot.Domains[i].byId = make(map[string]*ServiceDef)
|
||||
serviceRoot.Domains[i].byIndex = make(map[int16]*ServiceDef)
|
||||
sqo := make([]*ServiceDef, 0, len(serviceRoot.Domains[i].Services))
|
||||
for j, svc := range serviceRoot.Domains[i].Services {
|
||||
serviceRoot.Domains[i].byId[svc.Id] = &(serviceRoot.Domains[i].Services[j])
|
||||
serviceRoot.Domains[i].byIndex[svc.Index] = &(serviceRoot.Domains[i].Services[j])
|
||||
sqo = append(sqo, &(serviceRoot.Domains[i].Services[j]))
|
||||
}
|
||||
slices.SortFunc(sqo, func(a, b *ServiceDef) int {
|
||||
return a.LinkSequence - b.LinkSequence
|
||||
})
|
||||
serviceRoot.Domains[i].seqOrder = sqo
|
||||
serviceRoot.byName[dom.DomainName] = &(serviceRoot.Domains[i])
|
||||
}
|
||||
servicesCache, err = lru.New2Q(50)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
/* AmGetCommunityServices returns all the community service definitions for a community.
|
||||
* Parameters:
|
||||
* cid - Community ID to get services for.
|
||||
* Returns:
|
||||
* Array of ServiceDef pointers for the community's services.
|
||||
* Standard Go error status.
|
||||
*/
|
||||
func AmGetCommunityServices(cid int32) ([]*ServiceDef, error) {
|
||||
servicesCacheMutex.Lock()
|
||||
defer servicesCacheMutex.Unlock()
|
||||
rc, ok := servicesCache.Get(cid)
|
||||
if !ok {
|
||||
rs, err := amdb.Query("SELECT ftr_code FROM commftrs WHERE commid = ?", cid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dom := serviceRoot.byName["community"]
|
||||
a := make([]*ServiceDef, 0, len(dom.Services))
|
||||
for rs.Next() {
|
||||
var ndx int16
|
||||
rs.Scan(&ndx)
|
||||
a = append(a, dom.byIndex[ndx])
|
||||
}
|
||||
servicesCache.Add(cid, a)
|
||||
rc = a
|
||||
}
|
||||
return rc.([]*ServiceDef), nil
|
||||
}
|
||||
Reference in New Issue
Block a user