Scaffold Regress: Fastify+SQLite server and Vue frontend for the Ingress-style BTC Map capture game
- Server: NIP-98 nostr auth (session cookies), BTC Map sync, claim/link/field routes, physical-presence checks via geolocation distance. 31 tests passing. - Frontend: Leaflet map, team pick, claim/link UI, Archipelago identity bridge vendored (nostr-provider.js) for dashboard launch support. - Verified end-to-end against the live BTC Map API (167 real Madeira places) and via genuine NIP-98-signed HTTP requests against the built app.
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
import Database from 'better-sqlite3';
|
||||
import { mkdirSync } from 'node:fs';
|
||||
import { dirname } from 'node:path';
|
||||
import { migrations } from './migrations.js';
|
||||
|
||||
export type DB = Database.Database;
|
||||
|
||||
export function openDatabase(path: string): DB {
|
||||
if (path !== ':memory:') mkdirSync(dirname(path), { recursive: true });
|
||||
const db = new Database(path);
|
||||
db.pragma('journal_mode = WAL');
|
||||
db.pragma('foreign_keys = ON');
|
||||
migrate(db);
|
||||
return db;
|
||||
}
|
||||
|
||||
function migrate(db: DB): void {
|
||||
db.exec('CREATE TABLE IF NOT EXISTS schema_migrations (id INTEGER PRIMARY KEY, applied_at INTEGER NOT NULL)');
|
||||
const applied = new Set(
|
||||
db.prepare('SELECT id FROM schema_migrations').all().map((r) => (r as { id: number }).id),
|
||||
);
|
||||
const record = db.prepare('INSERT INTO schema_migrations (id, applied_at) VALUES (?, ?)');
|
||||
for (const m of migrations) {
|
||||
if (applied.has(m.id)) continue;
|
||||
db.transaction(() => {
|
||||
db.exec(m.sql);
|
||||
record.run(m.id, Math.floor(Date.now() / 1000));
|
||||
})();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
export interface Migration {
|
||||
id: number;
|
||||
sql: string;
|
||||
}
|
||||
|
||||
export const migrations: Migration[] = [
|
||||
{
|
||||
id: 1,
|
||||
sql: `
|
||||
CREATE TABLE users (
|
||||
pubkey TEXT PRIMARY KEY,
|
||||
team TEXT CHECK (team IN ('orange','green')),
|
||||
display_name TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
last_login_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE sessions (
|
||||
id TEXT PRIMARY KEY,
|
||||
pubkey TEXT NOT NULL REFERENCES users(pubkey),
|
||||
created_at INTEGER NOT NULL,
|
||||
expires_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE auth_events (
|
||||
event_id TEXT PRIMARY KEY,
|
||||
seen_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- Places are synced from BTC Map (https://btcmap.org) — one row per BTC Map place id.
|
||||
-- This table is a local cache the game plays against; re-synced periodically.
|
||||
CREATE TABLE places (
|
||||
id INTEGER PRIMARY KEY,
|
||||
lat REAL NOT NULL,
|
||||
lon REAL NOT NULL,
|
||||
name TEXT NOT NULL DEFAULT '',
|
||||
icon TEXT,
|
||||
address TEXT,
|
||||
osm_id TEXT,
|
||||
btcmap_updated_at TEXT,
|
||||
synced_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- Current capture state of a place. One row per place — capturing overwrites/deletes it.
|
||||
CREATE TABLE claims (
|
||||
place_id INTEGER PRIMARY KEY REFERENCES places(id) ON DELETE CASCADE,
|
||||
team TEXT NOT NULL CHECK (team IN ('orange','green')),
|
||||
claimed_by_pubkey TEXT NOT NULL REFERENCES users(pubkey),
|
||||
claimed_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- A link only exists while both endpoints remain claimed by the same team;
|
||||
-- recapturing either endpoint tears down every link touching it (see routes/claims.ts).
|
||||
CREATE TABLE links (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
from_place_id INTEGER NOT NULL REFERENCES places(id) ON DELETE CASCADE,
|
||||
to_place_id INTEGER NOT NULL REFERENCES places(id) ON DELETE CASCADE,
|
||||
team TEXT NOT NULL CHECK (team IN ('orange','green')),
|
||||
created_by_pubkey TEXT NOT NULL REFERENCES users(pubkey),
|
||||
created_at INTEGER NOT NULL,
|
||||
UNIQUE (from_place_id, to_place_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_links_from ON links(from_place_id);
|
||||
CREATE INDEX idx_links_to ON links(to_place_id);
|
||||
`,
|
||||
},
|
||||
];
|
||||
Reference in New Issue
Block a user