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.
|
// 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,
|
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
|
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=?`
|
WHERE s.topic=?`
|
||||||
args := []any{topic}
|
args := []any{topic}
|
||||||
if !keepDeleted {
|
if !keepDeleted {
|
||||||
@@ -3394,18 +3397,21 @@ func (a *adapter) UsersForTopic(topic string, keepDeleted bool, opts *t.QueryOpt
|
|||||||
var lastSeen sql.NullTime
|
var lastSeen sql.NullTime
|
||||||
var userAgent string
|
var userAgent string
|
||||||
var public, trusted any
|
var public, trusted any
|
||||||
|
var profileNickname, profileAvatarFileID, profileSignature, profileGender, profileRegion, profilePublicAccount sql.NullString
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
if err = rows.Scan(
|
if err = rows.Scan(
|
||||||
&sub.CreatedAt, &sub.UpdatedAt, &sub.DeletedAt,
|
&sub.CreatedAt, &sub.UpdatedAt, &sub.DeletedAt,
|
||||||
&sub.User, &sub.Topic, &sub.DelId, &sub.RecvSeqId,
|
&sub.User, &sub.Topic, &sub.DelId, &sub.RecvSeqId,
|
||||||
&sub.ReadSeqId, &sub.ModeWant, &sub.ModeGiven,
|
&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
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
sub.User = common.EncodeUidString(sub.User).String()
|
sub.User = common.EncodeUidString(sub.User).String()
|
||||||
sub.Private = common.FromJSON(sub.Private)
|
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.SetTrusted(common.FromJSON(trusted))
|
||||||
sub.SetLastSeenAndUA(&lastSeen.Time, userAgent)
|
sub.SetLastSeenAndUA(&lastSeen.Time, userAgent)
|
||||||
subs = append(subs, sub)
|
subs = append(subs, sub)
|
||||||
|
|||||||
@@ -1077,8 +1077,11 @@ func (a *adapter) TenantUsersForTopic(id t.TenantID, topic string, keepDeleted b
|
|||||||
}
|
}
|
||||||
tcat := t.GetTopicCat(topic)
|
tcat := t.GetTopicCat(topic)
|
||||||
q := `SELECT s.tenant_id,s.createdat,s.updatedat,s.deletedat,s.userid,s.topic,s.delid,s.recvseqid,
|
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
|
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=?`
|
WHERE s.tenant_id=? AND s.topic=?`
|
||||||
args := []any{id, topic}
|
args := []any{id, topic}
|
||||||
if !keepDeleted {
|
if !keepDeleted {
|
||||||
@@ -1119,14 +1122,17 @@ func (a *adapter) TenantUsersForTopic(id t.TenantID, topic string, keepDeleted b
|
|||||||
var sub t.Subscription
|
var sub t.Subscription
|
||||||
var lastSeen sql.NullTime
|
var lastSeen sql.NullTime
|
||||||
var public, trusted any
|
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,
|
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,
|
&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
|
return nil, err
|
||||||
}
|
}
|
||||||
sub.User = common.EncodeUidString(sub.User).String()
|
sub.User = common.EncodeUidString(sub.User).String()
|
||||||
sub.Private = common.FromJSON(sub.Private)
|
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.SetTrusted(common.FromJSON(trusted))
|
||||||
sub.SetLastSeenAndUA(&lastSeen.Time, userAgent)
|
sub.SetLastSeenAndUA(&lastSeen.Time, userAgent)
|
||||||
subs = append(subs, sub)
|
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 {
|
if opts.Limit <= 0 || opts.Limit > 100 {
|
||||||
opts.Limit = 30
|
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}
|
args := []any{topic, t.GroupMemberStatusActive}
|
||||||
if opts.Role != "" {
|
if opts.Role != "" {
|
||||||
where = append(where, "role=?")
|
memberIndex = "idx_im_group_members_topic_role"
|
||||||
|
where = append(where, "gm.role=?")
|
||||||
args = append(args, opts.Role)
|
args = append(args, opts.Role)
|
||||||
}
|
}
|
||||||
if opts.Query != "" {
|
if opts.Query != "" {
|
||||||
where = append(where, "nickname LIKE ?")
|
like := "%" + opts.Query + "%"
|
||||||
args = append(args, "%"+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 != "" {
|
if opts.Cursor != "" {
|
||||||
parsed, err := strconv.ParseInt(opts.Cursor, 10, 64)
|
parsed, err := strconv.ParseInt(opts.Cursor, 10, 64)
|
||||||
if err != nil || parsed <= 0 {
|
if err != nil || parsed < 0 {
|
||||||
return nil, "", t.ErrMalformed
|
return nil, "", t.ErrMalformed
|
||||||
}
|
}
|
||||||
where = append(where, "id>?")
|
if parsed > 0 {
|
||||||
args = append(args, parsed)
|
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)
|
args = append(args, opts.Limit+1)
|
||||||
ctx, cancel := a.getContext()
|
ctx, cancel := a.getContext()
|
||||||
if cancel != nil {
|
if cancel != nil {
|
||||||
defer cancel()
|
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 {
|
if err != nil {
|
||||||
return nil, "", err
|
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)
|
items := make([]t.GroupMember, 0, opts.Limit)
|
||||||
next := ""
|
next := ""
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var item t.GroupMember
|
row := struct {
|
||||||
if err = rows.StructScan(&item); err != nil {
|
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
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
item := row.GroupMember
|
||||||
scanGroupMember(&item)
|
scanGroupMember(&item)
|
||||||
if len(items) == opts.Limit {
|
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
|
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)
|
items = append(items, item)
|
||||||
}
|
}
|
||||||
return items, next, rows.Err()
|
return items, next, rows.Err()
|
||||||
|
|||||||
+53
-29
@@ -4807,8 +4807,14 @@ func serveIMGroupMembers(wrt http.ResponseWriter, req *http.Request, topic strin
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
limit := parseIMLimit(req.URL.Query().Get("limit"), 30, 100)
|
limit := parseIMLimit(req.URL.Query().Get("limit"), 30, 100)
|
||||||
cursor, err := parseIMNonNegativeCursor(req.URL.Query().Get("cursor"))
|
cursor := strings.TrimSpace(req.URL.Query().Get("cursor"))
|
||||||
if err != nil {
|
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"})
|
writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "malformed request", map[string]any{"field": "cursor"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -4822,25 +4828,22 @@ func serveIMGroupMembers(wrt http.ResponseWriter, req *http.Request, topic strin
|
|||||||
writeIMSuccess(wrt, req, cached)
|
writeIMSuccess(wrt, req, cached)
|
||||||
return
|
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 {
|
if err != nil {
|
||||||
writeIMError(wrt, req, http.StatusInternalServerError, 90000, imReasonInternal, "internal error", nil)
|
writeIMError(wrt, req, http.StatusInternalServerError, 90000, imReasonInternal, "internal error", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
items := make([]map[string]any, 0, limit)
|
items := make([]map[string]any, 0, limit)
|
||||||
seen := 0
|
|
||||||
nextCursor := ""
|
|
||||||
for _, member := range members {
|
for _, member := range members {
|
||||||
access := member.ModeGiven & member.ModeWant
|
access := groupMemberAccess(member)
|
||||||
mentionable := access.IsJoiner() && access.IsReader()
|
item := groupBusinessMemberPayload(member)
|
||||||
role := groupMemberRole(access)
|
|
||||||
if roleFilter != "" && role != roleFilter {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if mentionableOnly && !mentionable {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
item := groupMemberPayload(member, mentionable)
|
|
||||||
if eligibleRole != "" {
|
if eligibleRole != "" {
|
||||||
eligible, reason := groupRoleEligibility(selfAccess, access, eligibleRole)
|
eligible, reason := groupRoleEligibility(selfAccess, access, eligibleRole)
|
||||||
item["eligible"] = eligible
|
item["eligible"] = eligible
|
||||||
@@ -4855,19 +4858,7 @@ func serveIMGroupMembers(wrt http.ResponseWriter, req *http.Request, topic strin
|
|||||||
item["ineligible_reason"] = reason
|
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)
|
items = append(items, item)
|
||||||
seen++
|
|
||||||
}
|
}
|
||||||
permissions := map[string]any{
|
permissions := map[string]any{
|
||||||
"can_mention_all": selfAccess.IsAdmin(),
|
"can_mention_all": selfAccess.IsAdmin(),
|
||||||
@@ -7320,6 +7311,38 @@ func groupMemberPayload(sub types.Subscription, mentionable bool) map[string]any
|
|||||||
return data
|
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 {
|
func groupMemberProfilePayload(topic string, sub *types.Subscription, user *types.User, ctx *types.RelationshipContext) map[string]any {
|
||||||
data := groupMemberPayload(*sub, true)
|
data := groupMemberPayload(*sub, true)
|
||||||
data["topic"] = topic
|
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,
|
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{
|
return strings.Join([]string{
|
||||||
topic,
|
topic,
|
||||||
|
"v3",
|
||||||
uid.UserId(),
|
uid.UserId(),
|
||||||
access.String(),
|
access.String(),
|
||||||
strings.ToLower(strings.TrimSpace(query)),
|
strings.ToLower(strings.TrimSpace(query)),
|
||||||
strings.TrimSpace(roleFilter),
|
strings.TrimSpace(roleFilter),
|
||||||
strings.TrimSpace(eligibleRole),
|
strings.TrimSpace(eligibleRole),
|
||||||
strconv.Itoa(limit),
|
strconv.Itoa(limit),
|
||||||
strconv.Itoa(cursor),
|
strings.TrimSpace(cursor),
|
||||||
strconv.FormatBool(mentionableOnly),
|
strconv.FormatBool(mentionableOnly),
|
||||||
strconv.FormatBool(muteEligible),
|
strconv.FormatBool(muteEligible),
|
||||||
}, "|")
|
}, "|")
|
||||||
|
|||||||
+31
-13
@@ -345,6 +345,9 @@ type testGroupBusinessStore struct {
|
|||||||
mutes []types.GroupMute
|
mutes []types.GroupMute
|
||||||
files []types.GroupFile
|
files []types.GroupFile
|
||||||
invitations []types.GroupInvitation
|
invitations []types.GroupInvitation
|
||||||
|
listMembers []types.GroupMember
|
||||||
|
listMembersOpts types.GroupMemberListOptions
|
||||||
|
listMembersNext string
|
||||||
updatedSettings types.GroupSettingsPatch
|
updatedSettings types.GroupSettingsPatch
|
||||||
updatedMembers []types.GroupMemberPatch
|
updatedMembers []types.GroupMemberPatch
|
||||||
updatedRoleMode types.AccessMode
|
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
|
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) {
|
func (s *testGroupBusinessStore) ListMembers(_ types.TenantID, _ string, opts types.GroupMemberListOptions) ([]types.GroupMember, string, error) {
|
||||||
return nil, "", nil
|
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) {
|
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()
|
defer ctrl.Finish()
|
||||||
expectIMBearer(t, ctrl, "token-1", 7, 42)
|
expectIMBearer(t, ctrl, "token-1", 7, 42)
|
||||||
oldSubs := store.Subs
|
oldSubs := store.Subs
|
||||||
oldTopics := store.Topics
|
oldGroupBusiness := store.GroupBusiness
|
||||||
subs := mock_store.NewMockSubsPersistenceInterface(ctrl)
|
subs := mock_store.NewMockSubsPersistenceInterface(ctrl)
|
||||||
topics := mock_store.NewMockTopicsPersistenceInterface(ctrl)
|
|
||||||
store.Subs = subs
|
store.Subs = subs
|
||||||
store.Topics = topics
|
groupStore := &testGroupBusinessStore{listMembersNext: "101"}
|
||||||
|
store.GroupBusiness = groupStore
|
||||||
defer func() {
|
defer func() {
|
||||||
store.Subs = oldSubs
|
store.Subs = oldSubs
|
||||||
store.Topics = oldTopics
|
store.GroupBusiness = oldGroupBusiness
|
||||||
}()
|
}()
|
||||||
|
|
||||||
subs.EXPECT().Get(types.TenantID(7), "grpMention", types.Uid(42), false).Return(&types.Subscription{
|
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,
|
ModeWant: types.ModeCFull,
|
||||||
ModeGiven: types.ModeCFull,
|
ModeGiven: types.ModeCFull,
|
||||||
}, nil)
|
}, nil)
|
||||||
alice := types.Subscription{User: types.Uid(101).UserId(), ModeWant: types.ModeCPublic, ModeGiven: types.ModeCPublic}
|
groupStore.listMembers = []types.GroupMember{{
|
||||||
alice.SetPublic(map[string]any{"display_name": "Alice"})
|
ID: 101,
|
||||||
bob := types.Subscription{User: types.Uid(102).UserId(), ModeWant: types.ModeJoin, ModeGiven: types.ModeJoin}
|
Topic: "grpMention",
|
||||||
bob.SetPublic(map[string]any{"display_name": "Bob"})
|
UserID: types.Uid(101),
|
||||||
topics.EXPECT().GetUsers(types.TenantID(7), "grpMention", gomock.Any()).Return([]types.Subscription{alice, bob}, nil)
|
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")
|
req.Header.Set("Authorization", "Bearer token-1")
|
||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
|
|
||||||
@@ -3911,12 +3920,21 @@ func TestServeIMGroupMembers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
data := decodeIMHTTPResponse(t, rec).Data.(map[string]any)
|
data := decodeIMHTTPResponse(t, rec).Data.(map[string]any)
|
||||||
items := data["items"].([]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)
|
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 {
|
if data["permissions"].(map[string]any)["can_mention_all"] != true {
|
||||||
t.Fatalf("expected can_mention_all permission: %#v", data)
|
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) {
|
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)
|
items, nextCursor, err := Relations.ListFriends(tenantID, uid, "", "", cursor, 100)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logs.Warn.Printf("profile %s: refresh friend subscription projections failed: %v", uid, err)
|
logs.Warn.Printf("profile %s: refresh friend subscription projections failed: %v", uid, err)
|
||||||
return
|
break
|
||||||
}
|
}
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
if item.Friendship.Topic == "" || item.Friendship.PeerID.IsZero() {
|
if item.Friendship.Topic == "" || item.Friendship.PeerID.IsZero() {
|
||||||
@@ -2915,10 +2915,23 @@ func refreshUserProfileProjections(tenantID types.TenantID, uid types.Uid, profi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if nextCursor == "" {
|
if nextCursor == "" {
|
||||||
return
|
break
|
||||||
}
|
}
|
||||||
cursor = nextCursor
|
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 {
|
func userProfilePublicProjection(profile *types.UserProfile) map[string]any {
|
||||||
|
|||||||
@@ -73,6 +73,10 @@ type GroupMember struct {
|
|||||||
Role string `db:"role"`
|
Role string `db:"role"`
|
||||||
MemberStatus string `db:"member_status"`
|
MemberStatus string `db:"member_status"`
|
||||||
Nickname string `db:"nickname"`
|
Nickname string `db:"nickname"`
|
||||||
|
ModeWant AccessMode `db:"modewant"`
|
||||||
|
ModeGiven AccessMode `db:"modegiven"`
|
||||||
|
Public any `db:"public"`
|
||||||
|
Trusted any `db:"trusted"`
|
||||||
Pinned bool `db:"pinned"`
|
Pinned bool `db:"pinned"`
|
||||||
MuteNotifications bool `db:"mute_notifications"`
|
MuteNotifications bool `db:"mute_notifications"`
|
||||||
JoinSource string `db:"join_source"`
|
JoinSource string `db:"join_source"`
|
||||||
@@ -105,6 +109,7 @@ type GroupMemberListOptions struct {
|
|||||||
Query string
|
Query string
|
||||||
Cursor string
|
Cursor string
|
||||||
Limit int
|
Limit int
|
||||||
|
Mentionable bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type GroupInvitation struct {
|
type GroupInvitation struct {
|
||||||
|
|||||||
Reference in New Issue
Block a user