diff --git a/database/community.go b/database/community.go index 7b79f7a..83d6cac 100644 --- a/database/community.go +++ b/database/community.go @@ -11,6 +11,7 @@ package database import ( "fmt" + "sync" "time" lru "github.com/hashicorp/golang-lru" @@ -18,6 +19,7 @@ import ( // Community struct contains the high level data for a community. type Community struct { + Mutex sync.RWMutex Id int32 `db:"commid"` CreateDate time.Time `db:"createdate"` LastAccess *time.Time `db:"lastaccess"` @@ -25,6 +27,7 @@ type Community struct { ReadLevel uint16 `db:"read_lvl"` WriteLevel uint16 `db:"write_lvl"` CreateLevel uint16 `db:"create_lvl"` + DeleteLevel uint16 `db:"delete_lvl"` JoinLevel uint16 `db:"join_lvl"` ContactId int32 `db:"contactid"` HostUid *int32 `db:"host_uid"` @@ -45,6 +48,18 @@ type Community struct { // communityCache is the cache for Community objects. var communityCache *lru.TwoQueueCache = nil +// getCommunityMutex is a mutex on AmGetCommunity. +var getCommunityMutex sync.Mutex + +// init initializes the community cache. +func init() { + var err error + communityCache, err = lru.New2Q(50) + if err != nil { + panic(err) + } +} + /* AmGetCommunity returns a reference to the specified community. * Parameters: * id - The ID of the community. @@ -54,12 +69,8 @@ var communityCache *lru.TwoQueueCache = nil */ func AmGetCommunity(id int32) (*Community, error) { var err error = nil - if communityCache == nil { - communityCache, err = lru.New2Q(50) - if err != nil { - return nil, err - } - } + getCommunityMutex.Lock() + defer getCommunityMutex.Unlock() rc, ok := communityCache.Get(id) if !ok { var dbdata []Community @@ -75,3 +86,28 @@ func AmGetCommunity(id int32) (*Community, error) { } return rc.(*Community), err } + +/* AmGetCommunitiesForUser returns a list of communities the user is a member of. + * Parameters: + * uid - The ID of the user. + * Returns: + * Array of pointers to communities for the user + * Standard Go error status + */ +func AmGetCommunitiesForUser(uid int32) ([]*Community, error) { + var rc []*Community = make([]*Community, 0) + rows, err := amdb.Queryx("SELECT commid FROM commmember WHERE uid = ?", uid) + if err == nil { + defer rows.Close() + for err == nil && rows.Next() { + var cid int32 + var c *Community + rows.Scan(&cid) + c, err = AmGetCommunity(cid) + if err == nil { + rc = append(rc, c) + } + } + } + return rc, err +} diff --git a/database/user.go b/database/user.go index 7fb4ecf..1bf5e6f 100644 --- a/database/user.go +++ b/database/user.go @@ -10,8 +10,8 @@ package database import ( - "database/sql" "fmt" + "sync" "time" lru "github.com/hashicorp/golang-lru" @@ -19,6 +19,7 @@ import ( // User represents a user in the Amsterdam database. type User struct { + Mutex sync.RWMutex Uid int32 `db:"uid"` Username string `db:"username"` Passhash string `db:"passhash"` @@ -40,9 +41,21 @@ type User struct { // userCache is the cache for User objects. var userCache *lru.TwoQueueCache = nil +// getUserMutex is a mutex on AmGetUser. +var getUserMutex sync.Mutex + // anonUid is the UID of the "anonymous" user. var anonUid int32 = -1 +// init initializes the user cache. +func init() { + var err error + userCache, err = lru.New2Q(100) + if err != nil { + panic(err) + } +} + /* AmGetUser returns a reference to the specified user. * Parameters: * uid - The UID of the user. @@ -52,12 +65,8 @@ var anonUid int32 = -1 */ func AmGetUser(uid int32) (*User, error) { var err error = nil - if userCache == nil { - userCache, err = lru.New2Q(100) - if err != nil { - return nil, err - } - } + getUserMutex.Lock() + defer getUserMutex.Unlock() rc, ok := userCache.Get(uid) if !ok { var dbdata []User @@ -74,16 +83,10 @@ func AmGetUser(uid int32) (*User, error) { return rc.(*User), err } -/* AmGetAnonUser returns a reference to the anonymous user. - * Returns: - * Pointer to User containing anonymous user data, or nil - * Standard Go error status - */ -func AmGetAnonUser() (*User, error) { - var err error = nil +// getAnonUserID retrieves the UID of the "anonymous" user from the database. +func getAnonUserID() (int32, error) { if anonUid < 0 { - var rows *sql.Rows - rows, err = amdb.Query("SELECT uid FROM users WHERE is_anon = 1") + rows, err := amdb.Query("SELECT uid FROM users WHERE is_anon = 1") if err == nil { defer rows.Close() if rows.Next() { @@ -95,10 +98,35 @@ func AmGetAnonUser() (*User, error) { err = fmt.Errorf("no anonymous user in Amsterdam database") } } + if err != nil { + return -1, err + } } + return anonUid, nil +} + +/* AmIsUserAnon returns true if the specified user ID is the anonymous one. + * Parameters: + * uid = The user ID to test. + * Returns: + * true if the user is anonymous, false if not + * Standard Go error status + */ +func AmIsUserAnon(uid int32) (bool, error) { + auid, err := getAnonUserID() + return (uid == auid), err +} + +/* AmGetAnonUser returns a reference to the anonymous user. + * Returns: + * Pointer to User containing anonymous user data, or nil + * Standard Go error status + */ +func AmGetAnonUser() (*User, error) { var rc *User = nil + auid, err := getAnonUserID() if err == nil { - rc, err = AmGetUser(anonUid) + rc, err = AmGetUser(auid) } return rc, err } diff --git a/top.go b/top.go index 59a1d21..91632e3 100644 --- a/top.go +++ b/top.go @@ -31,7 +31,7 @@ type RenderedSidebox struct { Items []RenderedSideboxItem } -/* buildFeaturedCommunities creates the data for the "Featured Communities" sidebox. +/* buildCommunitiesSidebox creates the data for the "My/Featured Communities" sidebox. * Parameters: * uid - UID of the user rendering the page. * out - The RenderedSidebox to be built. @@ -39,9 +39,30 @@ type RenderedSidebox struct { * Returns: * Standard Go error status. */ -func buildFeaturedCommunities(uid int32, out *RenderedSidebox, in *database.Sidebox) error { - out.TemplateName = "sb_ftrcomm.jet" - return nil +func buildCommunitiesSidebox(uid int32, out *RenderedSidebox, in *database.Sidebox) error { + var err error + var anon bool + anon, err = database.AmIsUserAnon(uid) + if err == nil { + if anon { + out.Title = "Featured Communities" + } else { + out.Title = "Your Communities" + } + var l []*database.Community + l, err = database.AmGetCommunitiesForUser(uid) + if err == nil { + out.Items = make([]RenderedSideboxItem, len(l)) + for i, c := range l { + out.Items[i].Text = c.Name + out.Items[i].Link = new(string) + *out.Items[i].Link = "/TODO/community/" + c.Alias + out.Items[i].Flags = make([]string, 0) + } + out.TemplateName = "sb_ftrcomm.jet" + } + } + return err } /* buildFeaturedConferences creates the data for the "Featured Conferences" sidebox. @@ -81,7 +102,7 @@ func buildUsersOnline(uid int32, out *RenderedSidebox, in *database.Sidebox) err func buildRenderedSidebox(uid int32, out *RenderedSidebox, in *database.Sidebox) error { switch in.Boxid { case 1: - return buildFeaturedCommunities(uid, out, in) + return buildCommunitiesSidebox(uid, out, in) case 2: return buildFeaturedConferences(uid, out, in) case 3: diff --git a/ui/amcontext.go b/ui/amcontext.go index cd4263a..e7b79cc 100644 --- a/ui/amcontext.go +++ b/ui/amcontext.go @@ -28,11 +28,11 @@ type AmContext interface { RC() int OutputType() string Render(string) error - Scratchpad() map[string]any SubRender(string) ([]byte, error) Session() *sessions.Session SetOutputType(string) SetRC(int) + GetScratch(string) any SetScratch(string, any) URLPath() string VarMap() jet.VarMap @@ -117,6 +117,13 @@ func (c *amContext) SetRC(rc int) { c.httprc = rc } +func (c *amContext) GetScratch(name string) any { + if c.scratchpad == nil { + return nil + } + return c.scratchpad[name] +} + func (c *amContext) SetScratch(name string, val any) { if c.scratchpad == nil { c.scratchpad = make(map[string]any) diff --git a/ui/views/sb_ftrcomm.jet b/ui/views/sb_ftrcomm.jet index 54ddc1d..c5dd284 100644 --- a/ui/views/sb_ftrcomm.jet +++ b/ui/views/sb_ftrcomm.jet @@ -6,20 +6,24 @@ * 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/. *} - + +{{ sb := .GetScratch("__sidebox") }}
- {{ if .CurrentUser().IsAnon }} -

Featured Communities:

- {{ else }} -

Your Communities:

- {{ end }} +

{{ sb.Title }}

- 🟣 - La Piazza + {{ if len(sb.Items) > 0 }} + {{ range sb.Items }} +
+ 🟣 + {{ .Text }} +
+ {{ end }} + {{ else }} +
You are not a member of any communities.
+ {{ end }}