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:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user