refactor: read user profile fields from profile table
This commit is contained in:
@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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, " ")+`
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(?,?,?,?,?)`,
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user