From 7a1647bf175b25dd215b535419e1e6c794130758 Mon Sep 17 00:00:00 2001 From: richjohnrambo Date: Sat, 5 Sep 2026 18:31:07 +0800 Subject: [PATCH] Fix bootstrap meeting unread counts --- server/auth/token/auth_token_test.go | 4 ++++ server/db/adapter.go | 1 + server/db/mysql/tenant_adapter.go | 27 +++++++++++++++++++++++++++ server/im_http.go | 24 ++++++++++++++++++++++++ server/im_http_test.go | 18 ++++++++++++++---- server/store/store.go | 12 ++++++++++++ server/user_verification_test.go | 14 ++++++++++++++ 7 files changed, 96 insertions(+), 4 deletions(-) diff --git a/server/auth/token/auth_token_test.go b/server/auth/token/auth_token_test.go index 9ceb63c..31b51ff 100644 --- a/server/auth/token/auth_token_test.go +++ b/server/auth/token/auth_token_test.go @@ -75,6 +75,10 @@ func (s testSecurityStore) CountUnreadSystemNotifications(types.TenantID, types. return 0, s.err } +func (s testSecurityStore) CountUnreadSystemNotificationsByEventPrefix(types.TenantID, types.Uid, string) (int, error) { + return 0, s.err +} + func (s testSecurityStore) MarkSystemNotificationRead(types.TenantID, types.Uid, string, time.Time) error { return s.err } diff --git a/server/db/adapter.go b/server/db/adapter.go index 164f055..5cc623b 100644 --- a/server/db/adapter.go +++ b/server/db/adapter.go @@ -490,6 +490,7 @@ type TenantBusinessAdapter interface { TenantSystemNotificationCreate(t.TenantID, *t.SystemNotification) error TenantSystemNotificationList(t.TenantID, t.Uid, string, int) ([]t.SystemNotification, string, error) TenantSystemNotificationUnreadCount(t.TenantID, t.Uid) (int, error) + TenantSystemNotificationUnreadCountByEventPrefix(t.TenantID, t.Uid, string) (int, error) TenantSystemNotificationMarkRead(t.TenantID, t.Uid, string, time.Time) error TenantFriendRequestCreate(t.TenantID, *t.FriendRequest) (*t.FriendRequest, error) diff --git a/server/db/mysql/tenant_adapter.go b/server/db/mysql/tenant_adapter.go index b8a83a9..97ae6f3 100644 --- a/server/db/mysql/tenant_adapter.go +++ b/server/db/mysql/tenant_adapter.go @@ -8793,6 +8793,33 @@ func (a *adapter) TenantSystemNotificationUnreadCount(id t.TenantID, uid t.Uid) return count, nil } +func (a *adapter) TenantSystemNotificationUnreadCountByEventPrefix(id t.TenantID, uid t.Uid, eventPrefix string) (int, error) { + if !a.tenantScopedDB { + tenantAdp, err := a.routedTenantAdapter(id) + if err != nil { + return 0, err + } + if tenantAdp != a { + return tenantAdp.TenantSystemNotificationUnreadCountByEventPrefix(id, uid, eventPrefix) + } + } + eventPrefix = strings.TrimSpace(eventPrefix) + if err := validTenant(id); err != nil || uid.IsZero() || eventPrefix == "" { + return 0, t.ErrMalformed + } + ctx, cancel := a.getContext() + if cancel != nil { + defer cancel() + } + var count int + if err := a.db.GetContext(ctx, &count, + `SELECT COUNT(*) FROM im_system_notifications WHERE user_id=? AND read_at IS NULL AND event_type LIKE ?`, + decodeUID(uid), eventPrefix+"%"); err != nil { + return 0, err + } + return count, nil +} + func (a *adapter) TenantSystemNotificationMarkRead(id t.TenantID, uid t.Uid, eventID string, readAt time.Time) error { if !a.tenantScopedDB { tenantAdp, err := a.routedTenantAdapter(id) diff --git a/server/im_http.go b/server/im_http.go index 44b7d1a..28bd1ef 100644 --- a/server/im_http.go +++ b/server/im_http.go @@ -1711,6 +1711,10 @@ func serveIMBootstrap(wrt http.ResponseWriter, req *http.Request) { } } modulePayloads := workbenchModulePayloads(modules) + if err := populateWorkbenchModuleUnreadCounts(principal.TenantID, principal.Uid, modulePayloads); err != nil { + writeIMRelationError(wrt, req, err) + return + } entryBadges, entries, err := bootstrapEntryPayloads(principal.TenantID, principal.Uid, modulePayloads) if err != nil { writeIMRelationError(wrt, req, err) @@ -7855,6 +7859,26 @@ func workbenchModulePayloads(modules []types.WorkbenchModule) []map[string]any { return items } +func populateWorkbenchModuleUnreadCounts(tenantID types.TenantID, uid types.Uid, modules []map[string]any) error { + if store.Security == nil { + return nil + } + for _, module := range modules { + code, _ := module["code"].(string) + switch code { + case "meetings": + count, err := store.Security.CountUnreadSystemNotificationsByEventPrefix(tenantID, uid, "meeting.") + if err != nil && !errors.Is(err, types.ErrNotFound) && !errors.Is(err, types.ErrUnsupported) { + return err + } + if err == nil { + module["unread_count"] = count + } + } + } + return nil +} + func bootstrapEntryPayloads(tenantID types.TenantID, uid types.Uid, modules []map[string]any) (map[string]any, map[string]any, error) { newFriendsPending := 0 if store.Relations != nil { diff --git a/server/im_http_test.go b/server/im_http_test.go index 0b8a283..eee88d1 100644 --- a/server/im_http_test.go +++ b/server/im_http_test.go @@ -2406,14 +2406,21 @@ func TestServeIMBootstrapIncludesHomeCapabilities(t *testing.T) { oldWorkbench := store.Workbench oldRelations := store.Relations oldThirdParty := store.ThirdPartyConfig + readAt := time.Now().UTC() security := &testSecurityStore{ - realName: &types.RealNameVerification{Status: "verified"}, - unreadNotifications: 4, + realName: &types.RealNameVerification{Status: "verified"}, + notifications: []types.SystemNotification{ + {EventID: "evt-meeting-created", EventType: "meeting.created"}, + {EventID: "evt-meeting-updated", EventType: "meeting.updated"}, + {EventID: "evt-friend-request", EventType: "friend.request.created"}, + {EventID: "evt-daily-report", EventType: "daily_report.reminded"}, + {EventID: "evt-meeting-read", EventType: "meeting.created", ReadAt: &readAt}, + }, } relations := &testRelationsStore{pendingCount: 2} workbench := &testWorkbenchStore{version: 9, modules: []types.WorkbenchModule{ {Code: "organization", Name: "组织架构", Route: "/organization", UnreadCount: 3, Enabled: true, Permission: "enabled"}, - {Code: "meetings", Name: "会议通知", Route: "/meetings", UnreadCount: 5, Enabled: false, Permission: "enabled"}, + {Code: "meetings", Name: "会议通知", Route: "/meetings", UnreadCount: 0, Enabled: true, Permission: "enabled"}, {Code: "external", Name: "外链", Route: "https://example.com", UnreadCount: 9, Enabled: true, Permission: "enabled"}, }} store.Security = security @@ -2473,11 +2480,14 @@ func TestServeIMBootstrapIncludesHomeCapabilities(t *testing.T) { if modules[0].(map[string]any)["unread_count"].(float64) != 3 { t.Fatalf("unexpected bootstrap module unread: %#v", modules[0]) } + if modules[1].(map[string]any)["unread_count"].(float64) != 2 { + t.Fatalf("unexpected bootstrap meetings unread: %#v", modules[1]) + } badges := data["entry_badges"].(map[string]any) if badges["new_friends_pending_count"].(float64) != 2 || badges["contacts_total_unread"].(float64) != 2 || badges["system_unread_count"].(float64) != 4 || - badges["workbench_total_unread"].(float64) != 3 { + badges["workbench_total_unread"].(float64) != 5 { t.Fatalf("unexpected bootstrap badges: %#v", badges) } entries := data["entries"].(map[string]any) diff --git a/server/store/store.go b/server/store/store.go index 72a9771..3f64198 100644 --- a/server/store/store.go +++ b/server/store/store.go @@ -4258,6 +4258,7 @@ type UserSecurityPersistenceInterface interface { CreateSystemNotification(tenantID types.TenantID, notification *types.SystemNotification) error ListSystemNotifications(tenantID types.TenantID, uid types.Uid, cursor string, limit int) ([]types.SystemNotification, string, error) CountUnreadSystemNotifications(tenantID types.TenantID, uid types.Uid) (int, error) + CountUnreadSystemNotificationsByEventPrefix(tenantID types.TenantID, uid types.Uid, eventPrefix string) (int, error) MarkSystemNotificationRead(tenantID types.TenantID, uid types.Uid, eventID string, readAt time.Time) error } @@ -4421,6 +4422,17 @@ func (userSecurityMapper) CountUnreadSystemNotifications(tenantID types.TenantID return tenantAdp.TenantSystemNotificationUnreadCount(tenantID, uid) } +func (userSecurityMapper) CountUnreadSystemNotificationsByEventPrefix(tenantID types.TenantID, uid types.Uid, eventPrefix string) (int, error) { + if uid.IsZero() || strings.TrimSpace(eventPrefix) == "" { + return 0, types.ErrMalformed + } + tenantAdp, err := tenantBusinessAdapter(tenantID) + if err != nil { + return 0, err + } + return tenantAdp.TenantSystemNotificationUnreadCountByEventPrefix(tenantID, uid, eventPrefix) +} + func (userSecurityMapper) MarkSystemNotificationRead(tenantID types.TenantID, uid types.Uid, eventID string, readAt time.Time) error { tenantAdp, err := tenantBusinessAdapter(tenantID) if err != nil { diff --git a/server/user_verification_test.go b/server/user_verification_test.go index 04477ef..0d6b7e5 100644 --- a/server/user_verification_test.go +++ b/server/user_verification_test.go @@ -241,6 +241,7 @@ type testSecurityStore struct { nextCursor string unreadTenant types.TenantID unreadUser types.Uid + unreadEventPrefix string readTenant types.TenantID readUser types.Uid } @@ -402,6 +403,19 @@ func (s *testSecurityStore) CountUnreadSystemNotifications(tenantID types.Tenant return count, s.err } +func (s *testSecurityStore) CountUnreadSystemNotificationsByEventPrefix(tenantID types.TenantID, uid types.Uid, eventPrefix string) (int, error) { + s.unreadTenant = tenantID + s.unreadUser = uid + s.unreadEventPrefix = eventPrefix + count := 0 + for _, notification := range s.notifications { + if notification.ReadAt == nil && strings.HasPrefix(notification.EventType, eventPrefix) { + count++ + } + } + return count, s.err +} + func (s *testSecurityStore) MarkSystemNotificationRead(tenantID types.TenantID, uid types.Uid, eventID string, readAt time.Time) error { s.readTenant = tenantID s.readUser = uid