import { connect } from '@tursodatabase/database'; import { access, mkdir, readFile } from 'node:fs/promises'; import { dirname, resolve } from 'node:path'; import process from 'node:process'; import { initializeDefaultUsers } from './default-users.mjs'; const databasePath = resolve(process.env.TDR_DATABASE_PATH || './data/shd-build.db'); const schemaPath = resolve('./database/schema.sql'); let databaseAlreadyExisted = true; try { await access(databasePath); } catch { databaseAlreadyExisted = false; } await mkdir(dirname(databasePath), { recursive: true }); const database = await connect(databasePath); const schema = await readFile(schemaPath, 'utf8'); const usersTable = await (await database.prepare( "SELECT 1 FROM sqlite_master WHERE type='table' AND name='users'" )).get(); if (usersTable) { const userColumns = await (await database.prepare("PRAGMA table_info('users')")).all(); if (!userColumns.some((column) => column.name === 'login_username')) { console.log('Separating optional local login usernames from internal account identifiers.'); await database.exec('ALTER TABLE users ADD COLUMN login_username TEXT'); await database.exec('UPDATE users SET login_username=username WHERE password_hash IS NOT NULL'); } } // The project schema deliberately avoids semicolons inside string literals. const statements = schema .split(';') .map((statement) => statement.trim()) .filter(Boolean); for (const statement of statements) { await database.exec(statement); } const loginEventsDefinition = await (await database.prepare( "SELECT sql FROM sqlite_master WHERE type='table' AND name='login_events'" )).get(); if (typeof loginEventsDefinition?.sql === 'string' && !loginEventsDefinition.sql.includes('magic_link')) { console.log('Expanding login event providers for passwordless authentication.'); await database.exec('ALTER TABLE login_events RENAME TO login_events_legacy'); await database.exec(`CREATE TABLE login_events ( id TEXT PRIMARY KEY, user_id TEXT REFERENCES users(id) ON DELETE SET NULL, provider TEXT NOT NULL CHECK(provider IN ('password', 'discord', 'magic_link', 'passkey')), succeeded INTEGER NOT NULL CHECK(succeeded IN (0, 1)), created_at TEXT NOT NULL )`); await database.exec(`INSERT INTO login_events(id,user_id,provider,succeeded,created_at) SELECT id,user_id,provider,succeeded,created_at FROM login_events_legacy`); await database.exec('DROP TABLE login_events_legacy'); await database.exec('CREATE INDEX IF NOT EXISTS login_events_user_created_idx ON login_events(user_id, created_at DESC)'); } console.log(`Applied ${statements.length} schema statements to ${databasePath}`); if (!databaseAlreadyExisted) { console.log('New database detected; creating default users.'); await initializeDefaultUsers(database); }