feat: per-user visibility (own/shared), anonymous generations, private sessions (generate+forget), NSFW model gating

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNQ8ghPfzAfsyVYd6HgFb6
This commit is contained in:
tillandClaude Opus 4.8 committed 2026-07-23 21:46:47 +00:00
1 parent 2588a54eb7
commit e6b325eb53
14 files changed
+186 -35

No files matched your search

+10 -1
View File
@@ -11,7 +11,16 @@ export const GET: APIRoute = async ({ params, url, locals }) => {
const wantThumb = q.get('thumb') === '1';
const wantSrc = q.get('src') === '1';
const item = await one<any>(
`SELECT source_path, source_paths, result_path, thumb_path, filename, has_alpha FROM items WHERE id=$1`, [params.id]);
`SELECT i.source_path, i.source_paths, i.result_path, i.thumb_path, i.filename, i.has_alpha,
j.created_by, j.private FROM items i JOIN jobs j ON j.id=i.job_id WHERE i.id=$1`, [params.id]);
if (item) {
const isAdmin = locals.user.role === 'admin';
const own = item.created_by === locals.user.uid;
if (!isAdmin && !own) {
const s = await one<{ library_visibility: string }>('SELECT library_visibility FROM settings WHERE id=1');
if (item.private || s?.library_visibility !== 'shared') return new Response('Forbidden', { status: 403 });
}
}
// Quelle: Einzelquelle, sonst erste Kombinier-Quelle (für Vorher/Nachher).
const firstSource = item?.source_path || (Array.isArray(item?.source_paths) ? item.source_paths[0] : null);
const path = wantSrc ? firstSource
+9 -3
View File
@@ -1,5 +1,5 @@
import type { APIRoute } from 'astro';
import { query } from '../../../lib/db';
import { one, query } from '../../../lib/db';
export const prerender = false;
const json = (b: unknown, s = 200) =>
@@ -9,9 +9,14 @@ const json = (b: unknown, s = 200) =>
export const GET: APIRoute = async ({ url, locals }) => {
if (!locals.user) return new Response('Unauthorized', { status: 401 });
const q = url.searchParams;
const where: string[] = [`i.status='done'`];
const s = await one<any>('SELECT library_visibility, anonymous_generations FROM settings WHERE id=1');
const isAdmin = locals.user.role === 'admin';
const shared = s?.library_visibility === 'shared';
// Private Aufträge nie in der Bibliothek; Sichtbarkeit nach Einstellung.
const where: string[] = [`i.status='done'`, `j.private=false`];
const args: any[] = [];
const add = (cond: string, val: any) => { args.push(val); where.push(cond.replace('?', `$${args.length}`)); };
if (!isAdmin && !shared) add('j.created_by = ?', locals.user.uid);
if (q.get('folder')) add('i.folder_id = ?', q.get('folder'));
if (q.get('delivery')) add('i.delivery_status = ?', q.get('delivery'));
@@ -24,7 +29,8 @@ export const GET: APIRoute = async ({ url, locals }) => {
const rows = await query(
`SELECT i.id, i.filename, i.output_px, i.has_alpha, i.result_path, i.thumb_path, i.folder_id,
i.delivery_status, i.nas_status, i.prompt_used, i.created_at, i.model_used, i.variant_of, j.mode, u.display_name AS by_name,
i.delivery_status, i.nas_status, i.prompt_used, i.created_at, i.model_used, i.variant_of, j.mode,
${(s?.anonymous_generations && !isAdmin) ? 'NULL' : 'u.display_name'} AS by_name,
j.recipe_snapshot->'tasks' AS tasks
FROM items i
JOIN jobs j ON j.id = i.job_id