diff --git a/docs/IM-前后端对接文档.md b/docs/IM-前后端对接文档.md index 4d91c5e..dde8047 100644 --- a/docs/IM-前后端对接文档.md +++ b/docs/IM-前后端对接文档.md @@ -1500,7 +1500,7 @@ X-MeetingIM-APIKey: <预置的普通 API Key> **状态:新增。** - 查询最新公告:`GET /v1/im/groups/{topic}/announcements/latest`;发布/更新:`PUT /v1/im/groups/{topic}/announcements/current`,Body `{"content":"...","version":1}`。 -- 返回 `announcement_id,content,author,version,published_at`。仅有权限角色可写,使用乐观锁防覆盖。 +- 返回 `announcement_id,topic,content,author,version,published_at,permissions.can_edit`;即使暂无公告也返回空公告和编辑权限。仅有权限角色可写,使用乐观锁防覆盖。 - 发布成功后服务端生成 `group.announcement.updated` 群业务事件;顶部摘要使用接口返回数据,不解析聊天文本猜测。 ### 6.8 群公告状态/邀请进群申请同时存在.png diff --git a/server/im_http.go b/server/im_http.go index a8eb410..95252bf 100644 --- a/server/im_http.go +++ b/server/im_http.go @@ -5224,7 +5224,7 @@ func serveIMGroupAnnouncements(wrt http.ResponseWriter, req *http.Request, topic logs.Warn.Println("im group announcement latest: returning empty after store error", principal.TenantID, topic, err) ann = nil } - writeIMSuccess(wrt, req, groupAnnouncementPayload(ann)) + writeIMSuccess(wrt, req, groupAnnouncementPayloadWithPermissions(topic, ann, selfSub)) case tail == "announcements/current" && req.Method == http.MethodPut: if !(selfSub.ModeGiven & selfSub.ModeWant).IsAdmin() { writeIMError(wrt, req, http.StatusForbidden, 30005, imReasonPermissionDenied, "permission denied", nil) @@ -5257,7 +5257,7 @@ func serveIMGroupAnnouncements(wrt http.ResponseWriter, req *http.Request, topic return } dispatchIMGroupEvents(principal.TenantID) - writeIMSuccess(wrt, req, groupAnnouncementPayload(announcement)) + writeIMSuccess(wrt, req, groupAnnouncementPayloadWithPermissions(topic, announcement, selfSub)) case strings.HasPrefix(tail, "announcements/") && strings.HasSuffix(tail, "/read") && req.Method == http.MethodPost: announcementID := strings.TrimSuffix(strings.TrimPrefix(tail, "announcements/"), "/read") now := time.Now().UTC().Round(time.Millisecond) @@ -5272,7 +5272,7 @@ func serveIMGroupAnnouncements(wrt http.ResponseWriter, req *http.Request, topic logs.Warn.Println("im group announcement get: returning empty after store error", principal.TenantID, topic, err) ann = nil } - writeIMSuccess(wrt, req, groupAnnouncementPayload(ann)) + writeIMSuccess(wrt, req, groupAnnouncementPayloadWithPermissions(topic, ann, selfSub)) default: writeIMError(wrt, req, http.StatusMethodNotAllowed, 30002, imReasonMethodNotAllowed, "method not allowed", nil) } @@ -7097,6 +7097,19 @@ func groupAnnouncementPayload(ann *types.GroupAnnouncement) map[string]any { } } +func groupAnnouncementPayloadWithPermissions(topic string, ann *types.GroupAnnouncement, selfSub *types.Subscription) map[string]any { + data := groupAnnouncementPayload(ann) + data["topic"] = topic + access := types.ModeNone + if selfSub != nil { + access = selfSub.ModeGiven & selfSub.ModeWant + } + data["permissions"] = map[string]any{ + "can_edit": access.IsAdmin(), + } + return data +} + func groupMutePayload(mute types.GroupMute) map[string]any { data := map[string]any{ "user_id": mute.UserID.UserId(), diff --git a/server/im_http_test.go b/server/im_http_test.go index 4d55c29..d339ca8 100644 --- a/server/im_http_test.go +++ b/server/im_http_test.go @@ -4350,6 +4350,39 @@ func TestServeIMGroupAnnouncementGovernanceJoinRequest(t *testing.T) { } } +func TestServeIMGroupAnnouncementLatestIncludesEditPermission(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + oldStore := store.Store + oldGroupBusiness := store.GroupBusiness + oldSubs := store.Subs + mockStore := expectIMBearer(t, ctrl, "token-1", 7, 42) + store.Store = mockStore + subs := mock_store.NewMockSubsPersistenceInterface(ctrl) + store.Subs = subs + store.GroupBusiness = &testGroupBusinessStore{} + defer func() { + store.Store = oldStore + store.Subs = oldSubs + store.GroupBusiness = oldGroupBusiness + }() + selfSub := &types.Subscription{TenantID: 7, User: types.Uid(42).String(), Topic: "grpAlpha", ModeWant: types.ModeCFull, ModeGiven: types.ModeCFull} + subs.EXPECT().Get(types.TenantID(7), "grpAlpha", types.Uid(42), false).Return(selfSub, nil) + + req := httptest.NewRequest(http.MethodGet, "/v1/im/groups/grpAlpha/announcements/latest", nil) + req.Header.Set("Authorization", "Bearer token-1") + rec := httptest.NewRecorder() + serveIMGroupResource(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("latest announcement status: got %d body=%s", rec.Code, rec.Body.String()) + } + data := decodeIMHTTPResponse(t, rec).Data.(map[string]any) + permissions := data["permissions"].(map[string]any) + if data["topic"] != "grpAlpha" || data["announcement_id"] != "" || permissions["can_edit"] != true { + t.Fatalf("unexpected latest announcement response: %#v", data) + } +} + func TestServeIMGroupAdminsMutesAndOwnerTransfer(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish()