From 21a30aa4c8a6622bcadb693d52a878d79bfc127d Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Mon, 13 Jul 2026 13:32:23 +0800 Subject: [PATCH] =?UTF-8?q?feat(server/migrate):=20000026=20notices=20?= =?UTF-8?q?=E8=A1=A8=20+=20users.notices=5Fread=5Fat=20=E5=B7=B2=E8=AF=BB?= =?UTF-8?q?=E6=B0=B4=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01P9G7E3wmAYL9KeYCVZVsqu --- .../internal/store/codes_lib_migrate_test.go | 13 ++++++++----- server/internal/store/sqlite_migrate_test.go | 6 +++--- .../migrations/mysql/000026_notices.down.sql | 2 ++ server/migrations/mysql/000026_notices.up.sql | 18 ++++++++++++++++++ .../migrations/sqlite/000026_notices.down.sql | 2 ++ server/migrations/sqlite/000026_notices.up.sql | 18 ++++++++++++++++++ 6 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 server/migrations/mysql/000026_notices.down.sql create mode 100644 server/migrations/mysql/000026_notices.up.sql create mode 100644 server/migrations/sqlite/000026_notices.down.sql create mode 100644 server/migrations/sqlite/000026_notices.up.sql diff --git a/server/internal/store/codes_lib_migrate_test.go b/server/internal/store/codes_lib_migrate_test.go index ebcbc01..2a775f6 100644 --- a/server/internal/store/codes_lib_migrate_test.go +++ b/server/internal/store/codes_lib_migrate_test.go @@ -104,11 +104,14 @@ func TestCodesLibMigrateRoundTrip(t *testing.T) { // the reviewer's repro hit: the lib's `codes` table collides with the // name 000022's down script renames legacy_codes back to. m := newSQLiteStepper(t, db) - // 000025 (user_device_limit_override), 000024 (invite_rewards) and 000023 - // (pay_purchases/source-enum) now sit on top of 000022 - // (codes_lib_legacy_rename) and are unrelated to this collision — step - // them back down first so we land exactly on the 000022 boundary the - // test targets. + // 000026 (notices), 000025 (user_device_limit_override), 000024 + // (invite_rewards) and 000023 (pay_purchases/source-enum) now sit on top + // of 000022 (codes_lib_legacy_rename) and are unrelated to this + // collision — step them back down first so we land exactly on the + // 000022 boundary the test targets. + if err := m.Steps(-1); err != nil { + t.Fatalf("step 000026 down: %v", err) + } if err := m.Steps(-1); err != nil { t.Fatalf("step 000025 down: %v", err) } diff --git a/server/internal/store/sqlite_migrate_test.go b/server/internal/store/sqlite_migrate_test.go index ed40a07..4f4adb3 100644 --- a/server/internal/store/sqlite_migrate_test.go +++ b/server/internal/store/sqlite_migrate_test.go @@ -29,8 +29,8 @@ func TestSQLiteMigrateUpDown(t *testing.T) { if dirty { t.Fatalf("schema dirty after MigrateUp") } - if v != 25 { - t.Errorf("version = %d, want 25", v) + if v != 26 { + t.Errorf("version = %d, want 26", v) } // 2. Core tables exist. @@ -39,7 +39,7 @@ func TestSQLiteMigrateUpDown(t *testing.T) { "usage_daily", "audit_log", "providers", "nodes", "node_events", "directory_version", "provision_idempotency", "replacements", "admins", "connect_credentials", "usage_device_daily", "sessions", "usage_hourly", "usage_device_hourly", - "pay_purchases", "referrals", "reward_claims", + "pay_purchases", "referrals", "reward_claims", "notices", } { var name string err := db.QueryRow( diff --git a/server/migrations/mysql/000026_notices.down.sql b/server/migrations/mysql/000026_notices.down.sql new file mode 100644 index 0000000..fa031de --- /dev/null +++ b/server/migrations/mysql/000026_notices.down.sql @@ -0,0 +1,2 @@ +DROP TABLE notices; +ALTER TABLE users DROP COLUMN notices_read_at; diff --git a/server/migrations/mysql/000026_notices.up.sql b/server/migrations/mysql/000026_notices.up.sql new file mode 100644 index 0000000..148906f --- /dev/null +++ b/server/migrations/mysql/000026_notices.up.sql @@ -0,0 +1,18 @@ +ALTER TABLE users ADD COLUMN notices_read_at DATETIME(6) NULL; + +CREATE TABLE notices ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + type ENUM('important','feature','news','reward','version','promo') NOT NULL, + user_id BIGINT UNSIGNED NULL, + title_zh TEXT NOT NULL, + title_en TEXT NOT NULL, + body_zh TEXT NULL, + body_en TEXT NULL, + link TEXT NULL, + published_at DATETIME(6) NOT NULL, + expires_at DATETIME(6) NULL, + revoked_at DATETIME(6) NULL, + email_sent_at DATETIME(6) NULL, + INDEX ix_notices_user_pub (user_id, published_at), + FOREIGN KEY (user_id) REFERENCES users(id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/server/migrations/sqlite/000026_notices.down.sql b/server/migrations/sqlite/000026_notices.down.sql new file mode 100644 index 0000000..fa031de --- /dev/null +++ b/server/migrations/sqlite/000026_notices.down.sql @@ -0,0 +1,2 @@ +DROP TABLE notices; +ALTER TABLE users DROP COLUMN notices_read_at; diff --git a/server/migrations/sqlite/000026_notices.up.sql b/server/migrations/sqlite/000026_notices.up.sql new file mode 100644 index 0000000..03355f7 --- /dev/null +++ b/server/migrations/sqlite/000026_notices.up.sql @@ -0,0 +1,18 @@ +ALTER TABLE users ADD COLUMN notices_read_at DATETIME; + +CREATE TABLE notices ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + type TEXT NOT NULL CHECK (type IN ('important','feature','news','reward','version','promo')), + user_id INTEGER, -- NULL=全员广播;非空=定向该用户 + title_zh TEXT NOT NULL, + title_en TEXT NOT NULL, + body_zh TEXT, + body_en TEXT, + link TEXT, + published_at DATETIME NOT NULL, + expires_at DATETIME, + revoked_at DATETIME, + email_sent_at DATETIME, + FOREIGN KEY (user_id) REFERENCES users(id) +); +CREATE INDEX ix_notices_user_pub ON notices(user_id, published_at);