refactor: retire legacy tinode database routing
This commit is contained in:
@@ -43,7 +43,7 @@ const (
|
||||
adapterName = "mongodb"
|
||||
|
||||
defaultHost = "localhost:27017"
|
||||
defaultDatabase = "tinode"
|
||||
defaultDatabase = "meeting_im"
|
||||
|
||||
defaultMaxResults = 1024
|
||||
// This is capped by the Session's send queue limit (128).
|
||||
|
||||
@@ -248,6 +248,9 @@ func (a *adapter) Open(jsonconfig json.RawMessage) error {
|
||||
if a.dbName == "" {
|
||||
a.dbName = defaultDatabase
|
||||
}
|
||||
if strings.EqualFold(strings.TrimSpace(a.dbName), "tinode") {
|
||||
return errors.New("mysql database tinode is deprecated; use meeting_im")
|
||||
}
|
||||
config.Config.DBName = a.dbName
|
||||
a.baseConfig = &config
|
||||
a.tenantDBRouting = config.TenantDBRouting
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
ms "github.com/go-sql-driver/mysql"
|
||||
@@ -17,17 +19,17 @@ func TestNormalizeMySQLDSN(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "go driver dsn",
|
||||
dsn: "root:secret@tcp(127.0.0.1:3306)/tinode?parseTime=true",
|
||||
dsn: "root:secret@tcp(127.0.0.1:3306)/meeting_im?parseTime=true",
|
||||
user: "root",
|
||||
},
|
||||
{
|
||||
name: "url scheme dsn",
|
||||
dsn: "mysql://root:secret@tcp(127.0.0.1:3306)/tinode?parseTime=true",
|
||||
dsn: "mysql://root:secret@tcp(127.0.0.1:3306)/meeting_im?parseTime=true",
|
||||
user: "root",
|
||||
},
|
||||
{
|
||||
name: "legacy scheme dsn",
|
||||
dsn: "mysql:root:secret@tcp(127.0.0.1:3306)/tinode?parseTime=true",
|
||||
dsn: "mysql:root:secret@tcp(127.0.0.1:3306)/meeting_im?parseTime=true",
|
||||
user: "root",
|
||||
},
|
||||
}
|
||||
@@ -43,3 +45,13 @@ func TestNormalizeMySQLDSN(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenRejectsDeprecatedTinodeDatabase(t *testing.T) {
|
||||
config := json.RawMessage(`{"dsn":"mysql://root:secret@tcp(127.0.0.1:3306)/tinode?parseTime=true"}`)
|
||||
adp := &adapter{}
|
||||
|
||||
err := adp.Open(config)
|
||||
if err == nil || !strings.Contains(err.Error(), "tinode is deprecated") {
|
||||
t.Fatalf("Open() error = %v, want deprecated database error", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ const (
|
||||
adapterName = "rethinkdb"
|
||||
|
||||
defaultHost = "localhost:28015"
|
||||
defaultDatabase = "tinode"
|
||||
defaultDatabase = "meeting_im"
|
||||
|
||||
defaultMaxResults = 1024
|
||||
// This is capped by the Session's send queue limit (128).
|
||||
|
||||
+38
-1
@@ -3816,7 +3816,7 @@ func serveIMFriendRequestCreate(wrt http.ResponseWriter, req *http.Request) {
|
||||
source := strings.TrimSpace(body.Source)
|
||||
sourceTopic := strings.TrimSpace(body.SourceTopic)
|
||||
switch source {
|
||||
case "search", "qr":
|
||||
case "search", "qr", "profile_card":
|
||||
if sourceTopic != "" {
|
||||
writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "source_topic is only valid for group source", nil)
|
||||
return
|
||||
@@ -6037,6 +6037,10 @@ func serveIMUserProfile(wrt http.ResponseWriter, req *http.Request, identifier s
|
||||
writeIMSuccess(wrt, req, blockedUserProfilePayload(user, ctx))
|
||||
return
|
||||
}
|
||||
if ctx.Relationship == types.RelationshipNone {
|
||||
writeIMSuccess(wrt, req, limitedUserProfilePayload(user, ctx, privacy))
|
||||
return
|
||||
}
|
||||
writeIMError(wrt, req, http.StatusForbidden, 30005, imReasonPermissionDenied, "profile is private", nil)
|
||||
return
|
||||
}
|
||||
@@ -9030,6 +9034,39 @@ func blockedUserProfilePayload(user *types.User, ctx *types.RelationshipContext)
|
||||
return data
|
||||
}
|
||||
|
||||
func limitedUserProfilePayload(user *types.User, ctx *types.RelationshipContext, privacy *types.UserPrivacySettings) map[string]any {
|
||||
data := userSummaryPayload(user, "")
|
||||
data["username"] = user.Username
|
||||
if original := userOriginalDisplayName(user); original != "" {
|
||||
data["profile_display_name"] = original
|
||||
data["original_display_name"] = original
|
||||
}
|
||||
data["relationship"] = ctx.Relationship
|
||||
data["topic"] = ctx.Topic
|
||||
data["capabilities"] = map[string]any{
|
||||
"can_send": false,
|
||||
"can_call": false,
|
||||
"can_add_friend": ctx.Relationship == types.RelationshipNone && imPrivacyAllowsAdd(privacy, "profile_card"),
|
||||
}
|
||||
if ctx.RequestID != "" {
|
||||
data["request_id"] = ctx.RequestID
|
||||
}
|
||||
if public, ok := user.Public.(map[string]any); ok {
|
||||
copyLimitedProfileFields(data, public)
|
||||
} else if public, ok := user.Public.(types.KVMap); ok {
|
||||
copyLimitedProfileFields(data, map[string]any(public))
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func copyLimitedProfileFields(data map[string]any, public map[string]any) {
|
||||
for _, key := range []string{"nickname", "public_account"} {
|
||||
if value, ok := public[key]; ok {
|
||||
data[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func copyOptionalProfileFields(data map[string]any, public map[string]any) {
|
||||
for _, key := range []string{"nickname", "gender", "region", "public_account", "signature"} {
|
||||
if value, ok := public[key]; ok {
|
||||
|
||||
@@ -7549,6 +7549,37 @@ func TestServeIMFriendRequestCreate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeIMFriendRequestCreateAllowsProfileCardSource(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
mockStore := expectIMBearer(t, ctrl, "token-1", 7, 1)
|
||||
mockStore.EXPECT().GetUidString().Return("card-1")
|
||||
oldRelations := store.Relations
|
||||
oldProfiles := store.Profiles
|
||||
relations := &testRelationsStore{context: &types.RelationshipContext{Relationship: types.RelationshipNone}}
|
||||
store.Relations = relations
|
||||
store.Profiles = &testProfilesStore{}
|
||||
defer func() {
|
||||
store.Relations = oldRelations
|
||||
store.Profiles = oldProfiles
|
||||
}()
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/friend-requests",
|
||||
bytes.NewBufferString(`{"target_user_id":"`+types.Uid(2).UserId()+`","message":"你好","source":"profile_card"}`))
|
||||
req.Header.Set("Authorization", "Bearer token-1")
|
||||
req.Header.Set("Idempotency-Key", "fr-card-1")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
serveIMFriendRequests(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status: got %d, want %d; body=%s", rec.Code, http.StatusOK, rec.Body.String())
|
||||
}
|
||||
if relations.created == nil || relations.created.Source != "profile_card" || relations.created.SourceTopic != "" {
|
||||
t.Fatalf("unexpected friend request created from profile card: %#v", relations.created)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeIMFriendRequestCreateReturnsNotFoundForMissingTarget(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
@@ -8105,6 +8136,70 @@ func TestServeIMUserProfile(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeIMUserProfilePrivateNonFriendReturnsLimitedProfile(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
expectIMBearer(t, ctrl, "token-1", 7, 1)
|
||||
oldUsers := store.Users
|
||||
oldRelations := store.Relations
|
||||
oldProfiles := store.Profiles
|
||||
users := mock_store.NewMockUsersPersistenceInterface(ctrl)
|
||||
peer := types.Uid(2)
|
||||
users.EXPECT().Get(types.TenantID(7), peer).Return(&types.User{
|
||||
ObjHeader: types.ObjHeader{Id: peer.String()},
|
||||
TenantID: 7,
|
||||
Username: "alice7",
|
||||
Public: map[string]any{
|
||||
"display_name": "Alice",
|
||||
"nickname": "Alice",
|
||||
"avatar_file_id": "file-1",
|
||||
"signature": "hello",
|
||||
"gender": "female",
|
||||
"region": "Shanghai",
|
||||
"public_account": "alice001",
|
||||
},
|
||||
}, nil)
|
||||
store.Users = users
|
||||
store.Relations = &testRelationsStore{context: &types.RelationshipContext{
|
||||
TenantID: 7,
|
||||
UserID: 1,
|
||||
PeerID: peer,
|
||||
Topic: types.Uid(1).P2PName(peer),
|
||||
Relationship: types.RelationshipNone,
|
||||
CanSend: false,
|
||||
}}
|
||||
store.Profiles = &testProfilesStore{}
|
||||
defer func() {
|
||||
store.Users = oldUsers
|
||||
store.Relations = oldRelations
|
||||
store.Profiles = oldProfiles
|
||||
}()
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/v1/im/users/"+peer.UserId()+"/profile", nil)
|
||||
req.Header.Set("Authorization", "Bearer token-1")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
serveIMUserResource(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status: got %d, want %d; body=%s", rec.Code, http.StatusOK, rec.Body.String())
|
||||
}
|
||||
data := decodeIMHTTPResponse(t, rec).Data.(map[string]any)
|
||||
if data["relationship"] != types.RelationshipNone || data["display_name"] != "Alice" ||
|
||||
data["avatar_file_id"] != "file-1" || data["public_account"] != "alice001" || data["username"] != "alice7" {
|
||||
t.Fatalf("unexpected limited profile response: %#v", data)
|
||||
}
|
||||
capabilities := data["capabilities"].(map[string]any)
|
||||
if capabilities["can_send"] != false || capabilities["can_call"] != false || capabilities["can_add_friend"] != true {
|
||||
t.Fatalf("unexpected limited profile capabilities: %#v", data)
|
||||
}
|
||||
for _, field := range []string{"signature", "gender", "region"} {
|
||||
if _, ok := data[field]; ok {
|
||||
t.Fatalf("limited profile should omit private field %q: %#v", field, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeIMUserProfileBlockedByMeReturnsMinimalProfile(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
"use_adapter": "mysql",
|
||||
"adapters": {
|
||||
"mysql": {
|
||||
"dsn": "mysql://root:oWxz*2=sb),ouW%p@tcp(mysql.118im.com:30306)/tinode?parseTime=true&collation=utf8mb4_0900_ai_ci",
|
||||
"dsn": "mysql://root:oWxz*2=sb),ouW%p@tcp(mysql.118im.com:30306)/meeting_im?parseTime=true&collation=utf8mb4_0900_ai_ci",
|
||||
"tenant_registry_db": "hotgo",
|
||||
"tenant_db_routing": true,
|
||||
"max_open_conns": 64,
|
||||
|
||||
@@ -72,7 +72,7 @@ func TestLocalMySQLMessageCryptoFlow(t *testing.T) {
|
||||
"max_results":1024,
|
||||
"use_adapter":"mysql",
|
||||
"adapters":{"mysql":{
|
||||
"dsn":"mysql://root:root@tcp(127.0.0.1:3306)/tinode?parseTime=true&collation=utf8mb4_0900_ai_ci",
|
||||
"dsn":"mysql://root:root@tcp(127.0.0.1:3306)/meeting_im?parseTime=true&collation=utf8mb4_0900_ai_ci",
|
||||
"tenant_registry_db":"hotgo",
|
||||
"tenant_db_routing":true,
|
||||
"sql_timeout":10
|
||||
@@ -86,7 +86,7 @@ func TestLocalMySQLMessageCryptoFlow(t *testing.T) {
|
||||
store.SetAdminInternalConfigProvider(localCryptoProvider{key: base64.StdEncoding.EncodeToString([]byte("0123456789abcdef0123456789abcdef"))})
|
||||
store.InvalidateMessageEncryptionKeys(1)
|
||||
|
||||
db, err := sql.Open("mysql", "root:root@tcp(127.0.0.1:3306)/tinode?parseTime=true&collation=utf8mb4_0900_ai_ci")
|
||||
db, err := sql.Open("mysql", "root:root@tcp(127.0.0.1:3306)/meeting_im?parseTime=true&collation=utf8mb4_0900_ai_ci")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
+1
-1
@@ -81,7 +81,7 @@
|
||||
"use_adapter": "mysql",
|
||||
"adapters": {
|
||||
"mysql": {
|
||||
"dsn": "mysql://root:oWxz*2=sb),ouW%p@tcp(mysql.118im.com:30306)/tinode?parseTime=true&collation=utf8mb4_0900_ai_ci",
|
||||
"dsn": "mysql://root:oWxz*2=sb),ouW%p@tcp(mysql.118im.com:30306)/meeting_im?parseTime=true&collation=utf8mb4_0900_ai_ci",
|
||||
"tenant_registry_db": "hotgo",
|
||||
"tenant_db_routing": true,
|
||||
"max_open_conns": 64,
|
||||
|
||||
Reference in New Issue
Block a user