fix: improve IM user account search
This commit is contained in:
@@ -7132,8 +7132,12 @@ func (a *adapter) TenantUserSearch(id t.TenantID, viewer t.Uid, queryText, searc
|
|||||||
switch searchType {
|
switch searchType {
|
||||||
case "", "account":
|
case "", "account":
|
||||||
like := "%" + queryText + "%"
|
like := "%" + queryText + "%"
|
||||||
where = append(where, "(profile.nickname LIKE ? OR profile.public_account LIKE ? OR CAST(u.public AS CHAR) LIKE ? OR u.id=?)")
|
phoneCandidate := queryText
|
||||||
args = append(args, like, like, like, userIDNumericArg(queryText))
|
if phone, err := normalizeUserPhone(queryText); err == nil {
|
||||||
|
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=?)")
|
||||||
|
args = append(args, like, like, like, queryText, phoneCandidate, like, 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))
|
||||||
|
|||||||
@@ -326,6 +326,79 @@ func TestTenantAuthIsolation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTenantUserSearchMatchesUsernamePhoneAndPublicAccount(t *testing.T) {
|
||||||
|
businessAdp, ok := adp.(adapter.TenantBusinessAdapter)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("MySQL adapter does not implement TenantBusinessAdapter")
|
||||||
|
}
|
||||||
|
var tenantID types.TenantID
|
||||||
|
if err := db.Get(&tenantID, "SELECT id FROM im_tenant WHERE code='default'"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
now := types.TimeNow()
|
||||||
|
viewerID := now.UnixNano() & 0x3fffffffffffffff
|
||||||
|
targetID := viewerID + 1
|
||||||
|
viewer := store.EncodeUid(viewerID)
|
||||||
|
target := store.EncodeUid(targetID)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
db.Exec("DELETE FROM im_friendships WHERE user_id IN (?,?) OR peer_id IN (?,?)", viewerID, targetID, viewerID, targetID)
|
||||||
|
db.Exec("DELETE FROM im_user_privacy_settings WHERE user_id IN (?,?)", viewerID, targetID)
|
||||||
|
db.Exec("DELETE FROM im_user_profiles WHERE user_id IN (?,?)", viewerID, targetID)
|
||||||
|
db.Exec("DELETE FROM im_credentials WHERE userid IN (?,?)", viewerID, targetID)
|
||||||
|
db.Exec("DELETE FROM im_auth WHERE userid IN (?,?)", viewerID, targetID)
|
||||||
|
db.Exec("DELETE FROM im_users WHERE id IN (?,?)", viewerID, targetID)
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, user := range []struct {
|
||||||
|
id int64
|
||||||
|
username string
|
||||||
|
phone any
|
||||||
|
public string
|
||||||
|
}{
|
||||||
|
{viewerID, "viewerx", nil, `{"fn":"Viewer"}`},
|
||||||
|
{targetID, "potter", "+8615500000002", `{"fn":"Potter"}`},
|
||||||
|
} {
|
||||||
|
if _, err := db.Exec(
|
||||||
|
`INSERT INTO im_users(id,username,createdat,updatedat,state,access,phone,public,trusted)
|
||||||
|
VALUES(?,?,?,?,?,?,?,?,?)`,
|
||||||
|
user.id, user.username, now, now, types.StateOK, `{"Auth":"JRWPAS","Anon":"N"}`, user.phone, user.public, `{}`); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := db.Exec(
|
||||||
|
`INSERT INTO im_user_privacy_settings(id,user_id,add_me_methods,created_at,updated_at)
|
||||||
|
VALUES(?,?,?,?,?)`,
|
||||||
|
user.id+1000, user.id, `{"account":true,"phone":true,"qr":true,"group":true}`, now, now); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := db.Exec(
|
||||||
|
`INSERT INTO im_user_profiles(id,user_id,nickname,public_account,avatar_file_id,signature,gender,region,created_at,updated_at)
|
||||||
|
VALUES(?,?,?,?,?,?,?,?,?,?)`,
|
||||||
|
targetID+1000, targetID, "后端-potter", "15500000002", "avatar-1", "hi", "other", "CN", now, now); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
query string
|
||||||
|
searchType string
|
||||||
|
}{
|
||||||
|
{"username", "potter", "account"},
|
||||||
|
{"public_account", "15500000002", "account"},
|
||||||
|
{"phone_raw", "15500000002", "account"},
|
||||||
|
{"phone_e164", "+8615500000002", "phone"},
|
||||||
|
} {
|
||||||
|
users, _, err := businessAdp.TenantUserSearch(tenantID, viewer, tc.query, tc.searchType, "", 20)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s search failed: %v", tc.name, err)
|
||||||
|
}
|
||||||
|
if len(users) != 1 || users[0].Uid() != target {
|
||||||
|
t.Fatalf("%s search returned %+v, want %s", tc.name, users, target.UserId())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ================== Create tests ================================
|
// ================== Create tests ================================
|
||||||
func TestUserCreate(t *testing.T) {
|
func TestUserCreate(t *testing.T) {
|
||||||
for _, user := range testData.Users {
|
for _, user := range testData.Users {
|
||||||
|
|||||||
@@ -6460,6 +6460,11 @@ func friendListItemPayload(item types.FriendListItem) map[string]any {
|
|||||||
|
|
||||||
func userSearchItemPayload(item types.UserSearchItem) map[string]any {
|
func userSearchItemPayload(item types.UserSearchItem) map[string]any {
|
||||||
data := userSummaryPayload(item.User, "")
|
data := userSummaryPayload(item.User, "")
|
||||||
|
if public, ok := item.User.Public.(map[string]any); ok {
|
||||||
|
copyOptionalProfileFields(data, public)
|
||||||
|
} else if public, ok := item.User.Public.(types.KVMap); ok {
|
||||||
|
copyOptionalProfileFields(data, map[string]any(public))
|
||||||
|
}
|
||||||
data["relationship"] = item.Relationship
|
data["relationship"] = item.Relationship
|
||||||
data["can_add"] = item.CanAdd
|
data["can_add"] = item.CanAdd
|
||||||
data["action"] = item.Action
|
data["action"] = item.Action
|
||||||
|
|||||||
@@ -4077,7 +4077,7 @@ func TestServeIMUserSearch(t *testing.T) {
|
|||||||
oldRelations := store.Relations
|
oldRelations := store.Relations
|
||||||
peer := types.Uid(2)
|
peer := types.Uid(2)
|
||||||
store.Relations = &testRelationsStore{searchResults: []types.UserSearchItem{{
|
store.Relations = &testRelationsStore{searchResults: []types.UserSearchItem{{
|
||||||
User: &types.User{ObjHeader: types.ObjHeader{Id: peer.String()}, TenantID: 7, Public: map[string]any{"display_name": "Alice", "avatar_file_id": "file-1"}},
|
User: &types.User{ObjHeader: types.ObjHeader{Id: peer.String()}, TenantID: 7, Public: map[string]any{"display_name": "Alice", "avatar_file_id": "file-1", "public_account": "alice001"}},
|
||||||
Relationship: types.RelationshipNone,
|
Relationship: types.RelationshipNone,
|
||||||
CanAdd: true,
|
CanAdd: true,
|
||||||
Action: "add",
|
Action: "add",
|
||||||
@@ -4095,7 +4095,8 @@ func TestServeIMUserSearch(t *testing.T) {
|
|||||||
}
|
}
|
||||||
data := decodeIMHTTPResponse(t, rec).Data.(map[string]any)
|
data := decodeIMHTTPResponse(t, rec).Data.(map[string]any)
|
||||||
items := data["items"].([]any)
|
items := data["items"].([]any)
|
||||||
if len(items) != 1 || items[0].(map[string]any)["relationship"] != types.RelationshipNone || items[0].(map[string]any)["can_add"] != true {
|
if len(items) != 1 || items[0].(map[string]any)["relationship"] != types.RelationshipNone || items[0].(map[string]any)["can_add"] != true ||
|
||||||
|
items[0].(map[string]any)["public_account"] != "alice001" {
|
||||||
t.Fatalf("unexpected search response: %#v", data)
|
t.Fatalf("unexpected search response: %#v", data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user