- execute: actually call isInScheduleWindow/waitForWindow/sleepBetweenJobs in runSequential (they were dead code); emit queue_status SSE events (running/paused/sleeping/idle) so the pipeline's existing QueueStatus listener lights up - review: POST /:id/retry resets an errored plan to approved, wipes old done/error jobs, rebuilds command from current decisions, queues fresh job - scan: dev-mode DELETE now also wipes jobs + subtitle_files (previously orphaned after every dev reset) - biome: migrate config to 2.4 schema, autoformat 68 files (strings + indentation), relax opinionated a11y/hooks-deps/index-key rules that don't fit this codebase - routeTree.gen.ts regenerated after /nodes removal
110 lines
3.3 KiB
TypeScript
110 lines
3.3 KiB
TypeScript
import { Hono } from "hono";
|
|
import { getAllConfig, getDb, getEnvLockedKeys, setConfig } from "../db/index";
|
|
import { getUsers, testConnection as testJellyfin } from "../services/jellyfin";
|
|
import { testConnection as testRadarr } from "../services/radarr";
|
|
import { testConnection as testSonarr } from "../services/sonarr";
|
|
|
|
const app = new Hono();
|
|
|
|
app.get("/", (c) => {
|
|
const config = getAllConfig();
|
|
const envLocked = Array.from(getEnvLockedKeys());
|
|
return c.json({ config, envLocked });
|
|
});
|
|
|
|
app.post("/jellyfin", async (c) => {
|
|
const body = await c.req.json<{ url: string; api_key: string }>();
|
|
const url = body.url?.replace(/\/$/, "");
|
|
const apiKey = body.api_key;
|
|
|
|
if (!url || !apiKey) return c.json({ ok: false, error: "URL and API key are required" }, 400);
|
|
|
|
const result = await testJellyfin({ url, apiKey });
|
|
if (!result.ok) return c.json({ ok: false, error: result.error });
|
|
|
|
setConfig("jellyfin_url", url);
|
|
setConfig("jellyfin_api_key", apiKey);
|
|
setConfig("setup_complete", "1");
|
|
|
|
try {
|
|
const users = await getUsers({ url, apiKey });
|
|
const admin = users.find((u) => u.Name === "admin") ?? users[0];
|
|
if (admin?.Id) setConfig("jellyfin_user_id", admin.Id);
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
|
|
return c.json({ ok: true });
|
|
});
|
|
|
|
app.post("/radarr", async (c) => {
|
|
const body = await c.req.json<{ url?: string; api_key?: string }>();
|
|
const url = body.url?.replace(/\/$/, "");
|
|
const apiKey = body.api_key;
|
|
|
|
if (!url || !apiKey) {
|
|
setConfig("radarr_enabled", "0");
|
|
return c.json({ ok: false, error: "URL and API key are required" }, 400);
|
|
}
|
|
|
|
const result = await testRadarr({ url, apiKey });
|
|
if (!result.ok) return c.json({ ok: false, error: result.error });
|
|
|
|
setConfig("radarr_url", url);
|
|
setConfig("radarr_api_key", apiKey);
|
|
setConfig("radarr_enabled", "1");
|
|
|
|
return c.json({ ok: true });
|
|
});
|
|
|
|
app.post("/sonarr", async (c) => {
|
|
const body = await c.req.json<{ url?: string; api_key?: string }>();
|
|
const url = body.url?.replace(/\/$/, "");
|
|
const apiKey = body.api_key;
|
|
|
|
if (!url || !apiKey) {
|
|
setConfig("sonarr_enabled", "0");
|
|
return c.json({ ok: false, error: "URL and API key are required" }, 400);
|
|
}
|
|
|
|
const result = await testSonarr({ url, apiKey });
|
|
if (!result.ok) return c.json({ ok: false, error: result.error });
|
|
|
|
setConfig("sonarr_url", url);
|
|
setConfig("sonarr_api_key", apiKey);
|
|
setConfig("sonarr_enabled", "1");
|
|
|
|
return c.json({ ok: true });
|
|
});
|
|
|
|
app.post("/subtitle-languages", async (c) => {
|
|
const body = await c.req.json<{ langs: string[] }>();
|
|
if (body.langs?.length > 0) {
|
|
setConfig("subtitle_languages", JSON.stringify(body.langs));
|
|
}
|
|
return c.json({ ok: true });
|
|
});
|
|
|
|
app.post("/audio-languages", async (c) => {
|
|
const body = await c.req.json<{ langs: string[] }>();
|
|
setConfig("audio_languages", JSON.stringify(body.langs ?? []));
|
|
return c.json({ ok: true });
|
|
});
|
|
|
|
app.post("/clear-scan", (c) => {
|
|
const db = getDb();
|
|
// Delete children first to avoid slow cascade deletes
|
|
db.transaction(() => {
|
|
db.prepare("DELETE FROM stream_decisions").run();
|
|
db.prepare("DELETE FROM jobs").run();
|
|
db.prepare("DELETE FROM subtitle_files").run();
|
|
db.prepare("DELETE FROM review_plans").run();
|
|
db.prepare("DELETE FROM media_streams").run();
|
|
db.prepare("DELETE FROM media_items").run();
|
|
db.prepare("UPDATE config SET value = '0' WHERE key = 'scan_running'").run();
|
|
})();
|
|
return c.json({ ok: true });
|
|
});
|
|
|
|
export default app;
|