feat: add bootstrap entry badges
This commit is contained in:
@@ -432,10 +432,12 @@ 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)
|
||||
TenantSystemNotificationMarkRead(t.TenantID, t.Uid, string, time.Time) error
|
||||
|
||||
TenantFriendRequestCreate(t.TenantID, *t.FriendRequest) (*t.FriendRequest, error)
|
||||
TenantFriendRequestList(t.TenantID, t.Uid, string, string, string, int) ([]t.FriendRequestListItem, string, error)
|
||||
TenantFriendRequestPendingCount(t.TenantID, t.Uid) (int, error)
|
||||
TenantFriendRequestLatestBetween(t.TenantID, t.Uid, t.Uid) (*t.FriendRequest, error)
|
||||
TenantFriendRequestAccept(t.TenantID, string, t.Uid, time.Time) (*t.FriendRequest, error)
|
||||
TenantFriendRequestResolve(t.TenantID, string, t.Uid, string, time.Time) (*t.FriendRequest, error)
|
||||
|
||||
@@ -6221,6 +6221,32 @@ func (a *adapter) TenantSystemNotificationList(id t.TenantID, uid t.Uid, cursor
|
||||
return notifications, nextCursor, nil
|
||||
}
|
||||
|
||||
func (a *adapter) TenantSystemNotificationUnreadCount(id t.TenantID, uid t.Uid) (int, error) {
|
||||
if !a.tenantScopedDB {
|
||||
tenantAdp, err := a.routedTenantAdapter(id)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if tenantAdp != a {
|
||||
return tenantAdp.TenantSystemNotificationUnreadCount(id, uid)
|
||||
}
|
||||
}
|
||||
if err := validTenant(id); err != nil || uid.IsZero() {
|
||||
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`,
|
||||
decodeUID(uid)); 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)
|
||||
@@ -6430,6 +6456,32 @@ func (a *adapter) TenantFriendRequestList(id t.TenantID, uid t.Uid, direction, s
|
||||
return items, nextCursor, nil
|
||||
}
|
||||
|
||||
func (a *adapter) TenantFriendRequestPendingCount(id t.TenantID, uid t.Uid) (int, error) {
|
||||
if !a.tenantScopedDB {
|
||||
tenantAdp, err := a.routedTenantAdapter(id)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if tenantAdp != a {
|
||||
return tenantAdp.TenantFriendRequestPendingCount(id, uid)
|
||||
}
|
||||
}
|
||||
if err := validTenant(id); err != nil || uid.IsZero() {
|
||||
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_friend_requests WHERE target_id=? AND status=? AND expires_at>?`,
|
||||
decodeUID(uid), t.FriendRequestPending, t.TimeNow()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (a *adapter) TenantFriendRequestLatestBetween(id t.TenantID, uid1, uid2 t.Uid) (*t.FriendRequest, error) {
|
||||
if !a.tenantScopedDB {
|
||||
tenantAdp, err := a.routedTenantAdapter(id)
|
||||
|
||||
+91
-2
@@ -391,6 +391,7 @@ type imGroupCreateRequest struct {
|
||||
Name string `json:"name"`
|
||||
MemberUserIDs []string `json:"member_user_ids,omitempty"`
|
||||
AvatarFileID string `json:"avatar_file_id,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
}
|
||||
|
||||
type imGroupPatchRequest struct {
|
||||
@@ -1340,6 +1341,12 @@ func serveIMBootstrap(wrt http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
}
|
||||
modulePayloads := workbenchModulePayloads(modules)
|
||||
entryBadges, entries, err := bootstrapEntryPayloads(principal.TenantID, principal.Uid, modulePayloads)
|
||||
if err != nil {
|
||||
writeIMRelationError(wrt, req, err)
|
||||
return
|
||||
}
|
||||
writeIMSuccess(wrt, req, map[string]any{
|
||||
"features": map[string]any{
|
||||
"create_group": true,
|
||||
@@ -1354,7 +1361,9 @@ func serveIMBootstrap(wrt http.ResponseWriter, req *http.Request) {
|
||||
"user_search": true,
|
||||
"scan_qr": true,
|
||||
},
|
||||
"modules": workbenchModulePayloads(modules),
|
||||
"entry_badges": entryBadges,
|
||||
"entries": entries,
|
||||
"modules": modulePayloads,
|
||||
"workbench_version": workbenchVersion,
|
||||
"system_topic": map[string]any{
|
||||
"topic": "sys",
|
||||
@@ -3293,12 +3302,20 @@ func serveIMGroupCreate(wrt http.ResponseWriter, req *http.Request) {
|
||||
writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "malformed request", nil)
|
||||
return
|
||||
}
|
||||
description := strings.TrimSpace(body.Description)
|
||||
if len([]rune(description)) > 200 {
|
||||
writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "malformed request", map[string]any{"field": "description"})
|
||||
return
|
||||
}
|
||||
topicName := genTopicName()
|
||||
now := time.Now().UTC().Round(time.Millisecond)
|
||||
public := map[string]any{"name": name, "version": 1}
|
||||
if avatar := strings.TrimSpace(body.AvatarFileID); avatar != "" {
|
||||
public["avatar_file_id"] = avatar
|
||||
}
|
||||
if description != "" {
|
||||
public["description"] = description
|
||||
}
|
||||
topic := &types.Topic{
|
||||
ObjHeader: types.ObjHeader{Id: topicName},
|
||||
TenantID: principal.TenantID,
|
||||
@@ -3312,6 +3329,7 @@ func serveIMGroupCreate(wrt http.ResponseWriter, req *http.Request) {
|
||||
OwnerUserID: principal.Uid,
|
||||
Name: name,
|
||||
AvatarFileID: strings.TrimSpace(body.AvatarFileID),
|
||||
Description: description,
|
||||
GroupStatus: types.GroupStatusActive,
|
||||
MemberUploadFiles: true,
|
||||
MemberPrivateChat: true,
|
||||
@@ -5921,7 +5939,7 @@ func workbenchModulePayloads(modules []types.WorkbenchModule) []map[string]any {
|
||||
"name": module.Name,
|
||||
"icon_file_id": module.IconFileID,
|
||||
"route": module.Route,
|
||||
"unread_count": 0,
|
||||
"unread_count": module.UnreadCount,
|
||||
"enabled": module.Enabled,
|
||||
"permission": module.Permission,
|
||||
})
|
||||
@@ -5929,6 +5947,77 @@ func workbenchModulePayloads(modules []types.WorkbenchModule) []map[string]any {
|
||||
return items
|
||||
}
|
||||
|
||||
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 {
|
||||
count, err := store.Relations.CountPendingFriendRequests(tenantID, uid)
|
||||
if err != nil && !errors.Is(err, types.ErrNotFound) && !errors.Is(err, types.ErrUnsupported) {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err == nil {
|
||||
newFriendsPending = count
|
||||
}
|
||||
}
|
||||
|
||||
systemUnread := 0
|
||||
if store.Security != nil {
|
||||
count, err := store.Security.CountUnreadSystemNotifications(tenantID, uid)
|
||||
if err != nil && !errors.Is(err, types.ErrNotFound) && !errors.Is(err, types.ErrUnsupported) {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err == nil {
|
||||
systemUnread = count
|
||||
}
|
||||
}
|
||||
|
||||
workbenchTotalUnread := 0
|
||||
for _, module := range modules {
|
||||
enabled, _ := module["enabled"].(bool)
|
||||
if !enabled {
|
||||
continue
|
||||
}
|
||||
workbenchTotalUnread += intFromAny(module["unread_count"])
|
||||
}
|
||||
|
||||
entryBadges := map[string]any{
|
||||
"contacts_total_unread": newFriendsPending,
|
||||
"new_friends_pending_count": newFriendsPending,
|
||||
"groups_unread_count": 0,
|
||||
"file_assistant_unread_count": 0,
|
||||
"system_unread_count": systemUnread,
|
||||
"workbench_total_unread": workbenchTotalUnread,
|
||||
}
|
||||
entries := map[string]any{
|
||||
"new_friends": map[string]any{
|
||||
"enabled": true,
|
||||
"route": "/contacts/new-friends",
|
||||
"unread_count": newFriendsPending,
|
||||
"unread_source": "business",
|
||||
},
|
||||
"groups": map[string]any{
|
||||
"enabled": true,
|
||||
"route": "/contacts/groups",
|
||||
"unread_count": 0,
|
||||
"unread_source": "business",
|
||||
},
|
||||
"file_assistant": map[string]any{
|
||||
"enabled": true,
|
||||
"route": "/chat/file-assistant",
|
||||
"topic": "slf",
|
||||
"unread_count": 0,
|
||||
"unread_source": "topic",
|
||||
},
|
||||
"system_messages": map[string]any{
|
||||
"enabled": true,
|
||||
"route": "/chat/system",
|
||||
"topic": "sys",
|
||||
"unread_count": systemUnread,
|
||||
"unread_source": "business",
|
||||
},
|
||||
}
|
||||
return entryBadges, entries, nil
|
||||
}
|
||||
|
||||
func organizationDepartmentPayloads(departments []types.OrganizationDepartment) []map[string]any {
|
||||
items := make([]map[string]any, 0, len(departments))
|
||||
for _, department := range departments {
|
||||
|
||||
+58
-8
@@ -92,6 +92,9 @@ type testRelationsStore struct {
|
||||
deleted bool
|
||||
canSend *bool
|
||||
reason string
|
||||
pendingCount int
|
||||
countTenant types.TenantID
|
||||
countUser types.Uid
|
||||
friends []types.FriendListItem
|
||||
friendRequests []types.FriendRequestListItem
|
||||
searchResults []types.UserSearchItem
|
||||
@@ -794,6 +797,12 @@ func (s *testRelationsStore) ListFriendRequests(_ types.TenantID, _ types.Uid, _
|
||||
return s.friendRequests, "", nil
|
||||
}
|
||||
|
||||
func (s *testRelationsStore) CountPendingFriendRequests(tenantID types.TenantID, uid types.Uid) (int, error) {
|
||||
s.countTenant = tenantID
|
||||
s.countUser = uid
|
||||
return s.pendingCount, nil
|
||||
}
|
||||
|
||||
func (s *testRelationsStore) ResolveFriendRequest(_ types.TenantID, requestID string, actor types.Uid, status string, handledAt time.Time) (*types.FriendRequest, error) {
|
||||
return &types.FriendRequest{
|
||||
TenantID: 7,
|
||||
@@ -1695,15 +1704,23 @@ func TestServeIMBootstrapIncludesHomeCapabilities(t *testing.T) {
|
||||
expectIMBearer(t, ctrl, "token-1", 7, 42)
|
||||
oldSecurity := store.Security
|
||||
oldWorkbench := store.Workbench
|
||||
security := &testSecurityStore{realName: &types.RealNameVerification{Status: "verified"}}
|
||||
oldRelations := store.Relations
|
||||
security := &testSecurityStore{
|
||||
realName: &types.RealNameVerification{Status: "verified"},
|
||||
unreadNotifications: 4,
|
||||
}
|
||||
relations := &testRelationsStore{pendingCount: 2}
|
||||
workbench := &testWorkbenchStore{version: 9, modules: []types.WorkbenchModule{
|
||||
{Code: "organization", Name: "组织架构", Route: "/organization", Enabled: true, Permission: "enabled"},
|
||||
{Code: "external", Name: "外链", Route: "https://example.com", Enabled: true, Permission: "enabled"},
|
||||
{Code: "organization", Name: "组织架构", Route: "/organization", UnreadCount: 3, Enabled: true, Permission: "enabled"},
|
||||
{Code: "meetings", Name: "会议通知", Route: "/meetings", UnreadCount: 5, Enabled: false, Permission: "enabled"},
|
||||
{Code: "external", Name: "外链", Route: "https://example.com", UnreadCount: 9, Enabled: true, Permission: "enabled"},
|
||||
}}
|
||||
store.Security = security
|
||||
store.Relations = relations
|
||||
store.Workbench = workbench
|
||||
defer func() {
|
||||
store.Security = oldSecurity
|
||||
store.Relations = oldRelations
|
||||
store.Workbench = oldWorkbench
|
||||
}()
|
||||
|
||||
@@ -1723,6 +1740,12 @@ func TestServeIMBootstrapIncludesHomeCapabilities(t *testing.T) {
|
||||
if security.realNameTenant != 7 || security.realNameUser != 42 {
|
||||
t.Fatalf("bootstrap realname lookup scope: tenant=%d uid=%s", security.realNameTenant, security.realNameUser)
|
||||
}
|
||||
if security.unreadTenant != 7 || security.unreadUser != 42 {
|
||||
t.Fatalf("bootstrap system unread lookup scope: tenant=%d uid=%s", security.unreadTenant, security.unreadUser)
|
||||
}
|
||||
if relations.countTenant != 7 || relations.countUser != 42 {
|
||||
t.Fatalf("bootstrap friend request count scope: tenant=%d uid=%s", relations.countTenant, relations.countUser)
|
||||
}
|
||||
if !workbench.calledOnce || workbench.tenantID != 7 || workbench.uid != 42 {
|
||||
t.Fatalf("bootstrap workbench lookup scope: store=%#v", workbench)
|
||||
}
|
||||
@@ -1730,9 +1753,32 @@ func TestServeIMBootstrapIncludesHomeCapabilities(t *testing.T) {
|
||||
t.Fatalf("unexpected bootstrap workbench version: %#v", data)
|
||||
}
|
||||
modules := data["modules"].([]any)
|
||||
if len(modules) != 1 || modules[0].(map[string]any)["code"] != "organization" {
|
||||
if len(modules) != 2 || modules[0].(map[string]any)["code"] != "organization" || modules[1].(map[string]any)["code"] != "meetings" {
|
||||
t.Fatalf("unexpected bootstrap modules: %#v", modules)
|
||||
}
|
||||
if modules[0].(map[string]any)["unread_count"].(float64) != 3 {
|
||||
t.Fatalf("unexpected bootstrap module unread: %#v", modules[0])
|
||||
}
|
||||
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 {
|
||||
t.Fatalf("unexpected bootstrap badges: %#v", badges)
|
||||
}
|
||||
entries := data["entries"].(map[string]any)
|
||||
newFriends := entries["new_friends"].(map[string]any)
|
||||
if newFriends["route"] != "/contacts/new-friends" || newFriends["unread_count"].(float64) != 2 || newFriends["unread_source"] != "business" {
|
||||
t.Fatalf("unexpected new friends entry: %#v", newFriends)
|
||||
}
|
||||
systemMessages := entries["system_messages"].(map[string]any)
|
||||
if systemMessages["topic"] != "sys" || systemMessages["unread_count"].(float64) != 4 || systemMessages["unread_source"] != "business" {
|
||||
t.Fatalf("unexpected system messages entry: %#v", systemMessages)
|
||||
}
|
||||
fileAssistant := entries["file_assistant"].(map[string]any)
|
||||
if fileAssistant["topic"] != "slf" || fileAssistant["unread_source"] != "topic" {
|
||||
t.Fatalf("unexpected file assistant entry: %#v", fileAssistant)
|
||||
}
|
||||
realname := data["security"].(map[string]any)["realname"].(map[string]any)
|
||||
if realname["status"] != "verified" {
|
||||
t.Fatalf("unexpected bootstrap realname status: %#v", realname)
|
||||
@@ -2747,7 +2793,8 @@ func TestServeIMGroupCreate(t *testing.T) {
|
||||
subs := mock_store.NewMockSubsPersistenceInterface(ctrl)
|
||||
store.Topics = topics
|
||||
store.Subs = subs
|
||||
store.GroupBusiness = &testGroupBusinessStore{}
|
||||
groupBusiness := &testGroupBusinessStore{}
|
||||
store.GroupBusiness = groupBusiness
|
||||
defer func() {
|
||||
store.Store = oldStore
|
||||
store.Topics = oldTopics
|
||||
@@ -2760,7 +2807,7 @@ func TestServeIMGroupCreate(t *testing.T) {
|
||||
TenantID: 7,
|
||||
Owner: types.Uid(42).String(),
|
||||
SubCnt: 2,
|
||||
Public: map[string]any{"name": "New Group", "avatar_file_id": "file-1", "version": 1},
|
||||
Public: map[string]any{"name": "New Group", "avatar_file_id": "file-1", "description": "Project discussion", "version": 1},
|
||||
}, nil)
|
||||
subs.EXPECT().Get(types.TenantID(7), "grpnewgroup", types.Uid(42), false).Return(&types.Subscription{
|
||||
TenantID: 7,
|
||||
@@ -2770,7 +2817,7 @@ func TestServeIMGroupCreate(t *testing.T) {
|
||||
ModeGiven: types.ModeCFull,
|
||||
}, nil)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/groups", bytes.NewBufferString(`{"name":"New Group","member_user_ids":["usrAgAAAAAAAAA"],"avatar_file_id":"file-1"}`))
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/groups", bytes.NewBufferString(`{"name":"New Group","description":"Project discussion","member_user_ids":["usrAgAAAAAAAAA"],"avatar_file_id":"file-1"}`))
|
||||
req.Header.Set("Authorization", "Bearer token-1")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
@@ -2780,9 +2827,12 @@ func TestServeIMGroupCreate(t *testing.T) {
|
||||
t.Fatalf("status: got %d, want %d; body=%s", rec.Code, http.StatusOK, rec.Body.String())
|
||||
}
|
||||
data := decodeIMHTTPResponse(t, rec).Data.(map[string]any)
|
||||
if data["topic"] != "grpnewgroup" || data["name"] != "New Group" {
|
||||
if data["topic"] != "grpnewgroup" || data["name"] != "New Group" || data["description"] != "Project discussion" {
|
||||
t.Fatalf("unexpected group create response: %#v", data)
|
||||
}
|
||||
if groupBusiness.settings == nil || groupBusiness.settings.Description != "Project discussion" {
|
||||
t.Fatalf("group settings missing description: %#v", groupBusiness.settings)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeIMGroupDetailPatchSettingsProfileAndLeave(t *testing.T) {
|
||||
|
||||
@@ -2073,6 +2073,7 @@ func validUserProfilePatch(patch types.UserProfilePatch) bool {
|
||||
type RelationsPersistenceInterface interface {
|
||||
CreateFriendRequest(tenantID types.TenantID, req *types.FriendRequest) (*types.FriendRequest, error)
|
||||
ListFriendRequests(tenantID types.TenantID, uid types.Uid, direction, status, cursor string, limit int) ([]types.FriendRequestListItem, string, error)
|
||||
CountPendingFriendRequests(tenantID types.TenantID, uid types.Uid) (int, error)
|
||||
ResolveFriendRequest(tenantID types.TenantID, requestID string, actor types.Uid, status string, handledAt time.Time) (*types.FriendRequest, error)
|
||||
ListFriends(tenantID types.TenantID, uid types.Uid, query, initial, cursor string, limit int) ([]types.FriendListItem, string, error)
|
||||
GetRelationshipContext(tenantID types.TenantID, userID, peerID types.Uid, topic string) (*types.RelationshipContext, error)
|
||||
@@ -2182,6 +2183,17 @@ func (relationsMapper) ListFriendRequests(tenantID types.TenantID, uid types.Uid
|
||||
return tenantAdp.TenantFriendRequestList(tenantID, uid, direction, status, cursor, limit)
|
||||
}
|
||||
|
||||
func (relationsMapper) CountPendingFriendRequests(tenantID types.TenantID, uid types.Uid) (int, error) {
|
||||
if uid.IsZero() {
|
||||
return 0, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return tenantAdp.TenantFriendRequestPendingCount(tenantID, uid)
|
||||
}
|
||||
|
||||
func (relationsMapper) ResolveFriendRequest(tenantID types.TenantID, requestID string, actor types.Uid, status string, handledAt time.Time) (*types.FriendRequest, error) {
|
||||
if strings.TrimSpace(requestID) == "" || actor.IsZero() || (status != types.FriendRequestAccepted && status != types.FriendRequestRejected) {
|
||||
return nil, types.ErrMalformed
|
||||
@@ -3115,6 +3127,7 @@ type UserSecurityPersistenceInterface interface {
|
||||
SetYouthMode(tenantID types.TenantID, uid types.Uid, enabled bool, policyVersion int, now time.Time) (*types.YouthModeSettings, error)
|
||||
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)
|
||||
MarkSystemNotificationRead(tenantID types.TenantID, uid types.Uid, eventID string, readAt time.Time) error
|
||||
}
|
||||
|
||||
@@ -3245,6 +3258,17 @@ func (userSecurityMapper) ListSystemNotifications(tenantID types.TenantID, uid t
|
||||
return tenantAdp.TenantSystemNotificationList(tenantID, uid, cursor, limit)
|
||||
}
|
||||
|
||||
func (userSecurityMapper) CountUnreadSystemNotifications(tenantID types.TenantID, uid types.Uid) (int, error) {
|
||||
if uid.IsZero() {
|
||||
return 0, types.ErrMalformed
|
||||
}
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return tenantAdp.TenantSystemNotificationUnreadCount(tenantID, uid)
|
||||
}
|
||||
|
||||
func (userSecurityMapper) MarkSystemNotificationRead(tenantID types.TenantID, uid types.Uid, eventID string, readAt time.Time) error {
|
||||
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||
if err != nil {
|
||||
|
||||
@@ -3,15 +3,16 @@ package types
|
||||
import "time"
|
||||
|
||||
type WorkbenchModule struct {
|
||||
ID int64 `db:"id"`
|
||||
Code string `db:"code"`
|
||||
Name string `db:"name"`
|
||||
IconFileID string `db:"icon_file_id"`
|
||||
Route string `db:"route"`
|
||||
SortOrder int `db:"sort_order"`
|
||||
UnreadKey string `db:"unread_key"`
|
||||
Enabled bool `db:"enabled"`
|
||||
Permission string `db:"permission"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
ID int64 `db:"id"`
|
||||
Code string `db:"code"`
|
||||
Name string `db:"name"`
|
||||
IconFileID string `db:"icon_file_id"`
|
||||
Route string `db:"route"`
|
||||
SortOrder int `db:"sort_order"`
|
||||
UnreadKey string `db:"unread_key"`
|
||||
UnreadCount int `db:"unread_count"`
|
||||
Enabled bool `db:"enabled"`
|
||||
Permission string `db:"permission"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
@@ -227,6 +227,7 @@ type testSecurityStore struct {
|
||||
attempts int
|
||||
lockedUntil *time.Time
|
||||
notifications []types.SystemNotification
|
||||
unreadNotifications int
|
||||
readNotificationID string
|
||||
realNameTenant types.TenantID
|
||||
realNameUser types.Uid
|
||||
@@ -235,6 +236,8 @@ type testSecurityStore struct {
|
||||
listCursor string
|
||||
listLimit int
|
||||
nextCursor string
|
||||
unreadTenant types.TenantID
|
||||
unreadUser types.Uid
|
||||
readTenant types.TenantID
|
||||
readUser types.Uid
|
||||
}
|
||||
@@ -368,6 +371,21 @@ func (s *testSecurityStore) ListSystemNotifications(tenantID types.TenantID, uid
|
||||
return s.notifications, s.nextCursor, s.err
|
||||
}
|
||||
|
||||
func (s *testSecurityStore) CountUnreadSystemNotifications(tenantID types.TenantID, uid types.Uid) (int, error) {
|
||||
s.unreadTenant = tenantID
|
||||
s.unreadUser = uid
|
||||
if s.unreadNotifications != 0 {
|
||||
return s.unreadNotifications, s.err
|
||||
}
|
||||
count := 0
|
||||
for _, notification := range s.notifications {
|
||||
if notification.ReadAt == nil {
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user