From e6f157c337af18a2c3c7af74fa9352ff4079bfff Mon Sep 17 00:00:00 2001 From: Amy Gale Ruth Bowersox Date: Mon, 20 Oct 2025 22:16:10 -0600 Subject: [PATCH] patched up community display in find.jet --- communityadmin.go | 9 +++++++++ database/community.go | 47 +++++++++++++++++++++++++++++++++++++------ ui/amcontext.go | 3 +++ ui/templates.go | 45 ++++++++++++++++++++++++++++++++++++----- ui/views/find.jet | 4 ++-- util/timelist.go | 44 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 139 insertions(+), 13 deletions(-) diff --git a/communityadmin.go b/communityadmin.go index bd550f8..56ab4fb 100644 --- a/communityadmin.go +++ b/communityadmin.go @@ -239,6 +239,9 @@ func EditCommunityProfile(ctxt ui.AmContext) (string, any, error) { flags.Set(database.CommunityFlagPicturesInPosts, dlg.Field("pic_in_post").IsChecked()) err = comm.SaveFlags(flags) } + if err == nil { + err = comm.TouchUpdate() + } if err != nil { ctxt.ClearCommunityContext() return dlg.RenderError(ctxt, err.Error()) @@ -321,6 +324,9 @@ func EditCommunityLogo(ctxt ui.AmContext) (string, any, error) { photourl := fmt.Sprintf("/img/store/%d", img.ImgId) ci.PhotoURL = &photourl _, err = ci.Save() + if err == nil { + err = comm.TouchUpdate() + } if err == nil { return "redirect", "/comm/" + comm.Alias + "/admin/profile", nil } @@ -457,6 +463,9 @@ func CreateCommunity(ctxt ui.AmContext) (string, any, error) { if err == nil { err = comm.SetContactID(ci.ContactId) } + if err == nil { + err = comm.TouchUpdate() + } if err != nil { return dlg.RenderError(ctxt, err.Error()) } diff --git a/database/community.go b/database/community.go index e9f8ad9..a4c431f 100644 --- a/database/community.go +++ b/database/community.go @@ -209,8 +209,8 @@ func (c *Community) Membership(u *User) (bool, bool, uint16, error) { return false, false, uint16(0), err } -// MemberCountQ returns the number of members in the community, quietly. -func (c *Community) MemberCountQ(hidden bool) int { +// MemberCount returns the number of members in the community, quietly. +func (c *Community) MemberCount(hidden bool) (int, error) { var rs *sql.Rows var err error if hidden { @@ -219,14 +219,14 @@ func (c *Community) MemberCountQ(hidden bool) int { rs, err = amdb.Query("SELECT COUNT(*) FROM commmember WHERE commid = ? AND hidden = 0", c.Id) } if err != nil { - return -1 + return -1, err } if rs.Next() { var rc int rs.Scan(&rc) - return rc + return rc, nil } - return -1 + return -1, errors.New("internal error reading member count") } /* TestPermission is shorthand that tests if a user has a permission with respect to the community. @@ -254,7 +254,7 @@ func (c *Community) TestPermission(perm string, level uint16) bool { } } -// PermissionLevel returns trhe permission level for a permission name. +// PermissionLevel returns the permission level for a permission name. func (c *Community) PermissionLevel(perm string) uint16 { switch perm { case "Community.Read": @@ -348,6 +348,41 @@ func (c *Community) SetContactID(cid int32) error { return nil } +// Touch updates the last access time of the community. +func (c *Community) Touch() error { + c.Mutex.Lock() + defer c.Mutex.Unlock() + _, err := amdb.Exec("UPDATE communities SET lastaccess = NOW() WHERE commid = ?", c.Id) + if err == nil { + rs, err := amdb.Query("SELECT lastaccess FROM communities WHERE commid = ?", c.Id) + if err == nil { + rs.Next() + var na time.Time + rs.Scan(&na) + c.LastAccess = &na + } + } + return err +} + +// TouchUpdate updates the last access and last update times of the community. +func (c *Community) TouchUpdate() error { + c.Mutex.Lock() + defer c.Mutex.Unlock() + _, err := amdb.Exec("UPDATE communities SET lastaccess = NOW(), lastupdate = NOW() WHERE commid = ?", c.Id) + if err == nil { + rs, err := amdb.Query("SELECT lastaccess, lastupdate FROM communities WHERE commid = ?", c.Id) + if err != nil { + rs.Next() + var na, nu time.Time + rs.Scan(&na, &nu) + c.LastAccess = &na + c.LastUpdate = &nu + } + } + return err +} + /* AmGetCommunity returns a reference to the specified community. * Parameters: * id - The ID of the community. diff --git a/ui/amcontext.go b/ui/amcontext.go index ee2f2ae..41d7d2e 100644 --- a/ui/amcontext.go +++ b/ui/amcontext.go @@ -284,6 +284,9 @@ func (c *amContext) SubRender(name string) ([]byte, error) { } buf := new(bytes.Buffer) err = view.Execute(buf, c.VarMap(), c) + if err != nil { + log.Errorf("template \"%s\" failed subrender exec: %v", name, err) + } return buf.Bytes(), err } diff --git a/ui/templates.go b/ui/templates.go index 6bbb121..805e21e 100644 --- a/ui/templates.go +++ b/ui/templates.go @@ -107,6 +107,35 @@ func extractCommunityLogo(a jet.Arguments) reflect.Value { return reflect.ValueOf(rc) } +func displayActivity(a jet.Arguments) reflect.Value { + timeval := a.Get(0).Convert(reflect.TypeFor[*time.Time]()).Interface().(*time.Time) + ctxt := a.Get(1).Convert(reflect.TypeFor[AmContext]()).Interface().(AmContext) + prefs, err := ctxt.CurrentUser().Prefs() + if err == nil { + return reflect.ValueOf(util.AmActivityString(timeval, prefs.Localizer())) + } + return reflect.ValueOf(fmt.Sprintf("<<%v>>", err)) +} + +func displayMemberCount(a jet.Arguments) reflect.Value { + showHidden := false + comm := a.Get(0).Convert(reflect.TypeFor[*database.Community]()).Interface().(*database.Community) + ctxt := a.Get(1).Convert(reflect.TypeFor[AmContext]()).Interface().(AmContext) + level := ctxt.CurrentUser().BaseLevel + mbr, _, clevel, err := comm.Membership(ctxt.CurrentUser()) + if err == nil { + if mbr && clevel > level { + level = clevel + } + showHidden = comm.TestPermission("Community.ShowHiddenMembers", level) + } + count, err := comm.MemberCount(showHidden) + if err != nil { + return reflect.ValueOf(-1) + } + return reflect.ValueOf(count) +} + // SetupTemplates is called to set up the template renderer after the configuration is loaded. func SetupTemplates() { views = jet.NewSet( @@ -122,17 +151,19 @@ func SetupTemplates() { views.AddGlobalFunc("MakeIntRange", makeIntRange) views.AddGlobalFunc("MakeYearRange", makeYearRange) views.AddGlobalFunc("ExtractCommunityLogo", extractCommunityLogo) + views.AddGlobalFunc("DisplayActivity", displayActivity) + views.AddGlobalFunc("DisplayMemberCount", displayMemberCount) - views.AddGlobalFunc("GetCountryList", func(a jet.Arguments) reflect.Value { + views.AddGlobalFunc("GetCountryList", func(jet.Arguments) reflect.Value { return reflect.ValueOf(util.AmCountryList()) }) - views.AddGlobalFunc("GetLanguageList", func(a jet.Arguments) reflect.Value { + views.AddGlobalFunc("GetLanguageList", func(jet.Arguments) reflect.Value { return reflect.ValueOf(util.AmLanguageList()) }) - views.AddGlobalFunc("GetTimeZoneList", func(a jet.Arguments) reflect.Value { + views.AddGlobalFunc("GetTimeZoneList", func(jet.Arguments) reflect.Value { return reflect.ValueOf(util.AmTimeZoneList()) }) - views.AddGlobalFunc("GetMonthList", func(a jet.Arguments) reflect.Value { + views.AddGlobalFunc("GetMonthList", func(jet.Arguments) reflect.Value { return reflect.ValueOf(util.AmMonthList()) }) views.AddGlobalFunc("AmMenu", func(a jet.Arguments) reflect.Value { @@ -173,5 +204,9 @@ func (r *TemplateRenderer) Render(w io.Writer, name string, data any, c echo.Con if amctxt != nil { vmap = amctxt.VarMap() } - return view.Execute(w, vmap, data) + err = view.Execute(w, vmap, data) + if err != nil { + log.Errorf("Template \"%s\" failed exec: %v", name, err) + } + return err } diff --git a/ui/views/find.jet b/ui/views/find.jet index f23c216..6858474 100644 --- a/ui/views/find.jet +++ b/ui/views/find.jet @@ -208,7 +208,7 @@ {{ h := rx.HostQ() }} {{ h.Username }} - - {{ n := rx.MemberCountQ(false) }} + {{ n := DisplayMemberCount(rx, .) }} {{ if n == 1 }} 1 member {{ else }} @@ -216,7 +216,7 @@ {{ end }}
- Latest activity: [Today, 9:52:48 PM] + Latest activity: {{ DisplayActivity(rx.LastAccess, .)}}
{{ rx.Synopsis }}
diff --git a/util/timelist.go b/util/timelist.go index e60bd01..08b69b5 100644 --- a/util/timelist.go +++ b/util/timelist.go @@ -11,10 +11,12 @@ package util import ( + "fmt" "slices" "sync" "time" + "github.com/klauspost/lctime" "github.com/tkuchiki/go-timezone" ) @@ -51,6 +53,48 @@ func AmMonthList() []string { return rc } +/* AmActivityString generates a string to represent the activity based on the given timestamp + * and the current time. + * Parameters: + * timeval - The time value representing the last point of activity. + * loc - The localizer used to format the time. + * Returns: + * The string activity equivalent. + */ +func AmActivityString(timeval *time.Time, loc lctime.Localizer) string { + if timeval == nil { + return "Never" + } + now := time.Now().In(timeval.Location()) + day := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) + if timeval.Compare(day) == 1 { + return "Today, " + loc.Strftime("%X", *timeval) + } + day = time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, now.Location()) + if timeval.Compare(day) == 1 { + return "Yesterday, " + loc.Strftime("%X", *timeval) + } + duration := now.Sub(*timeval) + days := duration.Hours() / 24.0 + day = time.Date(now.Year(), now.Month()-1, now.Day(), 0, 0, 0, 0, now.Location()) + if timeval.Compare(day) == 1 { + return fmt.Sprintf("%d days ago", int(days)) + } + day = time.Date(now.Year()-1, now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) + if timeval.Compare(day) == 1 { + nm := int(days / 30.0) + if nm == 1 { + return "1 month ago" + } + return fmt.Sprintf("%d months ago", nm) + } + ny := int(days / 365.25) + if ny == 1 { + return "1 year ago" + } + return fmt.Sprintf("%d years ago", ny) +} + // init preloads the time zone list. func init() { go AmTimeZoneList()