fix: 优化群成员分页并补齐头像资料
This commit is contained in:
@@ -3340,8 +3340,11 @@ func (a *adapter) UsersForTopic(topic string, keepDeleted bool, opts *t.QueryOpt
|
||||
|
||||
// Fetch all subscribed users. The number of users is not large.
|
||||
q := `SELECT s.createdat,s.updatedat,s.deletedat,s.userid,s.topic,s.delid,s.recvseqid,
|
||||
s.readseqid,s.modewant,s.modegiven,u.public,u.trusted,u.lastseen,u.useragent,s.private
|
||||
s.readseqid,s.modewant,s.modegiven,u.public,u.trusted,u.lastseen,u.useragent,s.private,
|
||||
p.nickname AS profile_nickname,p.avatar_file_id AS profile_avatar_file_id,p.signature AS profile_signature,
|
||||
p.gender AS profile_gender,p.region AS profile_region,p.public_account AS profile_public_account
|
||||
FROM im_subscriptions AS s JOIN im_users AS u ON s.userid=u.id
|
||||
LEFT JOIN im_user_profiles AS p ON p.user_id=s.userid
|
||||
WHERE s.topic=?`
|
||||
args := []any{topic}
|
||||
if !keepDeleted {
|
||||
@@ -3394,18 +3397,21 @@ func (a *adapter) UsersForTopic(topic string, keepDeleted bool, opts *t.QueryOpt
|
||||
var lastSeen sql.NullTime
|
||||
var userAgent string
|
||||
var public, trusted any
|
||||
var profileNickname, profileAvatarFileID, profileSignature, profileGender, profileRegion, profilePublicAccount sql.NullString
|
||||
for rows.Next() {
|
||||
if err = rows.Scan(
|
||||
&sub.CreatedAt, &sub.UpdatedAt, &sub.DeletedAt,
|
||||
&sub.User, &sub.Topic, &sub.DelId, &sub.RecvSeqId,
|
||||
&sub.ReadSeqId, &sub.ModeWant, &sub.ModeGiven,
|
||||
&public, &trusted, &lastSeen, &userAgent, &sub.Private); err != nil {
|
||||
&public, &trusted, &lastSeen, &userAgent, &sub.Private,
|
||||
&profileNickname, &profileAvatarFileID, &profileSignature,
|
||||
&profileGender, &profileRegion, &profilePublicAccount); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
sub.User = common.EncodeUidString(sub.User).String()
|
||||
sub.Private = common.FromJSON(sub.Private)
|
||||
sub.SetPublic(common.FromJSON(public))
|
||||
sub.SetPublic(profilePublicFromFields(common.FromJSON(public), profileNickname, profileAvatarFileID, profileSignature, profileGender, profileRegion, profilePublicAccount))
|
||||
sub.SetTrusted(common.FromJSON(trusted))
|
||||
sub.SetLastSeenAndUA(&lastSeen.Time, userAgent)
|
||||
subs = append(subs, sub)
|
||||
|
||||
@@ -1077,8 +1077,11 @@ func (a *adapter) TenantUsersForTopic(id t.TenantID, topic string, keepDeleted b
|
||||
}
|
||||
tcat := t.GetTopicCat(topic)
|
||||
q := `SELECT s.tenant_id,s.createdat,s.updatedat,s.deletedat,s.userid,s.topic,s.delid,s.recvseqid,
|
||||
s.readseqid,s.modewant,s.modegiven,u.public,u.trusted,u.lastseen,u.useragent,s.private
|
||||
s.readseqid,s.modewant,s.modegiven,u.public,u.trusted,u.lastseen,u.useragent,s.private,
|
||||
p.nickname AS profile_nickname,p.avatar_file_id AS profile_avatar_file_id,p.signature AS profile_signature,
|
||||
p.gender AS profile_gender,p.region AS profile_region,p.public_account AS profile_public_account
|
||||
FROM im_subscriptions AS s JOIN im_users AS u ON s.tenant_id=u.tenant_id AND s.userid=u.id
|
||||
LEFT JOIN im_user_profiles AS p ON p.tenant_id=s.tenant_id AND p.user_id=s.userid
|
||||
WHERE s.tenant_id=? AND s.topic=?`
|
||||
args := []any{id, topic}
|
||||
if !keepDeleted {
|
||||
@@ -1119,14 +1122,17 @@ func (a *adapter) TenantUsersForTopic(id t.TenantID, topic string, keepDeleted b
|
||||
var sub t.Subscription
|
||||
var lastSeen sql.NullTime
|
||||
var public, trusted any
|
||||
var profileNickname, profileAvatarFileID, profileSignature, profileGender, profileRegion, profilePublicAccount sql.NullString
|
||||
if err = rows.Scan(&sub.TenantID, &sub.CreatedAt, &sub.UpdatedAt, &sub.DeletedAt,
|
||||
&sub.User, &sub.Topic, &sub.DelId, &sub.RecvSeqId, &sub.ReadSeqId, &sub.ModeWant, &sub.ModeGiven,
|
||||
&public, &trusted, &lastSeen, &userAgent, &sub.Private); err != nil {
|
||||
&public, &trusted, &lastSeen, &userAgent, &sub.Private,
|
||||
&profileNickname, &profileAvatarFileID, &profileSignature,
|
||||
&profileGender, &profileRegion, &profilePublicAccount); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sub.User = common.EncodeUidString(sub.User).String()
|
||||
sub.Private = common.FromJSON(sub.Private)
|
||||
sub.SetPublic(common.FromJSON(public))
|
||||
sub.SetPublic(profilePublicFromFields(common.FromJSON(public), profileNickname, profileAvatarFileID, profileSignature, profileGender, profileRegion, profilePublicAccount))
|
||||
sub.SetTrusted(common.FromJSON(trusted))
|
||||
sub.SetLastSeenAndUA(&lastSeen.Time, userAgent)
|
||||
subs = append(subs, sub)
|
||||
|
||||
@@ -248,30 +248,49 @@ func (a *adapter) TenantGroupMemberList(id t.TenantID, topic string, opts t.Grou
|
||||
if opts.Limit <= 0 || opts.Limit > 100 {
|
||||
opts.Limit = 30
|
||||
}
|
||||
where := []string{"topic=?", "member_status=?"}
|
||||
memberIndex := "idx_im_group_members_topic_status"
|
||||
where := []string{"gm.topic=?", "gm.member_status=?"}
|
||||
args := []any{topic, t.GroupMemberStatusActive}
|
||||
if opts.Role != "" {
|
||||
where = append(where, "role=?")
|
||||
memberIndex = "idx_im_group_members_topic_role"
|
||||
where = append(where, "gm.role=?")
|
||||
args = append(args, opts.Role)
|
||||
}
|
||||
if opts.Query != "" {
|
||||
where = append(where, "nickname LIKE ?")
|
||||
args = append(args, "%"+opts.Query+"%")
|
||||
like := "%" + opts.Query + "%"
|
||||
where = append(where, `(gm.nickname LIKE ? OR p.nickname LIKE ? OR p.public_account LIKE ?
|
||||
OR JSON_UNQUOTE(JSON_EXTRACT(u.public,'$.fn')) LIKE ?
|
||||
OR JSON_UNQUOTE(JSON_EXTRACT(u.public,'$.name')) LIKE ?
|
||||
OR JSON_UNQUOTE(JSON_EXTRACT(u.public,'$.display_name')) LIKE ?
|
||||
OR u.username LIKE ?)`)
|
||||
args = append(args, like, like, like, like, like, like, like)
|
||||
}
|
||||
if opts.Cursor != "" {
|
||||
parsed, err := strconv.ParseInt(opts.Cursor, 10, 64)
|
||||
if err != nil || parsed <= 0 {
|
||||
if err != nil || parsed < 0 {
|
||||
return nil, "", t.ErrMalformed
|
||||
}
|
||||
where = append(where, "id>?")
|
||||
args = append(args, parsed)
|
||||
if parsed > 0 {
|
||||
where = append(where, "gm.id>?")
|
||||
args = append(args, parsed)
|
||||
}
|
||||
}
|
||||
if opts.Mentionable {
|
||||
where = append(where, "INSTR(s.modewant,'J')>0 AND INSTR(s.modegiven,'J')>0")
|
||||
}
|
||||
args = append(args, opts.Limit+1)
|
||||
ctx, cancel := a.getContext()
|
||||
if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
rows, err := a.db.QueryxContext(ctx, "SELECT * FROM im_group_members WHERE "+strings.Join(where, " AND ")+" ORDER BY id LIMIT ?", args...)
|
||||
rows, err := a.db.QueryxContext(ctx, `SELECT STRAIGHT_JOIN gm.*,s.modewant,s.modegiven,u.public,u.trusted,
|
||||
p.nickname AS profile_nickname,p.avatar_file_id AS profile_avatar_file_id,p.signature AS profile_signature,
|
||||
p.gender AS profile_gender,p.region AS profile_region,p.public_account AS profile_public_account
|
||||
FROM im_group_members AS gm FORCE INDEX (`+memberIndex+`)
|
||||
JOIN im_subscriptions AS s ON s.topic=gm.topic AND s.userid=gm.user_id AND s.deletedat IS NULL
|
||||
JOIN im_users AS u ON u.id=gm.user_id AND u.state!=?
|
||||
LEFT JOIN im_user_profiles AS p ON p.user_id=gm.user_id
|
||||
WHERE `+strings.Join(where, " AND ")+` ORDER BY gm.id LIMIT ?`, append([]any{t.StateDeleted}, args...)...)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@@ -279,15 +298,30 @@ func (a *adapter) TenantGroupMemberList(id t.TenantID, topic string, opts t.Grou
|
||||
items := make([]t.GroupMember, 0, opts.Limit)
|
||||
next := ""
|
||||
for rows.Next() {
|
||||
var item t.GroupMember
|
||||
if err = rows.StructScan(&item); err != nil {
|
||||
row := struct {
|
||||
t.GroupMember
|
||||
Public any
|
||||
Trusted any
|
||||
ProfileNickname sql.NullString `db:"profile_nickname"`
|
||||
ProfileAvatarFileID sql.NullString `db:"profile_avatar_file_id"`
|
||||
ProfileSignature sql.NullString `db:"profile_signature"`
|
||||
ProfileGender sql.NullString `db:"profile_gender"`
|
||||
ProfileRegion sql.NullString `db:"profile_region"`
|
||||
ProfilePublicAccount sql.NullString `db:"profile_public_account"`
|
||||
}{}
|
||||
if err = rows.StructScan(&row); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
item := row.GroupMember
|
||||
scanGroupMember(&item)
|
||||
if len(items) == opts.Limit {
|
||||
next = strconv.FormatInt(item.ID, 10)
|
||||
if len(items) > 0 {
|
||||
next = strconv.FormatInt(items[len(items)-1].ID, 10)
|
||||
}
|
||||
continue
|
||||
}
|
||||
item.Public = profilePublicFromFields(common.FromJSON(row.Public), row.ProfileNickname, row.ProfileAvatarFileID, row.ProfileSignature, row.ProfileGender, row.ProfileRegion, row.ProfilePublicAccount)
|
||||
item.Trusted = common.FromJSON(row.Trusted)
|
||||
items = append(items, item)
|
||||
}
|
||||
return items, next, rows.Err()
|
||||
|
||||
+53
-29
@@ -4807,8 +4807,14 @@ func serveIMGroupMembers(wrt http.ResponseWriter, req *http.Request, topic strin
|
||||
return
|
||||
}
|
||||
limit := parseIMLimit(req.URL.Query().Get("limit"), 30, 100)
|
||||
cursor, err := parseIMNonNegativeCursor(req.URL.Query().Get("cursor"))
|
||||
if err != nil {
|
||||
cursor := strings.TrimSpace(req.URL.Query().Get("cursor"))
|
||||
if cursor != "" {
|
||||
if _, err := strconv.ParseInt(cursor, 10, 64); err != nil {
|
||||
writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "malformed request", map[string]any{"field": "cursor"})
|
||||
return
|
||||
}
|
||||
}
|
||||
if parsed, err := parseIMNonNegativeCursor(cursor); err != nil || parsed < 0 {
|
||||
writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "malformed request", map[string]any{"field": "cursor"})
|
||||
return
|
||||
}
|
||||
@@ -4822,25 +4828,22 @@ func serveIMGroupMembers(wrt http.ResponseWriter, req *http.Request, topic strin
|
||||
writeIMSuccess(wrt, req, cached)
|
||||
return
|
||||
}
|
||||
members, err := store.Topics.GetUsers(principal.TenantID, topic, &types.QueryOpt{Limit: 1000})
|
||||
members, nextCursor, err := store.GroupBusiness.ListMembers(principal.TenantID, topic, types.GroupMemberListOptions{
|
||||
Role: roleFilter,
|
||||
EligibleRole: eligibleRole,
|
||||
Query: query,
|
||||
Cursor: cursor,
|
||||
Limit: limit,
|
||||
Mentionable: mentionableOnly,
|
||||
})
|
||||
if err != nil {
|
||||
writeIMError(wrt, req, http.StatusInternalServerError, 90000, imReasonInternal, "internal error", nil)
|
||||
return
|
||||
}
|
||||
items := make([]map[string]any, 0, limit)
|
||||
seen := 0
|
||||
nextCursor := ""
|
||||
for _, member := range members {
|
||||
access := member.ModeGiven & member.ModeWant
|
||||
mentionable := access.IsJoiner() && access.IsReader()
|
||||
role := groupMemberRole(access)
|
||||
if roleFilter != "" && role != roleFilter {
|
||||
continue
|
||||
}
|
||||
if mentionableOnly && !mentionable {
|
||||
continue
|
||||
}
|
||||
item := groupMemberPayload(member, mentionable)
|
||||
access := groupMemberAccess(member)
|
||||
item := groupBusinessMemberPayload(member)
|
||||
if eligibleRole != "" {
|
||||
eligible, reason := groupRoleEligibility(selfAccess, access, eligibleRole)
|
||||
item["eligible"] = eligible
|
||||
@@ -4855,19 +4858,7 @@ func serveIMGroupMembers(wrt http.ResponseWriter, req *http.Request, topic strin
|
||||
item["ineligible_reason"] = reason
|
||||
}
|
||||
}
|
||||
if query != "" && !groupMemberMatches(item, query) {
|
||||
continue
|
||||
}
|
||||
if seen < cursor {
|
||||
seen++
|
||||
continue
|
||||
}
|
||||
if len(items) == limit {
|
||||
nextCursor = strconv.Itoa(seen)
|
||||
break
|
||||
}
|
||||
items = append(items, item)
|
||||
seen++
|
||||
}
|
||||
permissions := map[string]any{
|
||||
"can_mention_all": selfAccess.IsAdmin(),
|
||||
@@ -7320,6 +7311,38 @@ func groupMemberPayload(sub types.Subscription, mentionable bool) map[string]any
|
||||
return data
|
||||
}
|
||||
|
||||
func groupBusinessMemberPayload(member types.GroupMember) map[string]any {
|
||||
access := groupMemberAccess(member)
|
||||
user := &types.User{ObjHeader: types.ObjHeader{Id: member.UserID.String()}, Public: member.Public, Trusted: member.Trusted}
|
||||
data := userSummaryPayload(user, "")
|
||||
if nickname := strings.TrimSpace(member.Nickname); nickname != "" {
|
||||
data["group_nickname"] = nickname
|
||||
}
|
||||
data["role"] = groupMemberRole(access)
|
||||
data["mentionable"] = access.IsJoiner() && access.IsReader()
|
||||
data["acs"] = map[string]any{
|
||||
"mode": access.String(),
|
||||
"want": member.ModeWant.String(),
|
||||
"given": member.ModeGiven.String(),
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func groupMemberAccess(member types.GroupMember) types.AccessMode {
|
||||
access := member.ModeGiven & member.ModeWant
|
||||
if access != types.ModeNone {
|
||||
return access
|
||||
}
|
||||
switch member.Role {
|
||||
case types.GroupMemberRoleOwner:
|
||||
return types.ModeCFull
|
||||
case types.GroupMemberRoleAdmin:
|
||||
return types.ModeCPublic | types.ModeApprove
|
||||
default:
|
||||
return types.ModeCPublic
|
||||
}
|
||||
}
|
||||
|
||||
func groupMemberProfilePayload(topic string, sub *types.Subscription, user *types.User, ctx *types.RelationshipContext) map[string]any {
|
||||
data := groupMemberPayload(*sub, true)
|
||||
data["topic"] = topic
|
||||
@@ -7771,16 +7794,17 @@ func groupMemberMatches(item map[string]any, query string) bool {
|
||||
}
|
||||
|
||||
func groupMemberPageCacheKey(topic string, uid types.Uid, access types.AccessMode, query, roleFilter, eligibleRole string,
|
||||
limit, cursor int, mentionableOnly, muteEligible bool) string {
|
||||
limit int, cursor string, mentionableOnly, muteEligible bool) string {
|
||||
return strings.Join([]string{
|
||||
topic,
|
||||
"v3",
|
||||
uid.UserId(),
|
||||
access.String(),
|
||||
strings.ToLower(strings.TrimSpace(query)),
|
||||
strings.TrimSpace(roleFilter),
|
||||
strings.TrimSpace(eligibleRole),
|
||||
strconv.Itoa(limit),
|
||||
strconv.Itoa(cursor),
|
||||
strings.TrimSpace(cursor),
|
||||
strconv.FormatBool(mentionableOnly),
|
||||
strconv.FormatBool(muteEligible),
|
||||
}, "|")
|
||||
|
||||
+31
-13
@@ -345,6 +345,9 @@ type testGroupBusinessStore struct {
|
||||
mutes []types.GroupMute
|
||||
files []types.GroupFile
|
||||
invitations []types.GroupInvitation
|
||||
listMembers []types.GroupMember
|
||||
listMembersOpts types.GroupMemberListOptions
|
||||
listMembersNext string
|
||||
updatedSettings types.GroupSettingsPatch
|
||||
updatedMembers []types.GroupMemberPatch
|
||||
updatedRoleMode types.AccessMode
|
||||
@@ -402,8 +405,9 @@ func (s *testGroupBusinessStore) GetMember(_ types.TenantID, topic string, uid t
|
||||
return &types.GroupMember{Topic: topic, UserID: uid, Role: types.GroupMemberRoleMember, MemberStatus: types.GroupMemberStatusActive, Version: 1}, nil
|
||||
}
|
||||
|
||||
func (s *testGroupBusinessStore) ListMembers(_ types.TenantID, _ string, _ types.GroupMemberListOptions) ([]types.GroupMember, string, error) {
|
||||
return nil, "", nil
|
||||
func (s *testGroupBusinessStore) ListMembers(_ types.TenantID, _ string, opts types.GroupMemberListOptions) ([]types.GroupMember, string, error) {
|
||||
s.listMembersOpts = opts
|
||||
return s.listMembers, s.listMembersNext, nil
|
||||
}
|
||||
|
||||
func (s *testGroupBusinessStore) UpdateMember(_ types.TenantID, topic string, uid types.Uid, patch types.GroupMemberPatch) (*types.GroupMember, error) {
|
||||
@@ -3877,14 +3881,14 @@ func TestServeIMGroupMembers(t *testing.T) {
|
||||
defer ctrl.Finish()
|
||||
expectIMBearer(t, ctrl, "token-1", 7, 42)
|
||||
oldSubs := store.Subs
|
||||
oldTopics := store.Topics
|
||||
oldGroupBusiness := store.GroupBusiness
|
||||
subs := mock_store.NewMockSubsPersistenceInterface(ctrl)
|
||||
topics := mock_store.NewMockTopicsPersistenceInterface(ctrl)
|
||||
store.Subs = subs
|
||||
store.Topics = topics
|
||||
groupStore := &testGroupBusinessStore{listMembersNext: "101"}
|
||||
store.GroupBusiness = groupStore
|
||||
defer func() {
|
||||
store.Subs = oldSubs
|
||||
store.Topics = oldTopics
|
||||
store.GroupBusiness = oldGroupBusiness
|
||||
}()
|
||||
|
||||
subs.EXPECT().Get(types.TenantID(7), "grpMention", types.Uid(42), false).Return(&types.Subscription{
|
||||
@@ -3894,13 +3898,18 @@ func TestServeIMGroupMembers(t *testing.T) {
|
||||
ModeWant: types.ModeCFull,
|
||||
ModeGiven: types.ModeCFull,
|
||||
}, nil)
|
||||
alice := types.Subscription{User: types.Uid(101).UserId(), ModeWant: types.ModeCPublic, ModeGiven: types.ModeCPublic}
|
||||
alice.SetPublic(map[string]any{"display_name": "Alice"})
|
||||
bob := types.Subscription{User: types.Uid(102).UserId(), ModeWant: types.ModeJoin, ModeGiven: types.ModeJoin}
|
||||
bob.SetPublic(map[string]any{"display_name": "Bob"})
|
||||
topics.EXPECT().GetUsers(types.TenantID(7), "grpMention", gomock.Any()).Return([]types.Subscription{alice, bob}, nil)
|
||||
groupStore.listMembers = []types.GroupMember{{
|
||||
ID: 101,
|
||||
Topic: "grpMention",
|
||||
UserID: types.Uid(101),
|
||||
Role: types.GroupMemberRoleMember,
|
||||
ModeWant: types.ModeCPublic,
|
||||
ModeGiven: types.ModeCPublic,
|
||||
Public: map[string]any{"display_name": "Alice", "avatar_file_id": "alice.jpg"},
|
||||
MemberStatus: types.GroupMemberStatusActive,
|
||||
}}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/v1/im/groups/grpMention/members?query=ali&mentionable=true", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/v1/im/groups/grpMention/members?query=ali&mentionable=true&limit=20&cursor=99", nil)
|
||||
req.Header.Set("Authorization", "Bearer token-1")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
@@ -3911,12 +3920,21 @@ func TestServeIMGroupMembers(t *testing.T) {
|
||||
}
|
||||
data := decodeIMHTTPResponse(t, rec).Data.(map[string]any)
|
||||
items := data["items"].([]any)
|
||||
if len(items) != 1 || items[0].(map[string]any)["display_name"] != "Alice" || items[0].(map[string]any)["mentionable"] != true {
|
||||
if len(items) != 1 || items[0].(map[string]any)["display_name"] != "Alice" ||
|
||||
items[0].(map[string]any)["avatar_file_id"] != "alice.jpg" ||
|
||||
items[0].(map[string]any)["mentionable"] != true {
|
||||
t.Fatalf("unexpected members response: %#v", data)
|
||||
}
|
||||
if data["next_cursor"] != "101" {
|
||||
t.Fatalf("unexpected cursor: %#v", data)
|
||||
}
|
||||
if data["permissions"].(map[string]any)["can_mention_all"] != true {
|
||||
t.Fatalf("expected can_mention_all permission: %#v", data)
|
||||
}
|
||||
if groupStore.listMembersOpts.Query != "ali" || !groupStore.listMembersOpts.Mentionable ||
|
||||
groupStore.listMembersOpts.Limit != 20 || groupStore.listMembersOpts.Cursor != "99" {
|
||||
t.Fatalf("members options were not forwarded: %#v", groupStore.listMembersOpts)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroupMemberPayloadUserIDFormats(t *testing.T) {
|
||||
|
||||
+15
-2
@@ -2904,7 +2904,7 @@ func refreshUserProfileProjections(tenantID types.TenantID, uid types.Uid, profi
|
||||
items, nextCursor, err := Relations.ListFriends(tenantID, uid, "", "", cursor, 100)
|
||||
if err != nil {
|
||||
logs.Warn.Printf("profile %s: refresh friend subscription projections failed: %v", uid, err)
|
||||
return
|
||||
break
|
||||
}
|
||||
for _, item := range items {
|
||||
if item.Friendship.Topic == "" || item.Friendship.PeerID.IsZero() {
|
||||
@@ -2915,10 +2915,23 @@ func refreshUserProfileProjections(tenantID types.TenantID, uid types.Uid, profi
|
||||
}
|
||||
}
|
||||
if nextCursor == "" {
|
||||
return
|
||||
break
|
||||
}
|
||||
cursor = nextCursor
|
||||
}
|
||||
if topics, err := Users.GetTopics(tenantID, uid, &types.QueryOpt{Limit: 1000}); err != nil {
|
||||
logs.Warn.Printf("profile %s: refresh group member projections failed: %v", uid, err)
|
||||
} else {
|
||||
for _, sub := range topics {
|
||||
if types.GetTopicCat(sub.Topic) != types.TopicCatGrp {
|
||||
continue
|
||||
}
|
||||
if err := Subs.Update(tenantID, sub.Topic, uid, map[string]any{"Public": public}); err != nil {
|
||||
logs.Warn.Printf("profile %s: refresh group subscription %s public failed: %v", uid, sub.Topic, err)
|
||||
}
|
||||
invalidateGroupMemberPages(tenantID, sub.Topic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func userProfilePublicProjection(profile *types.UserProfile) map[string]any {
|
||||
|
||||
@@ -73,6 +73,10 @@ type GroupMember struct {
|
||||
Role string `db:"role"`
|
||||
MemberStatus string `db:"member_status"`
|
||||
Nickname string `db:"nickname"`
|
||||
ModeWant AccessMode `db:"modewant"`
|
||||
ModeGiven AccessMode `db:"modegiven"`
|
||||
Public any `db:"public"`
|
||||
Trusted any `db:"trusted"`
|
||||
Pinned bool `db:"pinned"`
|
||||
MuteNotifications bool `db:"mute_notifications"`
|
||||
JoinSource string `db:"join_source"`
|
||||
@@ -105,6 +109,7 @@ type GroupMemberListOptions struct {
|
||||
Query string
|
||||
Cursor string
|
||||
Limit int
|
||||
Mentionable bool
|
||||
}
|
||||
|
||||
type GroupInvitation struct {
|
||||
|
||||
Reference in New Issue
Block a user