fix(saas): migration robusta — recria state e anchor_session se schema legado, índices user_id após ALTER

This commit is contained in:
PontualTech / Karlão 2026-04-27 15:44:05 -03:00
parent 85b60a800c
commit a80adc7bdf

View file

@ -59,7 +59,6 @@ db.exec(`
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
); );
CREATE INDEX IF NOT EXISTS idx_media_parent ON media(parent_id); CREATE INDEX IF NOT EXISTS idx_media_parent ON media(parent_id);
CREATE INDEX IF NOT EXISTS idx_media_user ON media(user_id);
CREATE TABLE IF NOT EXISTS anchor_session ( CREATE TABLE IF NOT EXISTS anchor_session (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
@ -101,7 +100,6 @@ db.exec(`
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
); );
CREATE INDEX IF NOT EXISTS idx_shares_expires ON shares(expires_at); CREATE INDEX IF NOT EXISTS idx_shares_expires ON shares(expires_at);
CREATE INDEX IF NOT EXISTS idx_shares_user ON shares(user_id);
CREATE TABLE IF NOT EXISTS share_positions ( CREATE TABLE IF NOT EXISTS share_positions (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
@ -133,18 +131,74 @@ try {
} }
} catch (e) { /* ignore */ } } catch (e) { /* ignore */ }
// ===== Migração multi-tenant: adicionar user_id em tabelas existentes ===== // ===== Migração multi-tenant =====
// Idempotente: roda toda startup, só ALTER se coluna não existir // 1) Tabelas que mudaram PRIMARY KEY (state, anchor_session): se schema antigo detectado,
// recriar limpo. Sem perda de dados crítica neste momento (deploy inicial).
function recreateIfLegacy(table, newSchema) {
try {
const cols = db.prepare(`PRAGMA table_info(${table})`).all();
if (cols.length === 0) return; // tabela ainda não existe (CREATE TABLE pegou o schema novo)
const hasUserId = cols.some(c => c.name === 'user_id');
const idCol = cols.find(c => c.name === 'id');
// Schema antigo: id é PRIMARY KEY mas NÃO é AUTOINCREMENT (rowid alias com CHECK constraint)
const isLegacyPK = idCol && idCol.pk === 1 && !cols.some(c => c.type === 'INTEGER' && c.pk && c.name === 'id' && (c.dflt_value || '').toString().includes('AUTO'));
if (!hasUserId || isLegacyPK) {
console.log(`[migration] recreating ${table} (legacy schema detected)`);
db.exec(`DROP TABLE IF EXISTS ${table}; ${newSchema}`);
}
} catch (e) { console.warn(`[migration] recreate ${table}:`, e.message); }
}
recreateIfLegacy('state', `
CREATE TABLE state (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL DEFAULT 1,
data TEXT NOT NULL,
updated_at INTEGER NOT NULL,
UNIQUE(user_id),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
`);
recreateIfLegacy('anchor_session', `
CREATE TABLE anchor_session (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL DEFAULT 1,
active INTEGER NOT NULL DEFAULT 0,
boat_name TEXT,
anchor_lat REAL,
anchor_lng REAL,
radius INTEGER,
started_at INTEGER,
last_heartbeat INTEGER,
last_lat REAL,
last_lng REAL,
last_distance REAL,
alarm_fired INTEGER DEFAULT 0,
UNIQUE(user_id),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
`);
// 2) Tabelas que só ganharam coluna user_id: ALTER TABLE ADD COLUMN
function ensureUserIdColumn(table) { function ensureUserIdColumn(table) {
try { try {
const cols = db.prepare(`PRAGMA table_info(${table})`).all(); const cols = db.prepare(`PRAGMA table_info(${table})`).all();
if (cols.length === 0) return;
if (!cols.some(c => c.name === 'user_id')) { if (!cols.some(c => c.name === 'user_id')) {
db.exec(`ALTER TABLE ${table} ADD COLUMN user_id INTEGER NOT NULL DEFAULT 1`); db.exec(`ALTER TABLE ${table} ADD COLUMN user_id INTEGER NOT NULL DEFAULT 1`);
console.log(`[migration] added user_id to ${table}`); console.log(`[migration] added user_id to ${table}`);
} }
} catch (e) { console.warn(`[migration] ${table}:`, e.message); } } catch (e) { console.warn(`[migration] ${table}:`, e.message); }
} }
['state', 'media', 'anchor_session', 'alarm_log', 'shares', 'audit_log'].forEach(ensureUserIdColumn); ['media', 'alarm_log', 'shares', 'audit_log'].forEach(ensureUserIdColumn);
// 3) Índices em user_id rodam DEPOIS do ALTER TABLE (senão "no such column")
try {
db.exec(`
CREATE INDEX IF NOT EXISTS idx_media_user ON media(user_id);
CREATE INDEX IF NOT EXISTS idx_shares_user ON shares(user_id);
`);
} catch (e) { console.warn('[migration] user_id indexes:', e.message); }
// Garante user default (id=1, Karlão) — donos de dados pré-multi-tenant // Garante user default (id=1, Karlão) — donos de dados pré-multi-tenant
function ensureDefaultUser() { function ensureDefaultUser() {