Files
pangolin/server/migrations/sqlite/000021_devices_user_scoped_uuid.up.sql
T
wangjia 1198ecc556 fix(migrations): 恢复线性迁移编号,与生产已应用的对齐
集成分支曾把已上线的 000020_ad_bonus_minutes(#21)/000021_devices_user_scoped_uuid(#27)
两个迁移错误覆盖成 codes_lib_legacy_rename/pay_purchases,与生产(schema_migrations=21)
的实际血统冲突,导致 golang-migrate 在生产上找不到 version 21 文件而报错。
恢复为:020 ad_bonus / 021 devices / 022 codes_lib_legacy_rename / 023 pay_purchases。
生产库副本完整试跑通过:version→23,pay_purchases 建好,subscriptions CHECK 加 'pay',
codes 库表就位,3 用户/3 订阅数据零丢失。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
2026-07-11 14:25:09 +08:00

69 lines
3.1 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- 设备唯一键 UNIQUE(uuid) → UNIQUE(user_id, uuid)F3:同机换账号 403 死结)
--
-- device_id 是客户端按「安装」生成并持久的,跨账号复用;全局 UNIQUE(uuid) 使同一台
-- 机器登第二个账号时 RegisterIfAbsent 永远 403 → ConnectNode 判 DEVICE_NOT_REGISTERED,
-- 且提示的「重新登录」无法自救。改为按用户隔离:同一物理设备在每个账号下各有一行。
-- 顺手落地 migration 16 头注释里推迟的两件事:本唯一键改造 + platform CHECK 加 linux
-- (normalizePlatform 已接受 linux,但旧 CHECK 会拒 INSERT)。
--
-- SQLite 无法删除列级 UNIQUE,须重建表。sessions 对 devices 有 ON DELETE CASCADE 外键
-- 且连接开启 foreign_keys=ON:直接 DROP devices 会级联清空全部会话(=全员被登出)。
-- 安全次序(单事务内成立,不依赖 PRAGMA foreign_keys=OFF):
-- ① RENAME devices → devices_old(sessions 的 FK 定义随 rename 跟走到 devices_old);
-- ② 建新 devices(复合唯一键),按原 id 复制数据;
-- ③ RENAME sessions → sessions_old,建新 sessions(FK 指向新 devices),复制数据;
-- ④ 先 DROP 子表 sessions_old,再 DROP 已无引用的 devices_old;重建全部索引。
ALTER TABLE devices RENAME TO devices_old;
CREATE TABLE devices (
id INTEGER PRIMARY KEY AUTOINCREMENT,
uuid TEXT NOT NULL,
user_id INTEGER NOT NULL,
name TEXT NOT NULL,
platform TEXT NOT NULL CHECK (platform IN ('ios','android','windows','macos','linux')),
last_seen DATETIME NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
client_version TEXT NULL,
totp_trusted_until DATETIME NULL,
dp_uuid TEXT NULL,
UNIQUE (user_id, uuid),
FOREIGN KEY (user_id) REFERENCES users(id)
);
INSERT INTO devices (id, uuid, user_id, name, platform, last_seen, created_at,
client_version, totp_trusted_until, dp_uuid)
SELECT id, uuid, user_id, name, platform, last_seen, created_at,
client_version, totp_trusted_until, dp_uuid
FROM devices_old;
ALTER TABLE sessions RENAME TO sessions_old;
CREATE TABLE sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
device_id INTEGER NOT NULL,
refresh_jti TEXT NOT NULL UNIQUE,
client_ip TEXT NULL,
client_version TEXT NULL,
created_at DATETIME NOT NULL,
last_active DATETIME NULL,
revoked_at DATETIME NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (device_id) REFERENCES devices(id) ON DELETE CASCADE
);
INSERT INTO sessions (id, user_id, device_id, refresh_jti, client_ip, client_version,
created_at, last_active, revoked_at)
SELECT id, user_id, device_id, refresh_jti, client_ip, client_version,
created_at, last_active, revoked_at
FROM sessions_old;
DROP TABLE sessions_old;
DROP TABLE devices_old;
CREATE INDEX idx_devices_user ON devices (user_id);
CREATE UNIQUE INDEX idx_devices_dp_uuid ON devices (dp_uuid);
CREATE INDEX idx_sessions_user ON sessions (user_id);
CREATE INDEX idx_sessions_device ON sessions (device_id);