Files
klarbild-oss/src/pages/api/recipes.ts
T
tillandClaude Opus 5 e2481139a1
CI / check (push) Has been cancelled
Klarbild 1.0.0 — first public release
A self-hosted print studio: images at exact physical sizes, sheet layout with
crop marks, tiling, 1:1 PDF. Everything else — image generation, delivery,
share links, mail, Telegram — is an optional module that is off by default.

This is the neutral public twin of a private tool. Compared to that original:

- optional modules behind KLARBILD_MODULES; the default install is print-only
- English interface with German as a full second language (1,038 strings);
  the English sentence is the translation key
- no built-in accounts — first boot creates one admin from ADMIN_USERNAME /
  ADMIN_PASSWORD, or generates a password and logs it once
- delivery and backup targets are generic SFTP/FTPS endpoints, off by default
- English routes, English identifiers, English comments throughout
- two compose files: print-only by default, everything in the full one
- AGPL-3.0, CI (build, tests, type check, secret scan), CONTRIBUTING, SECURITY

124 tests green, type check clean, verified end to end against a live instance.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nh4A79bbqT9dcd2inzYUdg
2026-09-15 14:52:41 +00:00

31 lines
1.6 KiB
TypeScript

import type { APIRoute } from 'astro';
import { query, one } from '../../lib/db';
import { has, moduleOff } from '../../lib/modules';
export const prerender = false;
const json = (b: unknown, s = 200) =>
new Response(JSON.stringify(b), { status: s, headers: { 'Content-Type': 'application/json' } });
export const GET: APIRoute = async ({ locals }) => {
if (!has('ai')) return moduleOff('ai');
if (!locals.user) return new Response('Unauthorized', { status: 401 });
const rows = await query('SELECT * FROM recipes ORDER BY is_default DESC, name');
return json({ recipes: rows });
};
export const POST: APIRoute = async ({ request, locals }) => {
if (!has('ai')) return moduleOff('ai');
if (!locals.user) return new Response('Unauthorized', { status: 401 });
const b = await request.json();
const row = await one(
`INSERT INTO recipes (name, tasks, output_format, orientation, crop_mode, dpi, contour_mm,
model_key, delivery, delivery_folder, delivery_target_id, custom_instruction, mode, output_ext, is_default, created_by)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,false,$15) RETURNING *`,
[b.name, JSON.stringify(b.tasks || []), b.output_format, b.orientation, b.crop_mode || 'crop',
b.dpi || 300, b.contour_mm ?? null, b.model_key ?? null, b.delivery || 'library',
b.delivery_folder ?? null, b.delivery_target_id ?? null, b.custom_instruction ?? null,
['each', 'compose', 'generate'].includes(b.mode) ? b.mode : 'each',
(b.output_ext === 'jpg' || b.output_ext === 'png') ? b.output_ext : null, locals.user.uid]);
return json({ recipe: row });
};