import Fastify from 'fastify' import fastifyStatic from '@fastify/static' import { readFile, writeFile, mkdir } from 'fs/promises' import { join, dirname } from 'path' import { fileURLToPath } from 'url' import { existsSync } from 'fs' const __dirname = dirname(fileURLToPath(import.meta.url)) const ROOT = join(__dirname, '..') const DIST = join(ROOT, 'dist') const SAVES_DIR = join(ROOT, 'data', 'saves') // Ensure saves directory exists await mkdir(SAVES_DIR, { recursive: true }) const fastify = Fastify({ logger: false }) // Serve built frontend await fastify.register(fastifyStatic, { root: DIST, prefix: '/', }) // Load save state fastify.get('/api/save/:token', async (req, reply) => { const { token } = req.params if (!/^[0-9a-f]{8}$/.test(token)) { return reply.code(400).send({ error: 'Invalid token' }) } const path = join(SAVES_DIR, `${token}.json`) if (!existsSync(path)) { return reply.code(404).send({ error: 'Save not found' }) } try { const raw = await readFile(path, 'utf8') return reply.send(JSON.parse(raw)) } catch { return reply.code(500).send({ error: 'Failed to read save' }) } }) // Write save state fastify.post('/api/save/:token', async (req, reply) => { const { token } = req.params if (!/^[0-9a-f]{8}$/.test(token)) { return reply.code(400).send({ error: 'Invalid token' }) } if (!req.body || typeof req.body !== 'object') { return reply.code(400).send({ error: 'Invalid body' }) } const path = join(SAVES_DIR, `${token}.json`) try { await writeFile(path, JSON.stringify(req.body, null, 2), 'utf8') return reply.send({ ok: true }) } catch { return reply.code(500).send({ error: 'Failed to write save' }) } }) // Fallback — serve index.html for client-side routing fastify.setNotFoundHandler((req, reply) => { reply.sendFile('index.html') }) try { await fastify.listen({ port: 3000, host: '0.0.0.0' }) console.log('OTIVM server running on port 3000') } catch (err) { console.error(err) process.exit(1) }