From 7f0903cd31cc3769501c4a6133abe1cc45de0759 Mon Sep 17 00:00:00 2001 From: richjohnrambo Date: Wed, 23 Sep 2026 10:10:08 +0800 Subject: [PATCH] fix tenant scoping for group business tables --- server/db/mysql/group_schema.go | 70 +++++------ server/db/mysql/group_schema_test.go | 6 +- server/db/mysql/snowflake_primary_keys.go | 6 +- server/db/mysql/tenant_adapter.go | 2 +- server/db/mysql/tenant_groups.go | 144 +++++++++++----------- server/store/types/groups.go | 5 + 6 files changed, 119 insertions(+), 114 deletions(-) diff --git a/server/db/mysql/group_schema.go b/server/db/mysql/group_schema.go index 8537151..96bd8c7 100644 --- a/server/db/mysql/group_schema.go +++ b/server/db/mysql/group_schema.go @@ -19,9 +19,9 @@ func ensureIMGroupBusinessTables(exec sqlExecutor) error { version INT NOT NULL DEFAULT 1, created_by BIGINT NOT NULL, dissolved_by BIGINT, dissolved_at DATETIME(3), created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), - PRIMARY KEY(id), UNIQUE INDEX uk_im_group_settings_topic(topic), - INDEX idx_im_group_settings_owner(owner_user_id, group_status), - INDEX idx_im_group_settings_status_updated(group_status, updated_at) + PRIMARY KEY(id), UNIQUE INDEX uk_im_group_settings_topic(tenant_id, topic), + INDEX idx_im_group_settings_owner(tenant_id, owner_user_id, group_status), + INDEX idx_im_group_settings_status_updated(tenant_id, group_status, updated_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`, `CREATE TABLE IF NOT EXISTS im_group_messages ( tenant_id BIGINT NOT NULL DEFAULT 0, id BIGINT NOT NULL AUTO_INCREMENT, topic VARCHAR(64) NOT NULL, group_id BIGINT NOT NULL, @@ -53,10 +53,10 @@ func ensureIMGroupBusinessTables(exec sqlExecutor) error { removed_by BIGINT, version INT NOT NULL DEFAULT 1, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), - PRIMARY KEY(id), UNIQUE INDEX uk_im_group_members_topic_user(topic, user_id), - INDEX idx_im_group_members_user_status(user_id, member_status, updated_at), - INDEX idx_im_group_members_topic_role(topic, role, member_status, id), - INDEX idx_im_group_members_topic_status(topic, member_status, id) + PRIMARY KEY(id), UNIQUE INDEX uk_im_group_members_topic_user(tenant_id, topic, user_id), + INDEX idx_im_group_members_user_status(tenant_id, user_id, member_status, updated_at), + INDEX idx_im_group_members_topic_role(tenant_id, topic, role, member_status, id), + INDEX idx_im_group_members_topic_status(tenant_id, topic, member_status, id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`, `CREATE TABLE IF NOT EXISTS im_group_invitations ( tenant_id BIGINT NOT NULL DEFAULT 0, id BIGINT NOT NULL AUTO_INCREMENT, invitation_id VARCHAR(64) NOT NULL, topic VARCHAR(32) NOT NULL, @@ -66,10 +66,10 @@ func ensureIMGroupBusinessTables(exec sqlExecutor) error { expires_at DATETIME(3) NOT NULL, handled_at DATETIME(3), created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), - PRIMARY KEY(id), UNIQUE INDEX uk_im_group_invitations_invitation(invitation_id), - UNIQUE INDEX uk_im_group_invitations_idempotency(inviter_user_id, idempotency_key), - INDEX idx_im_group_invitations_target(target_user_id, status, expires_at), - INDEX idx_im_group_invitations_topic_status(topic, status, id) + PRIMARY KEY(id), UNIQUE INDEX uk_im_group_invitations_invitation(tenant_id, invitation_id), + UNIQUE INDEX uk_im_group_invitations_idempotency(tenant_id, inviter_user_id, idempotency_key), + INDEX idx_im_group_invitations_target(tenant_id, target_user_id, status, expires_at), + INDEX idx_im_group_invitations_topic_status(tenant_id, topic, status, id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`, `CREATE TABLE IF NOT EXISTS im_group_join_requests ( tenant_id BIGINT NOT NULL DEFAULT 0, id BIGINT NOT NULL AUTO_INCREMENT, request_id VARCHAR(64) NOT NULL, topic VARCHAR(32) NOT NULL, @@ -80,11 +80,11 @@ func ensureIMGroupBusinessTables(exec sqlExecutor) error { idempotency_key VARCHAR(128) NOT NULL DEFAULT '', version INT NOT NULL DEFAULT 1, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), - PRIMARY KEY(id), UNIQUE INDEX uk_im_group_join_requests_request(request_id), - UNIQUE INDEX uk_im_group_join_requests_idempotency(applicant_user_id, idempotency_key), - INDEX idx_im_group_join_requests_topic_status(topic, status, id), - INDEX idx_im_group_join_requests_applicant(applicant_user_id, topic, status, id), - INDEX idx_im_group_join_requests_expire(status, expires_at) + PRIMARY KEY(id), UNIQUE INDEX uk_im_group_join_requests_request(tenant_id, request_id), + UNIQUE INDEX uk_im_group_join_requests_idempotency(tenant_id, applicant_user_id, idempotency_key), + INDEX idx_im_group_join_requests_topic_status(tenant_id, topic, status, id), + INDEX idx_im_group_join_requests_applicant(tenant_id, applicant_user_id, topic, status, id), + INDEX idx_im_group_join_requests_expire(tenant_id, status, expires_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`, `CREATE TABLE IF NOT EXISTS im_group_announcements ( tenant_id BIGINT NOT NULL DEFAULT 0, id BIGINT NOT NULL AUTO_INCREMENT, announcement_id VARCHAR(64) NOT NULL, topic VARCHAR(32) NOT NULL, @@ -93,17 +93,17 @@ func ensureIMGroupBusinessTables(exec sqlExecutor) error { idempotency_key VARCHAR(128) NOT NULL DEFAULT '', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), - PRIMARY KEY(id), UNIQUE INDEX uk_im_group_announcements_announcement(announcement_id), - UNIQUE INDEX uk_im_group_announcements_idempotency(topic, author_user_id, idempotency_key), - INDEX idx_im_group_announcements_topic_status(topic, status, version DESC, id DESC), - INDEX idx_im_group_announcements_topic_time(topic, published_at DESC, id DESC) + PRIMARY KEY(id), UNIQUE INDEX uk_im_group_announcements_announcement(tenant_id, announcement_id), + UNIQUE INDEX uk_im_group_announcements_idempotency(tenant_id, topic, author_user_id, idempotency_key), + INDEX idx_im_group_announcements_topic_status(tenant_id, topic, status, version DESC, id DESC), + INDEX idx_im_group_announcements_topic_time(tenant_id, topic, published_at DESC, id DESC) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`, `CREATE TABLE IF NOT EXISTS im_group_announcement_reads ( tenant_id BIGINT NOT NULL DEFAULT 0, id BIGINT NOT NULL, announcement_id VARCHAR(64) NOT NULL, topic VARCHAR(32) NOT NULL, user_id BIGINT NOT NULL, read_at DATETIME(3) NOT NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), - PRIMARY KEY(id), UNIQUE INDEX uk_im_group_announcement_reads_announcement_user(announcement_id, user_id), - INDEX idx_im_group_announcement_reads_topic_user(topic, user_id, read_at) + PRIMARY KEY(id), UNIQUE INDEX uk_im_group_announcement_reads_announcement_user(tenant_id, announcement_id, user_id), + INDEX idx_im_group_announcement_reads_topic_user(tenant_id, topic, user_id, read_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`, `CREATE TABLE IF NOT EXISTS im_group_mutes ( tenant_id BIGINT NOT NULL DEFAULT 0, id BIGINT NOT NULL, topic VARCHAR(32) NOT NULL, user_id BIGINT NOT NULL, muted_by BIGINT NOT NULL, @@ -111,9 +111,9 @@ func ensureIMGroupBusinessTables(exec sqlExecutor) error { status VARCHAR(16) NOT NULL DEFAULT 'active', revoked_by BIGINT, revoked_at DATETIME(3), version INT NOT NULL DEFAULT 1, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), - PRIMARY KEY(id), UNIQUE INDEX uk_im_group_mutes_topic_user(topic, user_id), - INDEX idx_im_group_mutes_topic_status(topic, status, expires_at), - INDEX idx_im_group_mutes_user_status(user_id, status, expires_at) + PRIMARY KEY(id), UNIQUE INDEX uk_im_group_mutes_topic_user(tenant_id, topic, user_id), + INDEX idx_im_group_mutes_topic_status(tenant_id, topic, status, expires_at), + INDEX idx_im_group_mutes_user_status(tenant_id, user_id, status, expires_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`, `CREATE TABLE IF NOT EXISTS im_group_qrcodes ( tenant_id BIGINT NOT NULL DEFAULT 0, id BIGINT NOT NULL AUTO_INCREMENT, token_id VARCHAR(64) NOT NULL, token_hash CHAR(64) NOT NULL, @@ -121,8 +121,8 @@ func ensureIMGroupBusinessTables(exec sqlExecutor) error { expires_at DATETIME(3) NOT NULL, revoked_by BIGINT, revoked_at DATETIME(3), created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), - PRIMARY KEY(id), UNIQUE INDEX uk_im_group_qrcodes_token(token_id), - UNIQUE INDEX uk_im_group_qrcodes_hash(token_hash), INDEX idx_im_group_qrcodes_topic_status(topic, status, expires_at) + PRIMARY KEY(id), UNIQUE INDEX uk_im_group_qrcodes_token(tenant_id, token_id), + UNIQUE INDEX uk_im_group_qrcodes_hash(tenant_id, token_hash), INDEX idx_im_group_qrcodes_topic_status(tenant_id, topic, status, expires_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`, `CREATE TABLE IF NOT EXISTS im_group_files ( tenant_id BIGINT NOT NULL DEFAULT 0, id BIGINT NOT NULL AUTO_INCREMENT, group_file_id VARCHAR(64) NOT NULL, topic VARCHAR(32) NOT NULL, @@ -133,11 +133,11 @@ func ensureIMGroupBusinessTables(exec sqlExecutor) error { idempotency_key VARCHAR(128) NOT NULL DEFAULT '', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), - PRIMARY KEY(id), UNIQUE INDEX uk_im_group_files_group_file(group_file_id), - UNIQUE INDEX uk_im_group_files_topic_file(topic, file_id), - UNIQUE INDEX uk_im_group_files_idempotency(topic, uploader_id, idempotency_key), - INDEX idx_im_group_files_topic_type(topic, status, file_type, id DESC), - INDEX idx_im_group_files_topic_name(topic, status, file_name), INDEX idx_im_group_files_uploader(uploader_id, id DESC) + PRIMARY KEY(id), UNIQUE INDEX uk_im_group_files_group_file(tenant_id, group_file_id), + UNIQUE INDEX uk_im_group_files_topic_file(tenant_id, topic, file_id), + UNIQUE INDEX uk_im_group_files_idempotency(tenant_id, topic, uploader_id, idempotency_key), + INDEX idx_im_group_files_topic_type(tenant_id, topic, status, file_type, id DESC), + INDEX idx_im_group_files_topic_name(tenant_id, topic, status, file_name), INDEX idx_im_group_files_uploader(tenant_id, uploader_id, id DESC) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`, `CREATE TABLE IF NOT EXISTS im_group_admin_logs ( tenant_id BIGINT NOT NULL DEFAULT 0, id BIGINT NOT NULL AUTO_INCREMENT, log_id VARCHAR(64) NOT NULL, topic VARCHAR(32) NOT NULL, @@ -145,9 +145,9 @@ func ensureIMGroupBusinessTables(exec sqlExecutor) error { object_type VARCHAR(32) NOT NULL DEFAULT '', object_id VARCHAR(64) NOT NULL DEFAULT '', before_data JSON, after_data JSON, request_id VARCHAR(128) NOT NULL DEFAULT '', idempotency_key VARCHAR(128) NOT NULL DEFAULT '', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), - PRIMARY KEY(id), UNIQUE INDEX uk_im_group_admin_logs_log(log_id), - INDEX idx_im_group_admin_logs_topic_time(topic, id DESC), - INDEX idx_im_group_admin_logs_operator(operator_id, id DESC), INDEX idx_im_group_admin_logs_object(object_type, object_id) + PRIMARY KEY(id), UNIQUE INDEX uk_im_group_admin_logs_log(tenant_id, log_id), + INDEX idx_im_group_admin_logs_topic_time(tenant_id, topic, id DESC), + INDEX idx_im_group_admin_logs_operator(tenant_id, operator_id, id DESC), INDEX idx_im_group_admin_logs_object(tenant_id, object_type, object_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`, `UPDATE im_topics SET public=JSON_SET(public, '$.fn', JSON_UNQUOTE(JSON_EXTRACT(public, '$.name'))) WHERE name LIKE 'grp%' AND JSON_VALID(public) diff --git a/server/db/mysql/group_schema_test.go b/server/db/mysql/group_schema_test.go index c749da7..01b324a 100644 --- a/server/db/mysql/group_schema_test.go +++ b/server/db/mysql/group_schema_test.go @@ -40,9 +40,9 @@ func TestEnsureIMGroupBusinessTablesUsesSelectedTenantDatabase(t *testing.T) { t.Fatal("group idempotency table must not be created") } for _, expected := range []string{ - "primary key(id), unique index uk_im_group_settings_topic(topic)", - "primary key(id), unique index uk_im_group_announcement_reads_announcement_user(announcement_id, user_id)", - "primary key(id), unique index uk_im_group_mutes_topic_user(topic, user_id)", + "primary key(id), unique index uk_im_group_settings_topic(tenant_id, topic)", + "primary key(id), unique index uk_im_group_announcement_reads_announcement_user(tenant_id, announcement_id, user_id)", + "primary key(id), unique index uk_im_group_mutes_topic_user(tenant_id, topic, user_id)", } { if !strings.Contains(joined, expected) { t.Fatalf("group schema is missing snowflake primary-key definition %q", expected) diff --git a/server/db/mysql/snowflake_primary_keys.go b/server/db/mysql/snowflake_primary_keys.go index 4b5eeb5..f857f6e 100644 --- a/server/db/mysql/snowflake_primary_keys.go +++ b/server/db/mysql/snowflake_primary_keys.go @@ -29,9 +29,9 @@ var snowflakePrimaryKeySpecs = []snowflakePrimaryKeySpec{ {table: "im_friendships", uniqueIndex: "uk_im_friendships_pair", businessKey: []string{"user_id", "peer_id"}}, {table: "im_user_blocks", uniqueIndex: "uk_im_user_blocks_pair", businessKey: []string{"blocker_id", "blocked_id"}}, {table: "im_youth_mode_settings", uniqueIndex: "uk_im_youth_mode_settings_user_purpose", businessKey: []string{"user_id", "purpose"}}, - {table: "im_group_settings", uniqueIndex: "uk_im_group_settings_topic", businessKey: []string{"topic"}}, - {table: "im_group_announcement_reads", uniqueIndex: "uk_im_group_announcement_reads_announcement_user", businessKey: []string{"announcement_id", "user_id"}}, - {table: "im_group_mutes", uniqueIndex: "uk_im_group_mutes_topic_user", businessKey: []string{"topic", "user_id"}}, + {table: "im_group_settings", uniqueIndex: "uk_im_group_settings_topic", businessKey: []string{"tenant_id", "topic"}}, + {table: "im_group_announcement_reads", uniqueIndex: "uk_im_group_announcement_reads_announcement_user", businessKey: []string{"tenant_id", "announcement_id", "user_id"}}, + {table: "im_group_mutes", uniqueIndex: "uk_im_group_mutes_topic_user", businessKey: []string{"tenant_id", "topic", "user_id"}}, } const snowflakeMigrationLock = "CONCAT('im_snowflake_pk:', LEFT(SHA2(DATABASE(), 256), 40))" diff --git a/server/db/mysql/tenant_adapter.go b/server/db/mysql/tenant_adapter.go index 1dfb2bc..f6f336d 100644 --- a/server/db/mysql/tenant_adapter.go +++ b/server/db/mysql/tenant_adapter.go @@ -5270,7 +5270,7 @@ func (a *adapter) TenantFileUserHasAccess(id t.TenantID, fid string, uid t.Uid) SELECT 1 FROM im_group_files AS gf INNER JOIN im_subscriptions AS s ON s.tenant_id=fu.tenant_id AND s.topic=gf.topic AND s.userid=? - WHERE (gf.file_id=? OR gf.file_id LIKE CONCAT(?,'.%%')) AND gf.status='active' + WHERE gf.tenant_id=fu.tenant_id AND (gf.file_id=? OR gf.file_id LIKE CONCAT(?,'.%%')) AND gf.status='active' AND s.deletedat IS NULL AND INSTR(s.modewant,'R')>0 AND INSTR(s.modegiven,'R')>0 ) ) LIMIT 1`, tenantFriendshipsTable(id), tenantMessagesTable(id))), diff --git a/server/db/mysql/tenant_groups.go b/server/db/mysql/tenant_groups.go index b18534c..40a53b8 100644 --- a/server/db/mysql/tenant_groups.go +++ b/server/db/mysql/tenant_groups.go @@ -20,12 +20,12 @@ const ( groupSettingsSelectColumns = `tenant_id,topic,owner_user_id,name,avatar_file_id,description,group_status,join_approval,mute_all, member_upload_files,member_private_chat,member_invite,max_member_count,max_admin_count,member_count,version, created_by,dissolved_by,dissolved_at,created_at,updated_at` - groupAnnouncementSelectColumnsBase = `id,announcement_id,topic,author_user_id,content,status,version,published_at,idempotency_key,created_at,updated_at, - (SELECT COUNT(*) FROM im_group_announcement_reads AS ar WHERE ar.topic=im_group_announcements.topic AND ar.announcement_id=im_group_announcements.announcement_id) AS read_count, - (SELECT COUNT(*) FROM im_group_members AS gm WHERE gm.topic=im_group_announcements.topic AND gm.member_status='active') AS member_count` + groupAnnouncementSelectColumnsBase = `tenant_id,id,announcement_id,topic,author_user_id,content,status,version,published_at,idempotency_key,created_at,updated_at, + (SELECT COUNT(*) FROM im_group_announcement_reads AS ar WHERE ar.tenant_id=im_group_announcements.tenant_id AND ar.topic=im_group_announcements.topic AND ar.announcement_id=im_group_announcements.announcement_id) AS read_count, + (SELECT COUNT(*) FROM im_group_members AS gm WHERE gm.tenant_id=im_group_announcements.tenant_id AND gm.topic=im_group_announcements.topic AND gm.member_status='active') AS member_count` groupMemberSelectColumns = `id,topic,user_id,role,member_status,nickname,pinned,mute_notifications, join_source,joined_by,joined_at,left_at,removed_by,version,created_at,updated_at` - groupMuteSelectColumns = `topic,user_id,muted_by,muted_at,expires_at,reason,status,revoked_by,revoked_at,version,created_at,updated_at` + groupMuteSelectColumns = `tenant_id,topic,user_id,muted_by,muted_at,expires_at,reason,status,revoked_by,revoked_at,version,created_at,updated_at` ) func groupAnnouncementSelectColumns(tenantID t.TenantID) string { @@ -488,13 +488,13 @@ func (a *adapter) TenantGroupInvitationCreate(id t.TenantID, inv *t.GroupInvitat defer cancel() } if strings.TrimSpace(inv.JoinRequestID) != "" { - existing, err := a.groupInvitationByQuery(ctx, `SELECT inv.id,inv.invitation_id,inv.topic,inv.inviter_user_id,inv.target_user_id,inv.message,inv.status,inv.result, + existing, err := a.groupInvitationByQuery(ctx, `SELECT inv.tenant_id,inv.id,inv.invitation_id,inv.topic,inv.inviter_user_id,inv.target_user_id,inv.message,inv.status,inv.result, inv.join_request_id,inv.idempotency_key,inv.expires_at,inv.handled_at,inv.created_at,inv.updated_at FROM im_group_invitations AS inv - INNER JOIN im_group_join_requests AS req ON req.request_id=inv.join_request_id - WHERE inv.topic=? AND inv.target_user_id=? AND inv.status=? AND req.status=? + INNER JOIN im_group_join_requests AS req ON req.tenant_id=inv.tenant_id AND req.request_id=inv.join_request_id + WHERE inv.tenant_id=? AND inv.topic=? AND inv.target_user_id=? AND inv.status=? AND req.status=? ORDER BY req.id DESC LIMIT 1`, - inv.Topic, decodeUID(inv.TargetUserID), t.GroupInvitationStatusPending, t.GroupJoinRequestStatusPending) + id, inv.Topic, decodeUID(inv.TargetUserID), t.GroupInvitationStatusPending, t.GroupJoinRequestStatusPending) if err == nil { return existing, nil } @@ -507,16 +507,16 @@ func (a *adapter) TenantGroupInvitationCreate(id t.TenantID, inv *t.GroupInvitat return nil, err } defer tx.Rollback() - _, err = tx.ExecContext(ctx, `INSERT INTO im_group_invitations(invitation_id,topic,inviter_user_id,target_user_id,message,status,result,join_request_id,idempotency_key,expires_at,handled_at,created_at,updated_at) - VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)`, - inv.InvitationID, inv.Topic, decodeUID(inv.InviterUserID), decodeUID(inv.TargetUserID), inv.Message, inv.Status, inv.Result, inv.JoinRequestID, inv.IdempotencyKey, inv.ExpiresAt, inv.HandledAt, now, now) + _, err = tx.ExecContext(ctx, `INSERT INTO im_group_invitations(tenant_id,invitation_id,topic,inviter_user_id,target_user_id,message,status,result,join_request_id,idempotency_key,expires_at,handled_at,created_at,updated_at) + VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)`, + id, inv.InvitationID, inv.Topic, decodeUID(inv.InviterUserID), decodeUID(inv.TargetUserID), inv.Message, inv.Status, inv.Result, inv.JoinRequestID, inv.IdempotencyKey, inv.ExpiresAt, inv.HandledAt, now, now) if err != nil { return nil, err } if strings.TrimSpace(inv.JoinRequestID) != "" { - _, err = tx.ExecContext(ctx, `INSERT INTO im_group_join_requests(request_id,topic,applicant_user_id,inviter_user_id,source,qr_token_hash,message,status,reject_reason,expires_at,idempotency_key,version,created_at,updated_at) - VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)`, - inv.JoinRequestID, inv.Topic, decodeUID(inv.TargetUserID), nullableUID(inv.InviterUserID), "invitation", "", inv.Message, t.GroupJoinRequestStatusPending, "", inv.ExpiresAt, inv.IdempotencyKey, 1, now, now) + _, err = tx.ExecContext(ctx, `INSERT INTO im_group_join_requests(tenant_id,request_id,topic,applicant_user_id,inviter_user_id,source,qr_token_hash,message,status,reject_reason,expires_at,idempotency_key,version,created_at,updated_at) + VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`, + id, inv.JoinRequestID, inv.Topic, decodeUID(inv.TargetUserID), nullableUID(inv.InviterUserID), "invitation", "", inv.Message, t.GroupJoinRequestStatusPending, "", inv.ExpiresAt, inv.IdempotencyKey, 1, now, now) if err != nil { return nil, err } @@ -548,10 +548,10 @@ func (a *adapter) TenantGroupInvitationAccept(id t.TenantID, invitationID string var inv t.GroupInvitation var handledAt sql.NullTime - err = tx.QueryRowxContext(ctx, `SELECT id,invitation_id,topic,inviter_user_id,target_user_id,message,status,result, + err = tx.QueryRowxContext(ctx, `SELECT tenant_id,id,invitation_id,topic,inviter_user_id,target_user_id,message,status,result, join_request_id,idempotency_key,expires_at,handled_at,created_at,updated_at - FROM im_group_invitations WHERE invitation_id=? FOR UPDATE`, strings.TrimSpace(invitationID)). - Scan(&inv.ID, &inv.InvitationID, &inv.Topic, &inv.InviterUserRaw, &inv.TargetUserRaw, &inv.Message, &inv.Status, + FROM im_group_invitations WHERE tenant_id=? AND invitation_id=? FOR UPDATE`, id, strings.TrimSpace(invitationID)). + Scan(&inv.TenantID, &inv.ID, &inv.InvitationID, &inv.Topic, &inv.InviterUserRaw, &inv.TargetUserRaw, &inv.Message, &inv.Status, &inv.Result, &inv.JoinRequestID, &inv.IdempotencyKey, &inv.ExpiresAt, &handledAt, &inv.CreatedAt, &inv.UpdatedAt) if err == sql.ErrNoRows { return nil, t.ErrNotFound @@ -606,15 +606,15 @@ func (a *adapter) TenantGroupInvitationAccept(id t.TenantID, invitationID string return nil, err } if _, err = tx.ExecContext(ctx, `UPDATE im_group_invitations - SET status=?,result=?,handled_at=?,updated_at=? WHERE invitation_id=?`, - t.GroupInvitationStatusJoined, "joined", now, now, inv.InvitationID); err != nil { + SET status=?,result=?,handled_at=?,updated_at=? WHERE tenant_id=? AND invitation_id=?`, + t.GroupInvitationStatusJoined, "joined", now, now, id, inv.InvitationID); err != nil { return nil, err } if strings.TrimSpace(inv.JoinRequestID) != "" { if _, err = tx.ExecContext(ctx, `UPDATE im_group_join_requests SET status=?,handled_by=?,handled_at=?,updated_at=?,version=version+1 - WHERE topic=? AND request_id=? AND status=?`, - t.GroupJoinRequestStatusAccepted, decodeUID(uid), now, now, inv.Topic, inv.JoinRequestID, t.GroupJoinRequestStatusPending); err != nil { + WHERE tenant_id=? AND topic=? AND request_id=? AND status=?`, + t.GroupJoinRequestStatusAccepted, decodeUID(uid), now, now, id, inv.Topic, inv.JoinRequestID, t.GroupJoinRequestStatusPending); err != nil { return nil, err } } @@ -651,8 +651,8 @@ func (a *adapter) TenantGroupJoinRequestCreate(id t.TenantID, req *t.GroupJoinRe if cancel != nil { defer cancel() } - existing, err := a.groupJoinRequestByQuery(ctx, "SELECT * FROM im_group_join_requests WHERE topic=? AND applicant_user_id=? AND status=? ORDER BY id DESC LIMIT 1", - req.Topic, decodeUID(req.ApplicantUserID), t.GroupJoinRequestStatusPending) + existing, err := a.groupJoinRequestByQuery(ctx, "SELECT * FROM im_group_join_requests WHERE tenant_id=? AND topic=? AND applicant_user_id=? AND status=? ORDER BY id DESC LIMIT 1", + id, req.Topic, decodeUID(req.ApplicantUserID), t.GroupJoinRequestStatusPending) if err == nil { return existing, nil } @@ -664,16 +664,16 @@ func (a *adapter) TenantGroupJoinRequestCreate(id t.TenantID, req *t.GroupJoinRe return nil, err } defer tx.Rollback() - _, err = tx.ExecContext(ctx, `INSERT INTO im_group_join_requests(request_id,topic,applicant_user_id,inviter_user_id,source,qr_token_hash,message,status,reject_reason,expires_at,idempotency_key,version,created_at,updated_at) - VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)`, - req.RequestID, req.Topic, decodeUID(req.ApplicantUserID), nullableUID(req.InviterUserID), req.Source, req.QRTokenHash, req.Message, req.Status, req.RejectReason, req.ExpiresAt, req.IdempotencyKey, req.Version, now, now) + _, err = tx.ExecContext(ctx, `INSERT INTO im_group_join_requests(tenant_id,request_id,topic,applicant_user_id,inviter_user_id,source,qr_token_hash,message,status,reject_reason,expires_at,idempotency_key,version,created_at,updated_at) + VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`, + id, req.RequestID, req.Topic, decodeUID(req.ApplicantUserID), nullableUID(req.InviterUserID), req.Source, req.QRTokenHash, req.Message, req.Status, req.RejectReason, req.ExpiresAt, req.IdempotencyKey, req.Version, now, now) if err != nil { return nil, err } if err = tx.Commit(); err != nil { return nil, err } - return a.groupJoinRequestByQuery(ctx, "SELECT * FROM im_group_join_requests WHERE request_id=?", req.RequestID) + return a.groupJoinRequestByQuery(ctx, "SELECT * FROM im_group_join_requests WHERE tenant_id=? AND request_id=?", id, req.RequestID) } func (a *adapter) TenantGroupJoinRequestList(id t.TenantID, topic, status, cursor string, limit int) ([]t.GroupJoinRequest, string, error) { @@ -682,15 +682,15 @@ func (a *adapter) TenantGroupJoinRequestList(id t.TenantID, topic, status, curso } else if tenantAdp != a { return tenantAdp.TenantGroupJoinRequestList(id, topic, status, cursor, limit) } - where := []string{"r.topic=?"} - args := []any{topic} + where := []string{"r.tenant_id=?", "r.topic=?"} + args := []any{id, topic} if status != "" && status != "all" { where = append(where, "r.status=?") args = append(args, status) if status == t.GroupJoinRequestStatusPending { where = append(where, `NOT EXISTS ( SELECT 1 FROM im_group_join_requests AS newer - WHERE newer.topic=r.topic AND newer.applicant_user_id=r.applicant_user_id + WHERE newer.tenant_id=r.tenant_id AND newer.topic=r.topic AND newer.applicant_user_id=r.applicant_user_id AND newer.status=? AND newer.id>r.id )`) args = append(args, status) @@ -751,7 +751,7 @@ func (a *adapter) TenantGroupJoinRequestResolve(id t.TenantID, topic, requestID } defer tx.Rollback() var current t.GroupJoinRequest - err = tx.QueryRowxContext(ctx, "SELECT * FROM im_group_join_requests WHERE topic=? AND request_id=? FOR UPDATE", topic, requestID).StructScan(¤t) + err = tx.QueryRowxContext(ctx, "SELECT * FROM im_group_join_requests WHERE tenant_id=? AND topic=? AND request_id=? FOR UPDATE", id, topic, requestID).StructScan(¤t) if err == sql.ErrNoRows { return nil, t.ErrNotFound } @@ -780,7 +780,7 @@ func (a *adapter) TenantGroupJoinRequestResolve(id t.TenantID, topic, requestID } } if _, err = tx.ExecContext(ctx, `UPDATE im_group_join_requests SET status=?,reject_reason=?,handled_by=?,handled_at=?,updated_at=?,version=version+1 - WHERE topic=? AND request_id=? AND status=?`, status, rejectReason, decodeUID(actor), now, now, topic, requestID, t.GroupJoinRequestStatusPending); err != nil { + WHERE tenant_id=? AND topic=? AND request_id=? AND status=?`, status, rejectReason, decodeUID(actor), now, now, id, topic, requestID, t.GroupJoinRequestStatusPending); err != nil { return nil, err } if action == "accept" { @@ -794,7 +794,7 @@ func (a *adapter) TenantGroupJoinRequestResolve(id t.TenantID, topic, requestID if err = tx.Commit(); err != nil { return nil, err } - return a.groupJoinRequestByQuery(ctx, "SELECT * FROM im_group_join_requests WHERE topic=? AND request_id=?", topic, requestID) + return a.groupJoinRequestByQuery(ctx, "SELECT * FROM im_group_join_requests WHERE tenant_id=? AND topic=? AND request_id=?", id, topic, requestID) } func (a *adapter) TenantGroupAnnouncementLatest(id t.TenantID, topic string) (*t.GroupAnnouncement, error) { @@ -807,7 +807,7 @@ func (a *adapter) TenantGroupAnnouncementLatest(id t.TenantID, topic string) (*t if cancel != nil { defer cancel() } - return a.groupAnnouncementByQuery(ctx, "SELECT "+groupAnnouncementSelectColumns(id)+" FROM im_group_announcements WHERE topic=? AND status=? ORDER BY version DESC,id DESC LIMIT 1", topic, t.GroupAnnouncementStatusCurrent) + return a.groupAnnouncementByQuery(ctx, "SELECT "+groupAnnouncementSelectColumns(id)+" FROM im_group_announcements WHERE tenant_id=? AND topic=? AND status=? ORDER BY version DESC,id DESC LIMIT 1", id, topic, t.GroupAnnouncementStatusCurrent) } func (a *adapter) TenantGroupAnnouncementGet(id t.TenantID, topic, announcementID string) (*t.GroupAnnouncement, error) { @@ -820,7 +820,7 @@ func (a *adapter) TenantGroupAnnouncementGet(id t.TenantID, topic, announcementI if cancel != nil { defer cancel() } - return a.groupAnnouncementByQuery(ctx, "SELECT "+groupAnnouncementSelectColumns(id)+" FROM im_group_announcements WHERE topic=? AND announcement_id=? LIMIT 1", topic, announcementID) + return a.groupAnnouncementByQuery(ctx, "SELECT "+groupAnnouncementSelectColumns(id)+" FROM im_group_announcements WHERE tenant_id=? AND topic=? AND announcement_id=? LIMIT 1", id, topic, announcementID) } func (a *adapter) TenantGroupAnnouncementPut(id t.TenantID, ann *t.GroupAnnouncement, expectedVersion int) (*t.GroupAnnouncement, error) { @@ -843,7 +843,7 @@ func (a *adapter) TenantGroupAnnouncementPut(id t.TenantID, ann *t.GroupAnnounce } }() var currentVersion int - err = tx.QueryRowContext(ctx, "SELECT COALESCE(MAX(version),0) FROM im_group_announcements WHERE topic=?", ann.Topic).Scan(¤tVersion) + err = tx.QueryRowContext(ctx, "SELECT COALESCE(MAX(version),0) FROM im_group_announcements WHERE tenant_id=? AND topic=?", id, ann.Topic).Scan(¤tVersion) if err != nil { return nil, err } @@ -855,7 +855,7 @@ func (a *adapter) TenantGroupAnnouncementPut(id t.TenantID, ann *t.GroupAnnounce if now.IsZero() { now = time.Now().UTC().Round(time.Millisecond) } - if _, err = tx.ExecContext(ctx, "UPDATE im_group_announcements SET status=?,updated_at=? WHERE topic=? AND status=?", t.GroupAnnouncementStatusSuperseded, now, ann.Topic, t.GroupAnnouncementStatusCurrent); err != nil { + if _, err = tx.ExecContext(ctx, "UPDATE im_group_announcements SET status=?,updated_at=? WHERE tenant_id=? AND topic=? AND status=?", t.GroupAnnouncementStatusSuperseded, now, id, ann.Topic, t.GroupAnnouncementStatusCurrent); err != nil { return nil, err } if ann.AnnouncementID == "" { @@ -864,8 +864,8 @@ func (a *adapter) TenantGroupAnnouncementPut(id t.TenantID, ann *t.GroupAnnounce if ann.IdempotencyKey == "" { ann.IdempotencyKey = ann.AnnouncementID } - if _, err = tx.ExecContext(ctx, `INSERT INTO im_group_announcements(announcement_id,topic,author_user_id,content,status,version,published_at,idempotency_key,created_at,updated_at) - VALUES(?,?,?,?,?,?,?,?,?,?)`, ann.AnnouncementID, ann.Topic, decodeUID(ann.AuthorUserID), ann.Content, t.GroupAnnouncementStatusCurrent, version, now, ann.IdempotencyKey, now, now); err != nil { + if _, err = tx.ExecContext(ctx, `INSERT INTO im_group_announcements(tenant_id,announcement_id,topic,author_user_id,content,status,version,published_at,idempotency_key,created_at,updated_at) + VALUES(?,?,?,?,?,?,?,?,?,?,?)`, id, ann.AnnouncementID, ann.Topic, decodeUID(ann.AuthorUserID), ann.Content, t.GroupAnnouncementStatusCurrent, version, now, ann.IdempotencyKey, now, now); err != nil { return nil, err } if err = tx.Commit(); err != nil { @@ -888,8 +888,8 @@ func (a *adapter) TenantGroupAnnouncementRead(id t.TenantID, topic, announcement if err != nil { return err } - _, err = a.db.ExecContext(ctx, `INSERT INTO im_group_announcement_reads(id,announcement_id,topic,user_id,read_at) - VALUES(?,?,?,?,?) ON DUPLICATE KEY UPDATE read_at=VALUES(read_at),updated_at=CURRENT_TIMESTAMP(3)`, readID, announcementID, topic, decodeUID(uid), readAt) + _, err = a.db.ExecContext(ctx, `INSERT INTO im_group_announcement_reads(id,tenant_id,announcement_id,topic,user_id,read_at) + VALUES(?,?,?,?,?,?) ON DUPLICATE KEY UPDATE read_at=VALUES(read_at),updated_at=CURRENT_TIMESTAMP(3)`, readID, id, announcementID, topic, decodeUID(uid), readAt) return err } @@ -899,8 +899,8 @@ func (a *adapter) TenantGroupMuteList(id t.TenantID, topic, cursor string, limit } else if tenantAdp != a { return tenantAdp.TenantGroupMuteList(id, topic, cursor, limit) } - where := []string{"topic=?", "status=?"} - args := []any{topic, t.GroupMuteStatusActive} + where := []string{"tenant_id=?", "topic=?", "status=?"} + args := []any{id, topic, t.GroupMuteStatusActive} if cursor != "" { parsed, err := strconv.ParseInt(cursor, 10, 64) if err != nil || parsed <= 0 { @@ -950,8 +950,8 @@ func (a *adapter) TenantGroupMuteGet(id t.TenantID, topic string, uid t.Uid) (*t if cancel != nil { defer cancel() } - return a.groupMuteByQuery(ctx, "SELECT "+groupMuteSelectColumns+" FROM im_group_mutes WHERE topic=? AND user_id=? AND status=? AND (expires_at IS NULL OR expires_at>?)", - topic, decodeUID(uid), t.GroupMuteStatusActive, now) + return a.groupMuteByQuery(ctx, "SELECT "+groupMuteSelectColumns+" FROM im_group_mutes WHERE tenant_id=? AND topic=? AND user_id=? AND status=? AND (expires_at IS NULL OR expires_at>?)", + id, topic, decodeUID(uid), t.GroupMuteStatusActive, now) } func (a *adapter) TenantGroupMuteUpsert(id t.TenantID, mute *t.GroupMute) (*t.GroupMute, error) { @@ -980,17 +980,17 @@ func (a *adapter) TenantGroupMuteUpsert(id t.TenantID, mute *t.GroupMute) (*t.Gr if err != nil { return nil, err } - _, err = tx.ExecContext(ctx, `INSERT INTO im_group_mutes(id,topic,user_id,muted_by,muted_at,expires_at,reason,status,version) - VALUES(?,?,?,?,?,?,?,?,1) + _, err = tx.ExecContext(ctx, `INSERT INTO im_group_mutes(id,tenant_id,topic,user_id,muted_by,muted_at,expires_at,reason,status,version) + VALUES(?,?,?,?,?,?,?,?,?,1) ON DUPLICATE KEY UPDATE muted_by=VALUES(muted_by),muted_at=VALUES(muted_at),expires_at=VALUES(expires_at),reason=VALUES(reason),status=VALUES(status),revoked_by=NULL,revoked_at=NULL,version=version+1,updated_at=CURRENT_TIMESTAMP(3)`, - muteID, mute.Topic, decodeUID(mute.UserID), decodeUID(mute.MutedBy), now, mute.ExpiresAt, mute.Reason, mute.Status) + muteID, id, mute.Topic, decodeUID(mute.UserID), decodeUID(mute.MutedBy), now, mute.ExpiresAt, mute.Reason, mute.Status) if err != nil { return nil, err } if err = tx.Commit(); err != nil { return nil, err } - return a.groupMuteByQuery(ctx, "SELECT "+groupMuteSelectColumns+" FROM im_group_mutes WHERE topic=? AND user_id=?", mute.Topic, decodeUID(mute.UserID)) + return a.groupMuteByQuery(ctx, "SELECT "+groupMuteSelectColumns+" FROM im_group_mutes WHERE tenant_id=? AND topic=? AND user_id=?", id, mute.Topic, decodeUID(mute.UserID)) } func (a *adapter) TenantGroupMuteDelete(id t.TenantID, topic string, uid, actor t.Uid, now time.Time) error { @@ -1008,8 +1008,8 @@ func (a *adapter) TenantGroupMuteDelete(id t.TenantID, topic string, uid, actor return err } defer tx.Rollback() - res, err := tx.ExecContext(ctx, "UPDATE im_group_mutes SET status=?,revoked_by=?,revoked_at=?,version=version+1,updated_at=? WHERE topic=? AND user_id=? AND status=?", - t.GroupMuteStatusRevoked, decodeUID(actor), now, now, topic, decodeUID(uid), t.GroupMuteStatusActive) + res, err := tx.ExecContext(ctx, "UPDATE im_group_mutes SET status=?,revoked_by=?,revoked_at=?,version=version+1,updated_at=? WHERE tenant_id=? AND topic=? AND user_id=? AND status=?", + t.GroupMuteStatusRevoked, decodeUID(actor), now, now, id, topic, decodeUID(uid), t.GroupMuteStatusActive) if err != nil { return err } @@ -1028,8 +1028,8 @@ func (a *adapter) TenantGroupFileList(id t.TenantID, topic, fileType, query, cur if strings.TrimSpace(topic) == "" { return nil, "", t.ErrMalformed } - where := []string{"topic=?", "status='active'"} - args := []any{strings.TrimSpace(topic)} + where := []string{"tenant_id=?", "topic=?", "status='active'"} + args := []any{id, strings.TrimSpace(topic)} if fileType != "" { where = append(where, "file_type=?") args = append(args, strings.TrimSpace(fileType)) @@ -1051,7 +1051,7 @@ func (a *adapter) TenantGroupFileList(id t.TenantID, topic, fileType, query, cur if cancel != nil { defer cancel() } - rows, err := a.db.QueryxContext(ctx, `SELECT id,group_file_id,topic,file_id,source_topic,source_seq,uploader_id, + rows, err := a.db.QueryxContext(ctx, `SELECT tenant_id,id,group_file_id,topic,file_id,source_topic,source_seq,uploader_id, file_name,mime_type,file_type,file_size,status,deleted_by,deleted_at,idempotency_key,created_at,updated_at FROM im_group_files WHERE `+strings.Join(where, " AND ")+` ORDER BY id DESC LIMIT ?`, args...) if err != nil { @@ -1108,15 +1108,15 @@ func (a *adapter) TenantGroupFileCreate(id t.TenantID, file *t.GroupFile) (*t.Gr } defer tx.Rollback() _, err = tx.ExecContext(ctx, `INSERT INTO im_group_files - (group_file_id,topic,file_id,source_topic,source_seq,uploader_id,file_name,mime_type,file_type,file_size,status,idempotency_key,created_at,updated_at) - VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?) + (tenant_id,group_file_id,topic,file_id,source_topic,source_seq,uploader_id,file_name,mime_type,file_type,file_size,status,idempotency_key,created_at,updated_at) + VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE file_name=IF(file_name='', VALUES(file_name), file_name), mime_type=IF(mime_type='', VALUES(mime_type), mime_type), file_size=IF(file_size=0, VALUES(file_size), file_size), file_type=IF(file_type='' OR file_type='file', VALUES(file_type), file_type), updated_at=updated_at`, - file.GroupFileID, file.Topic, file.FileID, file.SourceTopic, file.SourceSeq, decodeUID(file.UploaderID), + id, file.GroupFileID, file.Topic, file.FileID, file.SourceTopic, file.SourceSeq, decodeUID(file.UploaderID), file.FileName, file.MIMEType, file.FileType, file.FileSize, file.Status, file.IdempotencyKey, now, now) if err != nil && !isDupe(err) { return nil, err @@ -1127,15 +1127,15 @@ func (a *adapter) TenantGroupFileCreate(id t.TenantID, file *t.GroupFile) (*t.Gr } if fid := t.ParseUid(uploadID); !fid.IsZero() { if a.tenantScopedDB { - _, err = tx.ExecContext(ctx, `INSERT INTO im_filemsglinks(createdat,fileid,topic) - SELECT ?,?,? WHERE NOT EXISTS ( - SELECT 1 FROM im_filemsglinks WHERE fileid=? AND topic=?)`, - now, decodeUID(fid), file.Topic, decodeUID(fid), file.Topic) - } else { _, err = tx.ExecContext(ctx, `INSERT INTO im_filemsglinks(tenant_id,createdat,fileid,topic) SELECT ?,?,?,? WHERE NOT EXISTS ( SELECT 1 FROM im_filemsglinks WHERE tenant_id=? AND fileid=? AND topic=?)`, id, now, decodeUID(fid), file.Topic, id, decodeUID(fid), file.Topic) + } else { + _, err = tx.ExecContext(ctx, `INSERT INTO im_filemsglinks(createdat,fileid,topic) + SELECT ?,?,? WHERE NOT EXISTS ( + SELECT 1 FROM im_filemsglinks WHERE fileid=? AND topic=?)`, + now, decodeUID(fid), file.Topic, decodeUID(fid), file.Topic) } if err != nil { return nil, err @@ -1144,7 +1144,7 @@ func (a *adapter) TenantGroupFileCreate(id t.TenantID, file *t.GroupFile) (*t.Gr if err = tx.Commit(); err != nil { return nil, err } - created, err := a.groupFileByQuery(ctx, "SELECT * FROM im_group_files WHERE topic=? AND file_id=?", file.Topic, file.FileID) + created, err := a.groupFileByQuery(ctx, "SELECT * FROM im_group_files WHERE tenant_id=? AND topic=? AND file_id=?", id, file.Topic, file.FileID) if err != nil { return nil, err } @@ -1200,8 +1200,8 @@ func (a *adapter) TenantGroupFileDelete(id t.TenantID, topic, groupFileID string } query := `UPDATE im_group_files SET status='deleted',deleted_by=?,deleted_at=?,updated_at=? - WHERE topic=? AND group_file_id=? AND status='active'` - args := []any{decodeUID(actor), now, now, strings.TrimSpace(topic), strings.TrimSpace(groupFileID)} + WHERE tenant_id=? AND topic=? AND group_file_id=? AND status='active'` + args := []any{decodeUID(actor), now, now, id, strings.TrimSpace(topic), strings.TrimSpace(groupFileID)} if !canManage { query += " AND uploader_id=?" args = append(args, decodeUID(actor)) @@ -1213,7 +1213,7 @@ func (a *adapter) TenantGroupFileDelete(id t.TenantID, topic, groupFileID string if affected, _ := res.RowsAffected(); affected == 0 { return nil, t.ErrNotFound } - return a.groupFileByQuery(ctx, "SELECT * FROM im_group_files WHERE topic=? AND group_file_id=?", strings.TrimSpace(topic), strings.TrimSpace(groupFileID)) + return a.groupFileByQuery(ctx, "SELECT * FROM im_group_files WHERE tenant_id=? AND topic=? AND group_file_id=?", id, strings.TrimSpace(topic), strings.TrimSpace(groupFileID)) } func (a *adapter) TenantGroupCreate(id t.TenantID, topic *t.Topic, owner t.Uid, ownerPrivate any, @@ -1319,8 +1319,8 @@ func (a *adapter) TenantGroupInviteMember(id t.TenantID, topic string, inviter, idempotencyKey += ":" + target.UserId() } if _, err = tx.ExecContext(ctx, `INSERT INTO im_group_invitations - (invitation_id,topic,inviter_user_id,target_user_id,message,status,result,idempotency_key,expires_at,handled_at,created_at,updated_at) - VALUES(?,?,?,?,?,?,?,?,?,?,?,?)`, invitationID, topic, decodeUID(inviter), decodeUID(target), message, + (tenant_id,invitation_id,topic,inviter_user_id,target_user_id,message,status,result,idempotency_key,expires_at,handled_at,created_at,updated_at) + VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)`, id, invitationID, topic, decodeUID(inviter), decodeUID(target), message, t.GroupInvitationStatusJoined, "joined", idempotencyKey, now.Add(7*24*time.Hour), now, now, now); err != nil { return false, err } @@ -1490,8 +1490,8 @@ func (a *adapter) TenantGroupCanPost(id t.TenantID, topic string, uid t.Uid, adm var muteAll, memberMuted bool err := a.db.QueryRowContext(ctx, `SELECT COALESCE((SELECT mute_all FROM im_group_settings WHERE tenant_id=? AND topic=? AND group_status=? LIMIT 1),0), - EXISTS(SELECT 1 FROM im_group_mutes WHERE topic=? AND user_id=? AND status=? AND (expires_at IS NULL OR expires_at>?))`, - id, topic, t.GroupStatusActive, topic, decodeUID(uid), t.GroupMuteStatusActive, now).Scan(&muteAll, &memberMuted) + EXISTS(SELECT 1 FROM im_group_mutes WHERE tenant_id=? AND topic=? AND user_id=? AND status=? AND (expires_at IS NULL OR expires_at>?))`, + id, topic, t.GroupStatusActive, id, topic, decodeUID(uid), t.GroupMuteStatusActive, now).Scan(&muteAll, &memberMuted) if err != nil { return false, err } @@ -1549,7 +1549,7 @@ func syncGroupMuteAllTx(tx *sqlx.Tx, tenantID t.TenantID, topic string, muteAll mode = t.ModeCPublic &^ t.ModeWrite } _, err := tx.Exec(tenantShardSQL(tenantID, `UPDATE im_subscriptions AS s - JOIN im_group_members AS gm ON gm.topic=s.topic AND gm.user_id=s.userid + JOIN im_group_members AS gm ON gm.tenant_id=s.tenant_id AND gm.topic=s.topic AND gm.user_id=s.userid SET s.modewant=?,s.modegiven=?,s.updatedat=?, gm.version=gm.version+1,gm.updated_at=? WHERE s.topic=? AND s.deletedat IS NULL @@ -1611,7 +1611,7 @@ func (a *adapter) groupJoinRequestByQuery(ctx context.Context, query string, arg func (a *adapter) groupInvitationByQuery(ctx context.Context, query string, args ...any) (*t.GroupInvitation, error) { var item t.GroupInvitation var handledAt sql.NullTime - err := a.db.QueryRowxContext(ctx, query, args...).Scan(&item.ID, &item.InvitationID, &item.Topic, &item.InviterUserRaw, &item.TargetUserRaw, + err := a.db.QueryRowxContext(ctx, query, args...).Scan(&item.TenantID, &item.ID, &item.InvitationID, &item.Topic, &item.InviterUserRaw, &item.TargetUserRaw, &item.Message, &item.Status, &item.Result, &item.JoinRequestID, &item.IdempotencyKey, &item.ExpiresAt, &handledAt, &item.CreatedAt, &item.UpdatedAt) if err == sql.ErrNoRows { return nil, t.ErrNotFound diff --git a/server/store/types/groups.go b/server/store/types/groups.go index 53fec90..6355f0c 100644 --- a/server/store/types/groups.go +++ b/server/store/types/groups.go @@ -118,6 +118,7 @@ type GroupMemberListOptions struct { } type GroupInvitation struct { + TenantID TenantID `db:"tenant_id"` ID int64 `db:"id"` InvitationID string `db:"invitation_id"` Topic string `db:"topic"` @@ -137,6 +138,7 @@ type GroupInvitation struct { } type GroupJoinRequest struct { + TenantID TenantID `db:"tenant_id"` ID int64 `db:"id"` RequestID string `db:"request_id"` Topic string `db:"topic"` @@ -160,6 +162,7 @@ type GroupJoinRequest struct { } type GroupAnnouncement struct { + TenantID TenantID `db:"tenant_id"` ID int64 `db:"id"` AnnouncementID string `db:"announcement_id"` Topic string `db:"topic"` @@ -177,6 +180,7 @@ type GroupAnnouncement struct { } type GroupMute struct { + TenantID TenantID `db:"tenant_id"` Topic string `db:"topic"` UserID Uid `db:"-"` MutedBy Uid `db:"-"` @@ -195,6 +199,7 @@ type GroupMute struct { } type GroupFile struct { + TenantID TenantID `db:"tenant_id"` ID int64 `db:"id"` GroupFileID string `db:"group_file_id"` Topic string `db:"topic"`