Phase 3: Express backend, SQLite persistence, auto-save

- server/db.ts: sql.js with migration system (hex_maps, hexes, hex_features)
- server/routes/maps.ts: CRUD for hex maps
- server/routes/hexes.ts: Bulk hex upsert, region load, sparse storage
- server/index.ts: Express 5, CORS, tile serving, SPA fallback
- src/data/api-client.ts: Frontend HTTP client for all API endpoints
- src/main.ts: Auto-save with 1s debounce, load map state on startup
- Port 3002 (Kiepenkerl uses 3001)
- Graceful fallback when API unavailable (works without server too)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Axel Meyer
2026-04-07 10:45:37 +00:00
parent 0e2903b789
commit 367ba8af07
8 changed files with 523 additions and 6 deletions

52
server/index.ts Normal file
View File

@@ -0,0 +1,52 @@
import express from 'express';
import cors from 'cors';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
import { existsSync } from 'fs';
import { initDb } from './db.js';
import mapsRouter from './routes/maps.js';
import hexesRouter from './routes/hexes.js';
const __dirname = dirname(fileURLToPath(import.meta.url));
const PORT = Number(process.env.PORT) || 3002;
const DIST_DIR = resolve(__dirname, '..', 'dist');
const TILES_DIR = resolve(__dirname, '..', 'tiles');
const PUBLIC_TILES_DIR = resolve(__dirname, '..', 'public', 'tiles');
async function main() {
await initDb();
const app = express();
app.use(cors());
app.use(express.json({ limit: '10mb' }));
// API routes
app.use('/api/v1/maps', mapsRouter);
app.use('/api/v1/maps', hexesRouter);
// Serve tiles (check multiple locations)
const tilesPath = existsSync(TILES_DIR) ? TILES_DIR : PUBLIC_TILES_DIR;
if (existsSync(tilesPath)) {
app.use('/tiles', express.static(tilesPath, { maxAge: '30d', immutable: true }));
console.log(`[server] Serving tiles from ${tilesPath}`);
}
// Serve static frontend
if (existsSync(DIST_DIR)) {
app.use(express.static(DIST_DIR));
// SPA fallback (Express 5 syntax)
app.get('/{*splat}', (_req, res) => {
res.sendFile(resolve(DIST_DIR, 'index.html'));
});
console.log(`[server] Serving frontend from ${DIST_DIR}`);
}
app.listen(PORT, () => {
console.log(`[server] Hexifyer running on http://localhost:${PORT}`);
});
}
main().catch(err => {
console.error('Failed to start:', err);
process.exit(1);
});