import express, { type Request, type Response } from 'express'; import fs from 'node:fs/promises'; import path from 'node:path'; import type { ControlPlaneState, CreateUserInput } from '../src/shared/contracts'; import { buildRuntimePaths, createUserRecord, deriveDashboardSnapshot, render3proxyConfig, validateCreateUserInput, type RuntimePaths, } from './lib/config'; import type { RuntimeController } from './lib/runtime'; import { StateStore } from './lib/store'; export interface AppServices { store: StateStore; runtime: RuntimeController; runtimeRootDir: string; } export function createApp({ store, runtime, runtimeRootDir }: AppServices) { const app = express(); const runtimePaths = buildRuntimePaths(runtimeRootDir); const distDir = path.resolve('dist'); app.use(express.json()); app.use(express.static(distDir)); app.get('/api/health', async (_request, response) => { const state = await store.read(); const previewConfig = render3proxyConfig(state, runtimePaths); response.json({ ok: true, runtime: runtime.getSnapshot(), users: state.userRecords.length, configBytes: Buffer.byteLength(previewConfig), }); }); app.get('/api/state', async (_request, response, next) => { try { const payload = await getSnapshot(store, runtime, runtimePaths); response.json(payload); } catch (error) { next(error); } }); app.post('/api/runtime/:action', async (request, response, next) => { try { const state = await store.read(); const action = request.params.action; const controller = action === 'restart' ? runtime.restart.bind(runtime) : runtime.start.bind(runtime); if (!['start', 'restart'].includes(action)) { response.status(404).json({ error: 'Unknown runtime action.' }); return; } const runtimeSnapshot = await controller(); state.service.lastEvent = action === 'restart' ? 'Runtime restarted from panel' : 'Runtime start requested from panel'; if (runtimeSnapshot.startedAt) { state.service.startedAt = runtimeSnapshot.startedAt; } await writeConfigAndState(store, state, runtimePaths); response.json(await getSnapshot(store, runtime, runtimePaths)); } catch (error) { next(error); } }); app.post('/api/users', async (request, response, next) => { try { const state = await store.read(); const input = validateCreateUserInput(request.body as Partial, state.system.services); const record = createUserRecord(state, input); state.userRecords.push(record); state.service.lastEvent = `User ${record.username} created from panel`; await persistRuntimeMutation(store, runtime, state, runtimePaths); response.status(201).json(await getSnapshot(store, runtime, runtimePaths)); } catch (error) { next(error); } }); app.post('/api/users/:id/pause', async (request, response, next) => { try { const state = await store.read(); const user = state.userRecords.find((entry) => entry.id === request.params.id); if (!user) { response.status(404).json({ error: 'User not found.' }); return; } user.paused = !user.paused; state.service.lastEvent = user.paused ? `User ${user.username} paused from panel` : `User ${user.username} resumed from panel`; await persistRuntimeMutation(store, runtime, state, runtimePaths); response.json(await getSnapshot(store, runtime, runtimePaths)); } catch (error) { next(error); } }); app.delete('/api/users/:id', async (request, response, next) => { try { const state = await store.read(); const index = state.userRecords.findIndex((entry) => entry.id === request.params.id); if (index === -1) { response.status(404).json({ error: 'User not found.' }); return; } const [removed] = state.userRecords.splice(index, 1); state.service.lastEvent = `User ${removed.username} deleted from panel`; await persistRuntimeMutation(store, runtime, state, runtimePaths); response.json(await getSnapshot(store, runtime, runtimePaths)); } catch (error) { next(error); } }); app.use(async (_request, response) => { const distPath = path.join(distDir, 'index.html'); try { const html = await fs.readFile(distPath, 'utf8'); response.type('html').send(html); } catch { response.status(404).send('Frontend build not found.'); } }); app.use((error: unknown, _request: Request, response: Response, _next: express.NextFunction) => { response.status(400).json({ error: error instanceof Error ? error.message : 'Unknown server error.', }); }); return app; } async function getSnapshot( store: StateStore, runtime: RuntimeController, runtimePaths: RuntimePaths, ) { const state = await store.read(); const previewConfig = render3proxyConfig(state, runtimePaths); return deriveDashboardSnapshot(state, runtime.getSnapshot(), previewConfig); } async function persistRuntimeMutation( store: StateStore, runtime: RuntimeController, state: ControlPlaneState, runtimePaths: RuntimePaths, ) { await writeConfigAndState(store, state, runtimePaths); await runtime.reload(); } async function writeConfigAndState( store: StateStore, state: ControlPlaneState, runtimePaths: RuntimePaths, ) { const config = render3proxyConfig(state, runtimePaths); await fs.mkdir(path.dirname(runtimePaths.configPath), { recursive: true }); await fs.mkdir(path.dirname(runtimePaths.logPath), { recursive: true }); await fs.mkdir(runtimePaths.reportDir, { recursive: true }); await fs.writeFile(runtimePaths.configPath, config, 'utf8'); await store.write(state); }