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.
|
// Fetch p2p users and join to p2p subscriptions.
|
||||||
if len(usrq) > 0 {
|
if len(usrq) > 0 {
|
||||||
q = "SELECT id,updatedat,state,access,lastseen,useragent,public,trusted " +
|
q = `SELECT u.id,u.updatedat,u.state,u.access,u.lastseen,u.useragent,u.public,u.trusted,
|
||||||
"FROM im_users WHERE id IN (?)"
|
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)
|
q, args, _ = sqlx.In(q, usrq)
|
||||||
if !keepDeleted {
|
if !keepDeleted {
|
||||||
// Optionally skip deleted users.
|
// Optionally skip deleted users.
|
||||||
q += " AND state!=?"
|
q += " AND u.state!=?"
|
||||||
args = append(args, t.StateDeleted)
|
args = append(args, t.StateDeleted)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3105,19 +3106,19 @@ func (a *adapter) TopicsForUser(uid t.Uid, keepDeleted bool, opts *t.QueryOpt) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var usr2 t.User
|
var usr2 profileUserRow
|
||||||
if err = rows.StructScan(&usr2); err != nil {
|
if err = rows.StructScan(&usr2); err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
joinOn := uid.P2PName(common.EncodeUidString(usr2.Id))
|
joinOn := uid.P2PName(common.EncodeUidString(usr2.ID))
|
||||||
if sub, ok := join[joinOn]; ok {
|
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.SetState(usr2.State)
|
||||||
sub.SetPublic(common.FromJSON(usr2.Public))
|
sub.SetPublic(usr2.profilePublic())
|
||||||
sub.SetTrusted(common.FromJSON(usr2.Trusted))
|
sub.SetTrusted(common.FromJSON(usr2.Trusted))
|
||||||
sub.SetDefaultAccess(usr2.Access.Auth, usr2.Access.Anon)
|
sub.SetDefaultAccess(usr2.Access.Auth, usr2.Access.Anon)
|
||||||
sub.SetLastSeenAndUA(usr2.LastSeen, usr2.UserAgent)
|
sub.SetLastSeenAndUA(usr2.lastSeen(), usr2.UserAgent)
|
||||||
join[joinOn] = sub
|
join[joinOn] = sub
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,83 @@ func stampTenantUser(id t.TenantID, user *t.User) *t.User {
|
|||||||
return 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 {
|
func stampTenantUsers(id t.TenantID, users []t.User) []t.User {
|
||||||
for i := range users {
|
for i := range users {
|
||||||
users[i].TenantID = id
|
users[i].TenantID = id
|
||||||
@@ -870,10 +947,12 @@ func (a *adapter) TenantTopicsForUser(id t.TenantID, uid t.Uid, keepDeleted bool
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(usrq) > 0 {
|
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)
|
q, args, _ = sqlx.In(q, id, usrq)
|
||||||
if !keepDeleted {
|
if !keepDeleted {
|
||||||
q += " AND state!=?"
|
q += " AND u.state!=?"
|
||||||
args = append(args, t.StateDeleted)
|
args = append(args, t.StateDeleted)
|
||||||
}
|
}
|
||||||
rows, err = a.db.QueryxContext(ctx, a.db.Rebind(q), args...)
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var user t.User
|
var user profileUserRow
|
||||||
if err = rows.StructScan(&user); err != nil {
|
if err = rows.StructScan(&user); err != nil {
|
||||||
rows.Close()
|
rows.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
joinOn := uid.P2PName(common.EncodeUidString(user.Id))
|
joinOn := uid.P2PName(common.EncodeUidString(user.ID))
|
||||||
if sub, ok := join[joinOn]; ok {
|
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.SetState(user.State)
|
||||||
sub.SetPublic(common.FromJSON(user.Public))
|
sub.SetPublic(user.profilePublic())
|
||||||
sub.SetTrusted(common.FromJSON(user.Trusted))
|
sub.SetTrusted(common.FromJSON(user.Trusted))
|
||||||
sub.SetDefaultAccess(user.Access.Auth, user.Access.Anon)
|
sub.SetDefaultAccess(user.Access.Auth, user.Access.Anon)
|
||||||
sub.SetLastSeenAndUA(user.LastSeen, user.UserAgent)
|
sub.SetLastSeenAndUA(user.lastSeen(), user.UserAgent)
|
||||||
join[joinOn] = sub
|
join[joinOn] = sub
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8791,7 +8870,8 @@ func (a *adapter) TenantFriendshipList(id t.TenantID, uid t.Uid, queryText, init
|
|||||||
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.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
|
FROM im_friendships AS fs
|
||||||
JOIN im_users AS u ON u.id=fs.peer_id
|
JOIN im_users AS u ON u.id=fs.peer_id
|
||||||
LEFT JOIN im_user_profiles AS p ON p.user_id=u.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 := ""
|
nextCursor := ""
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
row := struct {
|
row := struct {
|
||||||
ID int64
|
ID int64
|
||||||
UserID int64 `db:"user_id"`
|
UserID int64 `db:"user_id"`
|
||||||
PeerID int64 `db:"peer_id"`
|
PeerID int64 `db:"peer_id"`
|
||||||
Status string
|
Status string
|
||||||
Remark string
|
Remark string
|
||||||
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
|
Public any
|
||||||
Trusted 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 {
|
if err = rows.StructScan(&row); err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
@@ -8838,7 +8921,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: common.FromJSON(row.Public),
|
Public: profilePublicFromFields(common.FromJSON(row.Public), row.ProfileNickname, row.ProfileAvatarFileID),
|
||||||
Trusted: common.FromJSON(row.Trusted),
|
Trusted: common.FromJSON(row.Trusted),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,7 +3,10 @@
|
|||||||
|
|
||||||
package mysql
|
package mysql
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func TestNormalizeUserPhone(t *testing.T) {
|
func TestNormalizeUserPhone(t *testing.T) {
|
||||||
for input, want := range map[string]string{
|
for input, want := range map[string]string{
|
||||||
@@ -23,3 +26,34 @@ func TestNormalizeUserPhone(t *testing.T) {
|
|||||||
t.Fatal("invalid phone unexpectedly accepted")
|
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