import Fastify from 'fastify'; import sensible from '@fastify/sensible'; import fastifyStatic from '@fastify/static'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { dirname, join } from 'node:path'; import { openDb, seedIfEmpty } from './db.js'; import hostsRoutes from './routes/hosts.js'; import sitesRoutes from './routes/sites.js'; import roomsRoutes from './routes/rooms.js'; import serverTypesRoutes from './routes/server-types.js'; import interfacesRoutes from './routes/interfaces.js'; const __dirname = dirname(fileURLToPath(import.meta.url)); const PUBLIC_DIR = join(__dirname, '../public'); const DEFAULT_DB = join(__dirname, '../data/infrastructure.db'); export async function buildApp(opts = {}) { const dbPath = opts.dbPath ?? process.env.DB_PATH ?? DEFAULT_DB; const db = openDb(dbPath); if (opts.seed !== false) seedIfEmpty(db); const app = Fastify({ logger: opts.logger ?? false, }); app.decorate('db', db); app.addHook('onClose', (instance, done) => { instance.db.close(); done(); }); await app.register(sensible); app.setErrorHandler((err, req, reply) => { if (err.validation) { const details = err.validation.map((v) => `${v.instancePath || '/'} ${v.message}`); return reply.code(400).send({ error: 'validation failed', details }); } if (err.statusCode && err.statusCode < 500) { return reply.code(err.statusCode).send({ error: err.message }); } req.log?.error(err); return reply.code(500).send({ error: 'internal server error' }); }); app.setNotFoundHandler(async (req, reply) => { if (req.url.startsWith('/api/')) { return reply.code(404).send({ error: 'not found' }); } return reply.sendFile('index.html'); }); await app.register(async (api) => { await api.register(hostsRoutes, { prefix: '/hosts' }); await api.register(sitesRoutes, { prefix: '/sites' }); await api.register(roomsRoutes, { prefix: '/rooms' }); await api.register(serverTypesRoutes, { prefix: '/server-types' }); await api.register(interfacesRoutes, { prefix: '/interfaces' }); }, { prefix: '/api' }); await app.register(fastifyStatic, { root: PUBLIC_DIR, prefix: '/', }); return app; } const isMain = import.meta.url === pathToFileURL(process.argv[1] ?? '').href; if (isMain) { const port = Number(process.env.PORT ?? 3000); const host = process.env.HOST ?? '0.0.0.0'; const app = await buildApp({ logger: true }); try { await app.listen({ port, host }); } catch (err) { app.log.error(err); process.exit(1); } }