restructure to react spa + hono api, fix missing server/ and lib/
rewrite from monolithic hono jsx to react 19 spa with tanstack router + hono json api backend. add scan, review, execute, nodes, and setup pages. multi-stage dockerfile (node for vite build, bun for runtime). previously, server/ and src/shared/lib/ were silently excluded by global gitignore patterns (/server/ from emacs, lib/ from python). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
102
server/api/setup.ts
Normal file
102
server/api/setup.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { Hono } from 'hono';
|
||||
import { setConfig, getAllConfig, getDb, getEnvLockedKeys } from '../db/index';
|
||||
import { testConnection as testJellyfin, getUsers } 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('/paths', async (c) => {
|
||||
const body = await c.req.json<{ movies_path?: string; series_path?: string }>();
|
||||
const moviesPath = (body.movies_path ?? '').trim().replace(/\/$/, '');
|
||||
const seriesPath = (body.series_path ?? '').trim().replace(/\/$/, '');
|
||||
setConfig('movies_path', moviesPath);
|
||||
setConfig('series_path', seriesPath);
|
||||
return c.json({ ok: true });
|
||||
});
|
||||
|
||||
app.post('/clear-scan', (c) => {
|
||||
const db = getDb();
|
||||
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;
|
||||
Reference in New Issue
Block a user