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
|
// the reviewer's repro hit: the lib's `codes` table collides with the
|
||||||
// name 000022's down script renames legacy_codes back to.
|
// name 000022's down script renames legacy_codes back to.
|
||||||
m := newSQLiteStepper(t, db)
|
m := newSQLiteStepper(t, db)
|
||||||
// 000025 (user_device_limit_override), 000024 (invite_rewards) and 000023
|
// 000026 (notices), 000025 (user_device_limit_override), 000024
|
||||||
// (pay_purchases/source-enum) now sit on top of 000022
|
// (invite_rewards) and 000023 (pay_purchases/source-enum) now sit on top
|
||||||
// (codes_lib_legacy_rename) and are unrelated to this collision — step
|
// of 000022 (codes_lib_legacy_rename) and are unrelated to this
|
||||||
// them back down first so we land exactly on the 000022 boundary the
|
// collision — step them back down first so we land exactly on the
|
||||||
// test targets.
|
// 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 {
|
if err := m.Steps(-1); err != nil {
|
||||||
t.Fatalf("step 000025 down: %v", err)
|
t.Fatalf("step 000025 down: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ func TestSQLiteMigrateUpDown(t *testing.T) {
|
|||||||
if dirty {
|
if dirty {
|
||||||
t.Fatalf("schema dirty after MigrateUp")
|
t.Fatalf("schema dirty after MigrateUp")
|
||||||
}
|
}
|
||||||
if v != 25 {
|
if v != 26 {
|
||||||
t.Errorf("version = %d, want 25", v)
|
t.Errorf("version = %d, want 26", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Core tables exist.
|
// 2. Core tables exist.
|
||||||
@@ -39,7 +39,7 @@ func TestSQLiteMigrateUpDown(t *testing.T) {
|
|||||||
"usage_daily", "audit_log", "providers", "nodes", "node_events",
|
"usage_daily", "audit_log", "providers", "nodes", "node_events",
|
||||||
"directory_version", "provision_idempotency", "replacements", "admins",
|
"directory_version", "provision_idempotency", "replacements", "admins",
|
||||||
"connect_credentials", "usage_device_daily", "sessions", "usage_hourly", "usage_device_hourly",
|
"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
|
var name string
|
||||||
err := db.QueryRow(
|
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