feat(server/migrate): 000026 notices 表 + users.notices_read_at 已读水位

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P9G7E3wmAYL9KeYCVZVsqu
This commit is contained in:
wangjia
2026-07-13 13:32:23 +08:00
parent 0657a830d0
commit 21a30aa4c8
6 changed files with 51 additions and 8 deletions
@@ -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)
}
+3 -3
View File
@@ -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(
@@ -0,0 +1,2 @@
DROP TABLE notices;
ALTER TABLE users DROP COLUMN notices_read_at;
@@ -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;
@@ -0,0 +1,2 @@
DROP TABLE notices;
ALTER TABLE users DROP COLUMN notices_read_at;
@@ -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);