Read user profile display from profile table
This commit is contained in:
@@ -3084,12 +3084,13 @@ func (a *adapter) TopicsForUser(uid t.Uid, keepDeleted bool, opts *t.QueryOpt) (
|
||||
|
||||
// Fetch p2p users and join to p2p subscriptions.
|
||||
if len(usrq) > 0 {
|
||||
q = "SELECT id,updatedat,state,access,lastseen,useragent,public,trusted " +
|
||||
"FROM im_users WHERE id IN (?)"
|
||||
q = `SELECT u.id,u.updatedat,u.state,u.access,u.lastseen,u.useragent,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 (?)`
|
||||
q, args, _ = sqlx.In(q, usrq)
|
||||
if !keepDeleted {
|
||||
// Optionally skip deleted users.
|
||||
q += " AND state!=?"
|
||||
q += " AND u.state!=?"
|
||||
args = append(args, t.StateDeleted)
|
||||
}
|
||||
|
||||
@@ -3105,19 +3106,19 @@ func (a *adapter) TopicsForUser(uid t.Uid, keepDeleted bool, opts *t.QueryOpt) (
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var usr2 t.User
|
||||
var usr2 profileUserRow
|
||||
if err = rows.StructScan(&usr2); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
joinOn := uid.P2PName(common.EncodeUidString(usr2.Id))
|
||||
joinOn := uid.P2PName(common.EncodeUidString(usr2.ID))
|
||||
if sub, ok := join[joinOn]; ok {
|
||||
sub.UpdatedAt = common.SelectLatestTime(sub.UpdatedAt, usr2.UpdatedAt)
|
||||
sub.UpdatedAt = common.SelectLatestTime(sub.UpdatedAt, usr2.effectiveUpdatedAt())
|
||||
sub.SetState(usr2.State)
|
||||
sub.SetPublic(common.FromJSON(usr2.Public))
|
||||
sub.SetPublic(usr2.profilePublic())
|
||||
sub.SetTrusted(common.FromJSON(usr2.Trusted))
|
||||
sub.SetDefaultAccess(usr2.Access.Auth, usr2.Access.Anon)
|
||||
sub.SetLastSeenAndUA(usr2.LastSeen, usr2.UserAgent)
|
||||
sub.SetLastSeenAndUA(usr2.lastSeen(), usr2.UserAgent)
|
||||
join[joinOn] = sub
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,83 @@ func stampTenantUser(id t.TenantID, user *t.User) *t.User {
|
||||
return user
|
||||
}
|
||||
|
||||
type profileUserRow struct {
|
||||
TenantID t.TenantID `db:"tenant_id"`
|
||||
ID string `db:"id"`
|
||||
UpdatedAt time.Time `db:"updatedat"`
|
||||
State t.ObjState `db:"state"`
|
||||
Access t.DefaultAccess
|
||||
LastSeen sql.NullTime `db:"lastseen"`
|
||||
UserAgent string `db:"useragent"`
|
||||
Public any `db:"public"`
|
||||
Trusted any `db:"trusted"`
|
||||
ProfileNickname sql.NullString `db:"profile_nickname"`
|
||||
ProfileAvatarFileID sql.NullString `db:"profile_avatar_file_id"`
|
||||
ProfileUpdatedAt sql.NullTime `db:"profile_updated_at"`
|
||||
}
|
||||
|
||||
func (row profileUserRow) effectiveUpdatedAt() time.Time {
|
||||
if row.ProfileUpdatedAt.Valid {
|
||||
return common.SelectLatestTime(row.UpdatedAt, row.ProfileUpdatedAt.Time)
|
||||
}
|
||||
return row.UpdatedAt
|
||||
}
|
||||
|
||||
func (row profileUserRow) lastSeen() *time.Time {
|
||||
if row.LastSeen.Valid {
|
||||
return &row.LastSeen.Time
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (row profileUserRow) profilePublic() any {
|
||||
return profilePublicFromFields(common.FromJSON(row.Public), row.ProfileNickname, row.ProfileAvatarFileID)
|
||||
}
|
||||
|
||||
func profilePublicFromFields(base any, nickname, avatarFileID sql.NullString) any {
|
||||
public := publicMap(base)
|
||||
if nickname.Valid {
|
||||
value := strings.TrimSpace(nickname.String)
|
||||
for _, key := range []string{"display_name", "nickname", "fn", "name"} {
|
||||
if value == "" {
|
||||
delete(public, key)
|
||||
} else {
|
||||
public[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
if avatarFileID.Valid {
|
||||
value := strings.TrimSpace(avatarFileID.String)
|
||||
for _, key := range []string{"avatar_file_id", "photo", "avatar"} {
|
||||
if value == "" {
|
||||
delete(public, key)
|
||||
} else if key == "avatar_file_id" {
|
||||
public[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
return public
|
||||
}
|
||||
|
||||
func publicMap(value any) map[string]any {
|
||||
switch public := value.(type) {
|
||||
case map[string]any:
|
||||
out := make(map[string]any, len(public))
|
||||
for key, val := range public {
|
||||
out[key] = val
|
||||
}
|
||||
return out
|
||||
case map[string]string:
|
||||
out := make(map[string]any, len(public))
|
||||
for key, val := range public {
|
||||
out[key] = val
|
||||
}
|
||||
return out
|
||||
default:
|
||||
return map[string]any{}
|
||||
}
|
||||
}
|
||||
|
||||
func stampTenantUsers(id t.TenantID, users []t.User) []t.User {
|
||||
for i := range users {
|
||||
users[i].TenantID = id
|
||||
@@ -870,10 +947,12 @@ func (a *adapter) TenantTopicsForUser(id t.TenantID, uid t.Uid, keepDeleted bool
|
||||
}
|
||||
}
|
||||
if len(usrq) > 0 {
|
||||
q = "SELECT tenant_id,id,updatedat,state,access,lastseen,useragent,public,trusted FROM im_users WHERE tenant_id=? AND id IN (?)"
|
||||
q = `SELECT u.tenant_id,u.id,u.updatedat,u.state,u.access,u.lastseen,u.useragent,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.tenant_id=? AND u.id IN (?)`
|
||||
q, args, _ = sqlx.In(q, id, usrq)
|
||||
if !keepDeleted {
|
||||
q += " AND state!=?"
|
||||
q += " AND u.state!=?"
|
||||
args = append(args, t.StateDeleted)
|
||||
}
|
||||
rows, err = a.db.QueryxContext(ctx, a.db.Rebind(q), args...)
|
||||
@@ -881,19 +960,19 @@ func (a *adapter) TenantTopicsForUser(id t.TenantID, uid t.Uid, keepDeleted bool
|
||||
return nil, err
|
||||
}
|
||||
for rows.Next() {
|
||||
var user t.User
|
||||
var user profileUserRow
|
||||
if err = rows.StructScan(&user); err != nil {
|
||||
rows.Close()
|
||||
return nil, err
|
||||
}
|
||||
joinOn := uid.P2PName(common.EncodeUidString(user.Id))
|
||||
joinOn := uid.P2PName(common.EncodeUidString(user.ID))
|
||||
if sub, ok := join[joinOn]; ok {
|
||||
sub.UpdatedAt = common.SelectLatestTime(sub.UpdatedAt, user.UpdatedAt)
|
||||
sub.UpdatedAt = common.SelectLatestTime(sub.UpdatedAt, user.effectiveUpdatedAt())
|
||||
sub.SetState(user.State)
|
||||
sub.SetPublic(common.FromJSON(user.Public))
|
||||
sub.SetPublic(user.profilePublic())
|
||||
sub.SetTrusted(common.FromJSON(user.Trusted))
|
||||
sub.SetDefaultAccess(user.Access.Auth, user.Access.Anon)
|
||||
sub.SetLastSeenAndUA(user.LastSeen, user.UserAgent)
|
||||
sub.SetLastSeenAndUA(user.lastSeen(), user.UserAgent)
|
||||
join[joinOn] = sub
|
||||
}
|
||||
}
|
||||
@@ -8791,7 +8870,8 @@ func (a *adapter) TenantFriendshipList(id t.TenantID, uid t.Uid, queryText, init
|
||||
if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
rows, err := a.db.QueryxContext(ctx, `SELECT fs.*,u.public,u.trusted
|
||||
rows, err := a.db.QueryxContext(ctx, `SELECT fs.*,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_friendships AS fs
|
||||
JOIN im_users AS u ON u.id=fs.peer_id
|
||||
LEFT JOIN im_user_profiles AS p ON p.user_id=u.id
|
||||
@@ -8805,16 +8885,19 @@ func (a *adapter) TenantFriendshipList(id t.TenantID, uid t.Uid, queryText, init
|
||||
nextCursor := ""
|
||||
for rows.Next() {
|
||||
row := struct {
|
||||
ID int64
|
||||
UserID int64 `db:"user_id"`
|
||||
PeerID int64 `db:"peer_id"`
|
||||
Status string
|
||||
Remark string
|
||||
Topic string
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
Public any
|
||||
Trusted any
|
||||
ID int64
|
||||
UserID int64 `db:"user_id"`
|
||||
PeerID int64 `db:"peer_id"`
|
||||
Status string
|
||||
Remark string
|
||||
Topic string
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
Public any
|
||||
Trusted any
|
||||
ProfileNickname sql.NullString `db:"profile_nickname"`
|
||||
ProfileAvatarFileID sql.NullString `db:"profile_avatar_file_id"`
|
||||
ProfileUpdatedAt sql.NullTime `db:"profile_updated_at"`
|
||||
}{}
|
||||
if err = rows.StructScan(&row); err != nil {
|
||||
return nil, "", err
|
||||
@@ -8838,7 +8921,7 @@ func (a *adapter) TenantFriendshipList(id t.TenantID, uid t.Uid, queryText, init
|
||||
Peer: &t.User{
|
||||
ObjHeader: t.ObjHeader{Id: peerID.String()},
|
||||
TenantID: id,
|
||||
Public: common.FromJSON(row.Public),
|
||||
Public: profilePublicFromFields(common.FromJSON(row.Public), row.ProfileNickname, row.ProfileAvatarFileID),
|
||||
Trusted: common.FromJSON(row.Trusted),
|
||||
},
|
||||
})
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
|
||||
package mysql
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNormalizeUserPhone(t *testing.T) {
|
||||
for input, want := range map[string]string{
|
||||
@@ -23,3 +26,34 @@ func TestNormalizeUserPhone(t *testing.T) {
|
||||
t.Fatal("invalid phone unexpectedly accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProfilePublicFromFields(t *testing.T) {
|
||||
public := profilePublicFromFields(map[string]any{
|
||||
"fn": "旧昵称",
|
||||
"name": "旧昵称",
|
||||
"nickname": "旧昵称",
|
||||
"display_name": "旧昵称",
|
||||
"avatar_file_id": "old_avatar",
|
||||
"photo": "old_photo",
|
||||
"public_account": "usrAccount",
|
||||
}, sql.NullString{String: "新昵称", Valid: true}, sql.NullString{String: "file_avatar_1", Valid: true}).(map[string]any)
|
||||
|
||||
for _, key := range []string{"fn", "name", "nickname", "display_name"} {
|
||||
if public[key] != "新昵称" {
|
||||
t.Fatalf("nickname field %s not overlaid: %#v", key, public)
|
||||
}
|
||||
}
|
||||
if public["avatar_file_id"] != "file_avatar_1" || public["public_account"] != "usrAccount" {
|
||||
t.Fatalf("profile public not overlaid: %#v", public)
|
||||
}
|
||||
|
||||
public = profilePublicFromFields(public, 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"} {
|
||||
if _, ok := public[key]; ok {
|
||||
t.Fatalf("field %s should be hidden by empty profile value: %#v", key, public)
|
||||
}
|
||||
}
|
||||
if public["public_account"] != "usrAccount" {
|
||||
t.Fatalf("unrelated public field changed: %#v", public)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user