feat: include avatar url in member public meta

This commit is contained in:
richjohnrambo
2026-09-23 23:53:26 +08:00
parent da02eae6a4
commit 68a0228dff
3 changed files with 47 additions and 10 deletions
+5 -4
View File
@@ -2630,10 +2630,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.trusted,u.lastseen,u.useragent,s.private, s.readseqid,s.modewant,s.modegiven,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.nickname AS profile_nickname,p.avatar_file_id AS profile_avatar_file_id,pu.location AS profile_avatar_url,p.signature AS profile_signature,
p.gender AS profile_gender,p.region AS profile_region,p.public_account AS profile_public_account 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 LEFT JOIN im_user_profiles AS p ON p.user_id=s.userid
LEFT JOIN im_public_uploads AS pu ON pu.file_id=p.avatar_file_id
WHERE s.topic=?` WHERE s.topic=?`
args := []any{topic} args := []any{topic}
if !keepDeleted { if !keepDeleted {
@@ -2686,21 +2687,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 trusted any var trusted any
var profileNickname, profileAvatarFileID, profileSignature, profileGender, profileRegion, profilePublicAccount sql.NullString var profileNickname, profileAvatarFileID, profileAvatarURL, 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,
&trusted, &lastSeen, &userAgent, &sub.Private, &trusted, &lastSeen, &userAgent, &sub.Private,
&profileNickname, &profileAvatarFileID, &profileSignature, &profileNickname, &profileAvatarFileID, &profileAvatarURL, &profileSignature,
&profileGender, &profileRegion, &profilePublicAccount); err != nil { &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(profilePublicFromFields(profileNickname, profileAvatarFileID, profileSignature, profileGender, profileRegion, profilePublicAccount)) sub.SetPublic(profilePublicFromFieldsWithAvatarURL(profileNickname, profileAvatarFileID, profileAvatarURL, 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)
+28 -5
View File
@@ -62,6 +62,7 @@ type profileUserRow struct {
Trusted any `db:"trusted"` Trusted any `db:"trusted"`
ProfileNickname sql.NullString `db:"profile_nickname"` ProfileNickname sql.NullString `db:"profile_nickname"`
ProfileAvatarFileID sql.NullString `db:"profile_avatar_file_id"` ProfileAvatarFileID sql.NullString `db:"profile_avatar_file_id"`
ProfileAvatarURL sql.NullString `db:"profile_avatar_url"`
ProfileSignature sql.NullString `db:"profile_signature"` ProfileSignature sql.NullString `db:"profile_signature"`
ProfileGender sql.NullString `db:"profile_gender"` ProfileGender sql.NullString `db:"profile_gender"`
ProfileRegion sql.NullString `db:"profile_region"` ProfileRegion sql.NullString `db:"profile_region"`
@@ -85,7 +86,7 @@ func (row profileUserRow) lastSeen() *time.Time {
} }
func (row profileUserRow) profilePublic() any { func (row profileUserRow) profilePublic() any {
return profilePublicFromFields(row.ProfileNickname, row.ProfileAvatarFileID, row.ProfileSignature, row.ProfileGender, row.ProfileRegion, row.ProfilePublicAccount) return profilePublicFromFieldsWithAvatarURL(row.ProfileNickname, row.ProfileAvatarFileID, row.ProfileAvatarURL, row.ProfileSignature, row.ProfileGender, row.ProfileRegion, row.ProfilePublicAccount)
} }
func profileUserFromRow(row profileUserRow) t.User { func profileUserFromRow(row profileUserRow) t.User {
@@ -111,6 +112,10 @@ func profileUserFromRow(row profileUserRow) t.User {
} }
func profilePublicFromFields(nickname, avatarFileID, signature, gender, region, publicAccount sql.NullString) any { func profilePublicFromFields(nickname, avatarFileID, signature, gender, region, publicAccount sql.NullString) any {
return profilePublicFromFieldsWithAvatarURL(nickname, avatarFileID, sql.NullString{}, signature, gender, region, publicAccount)
}
func profilePublicFromFieldsWithAvatarURL(nickname, avatarFileID, avatarURL, signature, gender, region, publicAccount sql.NullString) any {
public := map[string]any{} public := map[string]any{}
if nickname.Valid { if nickname.Valid {
value := strings.TrimSpace(nickname.String) value := strings.TrimSpace(nickname.String)
@@ -124,6 +129,9 @@ func profilePublicFromFields(nickname, avatarFileID, signature, gender, region,
value := strings.TrimSpace(avatarFileID.String) value := strings.TrimSpace(avatarFileID.String)
if value != "" { if value != "" {
public["avatar_file_id"] = value public["avatar_file_id"] = value
if url := profileAvatarURL(value, avatarURL); url != "" {
public["avatar_url"] = url
}
} }
} }
if signature.Valid { if signature.Valid {
@@ -186,6 +194,20 @@ func sanitizeUserUpdate(update map[string]any) map[string]any {
return update return update
} }
func profileAvatarURL(avatarFileID string, avatarURL sql.NullString) string {
if avatarURL.Valid {
if value := strings.TrimSpace(avatarURL.String); value != "" {
return value
}
}
value := strings.TrimSpace(avatarFileID)
if strings.HasPrefix(value, "http://") || strings.HasPrefix(value, "https://") ||
strings.HasPrefix(value, "data:") || strings.HasPrefix(value, "/") {
return value
}
return ""
}
func profileSeedFromPublic(public any) (nickname, avatarFileID string) { func profileSeedFromPublic(public any) (nickname, avatarFileID string) {
values := publicMap(public) values := publicMap(public)
for _, key := range []string{"display_name", "nickname", "fn", "name"} { for _, key := range []string{"display_name", "nickname", "fn", "name"} {
@@ -1302,10 +1324,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.trusted,u.lastseen,u.useragent,s.private, s.readseqid,s.modewant,s.modegiven,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.nickname AS profile_nickname,p.avatar_file_id AS profile_avatar_file_id,pu.location AS profile_avatar_url,p.signature AS profile_signature,
p.gender AS profile_gender,p.region AS profile_region,p.public_account AS profile_public_account 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 LEFT JOIN im_user_profiles AS p ON p.tenant_id=s.tenant_id AND p.user_id=s.userid
LEFT JOIN im_public_uploads AS pu ON pu.file_id=p.avatar_file_id
WHERE s.tenant_id=? AND s.topic=?` WHERE s.tenant_id=? AND s.topic=?`
q = tenantShardSQL(id, q) q = tenantShardSQL(id, q)
args := []any{id, topic} args := []any{id, topic}
@@ -1347,17 +1370,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 trusted any var trusted any
var profileNickname, profileAvatarFileID, profileSignature, profileGender, profileRegion, profilePublicAccount sql.NullString var profileNickname, profileAvatarFileID, profileAvatarURL, 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,
&trusted, &lastSeen, &userAgent, &sub.Private, &trusted, &lastSeen, &userAgent, &sub.Private,
&profileNickname, &profileAvatarFileID, &profileSignature, &profileNickname, &profileAvatarFileID, &profileAvatarURL, &profileSignature,
&profileGender, &profileRegion, &profilePublicAccount); err != nil { &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(profilePublicFromFields(profileNickname, profileAvatarFileID, profileSignature, profileGender, profileRegion, profilePublicAccount)) sub.SetPublic(profilePublicFromFieldsWithAvatarURL(profileNickname, profileAvatarFileID, profileAvatarURL, 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)
+14 -1
View File
@@ -42,9 +42,22 @@ func TestProfilePublicFromFields(t *testing.T) {
t.Fatalf("profile public not overlaid: %#v", public) t.Fatalf("profile public not overlaid: %#v", public)
} }
public = profilePublicFromFieldsWithAvatarURL(sql.NullString{}, sql.NullString{String: "file_avatar_1", Valid: true},
sql.NullString{String: "https://cdn.example.com/avatar.png", Valid: true}, sql.NullString{}, sql.NullString{},
sql.NullString{}, sql.NullString{}).(map[string]any)
if public["avatar_file_id"] != "file_avatar_1" || public["avatar_url"] != "https://cdn.example.com/avatar.png" {
t.Fatalf("avatar url not overlaid: %#v", public)
}
public = profilePublicFromFields(sql.NullString{}, sql.NullString{String: "https://cdn.example.com/default.png", Valid: true},
sql.NullString{}, sql.NullString{}, sql.NullString{}, sql.NullString{}).(map[string]any)
if public["avatar_file_id"] != "https://cdn.example.com/default.png" || public["avatar_url"] != "https://cdn.example.com/default.png" {
t.Fatalf("absolute avatar value should be exposed as avatar_url: %#v", public)
}
public = profilePublicFromFields(sql.NullString{Valid: true}, sql.NullString{Valid: true}, sql.NullString{Valid: true}, public = profilePublicFromFields(sql.NullString{Valid: true}, sql.NullString{Valid: true}, sql.NullString{Valid: true},
sql.NullString{Valid: true}, sql.NullString{Valid: true}, sql.NullString{Valid: true}).(map[string]any) sql.NullString{Valid: true}, sql.NullString{Valid: true}, sql.NullString{Valid: true}).(map[string]any)
for _, key := range []string{"fn", "name", "nickname", "display_name", "avatar_file_id", "photo", "avatar", "signature", "gender", "region", "public_account"} { for _, key := range []string{"fn", "name", "nickname", "display_name", "avatar_file_id", "avatar_url", "photo", "avatar", "signature", "gender", "region", "public_account"} {
if _, ok := public[key]; ok { if _, ok := public[key]; ok {
t.Fatalf("legacy profile field %s should not be read from im_users.public: %#v", key, public) t.Fatalf("legacy profile field %s should not be read from im_users.public: %#v", key, public)
} }