Files
netfelix-audio-fix/server/api/nodes.ts
Felix Förtsch d5f4afd26b
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m54s
split audio/subtitle concerns, remove docker-in-docker, add per-node path mapping
- install ffmpeg in dockerfile (fixes exit code 127)
- buildCommand() now audio-only remux, no subtitle extraction
- add unapprove endpoint + ui button for approved items
- add batch extract-all subtitles endpoint + ui button
- audio detail page shows only video+audio streams
- remove global movies_path/series_path config, add per-node path mapping
- remove docker-in-docker command building (buildDockerCommand, buildDockerExtractOnlyCommand)
- ssh execution translates /movies/ and /series/ to node-specific paths
- remove media paths section from setup page
- add unraid-template.xml

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 16:48:00 +01:00

79 lines
3.3 KiB
TypeScript

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, moviesPath: string, seriesPath: 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';
moviesPath = (body.get('movies_path') as string) || '';
seriesPath = (body.get('series_path') as string) || '';
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; movies_path?: string; series_path?: 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';
moviesPath = body.movies_path || ''; seriesPath = body.series_path || '';
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, movies_path, series_path) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)')
.run(name, host, port, username, privateKey, ffmpegPath, workDir, moviesPath, seriesPath);
} 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;