React/Vite frontend and Express/SQLite backend for tracking visited and planned cities on a map, with per-city notes, photos, links, and participant-based sharing between family members. Includes session auth with admin-managed user accounts, an OSM/Nominatim geocoding proxy, and a single-image Docker deployment. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
36 lines
1,012 B
TypeScript
36 lines
1,012 B
TypeScript
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import type Database from 'better-sqlite3';
|
|
|
|
const MIGRATIONS_DIR = path.join(__dirname, 'migrations');
|
|
|
|
export function migrate(db: Database.Database): void {
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS _migrations (
|
|
name TEXT PRIMARY KEY,
|
|
applied_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
|
|
)
|
|
`);
|
|
|
|
const applied = new Set(
|
|
db.prepare('SELECT name FROM _migrations').all().map((row: any) => row.name as string),
|
|
);
|
|
|
|
const files = fs
|
|
.readdirSync(MIGRATIONS_DIR)
|
|
.filter((f) => f.endsWith('.sql'))
|
|
.sort();
|
|
|
|
for (const file of files) {
|
|
if (applied.has(file)) continue;
|
|
|
|
const sql = fs.readFileSync(path.join(MIGRATIONS_DIR, file), 'utf-8');
|
|
const run = db.transaction(() => {
|
|
db.exec(sql);
|
|
db.prepare('INSERT INTO _migrations (name) VALUES (?)').run(file);
|
|
});
|
|
run();
|
|
// eslint-disable-next-line no-console
|
|
console.log(`Applied migration ${file}`);
|
|
}
|
|
}
|