Sync profile source and message topic seq
This commit is contained in:
+82
-12
@@ -1055,6 +1055,9 @@ func (a *adapter) UpgradeDb() error {
|
|||||||
if err := a.ensureMessageTopicSeqIndex(); err != nil {
|
if err := a.ensureMessageTopicSeqIndex(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := a.ensureTopicSeqMatchesMessages(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if a.version == adpVersion {
|
if a.version == adpVersion {
|
||||||
return nil
|
return nil
|
||||||
@@ -1080,6 +1083,61 @@ func (a *adapter) ensureMessageTopicSeqIndex() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *adapter) ensureTopicSeqMatchesMessages() error {
|
||||||
|
ctx := context.Background()
|
||||||
|
conn, err := a.db.Connx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
topicsScoped, err := identityColumnExists(ctx, conn, "im_topics", "tenant_id")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
messagesScoped, err := identityColumnExists(ctx, conn, "im_messages", "tenant_id")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if topicsScoped && messagesScoped {
|
||||||
|
if _, err = conn.ExecContext(ctx, `UPDATE im_topics AS t
|
||||||
|
JOIN (SELECT tenant_id,topic,MAX(seqid) AS max_seq FROM im_messages GROUP BY tenant_id,topic) AS m
|
||||||
|
ON m.tenant_id=t.tenant_id AND m.topic COLLATE utf8mb4_unicode_ci=t.name
|
||||||
|
SET t.seqid=GREATEST(t.seqid,m.max_seq)
|
||||||
|
WHERE t.seqid<m.max_seq`); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if _, err = conn.ExecContext(ctx, `UPDATE im_topics AS t
|
||||||
|
JOIN (SELECT topic,MAX(seqid) AS max_seq FROM im_messages GROUP BY topic) AS m ON m.topic COLLATE utf8mb4_unicode_ci=t.name
|
||||||
|
SET t.seqid=GREATEST(t.seqid,m.max_seq)
|
||||||
|
WHERE t.seqid<m.max_seq`); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
groupMessagesExist, err := identityTableExists(ctx, conn, "im_group_messages")
|
||||||
|
if err != nil || !groupMessagesExist {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
groupMessagesScoped, err := identityColumnExists(ctx, conn, "im_group_messages", "tenant_id")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if topicsScoped && groupMessagesScoped {
|
||||||
|
_, err = conn.ExecContext(ctx, `UPDATE im_topics AS t
|
||||||
|
JOIN (SELECT tenant_id,topic,MAX(seqid) AS max_seq FROM im_group_messages GROUP BY tenant_id,topic) AS m
|
||||||
|
ON m.tenant_id=t.tenant_id AND m.topic COLLATE utf8mb4_unicode_ci=t.name
|
||||||
|
SET t.seqid=GREATEST(t.seqid,m.max_seq)
|
||||||
|
WHERE t.seqid<m.max_seq`)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = conn.ExecContext(ctx, `UPDATE im_topics AS t
|
||||||
|
JOIN (SELECT topic,MAX(seqid) AS max_seq FROM im_group_messages GROUP BY topic) AS m ON m.topic COLLATE utf8mb4_unicode_ci=t.name
|
||||||
|
SET t.seqid=GREATEST(t.seqid,m.max_seq)
|
||||||
|
WHERE t.seqid<m.max_seq`)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func createIMNotificationTables(tx *sql.Tx) error {
|
func createIMNotificationTables(tx *sql.Tx) error {
|
||||||
_, err := tx.Exec(
|
_, err := tx.Exec(
|
||||||
`CREATE TABLE im_system_notifications(
|
`CREATE TABLE im_system_notifications(
|
||||||
@@ -2019,6 +2077,10 @@ func (a *adapter) UserCreate(user *t.User) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
profileID, err := nextSnowflakeID()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
var phone any
|
var phone any
|
||||||
if strings.TrimSpace(user.Phone) != "" {
|
if strings.TrimSpace(user.Phone) != "" {
|
||||||
canonicalPhone, err := normalizeUserPhone(user.Phone)
|
canonicalPhone, err := normalizeUserPhone(user.Phone)
|
||||||
@@ -2048,6 +2110,11 @@ 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)
|
||||||
|
VALUES(?,?,?,?,?,?,?)`, profileID, decoded_uid, avatarFileID, nickname, user.Uid().UserId(), user.CreatedAt, user.UpdatedAt); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
user.Tags = nil
|
user.Tags = nil
|
||||||
|
|
||||||
return tx.Commit()
|
return tx.Commit()
|
||||||
@@ -2316,13 +2383,15 @@ func (a *adapter) UserGet(uid t.Uid) (*t.User, error) {
|
|||||||
if cancel != nil {
|
if cancel != nil {
|
||||||
defer cancel()
|
defer cancel()
|
||||||
}
|
}
|
||||||
var user t.User
|
var row profileUserRow
|
||||||
err := a.db.GetContext(ctx, &user, `SELECT id,username,createdat,updatedat,state,stateat,access,lastseen,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(phone,'') AS phone,public,trusted FROM im_users WHERE id=? AND state!=?`, store.DecodeUid(uid), t.StateDeleted)
|
COALESCE(u.phone,'') AS phone,u.public,u.trusted,
|
||||||
|
p.nickname AS profile_nickname,p.avatar_file_id AS profile_avatar_file_id,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=? AND u.state!=?`, store.DecodeUid(uid), t.StateDeleted)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
user := profileUserFromRow(row)
|
||||||
user.SetUid(uid)
|
user.SetUid(uid)
|
||||||
user.Public = common.FromJSON(user.Public)
|
|
||||||
user.Trusted = common.FromJSON(user.Trusted)
|
|
||||||
return &user, nil
|
return &user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2348,8 +2417,11 @@ func (a *adapter) UserGetAll(ids ...t.Uid) ([]t.User, error) {
|
|||||||
if cancel != nil {
|
if cancel != nil {
|
||||||
defer cancel()
|
defer cancel()
|
||||||
}
|
}
|
||||||
q, uids, _ := sqlx.In(`SELECT id,username,createdat,updatedat,state,stateat,access,lastseen,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(phone,'') AS phone,public,trusted FROM im_users WHERE id IN (?) AND state!=?`, uids, t.StateDeleted)
|
COALESCE(u.phone,'') AS phone,u.public,u.trusted,
|
||||||
|
p.nickname AS profile_nickname,p.avatar_file_id AS profile_avatar_file_id,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!=?`, uids, t.StateDeleted)
|
||||||
rows, err := a.db.QueryxContext(ctx, a.db.Rebind(q), uids...)
|
rows, err := a.db.QueryxContext(ctx, a.db.Rebind(q), uids...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -2357,14 +2429,12 @@ func (a *adapter) UserGetAll(ids ...t.Uid) ([]t.User, error) {
|
|||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
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 {
|
||||||
users = nil
|
users = nil
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
user.SetUid(common.EncodeUidString(user.Id))
|
user := profileUserFromRow(row)
|
||||||
user.Public = common.FromJSON(user.Public)
|
|
||||||
user.Trusted = common.FromJSON(user.Trusted)
|
|
||||||
|
|
||||||
users = append(users, user)
|
users = append(users, user)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,9 +50,13 @@ func stampTenantUser(id t.TenantID, user *t.User) *t.User {
|
|||||||
type profileUserRow struct {
|
type profileUserRow struct {
|
||||||
TenantID t.TenantID `db:"tenant_id"`
|
TenantID t.TenantID `db:"tenant_id"`
|
||||||
ID string `db:"id"`
|
ID string `db:"id"`
|
||||||
|
Username string
|
||||||
|
CreatedAt time.Time `db:"createdat"`
|
||||||
UpdatedAt time.Time `db:"updatedat"`
|
UpdatedAt time.Time `db:"updatedat"`
|
||||||
State t.ObjState `db:"state"`
|
State t.ObjState `db:"state"`
|
||||||
|
StateAt *time.Time `db:"stateat"`
|
||||||
Access t.DefaultAccess
|
Access t.DefaultAccess
|
||||||
|
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"`
|
Public any `db:"public"`
|
||||||
@@ -80,8 +84,31 @@ func (row profileUserRow) profilePublic() any {
|
|||||||
return profilePublicFromFields(common.FromJSON(row.Public), row.ProfileNickname, row.ProfileAvatarFileID)
|
return profilePublicFromFields(common.FromJSON(row.Public), row.ProfileNickname, row.ProfileAvatarFileID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func profileUserFromRow(row profileUserRow) t.User {
|
||||||
|
user := t.User{
|
||||||
|
ObjHeader: t.ObjHeader{
|
||||||
|
Id: row.ID,
|
||||||
|
CreatedAt: row.CreatedAt,
|
||||||
|
UpdatedAt: row.effectiveUpdatedAt(),
|
||||||
|
},
|
||||||
|
TenantID: row.TenantID,
|
||||||
|
State: row.State,
|
||||||
|
StateAt: row.StateAt,
|
||||||
|
Username: row.Username,
|
||||||
|
Access: row.Access,
|
||||||
|
LastSeen: row.lastSeen(),
|
||||||
|
UserAgent: row.UserAgent,
|
||||||
|
Phone: row.Phone,
|
||||||
|
Public: row.profilePublic(),
|
||||||
|
Trusted: common.FromJSON(row.Trusted),
|
||||||
|
}
|
||||||
|
user.SetUid(common.EncodeUidString(row.ID))
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
func profilePublicFromFields(base any, nickname, avatarFileID sql.NullString) any {
|
func profilePublicFromFields(base any, nickname, avatarFileID sql.NullString) any {
|
||||||
public := publicMap(base)
|
public := publicMap(base)
|
||||||
|
clearProfilePublicFields(public)
|
||||||
if nickname.Valid {
|
if nickname.Valid {
|
||||||
value := strings.TrimSpace(nickname.String)
|
value := strings.TrimSpace(nickname.String)
|
||||||
if value != "" {
|
if value != "" {
|
||||||
@@ -99,6 +126,29 @@ func profilePublicFromFields(base any, nickname, avatarFileID sql.NullString) an
|
|||||||
return public
|
return public
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func clearProfilePublicFields(public map[string]any) {
|
||||||
|
for _, key := range []string{"display_name", "nickname", "fn", "name", "avatar_file_id", "photo", "avatar"} {
|
||||||
|
delete(public, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func profileSeedFromPublic(public any) (nickname, avatarFileID string) {
|
||||||
|
values := publicMap(public)
|
||||||
|
for _, key := range []string{"display_name", "nickname", "fn", "name"} {
|
||||||
|
if value := strings.TrimSpace(fmt.Sprint(values[key])); value != "" && value != "<nil>" {
|
||||||
|
nickname = value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, key := range []string{"avatar_file_id", "photo", "avatar"} {
|
||||||
|
if value := strings.TrimSpace(fmt.Sprint(values[key])); value != "" && value != "<nil>" {
|
||||||
|
avatarFileID = value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nickname, avatarFileID
|
||||||
|
}
|
||||||
|
|
||||||
func publicMap(value any) map[string]any {
|
func publicMap(value any) map[string]any {
|
||||||
switch public := value.(type) {
|
switch public := value.(type) {
|
||||||
case map[string]any:
|
case map[string]any:
|
||||||
@@ -282,9 +332,12 @@ func (a *adapter) TenantUserGet(id t.TenantID, uid t.Uid) (*t.User, error) {
|
|||||||
if cancel != nil {
|
if cancel != nil {
|
||||||
defer cancel()
|
defer cancel()
|
||||||
}
|
}
|
||||||
var user t.User
|
var row profileUserRow
|
||||||
err = a.db.GetContext(ctx, &user, `SELECT tenant_id,id,username,createdat,updatedat,state,stateat,access,lastseen,useragent,
|
err = a.db.GetContext(ctx, &row, `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=? AND state!=?`,
|
COALESCE(u.phone,'') AS phone,u.public,u.trusted,
|
||||||
|
p.nickname AS profile_nickname,p.avatar_file_id AS profile_avatar_file_id,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=? AND u.state!=?`,
|
||||||
id, decodeUID(uid), t.StateDeleted)
|
id, decodeUID(uid), t.StateDeleted)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -292,7 +345,7 @@ func (a *adapter) TenantUserGet(id t.TenantID, uid t.Uid) (*t.User, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
scanTenantUser(&user)
|
user := profileUserFromRow(row)
|
||||||
return &user, nil
|
return &user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9264,8 +9317,10 @@ func (a *adapter) TenantUserSearch(id t.TenantID, viewer t.Uid, queryText, searc
|
|||||||
if cancel != nil {
|
if cancel != nil {
|
||||||
defer cancel()
|
defer cancel()
|
||||||
}
|
}
|
||||||
rows, err := a.db.QueryxContext(ctx, `SELECT DISTINCT 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 u.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 FROM im_users AS u `+strings.Join(joins, " ")+`
|
COALESCE(u.phone,'') AS phone,u.public,u.trusted,
|
||||||
|
profile.nickname AS profile_nickname,profile.avatar_file_id AS profile_avatar_file_id,profile.updated_at AS profile_updated_at
|
||||||
|
FROM im_users AS u `+strings.Join(joins, " ")+`
|
||||||
WHERE `+strings.Join(where, " AND ")+` ORDER BY u.id ASC LIMIT ?`, args...)
|
WHERE `+strings.Join(where, " AND ")+` ORDER BY u.id ASC LIMIT ?`, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
@@ -9274,11 +9329,11 @@ func (a *adapter) TenantUserSearch(id t.TenantID, viewer t.Uid, queryText, searc
|
|||||||
users := make([]t.User, 0, limit)
|
users := make([]t.User, 0, limit)
|
||||||
nextCursor := ""
|
nextCursor := ""
|
||||||
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)
|
user := profileUserFromRow(row)
|
||||||
if len(users) == limit {
|
if len(users) == limit {
|
||||||
nextCursor = strconv.FormatInt(decodeUID(user.Uid()), 10)
|
nextCursor = strconv.FormatInt(decodeUID(user.Uid()), 10)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -108,6 +108,9 @@ 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
indexName := "users_phone"
|
indexName := "users_phone"
|
||||||
@@ -416,6 +419,53 @@ 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')), ''),
|
||||||
|
NULLIF(JSON_UNQUOTE(JSON_EXTRACT(u.public,'$.photo')), ''),
|
||||||
|
NULLIF(JSON_UNQUOTE(JSON_EXTRACT(u.public,'$.avatar')), '')
|
||||||
|
)`
|
||||||
|
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 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 {
|
||||||
|
|||||||
@@ -52,7 +52,22 @@ func TestProfilePublicFromFields(t *testing.T) {
|
|||||||
"avatar_file_id": "old_avatar",
|
"avatar_file_id": "old_avatar",
|
||||||
"public_account": "usrAccount",
|
"public_account": "usrAccount",
|
||||||
}, sql.NullString{Valid: true}, sql.NullString{Valid: true}).(map[string]any)
|
}, sql.NullString{Valid: true}, sql.NullString{Valid: true}).(map[string]any)
|
||||||
if public["fn"] != "旧昵称" || public["avatar_file_id"] != "old_avatar" || public["public_account"] != "usrAccount" {
|
for _, key := range []string{"fn", "name", "nickname", "display_name", "avatar_file_id", "photo", "avatar"} {
|
||||||
t.Fatalf("empty profile fields should keep existing public values: %#v", public)
|
if _, ok := public[key]; ok {
|
||||||
|
t.Fatalf("legacy profile field %s should not be read from im_users.public: %#v", key, public)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if public["public_account"] != "usrAccount" {
|
||||||
|
t.Fatalf("unrelated public field changed: %#v", public)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProfileSeedFromPublic(t *testing.T) {
|
||||||
|
nickname, avatar := profileSeedFromPublic(map[string]any{
|
||||||
|
"fn": "旧昵称",
|
||||||
|
"photo": "file_avatar_1",
|
||||||
|
})
|
||||||
|
if nickname != "旧昵称" || avatar != "file_avatar_1" {
|
||||||
|
t.Fatalf("unexpected profile seed nickname=%q avatar=%q", nickname, avatar)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3374,6 +3374,9 @@ func (messagesMapper) PersistPrepared(tenantID types.TenantID, msg *types.Messag
|
|||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if err = tenantAdp.TenantTopicUpdateOnMessage(tenantID, msg.Topic, msg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if isGroupMessage && groupMsgRuntime != nil && clientMsgID != "" {
|
if isGroupMessage && groupMsgRuntime != nil && clientMsgID != "" {
|
||||||
if err := groupMsgRuntime.MarkDone(tenantID, msg.Topic, fromUid, clientMsgID, msg.SeqId, msg.Uid().String()); err != nil {
|
if err := groupMsgRuntime.MarkDone(tenantID, msg.Topic, fromUid, clientMsgID, msg.SeqId, msg.Uid().String()); err != nil {
|
||||||
logs.Warn.Printf("topic[%s]: failed to mark group message unique key done: %v", msg.Topic, err)
|
logs.Warn.Printf("topic[%s]: failed to mark group message unique key done: %v", msg.Topic, err)
|
||||||
|
|||||||
Reference in New Issue
Block a user