All checks were successful
Build and Push Docker Image / build (push) Successful in 1m54s
- 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>
79 lines
3.3 KiB
TypeScript
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;
|