d1f180ee0a
Offline Electron app seeds 10 system presets, supports create-from-scratch designer with palette/drag/resize/bricks, and exports .ttrpg-system.json for the player. Co-authored-by: Cursor <cursoragent@cursor.com>
138 lines
4.3 KiB
TypeScript
138 lines
4.3 KiB
TypeScript
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
|
|
import Database from 'better-sqlite3';
|
|
|
|
import type { CharacterSheet, SystemDefinition } from '../shared/types.ts';
|
|
import { validateSystemDefinition } from '../shared/systemValidate.ts';
|
|
|
|
export class AppStore {
|
|
private readonly db: Database.Database;
|
|
|
|
constructor(dbPath: string) {
|
|
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
this.db = new Database(dbPath);
|
|
this.db.exec(`
|
|
CREATE TABLE IF NOT EXISTS systems (
|
|
id TEXT PRIMARY KEY,
|
|
revision INTEGER NOT NULL,
|
|
schema_version INTEGER NOT NULL,
|
|
name TEXT NOT NULL,
|
|
json TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE TABLE IF NOT EXISTS characters (
|
|
id TEXT PRIMARY KEY,
|
|
system_id TEXT NOT NULL,
|
|
name TEXT NOT NULL,
|
|
json TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
`);
|
|
}
|
|
|
|
listSystems(): Array<{ id: string; name: string; revision: number; schemaVersion: number }> {
|
|
return this.db
|
|
.prepare('SELECT id, name, revision, schema_version AS schemaVersion FROM systems ORDER BY name')
|
|
.all() as Array<{ id: string; name: string; revision: number; schemaVersion: number }>;
|
|
}
|
|
|
|
getSystem(id: string): SystemDefinition | null {
|
|
const row = this.db.prepare('SELECT json FROM systems WHERE id = ?').get(id) as
|
|
| { json: string }
|
|
| undefined;
|
|
return row ? (JSON.parse(row.json) as SystemDefinition) : null;
|
|
}
|
|
|
|
upsertSystem(system: SystemDefinition): SystemDefinition {
|
|
const check = validateSystemDefinition(system);
|
|
if (!check.ok) {
|
|
throw new Error(check.errors.map((e) => e.message).join('; '));
|
|
}
|
|
const existing = this.getSystem(system.id);
|
|
const next: SystemDefinition = {
|
|
...system,
|
|
revision: existing ? existing.revision + 1 : system.revision || 1,
|
|
};
|
|
const now = new Date().toISOString();
|
|
this.db
|
|
.prepare(
|
|
`INSERT INTO systems (id, revision, schema_version, name, json, updated_at)
|
|
VALUES (@id, @revision, @schemaVersion, @name, @json, @updatedAt)
|
|
ON CONFLICT(id) DO UPDATE SET
|
|
revision=excluded.revision,
|
|
schema_version=excluded.schema_version,
|
|
name=excluded.name,
|
|
json=excluded.json,
|
|
updated_at=excluded.updated_at`,
|
|
)
|
|
.run({
|
|
id: next.id,
|
|
revision: next.revision,
|
|
schemaVersion: next.schemaVersion,
|
|
name: next.name,
|
|
json: JSON.stringify(next),
|
|
updatedAt: now,
|
|
});
|
|
return next;
|
|
}
|
|
|
|
deleteSystem(id: string): void {
|
|
const tx = this.db.transaction(() => {
|
|
this.db.prepare('DELETE FROM characters WHERE system_id = ?').run(id);
|
|
this.db.prepare('DELETE FROM systems WHERE id = ?').run(id);
|
|
});
|
|
tx();
|
|
}
|
|
|
|
listCharacters(systemId?: string): Array<{ id: string; name: string; systemId: string }> {
|
|
if (systemId) {
|
|
return this.db
|
|
.prepare(
|
|
'SELECT id, name, system_id AS systemId FROM characters WHERE system_id = ? ORDER BY name',
|
|
)
|
|
.all(systemId) as Array<{ id: string; name: string; systemId: string }>;
|
|
}
|
|
return this.db
|
|
.prepare('SELECT id, name, system_id AS systemId FROM characters ORDER BY name')
|
|
.all() as Array<{ id: string; name: string; systemId: string }>;
|
|
}
|
|
|
|
getCharacter(id: string): CharacterSheet | null {
|
|
const row = this.db.prepare('SELECT json FROM characters WHERE id = ?').get(id) as
|
|
| { json: string }
|
|
| undefined;
|
|
return row ? (JSON.parse(row.json) as CharacterSheet) : null;
|
|
}
|
|
|
|
upsertCharacter(character: CharacterSheet): CharacterSheet {
|
|
const now = new Date().toISOString();
|
|
this.db
|
|
.prepare(
|
|
`INSERT INTO characters (id, system_id, name, json, updated_at)
|
|
VALUES (@id, @systemId, @name, @json, @updatedAt)
|
|
ON CONFLICT(id) DO UPDATE SET
|
|
system_id=excluded.system_id,
|
|
name=excluded.name,
|
|
json=excluded.json,
|
|
updated_at=excluded.updated_at`,
|
|
)
|
|
.run({
|
|
id: character.id,
|
|
systemId: character.systemId,
|
|
name: character.name,
|
|
json: JSON.stringify(character),
|
|
updatedAt: now,
|
|
});
|
|
return character;
|
|
}
|
|
|
|
deleteCharacter(id: string): void {
|
|
this.db.prepare('DELETE FROM characters WHERE id = ?').run(id);
|
|
}
|
|
|
|
close(): void {
|
|
this.db.close();
|
|
}
|
|
}
|