include join request user profiles
This commit is contained in:
+2
-2
@@ -1627,7 +1627,7 @@ X-MeetingIM-APIKey: <预置的普通 API Key>
|
||||
|
||||
**状态:新增。**
|
||||
|
||||
- `GET /v1/im/groups/{topic}/join-requests?status=pending&cursor=` 返回申请人、邀请人、来源、时间、状态和 `version`。
|
||||
- `GET /v1/im/groups/{topic}/join-requests?status=pending&cursor=` 返回申请人、邀请人、来源、时间、状态和 `version`;申请人展示字段直接带 `display_name/avatar_file_id/user`,邀请人展示字段带 `inviter_display_name/inviter_avatar_file_id/inviter`。
|
||||
- 同意:`POST /v1/im/groups/{topic}/join-requests/{id}/accept`;拒绝:`POST /v1/im/groups/{topic}/join-requests/{id}/reject`,带幂等键与 `version`。
|
||||
- 仅群主/管理员可处理;同意时原子校验群成员上限。并发处理返回 `REQUEST_ALREADY_HANDLED` 和最终状态。
|
||||
|
||||
@@ -1886,7 +1886,7 @@ X-MeetingIM-APIKey: <预置的普通 API Key>
|
||||
| `POST /v1/im/groups/{topic}/invitations` | `{"user_ids":["usr..."],"message":"进群一起讨论"}` | `{"topic":"grp...","items":[{"user_id":"usr...","status":"joined","invitation_id":"ginv_1"}]}` |
|
||||
| `POST /v1/im/group-invitations/{invitation_id}/accept` 待实现 | `{}` | `{"invitation_id":"ginv_1","status":"joined","topic":"grp..."}` |
|
||||
| `POST /v1/im/groups/{topic}/join-requests` | `{"message":"申请入群","source":"qr","qr_token":"token..."}` | `{"request_id":"gjr_1","topic":"grp...","status":"pending","created_at":"2026-08-20T09:00:00Z","version":1}` |
|
||||
| `GET /v1/im/groups/{topic}/join-requests?status=pending&cursor=` | Query only | `{"topic":"grp...","items":[{"request_id":"gjr_1","applicant":{"user_id":"usr...","display_name":"张三"},"status":"pending","source":"qr","created_at":"2026-08-20T09:00:00Z","version":1}],"next_cursor":""}` |
|
||||
| `GET /v1/im/groups/{topic}/join-requests?status=pending&cursor=` | Query only | `{"topic":"grp...","items":[{"request_id":"gjr_1","user_id":"usr...","display_name":"张三","avatar_file_id":"avatar.jpg","user":{"user_id":"usr...","display_name":"张三","avatar_file_id":"avatar.jpg"},"inviter_id":"usr_inviter","inviter_display_name":"李四","inviter_avatar_file_id":"inviter.jpg","inviter":{"user_id":"usr_inviter","display_name":"李四","avatar_file_id":"inviter.jpg"},"status":"pending","source":"invitation","created_at":"2026-08-20T09:00:00Z","version":1}],"next_cursor":""}` |
|
||||
| `POST /v1/im/groups/{topic}/join-requests/{id}/accept` | `{"version":1}` | `{"request_id":"gjr_1","topic":"grp...","status":"accepted","handled_at":"2026-08-20T09:00:00Z","version":2}` |
|
||||
| `POST /v1/im/groups/{topic}/join-requests/{id}/reject` | `{"version":1,"reason":"人数已满"}` | `{"request_id":"gjr_1","topic":"grp...","status":"rejected","handled_at":"2026-08-20T09:00:00Z","version":2}` |
|
||||
| `GET /v1/im/groups/{topic}/summary` | Path topic | `{"topic":"grp...","pending_join_request_count":0,"announcement":{"announcement_id":"ann_1","content":"公告"},"permissions":{"can_manage_members":true}}` |
|
||||
|
||||
+68
-1
@@ -5641,7 +5641,12 @@ func serveIMGroupJoinRequests(wrt http.ResponseWriter, req *http.Request, topic
|
||||
}
|
||||
items = nil
|
||||
}
|
||||
writeIMSuccess(wrt, req, map[string]any{"topic": topic, "items": groupJoinRequestPayloads(items), "next_cursor": nextCursor})
|
||||
payloads, err := groupJoinRequestPayloadsWithUsers(principal.TenantID, items)
|
||||
if err != nil {
|
||||
writeIMRelationError(wrt, req, err)
|
||||
return
|
||||
}
|
||||
writeIMSuccess(wrt, req, map[string]any{"topic": topic, "items": payloads, "next_cursor": nextCursor})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7782,6 +7787,68 @@ func groupJoinRequestPayloads(items []types.GroupJoinRequest) []map[string]any {
|
||||
return out
|
||||
}
|
||||
|
||||
func groupJoinRequestPayloadsWithUsers(tenantID types.TenantID, items []types.GroupJoinRequest) ([]map[string]any, error) {
|
||||
payloads := groupJoinRequestPayloads(items)
|
||||
if len(items) == 0 || store.Users == nil {
|
||||
return payloads, nil
|
||||
}
|
||||
seen := make(map[types.Uid]struct{}, len(items)*2)
|
||||
uids := make([]types.Uid, 0, len(items)*2)
|
||||
for i := range items {
|
||||
for _, uid := range []types.Uid{items[i].ApplicantUserID, items[i].InviterUserID} {
|
||||
if uid.IsZero() {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[uid]; ok {
|
||||
continue
|
||||
}
|
||||
seen[uid] = struct{}{}
|
||||
uids = append(uids, uid)
|
||||
}
|
||||
}
|
||||
if len(uids) == 0 {
|
||||
return payloads, nil
|
||||
}
|
||||
users, err := store.Users.GetAll(tenantID, uids...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
byID := make(map[types.Uid]*types.User, len(users))
|
||||
for i := range users {
|
||||
uid := types.ParseUid(users[i].Id)
|
||||
if uid.IsZero() {
|
||||
uid = types.ParseUserId(users[i].Id)
|
||||
}
|
||||
if uid.IsZero() {
|
||||
continue
|
||||
}
|
||||
byID[uid] = &users[i]
|
||||
}
|
||||
for i := range items {
|
||||
enrichGroupJoinRequestUserPayload(payloads[i], "user", "", items[i].ApplicantUserID, byID)
|
||||
enrichGroupJoinRequestUserPayload(payloads[i], "inviter", "inviter_", items[i].InviterUserID, byID)
|
||||
}
|
||||
return payloads, nil
|
||||
}
|
||||
|
||||
func enrichGroupJoinRequestUserPayload(data map[string]any, objectKey, fieldPrefix string, uid types.Uid, users map[types.Uid]*types.User) {
|
||||
if data == nil || uid.IsZero() {
|
||||
return
|
||||
}
|
||||
user := users[uid]
|
||||
summary := map[string]any{"user_id": uid.UserId(), "display_name": uid.UserId()}
|
||||
if user != nil {
|
||||
summary = userSummaryPayload(user, "")
|
||||
}
|
||||
data[objectKey] = summary
|
||||
if displayName, ok := summary["display_name"]; ok {
|
||||
data[fieldPrefix+"display_name"] = displayName
|
||||
}
|
||||
if avatarFileID, ok := summary["avatar_file_id"]; ok {
|
||||
data[fieldPrefix+"avatar_file_id"] = avatarFileID
|
||||
}
|
||||
}
|
||||
|
||||
func groupAnnouncementPayload(ann *types.GroupAnnouncement) map[string]any {
|
||||
if ann == nil {
|
||||
return map[string]any{
|
||||
|
||||
@@ -4765,16 +4765,20 @@ func TestServeIMGroupInvitationCreatesApprovalRequest(t *testing.T) {
|
||||
mockStore.EXPECT().GetUidString().Return("request")
|
||||
oldSubs := store.Subs
|
||||
oldGroupBusiness := store.GroupBusiness
|
||||
oldUsers := store.Users
|
||||
subs := mock_store.NewMockSubsPersistenceInterface(ctrl)
|
||||
users := mock_store.NewMockUsersPersistenceInterface(ctrl)
|
||||
groupStore := &testGroupBusinessStore{settings: &types.GroupSettings{
|
||||
Topic: "grpAlpha", OwnerUserID: types.Uid(42), Name: "Alpha", GroupStatus: types.GroupStatusActive,
|
||||
JoinApproval: true, MemberInvite: true, MaxAdminCount: groupMaxAdminCount, Version: 1,
|
||||
}}
|
||||
store.Subs = subs
|
||||
store.Users = users
|
||||
store.GroupBusiness = groupStore
|
||||
defer func() {
|
||||
store.Subs = oldSubs
|
||||
store.GroupBusiness = oldGroupBusiness
|
||||
store.Users = oldUsers
|
||||
}()
|
||||
owner := types.Uid(42)
|
||||
target := types.Uid(43)
|
||||
@@ -4817,6 +4821,10 @@ func TestServeIMGroupInvitationCreatesApprovalRequest(t *testing.T) {
|
||||
t.Fatalf("unexpected summary response: %#v", summary)
|
||||
}
|
||||
|
||||
users.EXPECT().GetAll(types.TenantID(7), target, owner).Return([]types.User{
|
||||
{ObjHeader: types.ObjHeader{Id: target.String()}, Public: map[string]any{"display_name": "申请人", "avatar_file_id": "avatar-target.jpg"}},
|
||||
{ObjHeader: types.ObjHeader{Id: owner.String()}, Public: map[string]any{"display_name": "邀请人", "avatar_file_id": "avatar-owner.jpg"}},
|
||||
}, nil)
|
||||
req = httptest.NewRequest(http.MethodGet, "/v1/im/groups/grpAlpha/join-requests?status=pending", nil)
|
||||
req.Header.Set("Authorization", "Bearer token-1")
|
||||
rec = httptest.NewRecorder()
|
||||
@@ -4829,6 +4837,16 @@ func TestServeIMGroupInvitationCreatesApprovalRequest(t *testing.T) {
|
||||
if len(requests) != 1 || requests[0].(map[string]any)["request_id"] != "gjr_request" {
|
||||
t.Fatalf("unexpected join requests response: %#v", list)
|
||||
}
|
||||
requestItem := requests[0].(map[string]any)
|
||||
if requestItem["display_name"] != "申请人" || requestItem["avatar_file_id"] != "avatar-target.jpg" {
|
||||
t.Fatalf("join request applicant profile was not populated: %#v", requestItem)
|
||||
}
|
||||
if requestItem["inviter_display_name"] != "邀请人" || requestItem["inviter_avatar_file_id"] != "avatar-owner.jpg" {
|
||||
t.Fatalf("join request inviter profile was not populated: %#v", requestItem)
|
||||
}
|
||||
if user := requestItem["user"].(map[string]any); user["display_name"] != "申请人" || user["avatar_file_id"] != "avatar-target.jpg" {
|
||||
t.Fatalf("join request user object was not populated: %#v", user)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeIMGroupInvitationAccept(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user