Fix bootstrap meeting unread counts

This commit is contained in:
richjohnrambo
2026-09-05 18:31:13 +08:00
parent baf15848d3
commit 7a1647bf17
7 changed files with 96 additions and 4 deletions
+4
View File
@@ -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
}
+1
View File
@@ -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)
+27
View File
@@ -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)
+24
View File
@@ -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 {
+14 -4
View File
@@ -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)
+12
View File
@@ -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 {
+14
View File
@@ -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