init: Phase 1 project skeleton
Express entry point, SQLite schema init with seed data, static frontend shell, and empty route/JS stubs per CLAUDE.md Phase 1 scope.
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
data/mythic-oracle.db
|
||||
Generated
+1252
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "mythic-oracle",
|
||||
"version": "1.0.0",
|
||||
"description": "Solo TTRPG session companion — Mythic GME oracle, campaign tracking, character management, and session notes.",
|
||||
"private": true,
|
||||
"main": "server/index.js",
|
||||
"scripts": {
|
||||
"start": "node server/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.19.2",
|
||||
"better-sqlite3": "^11.3.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/* Mythic Oracle — styles */
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Mythic Oracle</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,500;0,600;0,700;1,400&family=Lora:ital,wght@0,400;0,500;0,600;1,400&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
|
||||
<link rel="stylesheet" href="css/styles.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
<script src="js/app.js" type="module"></script>
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
// Mythic Oracle — shared fetch wrapper, all API calls
|
||||
@@ -0,0 +1 @@
|
||||
// Mythic Oracle — entry, sidebar routing, campaign context
|
||||
@@ -0,0 +1 @@
|
||||
// Mythic Oracle — character sheets
|
||||
@@ -0,0 +1 @@
|
||||
// Mythic Oracle — dice
|
||||
@@ -0,0 +1 @@
|
||||
// Mythic Oracle — meaning tables
|
||||
@@ -0,0 +1 @@
|
||||
// Mythic Oracle — notes
|
||||
@@ -0,0 +1 @@
|
||||
// Mythic Oracle — NPCs
|
||||
@@ -0,0 +1 @@
|
||||
// Mythic Oracle — oracle
|
||||
@@ -0,0 +1 @@
|
||||
// Mythic Oracle — tables
|
||||
@@ -0,0 +1 @@
|
||||
// Mythic Oracle — threads
|
||||
@@ -0,0 +1 @@
|
||||
// Mythic Oracle — UNE
|
||||
+235
@@ -0,0 +1,235 @@
|
||||
const path = require('path');
|
||||
const Database = require('better-sqlite3');
|
||||
|
||||
const db = new Database(path.join(__dirname, '..', 'data', 'mythic-oracle.db'));
|
||||
|
||||
db.pragma('foreign_keys = ON');
|
||||
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS systems (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
slug TEXT NOT NULL UNIQUE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS campaigns (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
system_id INTEGER NOT NULL REFERENCES systems(id),
|
||||
chaos_factor INTEGER NOT NULL DEFAULT 5,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS threads (
|
||||
id INTEGER PRIMARY KEY,
|
||||
campaign_id INTEGER NOT NULL REFERENCES campaigns(id),
|
||||
title TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'active',
|
||||
notes TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS npcs (
|
||||
id INTEGER PRIMARY KEY,
|
||||
campaign_id INTEGER NOT NULL REFERENCES campaigns(id),
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
notes TEXT,
|
||||
motivations TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS session_logs (
|
||||
id INTEGER PRIMARY KEY,
|
||||
campaign_id INTEGER NOT NULL REFERENCES campaigns(id),
|
||||
title TEXT,
|
||||
content TEXT,
|
||||
session_date TEXT NOT NULL DEFAULT (date('now')),
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS campaign_docs (
|
||||
id INTEGER PRIMARY KEY,
|
||||
campaign_id INTEGER NOT NULL REFERENCES campaigns(id),
|
||||
doc_type TEXT NOT NULL,
|
||||
content TEXT,
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
UNIQUE (campaign_id, doc_type)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS custom_tables (
|
||||
id INTEGER PRIMARY KEY,
|
||||
campaign_id INTEGER REFERENCES campaigns(id),
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
entries TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS characters_dnd5e (
|
||||
id INTEGER PRIMARY KEY,
|
||||
campaign_id INTEGER NOT NULL UNIQUE REFERENCES campaigns(id),
|
||||
name TEXT, class TEXT, level INTEGER DEFAULT 1,
|
||||
background TEXT, race TEXT, alignment TEXT,
|
||||
str INTEGER, dex INTEGER, con INTEGER,
|
||||
int_score INTEGER, wis INTEGER, cha INTEGER,
|
||||
hp_max INTEGER, hp_current INTEGER, hp_temp INTEGER,
|
||||
ac INTEGER, speed INTEGER,
|
||||
proficiency INTEGER DEFAULT 2,
|
||||
inspiration INTEGER DEFAULT 0,
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS characters_morkborg (
|
||||
id INTEGER PRIMARY KEY,
|
||||
campaign_id INTEGER NOT NULL UNIQUE REFERENCES campaigns(id),
|
||||
name TEXT, class TEXT,
|
||||
str INTEGER, agi INTEGER, pre INTEGER, tou INTEGER,
|
||||
hp_max INTEGER, hp_current INTEGER,
|
||||
omens_max INTEGER, omens_current INTEGER,
|
||||
silver INTEGER DEFAULT 0,
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS characters_cairn (
|
||||
id INTEGER PRIMARY KEY,
|
||||
campaign_id INTEGER NOT NULL UNIQUE REFERENCES campaigns(id),
|
||||
name TEXT, background TEXT,
|
||||
str INTEGER, dex INTEGER, wil INTEGER,
|
||||
hp_max INTEGER, hp_current INTEGER,
|
||||
armor INTEGER DEFAULT 0,
|
||||
deprived INTEGER DEFAULT 0,
|
||||
gold INTEGER DEFAULT 0,
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cairn_inventory (
|
||||
id INTEGER PRIMARY KEY,
|
||||
character_id INTEGER NOT NULL REFERENCES characters_cairn(id),
|
||||
slot INTEGER,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
is_bulky INTEGER DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS characters_chaalt (
|
||||
id INTEGER PRIMARY KEY,
|
||||
campaign_id INTEGER NOT NULL UNIQUE REFERENCES campaigns(id),
|
||||
name TEXT, class TEXT, level INTEGER DEFAULT 1, race TEXT,
|
||||
str INTEGER, dex INTEGER, con INTEGER,
|
||||
int_score INTEGER, wis INTEGER, cha INTEGER,
|
||||
hp_max INTEGER, hp_current INTEGER,
|
||||
ac INTEGER, thac0 INTEGER,
|
||||
gold INTEGER DEFAULT 0,
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS characters_ironsworn (
|
||||
id INTEGER PRIMARY KEY,
|
||||
campaign_id INTEGER NOT NULL UNIQUE REFERENCES campaigns(id),
|
||||
name TEXT,
|
||||
edge INTEGER DEFAULT 1, heart INTEGER DEFAULT 1,
|
||||
iron INTEGER DEFAULT 1, shadow INTEGER DEFAULT 1,
|
||||
wits INTEGER DEFAULT 1,
|
||||
health INTEGER DEFAULT 5, spirit INTEGER DEFAULT 5,
|
||||
supply INTEGER DEFAULT 5,
|
||||
momentum INTEGER DEFAULT 2, momentum_reset INTEGER DEFAULT 2,
|
||||
momentum_max INTEGER DEFAULT 10,
|
||||
wounded INTEGER DEFAULT 0, shaken INTEGER DEFAULT 0,
|
||||
unprepared INTEGER DEFAULT 0, encumbered INTEGER DEFAULT 0,
|
||||
maimed INTEGER DEFAULT 0, corrupted INTEGER DEFAULT 0,
|
||||
cursed INTEGER DEFAULT 0, tormented INTEGER DEFAULT 0,
|
||||
experience INTEGER DEFAULT 0,
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ironsworn_assets (
|
||||
id INTEGER PRIMARY KEY,
|
||||
character_id INTEGER NOT NULL REFERENCES characters_ironsworn(id),
|
||||
name TEXT NOT NULL,
|
||||
asset_type TEXT,
|
||||
ability_1 TEXT, ability_1_checked INTEGER DEFAULT 0,
|
||||
ability_2 TEXT, ability_2_checked INTEGER DEFAULT 0,
|
||||
ability_3 TEXT, ability_3_checked INTEGER DEFAULT 0,
|
||||
notes TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ironsworn_vows (
|
||||
id INTEGER PRIMARY KEY,
|
||||
campaign_id INTEGER NOT NULL REFERENCES campaigns(id),
|
||||
title TEXT NOT NULL,
|
||||
rank TEXT NOT NULL,
|
||||
progress INTEGER DEFAULT 0,
|
||||
status TEXT NOT NULL DEFAULT 'active',
|
||||
notes TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS characters_shadowrun (
|
||||
id INTEGER PRIMARY KEY,
|
||||
campaign_id INTEGER NOT NULL UNIQUE REFERENCES campaigns(id),
|
||||
name TEXT, metatype TEXT, archetype TEXT,
|
||||
gender TEXT, age INTEGER,
|
||||
body INTEGER, agility INTEGER, reaction INTEGER,
|
||||
strength INTEGER, willpower INTEGER, logic INTEGER,
|
||||
intuition INTEGER, charisma INTEGER,
|
||||
essence REAL DEFAULT 6.0,
|
||||
edge INTEGER, edge_current INTEGER,
|
||||
magic_resonance INTEGER,
|
||||
nuyen INTEGER DEFAULT 0,
|
||||
karma INTEGER DEFAULT 0, total_karma INTEGER DEFAULT 0,
|
||||
street_cred INTEGER DEFAULT 0,
|
||||
notoriety INTEGER DEFAULT 0, reputation INTEGER DEFAULT 0,
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS shadowrun_skills (
|
||||
id INTEGER PRIMARY KEY,
|
||||
character_id INTEGER NOT NULL REFERENCES characters_shadowrun(id),
|
||||
name TEXT NOT NULL,
|
||||
rating INTEGER DEFAULT 0,
|
||||
specialization TEXT,
|
||||
linked_attr TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS shadowrun_contacts (
|
||||
id INTEGER PRIMARY KEY,
|
||||
character_id INTEGER NOT NULL REFERENCES characters_shadowrun(id),
|
||||
name TEXT NOT NULL,
|
||||
loyalty INTEGER,
|
||||
connection INTEGER,
|
||||
notes TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS shadowrun_qualities (
|
||||
id INTEGER PRIMARY KEY,
|
||||
character_id INTEGER NOT NULL REFERENCES characters_shadowrun(id),
|
||||
name TEXT NOT NULL,
|
||||
quality_type TEXT NOT NULL,
|
||||
karma_cost INTEGER,
|
||||
description TEXT
|
||||
);
|
||||
`);
|
||||
|
||||
const systemCount = db.prepare('SELECT COUNT(*) AS count FROM systems').get().count;
|
||||
|
||||
if (systemCount === 0) {
|
||||
const insertSystem = db.prepare('INSERT INTO systems (name, slug) VALUES (?, ?)');
|
||||
const seedSystems = db.transaction((systems) => {
|
||||
for (const system of systems) {
|
||||
insertSystem.run(system.name, system.slug);
|
||||
}
|
||||
});
|
||||
|
||||
seedSystems([
|
||||
{ name: 'D&D 5e', slug: 'dnd5e' },
|
||||
{ name: 'Mork Borg', slug: 'morkborg' },
|
||||
{ name: 'Cairn', slug: 'cairn' },
|
||||
{ name: "Cha'alt", slug: 'chaalt' },
|
||||
{ name: 'Ironsworn', slug: 'ironsworn' },
|
||||
{ name: 'Shadowrun', slug: 'shadowrun' },
|
||||
]);
|
||||
}
|
||||
|
||||
module.exports = db;
|
||||
@@ -0,0 +1,31 @@
|
||||
const path = require('path');
|
||||
const express = require('express');
|
||||
require('./db');
|
||||
|
||||
const campaignsRouter = require('./routes/campaigns');
|
||||
const charactersRouter = require('./routes/characters');
|
||||
const threadsRouter = require('./routes/threads');
|
||||
const npcsRouter = require('./routes/npcs');
|
||||
const notesRouter = require('./routes/notes');
|
||||
const tablesRouter = require('./routes/tables');
|
||||
|
||||
const app = express();
|
||||
const PORT = 4000;
|
||||
|
||||
app.use(express.json());
|
||||
app.use(express.static(path.join(__dirname, '..', 'public')));
|
||||
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({ status: 'ok' });
|
||||
});
|
||||
|
||||
app.use('/api/campaigns', campaignsRouter);
|
||||
app.use('/api/characters', charactersRouter);
|
||||
app.use('/api/threads', threadsRouter);
|
||||
app.use('/api/npcs', npcsRouter);
|
||||
app.use('/api/notes', notesRouter);
|
||||
app.use('/api/tables', tablesRouter);
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Mythic Oracle running at http://localhost:${PORT}`);
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,4 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,4 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,4 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,4 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,4 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user