From 7a52db34370644a07b8fc700fb44841a09c9dd28 Mon Sep 17 00:00:00 2001 From: Austin Date: Fri, 18 Sep 2026 10:55:13 +0800 Subject: [PATCH] fix: auto-provision im_user_privacy_settings columns on startup Zentao #24/#25: commit 50f8581 added friend_request_verification and group_invite_verification to userPrivacyByUID, but existing databases lack those columns, so GetPrivacy failed with Error 1054 and every cross-user profile/qrcodes/friend-request endpoint returned 500. Add user_privacy_schema.go which, under GET_LOCK, checks information_schema and ALTERs in any missing privacy columns, wired into the root adapter Open() and per-tenant TenantAdapterFor so tenant-scoped databases are provisioned too. --- server/db/mysql/adapter.go | 7 +++ server/db/mysql/user_privacy_schema.go | 64 ++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 server/db/mysql/user_privacy_schema.go diff --git a/server/db/mysql/adapter.go b/server/db/mysql/adapter.go index abe01e2..da0602a 100644 --- a/server/db/mysql/adapter.go +++ b/server/db/mysql/adapter.go @@ -284,6 +284,9 @@ func (a *adapter) Open(jsonconfig json.RawMessage) error { return errors.New("mysql adapter failed to open tenant registry database: " + err.Error()) } } + if err = ensureUserPrivacySchema(a.db); err != nil { + return errors.New("mysql adapter failed to ensure user privacy schema: " + err.Error()) + } } return err } @@ -494,6 +497,10 @@ func (a *adapter) TenantAdapterFor(tenantID t.TenantID) (dbadapter.TenantBusines db.Close() return nil, nil, err } + if err = ensureUserPrivacySchema(db); err != nil { + db.Close() + return nil, nil, errors.New("mysql adapter failed to ensure user privacy schema for tenant: " + err.Error()) + } registryDB := a.registryDB if registryDB == nil { registryDB = a.db diff --git a/server/db/mysql/user_privacy_schema.go b/server/db/mysql/user_privacy_schema.go new file mode 100644 index 0000000..b05ebb7 --- /dev/null +++ b/server/db/mysql/user_privacy_schema.go @@ -0,0 +1,64 @@ +//go:build mysql +// +build mysql + +package mysql + +import ( + "context" + "database/sql" + "errors" + + "github.com/jmoiron/sqlx" +) + +const userPrivacyMigrationLock = "CONCAT('im_user_privacy:', LEFT(SHA2(DATABASE(), 256), 40))" + +// ensureUserPrivacySchema adds columns of im_user_privacy_settings that were +// introduced after the table's initial release. Databases created fresh by +// createIMUserProductTables already have them; legacy databases are upgraded +// in place. A missing table is skipped: it is created by the fresh-install +// path and there is nothing to upgrade yet. +func ensureUserPrivacySchema(db *sqlx.DB) error { + ctx := context.Background() + conn, err := db.Connx(ctx) + if err != nil { + return err + } + defer conn.Close() + + var locked sql.NullInt64 + if err = conn.QueryRowContext(ctx, "SELECT GET_LOCK("+userPrivacyMigrationLock+", 60)").Scan(&locked); err != nil { + return err + } + if !locked.Valid || locked.Int64 != 1 { + return errors.New("mysql: timed out waiting for user privacy migration lock") + } + defer conn.ExecContext(ctx, "SELECT RELEASE_LOCK("+userPrivacyMigrationLock+")") + + tableExists, err := identityTableExists(ctx, conn, "im_user_privacy_settings") + if err != nil || !tableExists { + return err + } + + // Column definitions mirror createIMUserProductTables; the SELECT in + // userPrivacyByUID references every one of them. + columns := []struct{ name, ddl string }{ + {"profile_visibility", "VARCHAR(32) NOT NULL DEFAULT 'friends'"}, + {"last_seen_visibility", "VARCHAR(32) NOT NULL DEFAULT 'friends'"}, + {"read_receipts", "TINYINT NOT NULL DEFAULT 1"}, + {"friend_request_verification", "TINYINT NOT NULL DEFAULT 1"}, + {"group_invite_verification", "TINYINT NOT NULL DEFAULT 0"}, + } + for _, col := range columns { + exists, err := identityColumnExists(ctx, conn, "im_user_privacy_settings", col.name) + if err != nil { + return err + } + if !exists { + if _, err = conn.ExecContext(ctx, "ALTER TABLE im_user_privacy_settings ADD COLUMN "+col.name+" "+col.ddl); err != nil { + return err + } + } + } + return nil +}