import { Hono } from 'hono'; import { getDb } from '../db/index'; import { testConnection } from '../services/ssh'; import type { Node } from '../types'; const app = new Hono(); app.get('/', (c) => { const db = getDb(); const nodes = db.prepare('SELECT * FROM nodes ORDER BY name').all() as Node[]; return c.json({ nodes }); }); app.post('/', async (c) => { const db = getDb(); const contentType = c.req.header('Content-Type') ?? ''; let name: string, host: string, port: number, username: string, ffmpegPath: string, workDir: string, privateKey: string; // Support both multipart (file upload) and JSON if (contentType.includes('multipart/form-data')) { const body = await c.req.formData(); name = body.get('name') as string; host = body.get('host') as string; port = Number(body.get('port') ?? '22'); username = body.get('username') as string; ffmpegPath = (body.get('ffmpeg_path') as string) || 'ffmpeg'; workDir = (body.get('work_dir') as string) || '/tmp'; const keyFile = body.get('private_key') as File | null; if (!name || !host || !username || !keyFile) return c.json({ ok: false, error: 'All fields are required' }, 400); privateKey = await keyFile.text(); } else { const body = await c.req.json<{ name: string; host: string; port?: number; username: string; ffmpeg_path?: string; work_dir?: string; private_key: string }>(); name = body.name; host = body.host; port = body.port ?? 22; username = body.username; ffmpegPath = body.ffmpeg_path || 'ffmpeg'; workDir = body.work_dir || '/tmp'; privateKey = body.private_key; if (!name || !host || !username || !privateKey) return c.json({ ok: false, error: 'All fields are required' }, 400); } try { db.prepare('INSERT INTO nodes (name, host, port, username, private_key, ffmpeg_path, work_dir) VALUES (?, ?, ?, ?, ?, ?, ?)') .run(name, host, port, username, privateKey, ffmpegPath, workDir); } catch (e) { if (String(e).includes('UNIQUE')) return c.json({ ok: false, error: `A node named "${name}" already exists` }, 409); throw e; } const nodes = db.prepare('SELECT * FROM nodes ORDER BY name').all() as Node[]; return c.json({ ok: true, nodes }); }); app.delete('/:id', (c) => { const db = getDb(); db.prepare('DELETE FROM nodes WHERE id = ?').run(Number(c.req.param('id'))); return c.json({ ok: true }); }); // Legacy POST delete for HTML-form compat (may be removed later) app.post('/:id/delete', (c) => { const db = getDb(); db.prepare('DELETE FROM nodes WHERE id = ?').run(Number(c.req.param('id'))); return c.json({ ok: true }); }); app.post('/:id/test', async (c) => { const db = getDb(); const id = Number(c.req.param('id')); const node = db.prepare('SELECT * FROM nodes WHERE id = ?').get(id) as Node | undefined; if (!node) return c.notFound(); const result = await testConnection(node); const status = result.ok ? 'ok' : `error: ${result.error}`; db.prepare("UPDATE nodes SET status = ?, last_checked_at = datetime('now') WHERE id = ?").run(status, id); return c.json({ ok: result.ok, status, error: result.error }); }); export default app;