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
This commit is contained in:
164 files changed
+27190
No files matched your search
@@ -0,0 +1,53 @@
|
||||
import type { APIRoute } from 'astro';
|
||||
import { query } from '../../../lib/db';
|
||||
import { updateShare, revokeShare, unrevokeShare, deleteShare } from '../../../lib/shares';
|
||||
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' } });
|
||||
|
||||
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
|
||||
/** The most recent views of a link — for the report in the frontend. */
|
||||
export const GET: APIRoute = async ({ params, locals }) => {
|
||||
if (!has('share')) return moduleOff('share');
|
||||
if (!locals.user) return new Response('Unauthorized', { status: 401 });
|
||||
if (!UUID.test(params.id || '')) return json({ error: 'Unknown link.' }, 404);
|
||||
const isAdmin = locals.user.role === 'admin';
|
||||
const rows = await query<any>(
|
||||
`SELECT v.kind, v.at, v.user_agent
|
||||
FROM share_views v JOIN shares s ON s.id = v.share_id
|
||||
WHERE v.share_id = $1 AND ($3::boolean OR s.created_by = $2)
|
||||
ORDER BY v.at DESC LIMIT 100`, [params.id, locals.user.uid, isAdmin]);
|
||||
return json({ visits: rows });
|
||||
};
|
||||
|
||||
/** Change the title, expiry, passphrase or download permission; revoke or release. */
|
||||
export const PATCH: APIRoute = async ({ params, request, locals }) => {
|
||||
if (!has('share')) return moduleOff('share');
|
||||
if (!locals.user) return new Response('Unauthorized', { status: 401 });
|
||||
if (!UUID.test(params.id || '')) return json({ error: 'Unknown link.' }, 404);
|
||||
const b = await request.json().catch(() => ({}));
|
||||
const isAdmin = locals.user.role === 'admin';
|
||||
|
||||
if (b.action === 'revoke') {
|
||||
return json({ ok: await revokeShare(params.id!, locals.user.uid, isAdmin) });
|
||||
}
|
||||
if (b.action === 'unrevoke') {
|
||||
return json({ ok: await unrevokeShare(params.id!, locals.user.uid, isAdmin) });
|
||||
}
|
||||
const ok = await updateShare(params.id!, locals.user.uid, isAdmin, {
|
||||
title: b.title, note: b.note, days: b.days,
|
||||
allowDownload: b.allowDownload, password: b.password,
|
||||
});
|
||||
return ok ? json({ ok: true }) : json({ error: 'Unknown link.' }, 404);
|
||||
};
|
||||
|
||||
export const DELETE: APIRoute = async ({ params, locals }) => {
|
||||
if (!has('share')) return moduleOff('share');
|
||||
if (!locals.user) return new Response('Unauthorized', { status: 401 });
|
||||
if (!UUID.test(params.id || '')) return json({ error: 'Unknown link.' }, 404);
|
||||
const ok = await deleteShare(params.id!, locals.user.uid, locals.user.role === 'admin');
|
||||
return ok ? json({ ok: true }) : json({ error: 'Unknown link.' }, 404);
|
||||
};
|
||||
Reference in new issue
Block a user