refactor: read user profile fields from profile table

This commit is contained in:
richjohnrambo
2026-09-21 13:44:04 +08:00
parent d398467fb9
commit 5f717c2162
9 changed files with 117 additions and 203 deletions
+2 -2
View File
@@ -136,8 +136,8 @@ func TestUserQuerySupportsKeywordAndUserIDs(t *testing.T) {
"AS register_type", "AS register_type",
"im_registration_invite_uses", "im_registration_invite_uses",
"im_fileuploads AS avatar", "im_fileuploads AS avatar",
"JSON_EXTRACT(u.public,'$.avatar')", "p.avatar_file_id",
"JSON_EXTRACT(u.public,'$.photo.data')", "im_user_profiles AS p",
} { } {
if !strings.Contains(usersInternalQuerySpec().Select+usersInternalQuerySpec().From, field) { if !strings.Contains(usersInternalQuerySpec().Select+usersInternalQuerySpec().From, field) {
t.Fatalf("user query missing list field %q", field) t.Fatalf("user query missing list field %q", field)
+13 -13
View File
@@ -1352,13 +1352,14 @@ func (a *adapter) UserCreate(user *t.User) error {
if err != nil { if err != nil {
return err return err
} }
insert := "INSERT INTO im_users(id,username,createdat,updatedat,state,access,phone,public,trusted) VALUES(?,?,?,?,?,?,?,?,?)" nickname, avatarFileID := profileSeedFromPublic(user.Public)
insert := "INSERT INTO im_users(id,username,createdat,updatedat,state,access,phone,trusted) VALUES(?,?,?,?,?,?,?,?)"
args := []any{decoded_uid, user.Username, user.CreatedAt, user.UpdatedAt, user.State, user.Access, phone, args := []any{decoded_uid, user.Username, user.CreatedAt, user.UpdatedAt, user.State, user.Access, phone,
common.ToJSON(user.Public), common.ToJSON(user.Trusted)} common.ToJSON(user.Trusted)}
if hasAccountNo { if hasAccountNo {
insert = "INSERT INTO im_users(id,account_no,username,createdat,updatedat,state,access,phone,public,trusted) VALUES(?,?,?,?,?,?,?,?,?,?)" insert = "INSERT INTO im_users(id,account_no,username,createdat,updatedat,state,access,phone,trusted) VALUES(?,?,?,?,?,?,?,?,?)"
args = []any{decoded_uid, accountNo, user.Username, user.CreatedAt, user.UpdatedAt, user.State, user.Access, phone, args = []any{decoded_uid, accountNo, user.Username, user.CreatedAt, user.UpdatedAt, user.State, user.Access, phone,
common.ToJSON(user.Public), common.ToJSON(user.Trusted)} common.ToJSON(user.Trusted)}
} }
if _, err = tx.Exec(insert, args...); err != nil { if _, err = tx.Exec(insert, args...); err != nil {
if isDupe(err) { if isDupe(err) {
@@ -1371,7 +1372,6 @@ func (a *adapter) UserCreate(user *t.User) error {
securityID, decoded_uid, 1, user.CreatedAt, user.UpdatedAt); err != nil { securityID, decoded_uid, 1, user.CreatedAt, user.UpdatedAt); err != nil {
return err return err
} }
nickname, avatarFileID := profileSeedFromPublic(user.Public)
if _, err = tx.Exec(`INSERT IGNORE INTO im_user_profiles(id,user_id,avatar_file_id,nickname,public_account,created_at,updated_at) if _, err = tx.Exec(`INSERT IGNORE INTO im_user_profiles(id,user_id,avatar_file_id,nickname,public_account,created_at,updated_at)
VALUES(?,?,?,?,?,?,?)`, profileID, decoded_uid, avatarFileID, nickname, user.Uid().UserId(), user.CreatedAt, user.UpdatedAt); err != nil { VALUES(?,?,?,?,?,?,?)`, profileID, decoded_uid, avatarFileID, nickname, user.Uid().UserId(), user.CreatedAt, user.UpdatedAt); err != nil {
return err return err
@@ -1689,7 +1689,7 @@ func (a *adapter) UserGet(uid t.Uid) (*t.User, error) {
} }
var row profileUserRow var row profileUserRow
err := a.db.GetContext(ctx, &row, `SELECT u.id,u.username,u.createdat,u.updatedat,u.state,u.stateat,u.access,u.lastseen,u.useragent, err := a.db.GetContext(ctx, &row, `SELECT u.id,u.username,u.createdat,u.updatedat,u.state,u.stateat,u.access,u.lastseen,u.useragent,
COALESCE(u.phone,'') AS phone,u.public,u.trusted, COALESCE(u.phone,'') AS phone,u.trusted,
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,p.signature AS profile_signature,
p.gender AS profile_gender,p.region AS profile_region,p.public_account AS profile_public_account,p.updated_at AS profile_updated_at p.gender AS profile_gender,p.region AS profile_region,p.public_account AS profile_public_account,p.updated_at AS profile_updated_at
FROM im_users AS u LEFT JOIN im_user_profiles AS p ON p.user_id=u.id FROM im_users AS u LEFT JOIN im_user_profiles AS p ON p.user_id=u.id
@@ -1723,7 +1723,7 @@ func (a *adapter) UserGetAll(ids ...t.Uid) ([]t.User, error) {
defer cancel() defer cancel()
} }
q, uids, _ := sqlx.In(`SELECT u.id,u.username,u.createdat,u.updatedat,u.state,u.stateat,u.access,u.lastseen,u.useragent, q, uids, _ := sqlx.In(`SELECT u.id,u.username,u.createdat,u.updatedat,u.state,u.stateat,u.access,u.lastseen,u.useragent,
COALESCE(u.phone,'') AS phone,u.public,u.trusted, COALESCE(u.phone,'') AS phone,u.trusted,
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,p.signature AS profile_signature,
p.gender AS profile_gender,p.region AS profile_region,p.public_account AS profile_public_account,p.updated_at AS profile_updated_at p.gender AS profile_gender,p.region AS profile_region,p.public_account AS profile_public_account,p.updated_at AS profile_updated_at
FROM im_users AS u LEFT JOIN im_user_profiles AS p ON p.user_id=u.id FROM im_users AS u LEFT JOIN im_user_profiles AS p ON p.user_id=u.id
@@ -1946,7 +1946,7 @@ func (a *adapter) UserUpdate(uid t.Uid, update map[string]any) error {
} }
}() }()
update = withoutUserTags(update) update = sanitizeUserUpdate(update)
cols, args := common.UpdateByMap(update) cols, args := common.UpdateByMap(update)
if len(cols) == 0 { if len(cols) == 0 {
return tx.Commit() return tx.Commit()
@@ -2487,7 +2487,7 @@ func (a *adapter) TopicsForUser(uid t.Uid, keepDeleted bool, opts *t.QueryOpt) (
// Fetch p2p users and join to p2p subscriptions. // Fetch p2p users and join to p2p subscriptions.
if len(usrq) > 0 { if len(usrq) > 0 {
q = `SELECT u.id,u.updatedat,u.state,u.access,u.lastseen,u.useragent,u.public,u.trusted, q = `SELECT u.id,u.updatedat,u.state,u.access,u.lastseen,u.useragent,u.trusted,
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,p.signature AS profile_signature,
p.gender AS profile_gender,p.region AS profile_region,p.public_account AS profile_public_account,p.updated_at AS profile_updated_at, p.gender AS profile_gender,p.region AS profile_region,p.public_account AS profile_public_account,p.updated_at AS profile_updated_at,
fs.remark AS friendship_remark fs.remark AS friendship_remark
@@ -2561,7 +2561,7 @@ 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.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,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
@@ -2617,14 +2617,14 @@ func (a *adapter) UsersForTopic(topic string, keepDeleted bool, opts *t.QueryOpt
var subs []t.Subscription var subs []t.Subscription
var lastSeen sql.NullTime var lastSeen sql.NullTime
var userAgent string var userAgent string
var public, trusted any var trusted any
var profileNickname, profileAvatarFileID, profileSignature, profileGender, profileRegion, profilePublicAccount sql.NullString 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, &trusted, &lastSeen, &userAgent, &sub.Private,
&profileNickname, &profileAvatarFileID, &profileSignature, &profileNickname, &profileAvatarFileID, &profileSignature,
&profileGender, &profileRegion, &profilePublicAccount); err != nil { &profileGender, &profileRegion, &profilePublicAccount); err != nil {
break break
@@ -2632,7 +2632,7 @@ func (a *adapter) UsersForTopic(topic string, keepDeleted bool, opts *t.QueryOpt
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(common.FromJSON(public), profileNickname, profileAvatarFileID, profileSignature, profileGender, profileRegion, profilePublicAccount)) sub.SetPublic(profilePublicFromFields(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)
+79 -58
View File
@@ -59,7 +59,6 @@ type profileUserRow struct {
Phone string `db:"phone"` Phone string `db:"phone"`
LastSeen sql.NullTime `db:"lastseen"` LastSeen sql.NullTime `db:"lastseen"`
UserAgent string `db:"useragent"` UserAgent string `db:"useragent"`
Public any `db:"public"`
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"`
@@ -86,7 +85,7 @@ func (row profileUserRow) lastSeen() *time.Time {
} }
func (row profileUserRow) profilePublic() any { func (row profileUserRow) profilePublic() any {
return profilePublicFromFields(common.FromJSON(row.Public), row.ProfileNickname, row.ProfileAvatarFileID, row.ProfileSignature, row.ProfileGender, row.ProfileRegion, row.ProfilePublicAccount) return profilePublicFromFields(row.ProfileNickname, row.ProfileAvatarFileID, row.ProfileSignature, row.ProfileGender, row.ProfileRegion, row.ProfilePublicAccount)
} }
func profileUserFromRow(row profileUserRow) t.User { func profileUserFromRow(row profileUserRow) t.User {
@@ -111,9 +110,8 @@ func profileUserFromRow(row profileUserRow) t.User {
return user return user
} }
func profilePublicFromFields(base any, nickname, avatarFileID, signature, gender, region, publicAccount sql.NullString) any { func profilePublicFromFields(nickname, avatarFileID, signature, gender, region, publicAccount sql.NullString) any {
public := publicMap(base) public := map[string]any{}
clearProfilePublicFields(public)
if nickname.Valid { if nickname.Valid {
value := strings.TrimSpace(nickname.String) value := strings.TrimSpace(nickname.String)
if value != "" { if value != "" {
@@ -181,10 +179,11 @@ func ensurePrivateMutationVersion(private any, version int64) any {
return data return data
} }
func clearProfilePublicFields(public map[string]any) { func sanitizeUserUpdate(update map[string]any) map[string]any {
for _, key := range []string{"display_name", "nickname", "fn", "name", "avatar_file_id", "photo", "avatar", "signature", "gender", "region", "public_account"} { update = withoutUserTags(update)
delete(public, key) delete(update, "Public")
} delete(update, "public")
return update
} }
func profileSeedFromPublic(public any) (nickname, avatarFileID string) { func profileSeedFromPublic(public any) (nickname, avatarFileID string) {
@@ -403,7 +402,7 @@ func (a *adapter) TenantUserGet(id t.TenantID, uid t.Uid) (*t.User, error) {
} }
var row profileUserRow var row profileUserRow
err = a.db.GetContext(ctx, &row, `SELECT ? AS tenant_id,u.id,u.username,u.createdat,u.updatedat,u.state,u.stateat,u.access,u.lastseen,u.useragent, err = a.db.GetContext(ctx, &row, `SELECT ? AS tenant_id,u.id,u.username,u.createdat,u.updatedat,u.state,u.stateat,u.access,u.lastseen,u.useragent,
COALESCE(u.phone,'') AS phone,u.public,u.trusted, COALESCE(u.phone,'') AS phone,u.trusted,
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,p.signature AS profile_signature,
p.gender AS profile_gender,p.region AS profile_region,p.public_account AS profile_public_account,p.updated_at AS profile_updated_at p.gender AS profile_gender,p.region AS profile_region,p.public_account AS profile_public_account,p.updated_at AS profile_updated_at
FROM im_users AS u LEFT JOIN im_user_profiles AS p ON p.user_id=u.id FROM im_users AS u LEFT JOIN im_user_profiles AS p ON p.user_id=u.id
@@ -493,10 +492,18 @@ func (a *adapter) tenantUserGetAllTx(ctx context.Context, tx *sqlx.Tx, id t.Tena
var params []any var params []any
if a.tenantScopedDB { if a.tenantScopedDB {
q, params, _ = sqlx.In(`SELECT id,username,createdat,updatedat,state,stateat,access,lastseen,useragent, q, params, _ = sqlx.In(`SELECT id,username,createdat,updatedat,state,stateat,access,lastseen,useragent,
COALESCE(phone,'') AS phone,public,trusted FROM im_users WHERE id IN (?) AND state!=?`, args, t.StateDeleted) COALESCE(phone,'') AS phone,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,p.updated_at AS profile_updated_at
FROM im_users AS u LEFT JOIN im_user_profiles AS p ON p.user_id=u.id
WHERE u.id IN (?) AND u.state!=?`, args, t.StateDeleted)
} else { } else {
q, params, _ = sqlx.In(`SELECT tenant_id,id,username,createdat,updatedat,state,stateat,access,lastseen,useragent, q, params, _ = sqlx.In(`SELECT u.tenant_id,u.id,u.username,u.createdat,u.updatedat,u.state,u.stateat,u.access,u.lastseen,u.useragent,
COALESCE(phone,'') AS phone,public,trusted FROM im_users WHERE tenant_id=? AND id IN (?) AND state!=?`, id, args, t.StateDeleted) COALESCE(u.phone,'') AS phone,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,p.updated_at AS profile_updated_at
FROM im_users AS u LEFT JOIN im_user_profiles AS p ON p.tenant_id=u.tenant_id AND p.user_id=u.id
WHERE u.tenant_id=? AND u.id IN (?) AND u.state!=?`, id, args, t.StateDeleted)
} }
query := a.db.Rebind(q) query := a.db.Rebind(q)
var rows *sqlx.Rows var rows *sqlx.Rows
@@ -513,12 +520,11 @@ func (a *adapter) tenantUserGetAllTx(ctx context.Context, tx *sqlx.Tx, id t.Tena
var users []t.User var users []t.User
for rows.Next() { for rows.Next() {
var user t.User var row profileUserRow
if err = rows.StructScan(&user); err != nil { if err = rows.StructScan(&row); err != nil {
return nil, err return nil, err
} }
scanTenantUser(&user) users = append(users, profileUserFromRow(row))
users = append(users, user)
} }
return users, rows.Err() return users, rows.Err()
} }
@@ -680,7 +686,7 @@ func (a *adapter) TenantUserUpdate(id t.TenantID, uid t.Uid, update map[string]a
} }
}() }()
decoded := decodeUID(uid) decoded := decodeUID(uid)
update = withoutUserTags(update) update = sanitizeUserUpdate(update)
cols, args := common.UpdateByMap(update) cols, args := common.UpdateByMap(update)
if len(cols) == 0 { if len(cols) == 0 {
return tx.Commit() return tx.Commit()
@@ -1080,7 +1086,7 @@ func (a *adapter) TenantTopicsForUser(id t.TenantID, uid t.Uid, keepDeleted bool
} }
} }
if len(usrq) > 0 { if len(usrq) > 0 {
q = `SELECT u.tenant_id,u.id,u.updatedat,u.state,u.access,u.lastseen,u.useragent,u.public,u.trusted, q = `SELECT u.tenant_id,u.id,u.updatedat,u.state,u.access,u.lastseen,u.useragent,u.trusted,
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,p.signature AS profile_signature,
p.gender AS profile_gender,p.region AS profile_region,p.public_account AS profile_public_account,p.updated_at AS profile_updated_at, p.gender AS profile_gender,p.region AS profile_region,p.public_account AS profile_public_account,p.updated_at AS profile_updated_at,
fs.remark AS friendship_remark fs.remark AS friendship_remark
@@ -1147,7 +1153,7 @@ 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.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,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
@@ -1191,18 +1197,18 @@ func (a *adapter) TenantUsersForTopic(id t.TenantID, topic string, keepDeleted b
for rows.Next() { for rows.Next() {
var sub t.Subscription var sub t.Subscription
var lastSeen sql.NullTime var lastSeen sql.NullTime
var public, trusted any var trusted any
var profileNickname, profileAvatarFileID, profileSignature, profileGender, profileRegion, profilePublicAccount sql.NullString 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, &trusted, &lastSeen, &userAgent, &sub.Private,
&profileNickname, &profileAvatarFileID, &profileSignature, &profileNickname, &profileAvatarFileID, &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(common.FromJSON(public), profileNickname, profileAvatarFileID, profileSignature, profileGender, profileRegion, profilePublicAccount)) sub.SetPublic(profilePublicFromFields(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)
@@ -6280,9 +6286,12 @@ func (a *adapter) TenantFriendRequestList(id t.TenantID, uid t.Uid, direction, s
args = append(args, parsed) args = append(args, parsed)
} }
args = append(args, limit+1) args = append(args, limit+1)
query := `SELECT fr.*,u.public,u.trusted query := `SELECT fr.*,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_friend_requests AS fr FROM im_friend_requests AS fr
JOIN im_users AS u ON u.id=IF(fr.applicant_id=?,fr.target_id,fr.applicant_id) JOIN im_users AS u ON u.id=IF(fr.applicant_id=?,fr.target_id,fr.applicant_id)
LEFT JOIN im_user_profiles AS p ON p.user_id=u.id
WHERE ` + strings.Join(where, " AND ") + ` WHERE ` + strings.Join(where, " AND ") + `
ORDER BY fr.id DESC LIMIT ?` ORDER BY fr.id DESC LIMIT ?`
args = append([]any{decodedUID}, args...) args = append([]any{decodedUID}, args...)
@@ -6299,21 +6308,26 @@ func (a *adapter) TenantFriendRequestList(id t.TenantID, uid t.Uid, direction, s
nextCursor := "" nextCursor := ""
for rows.Next() { for rows.Next() {
row := struct { row := struct {
ID int64 ID int64
RequestID string `db:"request_id"` RequestID string `db:"request_id"`
ApplicantID int64 `db:"applicant_id"` ApplicantID int64 `db:"applicant_id"`
TargetID int64 `db:"target_id"` TargetID int64 `db:"target_id"`
Message string Message string
Source string Source string
SourceTopic string `db:"source_topic"` SourceTopic string `db:"source_topic"`
Status string Status string
IdempotencyKey string `db:"idempotency_key"` IdempotencyKey string `db:"idempotency_key"`
ExpiresAt time.Time `db:"expires_at"` ExpiresAt time.Time `db:"expires_at"`
HandledAt *time.Time `db:"handled_at"` HandledAt *time.Time `db:"handled_at"`
CreatedAt time.Time `db:"created_at"` CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"` UpdatedAt time.Time `db:"updated_at"`
Public any Trusted 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 { if err = rows.StructScan(&row); err != nil {
return nil, "", err return nil, "", err
@@ -6350,7 +6364,7 @@ func (a *adapter) TenantFriendRequestList(id t.TenantID, uid t.Uid, direction, s
Peer: &t.User{ Peer: &t.User{
ObjHeader: t.ObjHeader{Id: peerID.String()}, ObjHeader: t.ObjHeader{Id: peerID.String()},
TenantID: id, TenantID: id,
Public: common.FromJSON(row.Public), Public: profilePublicFromFields(row.ProfileNickname, row.ProfileAvatarFileID, row.ProfileSignature, row.ProfileGender, row.ProfileRegion, row.ProfilePublicAccount),
Trusted: common.FromJSON(row.Trusted), Trusted: common.FromJSON(row.Trusted),
}, },
Direction: direction, Direction: direction,
@@ -6699,20 +6713,20 @@ func (a *adapter) TenantFriendshipList(id t.TenantID, uid t.Uid, queryText, init
} }
if q := strings.TrimSpace(queryText); q != "" { if q := strings.TrimSpace(queryText); q != "" {
like := "%" + q + "%" like := "%" + q + "%"
where = append(where, "(fs.remark LIKE ? OR p.nickname LIKE ? OR p.public_account LIKE ? OR CAST(u.public AS CHAR) LIKE ?)") where = append(where, "(fs.remark LIKE ? OR p.nickname LIKE ? OR p.public_account LIKE ?)")
args = append(args, like, like, like, like) args = append(args, like, like, like)
} }
if initial = strings.TrimSpace(initial); initial != "" { if initial = strings.TrimSpace(initial); initial != "" {
like := initial + "%" like := initial + "%"
where = append(where, "(fs.remark LIKE ? OR p.nickname LIKE ? OR CAST(u.public AS CHAR) LIKE ?)") where = append(where, "(fs.remark LIKE ? OR p.nickname LIKE ?)")
args = append(args, like, like, like) args = append(args, like, like)
} }
args = append(args, limit+1) args = append(args, 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 fs.*,u.public,u.trusted, rows, err := a.db.QueryxContext(ctx, `SELECT fs.*,u.trusted,
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,p.signature AS profile_signature,
p.gender AS profile_gender,p.region AS profile_region,p.public_account AS profile_public_account,p.updated_at AS profile_updated_at p.gender AS profile_gender,p.region AS profile_region,p.public_account AS profile_public_account,p.updated_at AS profile_updated_at
FROM im_friendships AS fs FROM im_friendships AS fs
@@ -6736,7 +6750,6 @@ func (a *adapter) TenantFriendshipList(id t.TenantID, uid t.Uid, queryText, init
Topic string Topic string
CreatedAt time.Time `db:"created_at"` CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"` UpdatedAt time.Time `db:"updated_at"`
Public any
Trusted any Trusted any
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"`
@@ -6768,7 +6781,7 @@ func (a *adapter) TenantFriendshipList(id t.TenantID, uid t.Uid, queryText, init
Peer: &t.User{ Peer: &t.User{
ObjHeader: t.ObjHeader{Id: peerID.String()}, ObjHeader: t.ObjHeader{Id: peerID.String()},
TenantID: id, TenantID: id,
Public: profilePublicFromFields(common.FromJSON(row.Public), row.ProfileNickname, row.ProfileAvatarFileID, row.ProfileSignature, row.ProfileGender, row.ProfileRegion, row.ProfilePublicAccount), Public: profilePublicFromFields(row.ProfileNickname, row.ProfileAvatarFileID, row.ProfileSignature, row.ProfileGender, row.ProfileRegion, row.ProfilePublicAccount),
Trusted: common.FromJSON(row.Trusted), Trusted: common.FromJSON(row.Trusted),
}, },
}) })
@@ -6956,9 +6969,12 @@ func (a *adapter) TenantUserBlockList(id t.TenantID, blockerID t.Uid, cursor str
if cancel != nil { if cancel != nil {
defer cancel() defer cancel()
} }
rows, err := a.db.QueryxContext(ctx, `SELECT b.blocker_id,b.blocked_id,b.created_at,b.updated_at,u.public,u.trusted rows, err := a.db.QueryxContext(ctx, `SELECT b.blocker_id,b.blocked_id,b.created_at,b.updated_at,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_user_blocks AS b FROM im_user_blocks AS b
LEFT JOIN im_users AS u ON u.id=b.blocked_id LEFT JOIN im_users AS u ON u.id=b.blocked_id
LEFT JOIN im_user_profiles AS p ON p.user_id=b.blocked_id
WHERE `+strings.Join(where, " AND ")+` WHERE `+strings.Join(where, " AND ")+`
ORDER BY b.blocked_id DESC LIMIT ?`, args...) ORDER BY b.blocked_id DESC LIMIT ?`, args...)
if err != nil { if err != nil {
@@ -6969,12 +6985,17 @@ func (a *adapter) TenantUserBlockList(id t.TenantID, blockerID t.Uid, cursor str
nextCursor := "" nextCursor := ""
for rows.Next() { for rows.Next() {
row := struct { row := struct {
BlockerID int64 `db:"blocker_id"` BlockerID int64 `db:"blocker_id"`
BlockedID int64 `db:"blocked_id"` BlockedID int64 `db:"blocked_id"`
CreatedAt time.Time `db:"created_at"` CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"` UpdatedAt time.Time `db:"updated_at"`
Public any Trusted 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 { if err = rows.StructScan(&row); err != nil {
return nil, "", err return nil, "", err
@@ -6995,7 +7016,7 @@ func (a *adapter) TenantUserBlockList(id t.TenantID, blockerID t.Uid, cursor str
User: &t.User{ User: &t.User{
ObjHeader: t.ObjHeader{Id: blockedID.String()}, ObjHeader: t.ObjHeader{Id: blockedID.String()},
TenantID: id, TenantID: id,
Public: common.FromJSON(row.Public), Public: profilePublicFromFields(row.ProfileNickname, row.ProfileAvatarFileID, row.ProfileSignature, row.ProfileGender, row.ProfileRegion, row.ProfilePublicAccount),
Trusted: common.FromJSON(row.Trusted), Trusted: common.FromJSON(row.Trusted),
}, },
}) })
@@ -7101,8 +7122,8 @@ func (a *adapter) TenantUserSearch(id t.TenantID, viewer t.Uid, queryText, searc
if phone, err := normalizeUserPhone(queryText); err == nil { if phone, err := normalizeUserPhone(queryText); err == nil {
phoneCandidate = phone phoneCandidate = phone
} }
where = append(where, "(profile.nickname LIKE ? OR profile.public_account LIKE ? OR u.username LIKE ? OR u.phone=? OR u.phone=? OR CAST(u.public AS CHAR) LIKE ? OR u.id=?)") where = append(where, "(profile.nickname LIKE ? OR profile.public_account LIKE ? OR u.username LIKE ? OR u.phone=? OR u.phone=? OR u.id=?)")
args = append(args, like, like, like, queryText, phoneCandidate, like, userIDNumericArg(queryText)) args = append(args, like, like, like, queryText, phoneCandidate, userIDNumericArg(queryText))
case "exact_account": case "exact_account":
where = append(where, "(profile.public_account=? OR u.username=? OR u.id=?)") where = append(where, "(profile.public_account=? OR u.username=? OR u.id=?)")
args = append(args, queryText, strings.TrimPrefix(queryText, "basic:"), userIDNumericArg(queryText)) args = append(args, queryText, strings.TrimPrefix(queryText, "basic:"), userIDNumericArg(queryText))
@@ -7125,7 +7146,7 @@ func (a *adapter) TenantUserSearch(id t.TenantID, viewer t.Uid, queryText, searc
defer cancel() defer cancel()
} }
rows, err := a.db.QueryxContext(ctx, `SELECT DISTINCT ? AS tenant_id,u.id,u.username,u.createdat,u.updatedat,u.state,u.stateat,u.access,u.lastseen,u.useragent, rows, err := a.db.QueryxContext(ctx, `SELECT DISTINCT ? AS tenant_id,u.id,u.username,u.createdat,u.updatedat,u.state,u.stateat,u.access,u.lastseen,u.useragent,
COALESCE(u.phone,'') AS phone,u.public,u.trusted, COALESCE(u.phone,'') AS phone,u.trusted,
profile.nickname AS profile_nickname,profile.avatar_file_id AS profile_avatar_file_id,profile.signature AS profile_signature, profile.nickname AS profile_nickname,profile.avatar_file_id AS profile_avatar_file_id,profile.signature AS profile_signature,
profile.gender AS profile_gender,profile.region AS profile_region,profile.public_account AS profile_public_account,profile.updated_at AS profile_updated_at profile.gender AS profile_gender,profile.region AS profile_region,profile.public_account AS profile_public_account,profile.updated_at AS profile_updated_at
FROM im_users AS u `+strings.Join(joins, " ")+` FROM im_users AS u `+strings.Join(joins, " ")+`
+3 -7
View File
@@ -322,11 +322,8 @@ func (a *adapter) TenantGroupMemberList(id t.TenantID, topic string, opts t.Grou
if opts.Query != "" { if opts.Query != "" {
like := "%" + opts.Query + "%" like := "%" + opts.Query + "%"
where = append(where, `(gm.nickname LIKE ? OR p.nickname LIKE ? OR p.public_account LIKE ? 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 ?)`) OR u.username LIKE ?)`)
args = append(args, like, like, like, like, like, like, like) args = append(args, 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)
@@ -346,7 +343,7 @@ func (a *adapter) TenantGroupMemberList(id t.TenantID, topic string, opts t.Grou
if cancel != nil { if cancel != nil {
defer cancel() defer cancel()
} }
rows, err := a.db.QueryxContext(ctx, `SELECT STRAIGHT_JOIN gm.*,s.modewant,s.modegiven,u.public,u.trusted, rows, err := a.db.QueryxContext(ctx, `SELECT STRAIGHT_JOIN gm.*,s.modewant,s.modegiven,u.trusted,
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,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_group_members AS gm FORCE INDEX (`+memberIndex+`) FROM im_group_members AS gm FORCE INDEX (`+memberIndex+`)
@@ -363,7 +360,6 @@ func (a *adapter) TenantGroupMemberList(id t.TenantID, topic string, opts t.Grou
for rows.Next() { for rows.Next() {
row := struct { row := struct {
t.GroupMember t.GroupMember
Public any
Trusted any Trusted any
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"`
@@ -383,7 +379,7 @@ func (a *adapter) TenantGroupMemberList(id t.TenantID, topic string, opts t.Grou
} }
continue continue
} }
item.Public = profilePublicFromFields(common.FromJSON(row.Public), row.ProfileNickname, row.ProfileAvatarFileID, row.ProfileSignature, row.ProfileGender, row.ProfileRegion, row.ProfilePublicAccount) item.Public = profilePublicFromFields(row.ProfileNickname, row.ProfileAvatarFileID, row.ProfileSignature, row.ProfileGender, row.ProfileRegion, row.ProfilePublicAccount)
item.Trusted = common.FromJSON(row.Trusted) item.Trusted = common.FromJSON(row.Trusted)
items = append(items, item) items = append(items, item)
} }
+16 -8
View File
@@ -289,8 +289,8 @@ func TestTenantAuthIsolation(t *testing.T) {
userBID := userAID + 1 userBID := userAID + 1
for index, userID := range []int64{userAID, userBID} { for index, userID := range []int64{userAID, userBID} {
tenantDB := quoteIdent(dbNames[index]) tenantDB := quoteIdent(dbNames[index])
if _, err := db.Exec("INSERT INTO "+tenantDB+".im_users(id,account_no,username,createdat,updatedat,state,access,phone,public,trusted) "+ if _, err := db.Exec("INSERT INTO "+tenantDB+".im_users(id,account_no,username,createdat,updatedat,state,access,phone,trusted) "+
"VALUES(?,?,?,?,?,?,?,?,?,?)", userID, 1000+index, "alice", now, now, types.StateOK, `{}`, "13800000000", nil, nil); err != nil { "VALUES(?,?,?,?,?,?,?,?,?)", userID, 1000+index, "alice", now, now, types.StateOK, `{}`, "13800000000", nil); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if _, err := db.Exec("INSERT INTO "+tenantDB+".im_auth(userid,authlvl,secret) VALUES(?,?,?)", if _, err := db.Exec("INSERT INTO "+tenantDB+".im_auth(userid,authlvl,secret) VALUES(?,?,?)",
@@ -354,17 +354,25 @@ func TestTenantUserSearchMatchesUsernamePhoneAndPublicAccount(t *testing.T) {
id int64 id int64
username string username string
phone any phone any
public string nickname string
}{ }{
{viewerID, "viewerx", nil, `{"fn":"Viewer"}`}, {viewerID, "viewerx", nil, "Viewer"},
{targetID, "potter", "+8615500000002", `{"fn":"Potter"}`}, {targetID, "potter", "+8615500000002", "Potter"},
} { } {
if _, err := db.Exec( if _, err := db.Exec(
`INSERT INTO im_users(id,account_no,username,createdat,updatedat,state,access,phone,public,trusted) `INSERT INTO im_users(id,account_no,username,createdat,updatedat,state,access,phone,trusted)
VALUES(?,?,?,?,?,?,?,?,?,?)`, VALUES(?,?,?,?,?,?,?,?,?)`,
user.id, user.id%1000000, user.username, now, now, types.StateOK, `{"Auth":"JRWPAS","Anon":"N"}`, user.phone, user.public, `{}`); err != nil { user.id, user.id%1000000, user.username, now, now, types.StateOK, `{"Auth":"JRWPAS","Anon":"N"}`, user.phone, `{}`); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if user.id != targetID {
if _, err := db.Exec(
`INSERT INTO im_user_profiles(id,user_id,nickname,public_account,created_at,updated_at)
VALUES(?,?,?,?,?,?)`,
user.id+2000, user.id, user.nickname, store.EncodeUid(user.id).UserId(), now, now); err != nil {
t.Fatal(err)
}
}
if _, err := db.Exec( if _, err := db.Exec(
`INSERT INTO im_user_privacy_settings(id,user_id,add_me_methods,created_at,updated_at) `INSERT INTO im_user_privacy_settings(id,user_id,add_me_methods,created_at,updated_at)
VALUES(?,?,?,?,?)`, VALUES(?,?,?,?,?)`,
-90
View File
@@ -156,12 +156,6 @@ func ensureUserIdentitySchema(db *sqlx.DB) error {
if err = backfillProfileEmails(ctx, conn, usersScoped); err != nil { if err = backfillProfileEmails(ctx, conn, usersScoped); err != nil {
return err return err
} }
if err = backfillProfileDisplay(ctx, conn, usersScoped); err != nil {
return err
}
if err = backfillUserPublicFromProfile(ctx, conn, usersScoped); err != nil {
return err
}
} }
indexName := "users_phone" indexName := "users_phone"
@@ -490,90 +484,6 @@ func backfillProfileEmails(ctx context.Context, conn *sqlx.Conn, usersScoped boo
return nil return nil
} }
func backfillProfileDisplay(ctx context.Context, conn *sqlx.Conn, usersScoped bool) error {
profilesScoped, err := identityColumnExists(ctx, conn, "im_user_profiles", "tenant_id")
if err != nil {
return err
}
if profilesScoped != usersScoped {
return errors.New("mysql: im_users and im_user_profiles tenant scope mismatch")
}
nicknameExpr := `COALESCE(
NULLIF(JSON_UNQUOTE(JSON_EXTRACT(u.public,'$.display_name')), ''),
NULLIF(JSON_UNQUOTE(JSON_EXTRACT(u.public,'$.nickname')), ''),
NULLIF(JSON_UNQUOTE(JSON_EXTRACT(u.public,'$.fn')), ''),
NULLIF(JSON_UNQUOTE(JSON_EXTRACT(u.public,'$.name')), '')
)`
avatarExpr := `COALESCE(
NULLIF(JSON_UNQUOTE(JSON_EXTRACT(u.public,'$.avatar_file_id')), '')
)`
if usersScoped {
if _, err = conn.ExecContext(ctx, `INSERT IGNORE INTO im_user_profiles(id,tenant_id,user_id,nickname,avatar_file_id,public_account)
SELECT u.id,u.tenant_id,u.id,COALESCE(`+nicknameExpr+`,''),COALESCE(`+avatarExpr+`,''),''
FROM im_users AS u
WHERE `+nicknameExpr+` IS NOT NULL OR `+avatarExpr+` IS NOT NULL`); err != nil {
return err
}
_, err = conn.ExecContext(ctx, `UPDATE im_user_profiles AS p
JOIN im_users AS u ON u.tenant_id=p.tenant_id AND u.id=p.user_id
SET p.nickname=IF(p.nickname='' AND `+nicknameExpr+` IS NOT NULL, `+nicknameExpr+`, p.nickname),
p.avatar_file_id=IF(p.avatar_file_id='' AND `+avatarExpr+` IS NOT NULL, `+avatarExpr+`, p.avatar_file_id)
WHERE (p.nickname='' AND `+nicknameExpr+` IS NOT NULL) OR (p.avatar_file_id='' AND `+avatarExpr+` IS NOT NULL)`)
return err
}
if _, err = conn.ExecContext(ctx, `INSERT IGNORE INTO im_user_profiles(id,user_id,nickname,avatar_file_id,public_account)
SELECT u.id,u.id,COALESCE(`+nicknameExpr+`,''),COALESCE(`+avatarExpr+`,''),''
FROM im_users AS u
WHERE `+nicknameExpr+` IS NOT NULL OR `+avatarExpr+` IS NOT NULL`); err != nil {
return err
}
_, err = conn.ExecContext(ctx, `UPDATE im_user_profiles AS p
JOIN im_users AS u ON u.id=p.user_id
SET p.nickname=IF(p.nickname='' AND `+nicknameExpr+` IS NOT NULL, `+nicknameExpr+`, p.nickname),
p.avatar_file_id=IF(p.avatar_file_id='' AND `+avatarExpr+` IS NOT NULL, `+avatarExpr+`, p.avatar_file_id)
WHERE (p.nickname='' AND `+nicknameExpr+` IS NOT NULL) OR (p.avatar_file_id='' AND `+avatarExpr+` IS NOT NULL)`)
return err
}
func backfillUserPublicFromProfile(ctx context.Context, conn *sqlx.Conn, usersScoped bool) error {
profilesScoped, err := identityColumnExists(ctx, conn, "im_user_profiles", "tenant_id")
if err != nil {
return err
}
if profilesScoped != usersScoped {
return errors.New("mysql: im_users and im_user_profiles tenant scope mismatch")
}
publicDoc := "COALESCE(NULLIF(u.public,''), JSON_OBJECT())"
nicknamePatch := `u.public=JSON_SET(` + publicDoc + `,
'$.display_name',p.nickname,'$.nickname',p.nickname,'$.fn',p.nickname,'$.name',p.nickname)`
avatarPatch := `u.public=JSON_SET(` + publicDoc + `,'$.avatar_file_id',p.avatar_file_id)`
accountPatch := `u.public=JSON_SET(` + publicDoc + `,'$.public_account',p.public_account)`
join := "JOIN im_users AS u ON u.id=p.user_id"
if usersScoped {
join = "JOIN im_users AS u ON u.tenant_id=p.tenant_id AND u.id=p.user_id"
}
for _, query := range []string{
`UPDATE im_user_profiles AS p ` + join + `
SET ` + nicknamePatch + `
WHERE p.nickname<>'' AND (
COALESCE(JSON_UNQUOTE(JSON_EXTRACT(u.public,'$.display_name')),'')<>p.nickname OR
COALESCE(JSON_UNQUOTE(JSON_EXTRACT(u.public,'$.nickname')),'')<>p.nickname OR
COALESCE(JSON_UNQUOTE(JSON_EXTRACT(u.public,'$.fn')),'')<>p.nickname OR
COALESCE(JSON_UNQUOTE(JSON_EXTRACT(u.public,'$.name')),'')<>p.nickname)`,
`UPDATE im_user_profiles AS p ` + join + `
SET ` + avatarPatch + `
WHERE p.avatar_file_id<>'' AND COALESCE(JSON_UNQUOTE(JSON_EXTRACT(u.public,'$.avatar_file_id')),'')<>p.avatar_file_id`,
`UPDATE im_user_profiles AS p ` + join + `
SET ` + accountPatch + `
WHERE p.public_account<>'' AND COALESCE(JSON_UNQUOTE(JSON_EXTRACT(u.public,'$.public_account')),'')<>p.public_account`,
} {
if _, err = conn.ExecContext(ctx, query); err != nil {
return err
}
}
return nil
}
func identityCandidates(ctx context.Context, conn *sqlx.Conn, scoped bool, method string) ([]identityValue, error) { func identityCandidates(ctx context.Context, conn *sqlx.Conn, scoped bool, method string) ([]identityValue, error) {
var out []identityValue var out []identityValue
appendQuery := func(query string, args ...any) error { appendQuery := func(query string, args ...any) error {
+2 -18
View File
@@ -28,18 +28,7 @@ func TestNormalizeUserPhone(t *testing.T) {
} }
func TestProfilePublicFromFields(t *testing.T) { func TestProfilePublicFromFields(t *testing.T) {
public := profilePublicFromFields(map[string]any{ public := profilePublicFromFields(sql.NullString{String: "新昵称", Valid: true}, sql.NullString{String: "file_avatar_1", Valid: true},
"fn": "旧昵称",
"name": "旧昵称",
"nickname": "旧昵称",
"display_name": "旧昵称",
"avatar_file_id": "old_avatar",
"signature": "旧签名",
"gender": "old_gender",
"region": "old_region",
"photo": "old_photo",
"public_account": "usrAccount",
}, sql.NullString{String: "新昵称", Valid: true}, sql.NullString{String: "file_avatar_1", Valid: true},
sql.NullString{String: "新签名", Valid: true}, sql.NullString{String: "male", Valid: true}, sql.NullString{String: "新签名", Valid: true}, sql.NullString{String: "male", Valid: true},
sql.NullString{String: "上海", Valid: true}, sql.NullString{String: "usrProfile", Valid: true}).(map[string]any) sql.NullString{String: "上海", Valid: true}, sql.NullString{String: "usrProfile", Valid: true}).(map[string]any)
@@ -53,12 +42,7 @@ func TestProfilePublicFromFields(t *testing.T) {
t.Fatalf("profile public not overlaid: %#v", public) t.Fatalf("profile public not overlaid: %#v", public)
} }
public = profilePublicFromFields(map[string]any{ public = profilePublicFromFields(sql.NullString{Valid: true}, sql.NullString{Valid: true}, sql.NullString{Valid: true},
"fn": "旧昵称",
"avatar_file_id": "old_avatar",
"public_account": "usrAccount",
"signature": "旧签名",
}, 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", "photo", "avatar", "signature", "gender", "region", "public_account"} {
if _, ok := public[key]; ok { if _, ok := public[key]; ok {
+1 -6
View File
@@ -326,16 +326,11 @@ func internalAdminQuerySpec(resource string) (*internalQuerySpec, bool) {
func usersInternalQuerySpec() *internalQuerySpec { func usersInternalQuerySpec() *internalQuerySpec {
return &internalQuerySpec{ return &internalQuerySpec{
Select: `SELECT CAST(u.id AS CHAR) AS user_id,u.account_no AS display_no,u.username,u.phone,u.state,u.stateat AS state_at,u.access,u.lastseen AS last_seen,u.useragent AS user_agent,u.public,u.trusted, Select: `SELECT CAST(u.id AS CHAR) AS user_id,u.account_no AS display_no,u.username,u.phone,u.state,u.stateat AS state_at,u.access,u.lastseen AS last_seen,u.useragent AS user_agent,u.trusted,
p.nickname,p.public_account,p.gender,p.region,p.avatar_file_id,COALESCE(p.friend_add_enabled,1) AS friend_add_enabled, p.nickname,p.public_account,p.gender,p.region,p.avatar_file_id,COALESCE(p.friend_add_enabled,1) AS friend_add_enabled,
COALESCE( COALESCE(
NULLIF(avatar.location,''), NULLIF(avatar.location,''),
NULLIF(CASE WHEN p.avatar_file_id REGEXP '^(https?://|data:)' THEN p.avatar_file_id ELSE '' END,''), NULLIF(CASE WHEN p.avatar_file_id REGEXP '^(https?://|data:)' THEN p.avatar_file_id ELSE '' END,''),
NULLIF(JSON_UNQUOTE(JSON_EXTRACT(u.public,'$.avatar')),''),
NULLIF(CASE WHEN JSON_TYPE(JSON_EXTRACT(u.public,'$.photo'))='STRING' THEN JSON_UNQUOTE(JSON_EXTRACT(u.public,'$.photo')) ELSE '' END,''),
CASE WHEN JSON_TYPE(JSON_EXTRACT(u.public,'$.photo'))='OBJECT' AND NULLIF(JSON_UNQUOTE(JSON_EXTRACT(u.public,'$.photo.data')),'') IS NOT NULL
THEN CONCAT('data:image/',COALESCE(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(u.public,'$.photo.type')),''),'jpeg'),';base64,',JSON_UNQUOTE(JSON_EXTRACT(u.public,'$.photo.data'))) ELSE NULL END,
NULLIF(CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(u.public,'$.avatar_file_id')) REGEXP '^(https?://|data:)' THEN JSON_UNQUOTE(JSON_EXTRACT(u.public,'$.avatar_file_id')) ELSE '' END,''),
'' ''
) AS avatar, ) AS avatar,
p.signature,sec.auth_version,sec.password_updated_at,u.createdat AS created_at,u.updatedat AS updated_at, p.signature,sec.auth_version,sec.password_updated_at,u.createdat AS created_at,u.updatedat AS updated_at,
+1 -1
View File
@@ -523,7 +523,7 @@ func serveInternalGroupMembersQuery(wrt http.ResponseWriter, req *http.Request,
return return
} }
spec := &internalQuerySpec{ spec := &internalQuerySpec{
Select: `SELECT gm.id,gm.topic,gm.user_id,u.state,u.public,p.nickname,p.public_account,s.modewant AS mode_want,s.modegiven AS mode_given,s.recvseqid AS recv_seq_id,s.readseqid AS read_seq_id,s.delid AS del_id, Select: `SELECT gm.id,gm.topic,gm.user_id,u.state,p.nickname,p.public_account,p.avatar_file_id,p.signature,p.gender,p.region,s.modewant AS mode_want,s.modegiven AS mode_given,s.recvseqid AS recv_seq_id,s.readseqid AS read_seq_id,s.delid AS del_id,
gm.role,gm.member_status,gm.nickname AS group_nickname,gm.pinned,gm.mute_notifications,gm.join_source,gm.joined_by,gm.joined_at,gm.left_at,gm.removed_by,gm.version,gm.created_at,gm.updated_at`, gm.role,gm.member_status,gm.nickname AS group_nickname,gm.pinned,gm.mute_notifications,gm.join_source,gm.joined_by,gm.joined_at,gm.left_at,gm.removed_by,gm.version,gm.created_at,gm.updated_at`,
From: "FROM im_group_members AS gm LEFT JOIN im_users AS u ON u.id=gm.user_id LEFT JOIN im_user_profiles AS p ON p.user_id=gm.user_id LEFT JOIN im_subscriptions AS s ON s.topic=gm.topic AND s.userid=gm.user_id", From: "FROM im_group_members AS gm LEFT JOIN im_users AS u ON u.id=gm.user_id LEFT JOIN im_user_profiles AS p ON p.user_id=gm.user_id LEFT JOIN im_subscriptions AS s ON s.topic=gm.topic AND s.userid=gm.user_id",
DefaultSort: "ORDER BY gm.id DESC", DefaultSort: "ORDER BY gm.id DESC",