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:
2026-07-23 21:46:47 +00:00
parent 2588a54eb7
commit e6b325eb53
14 changed files with 186 additions and 35 deletions
+18 -13
View File
@@ -61,10 +61,11 @@ export async function processItem(itemId: string): Promise<ProcessResult> {
'SELECT id, job_id, source_path, source_paths, filename FROM items WHERE id=$1', [itemId]);
if (!item) return { ok: false, cost: 0, error: 'Position nicht gefunden' };
const job = await one<{ recipe_snapshot: RecipeSnapshot; mode: Mode }>(
'SELECT recipe_snapshot, mode FROM jobs WHERE id=$1', [item.job_id]);
const job = await one<{ recipe_snapshot: RecipeSnapshot; mode: Mode; private: boolean }>(
'SELECT recipe_snapshot, mode, private FROM jobs WHERE id=$1', [item.job_id]);
const r = (job?.recipe_snapshot || {}) as RecipeSnapshot;
const mode: Mode = job?.mode || 'each';
const isPrivate = !!job?.private;
const tasks = r.tasks || [];
const dpi = r.dpi ?? 300;
const cropMode: CropMode = r.crop_mode === 'extend' ? 'extend' : 'crop';
@@ -146,26 +147,30 @@ export async function processItem(itemId: string): Promise<ProcessResult> {
const filename = buildResultFilename(item.filename, formatToken(r.output_format), 'png');
const outputPx = `${fin.width}x${fin.height}`;
const deliveryStatus = (r.delivery === 'picdrop' || r.delivery === 'both') ? 'pending' : 'none';
const nasStatus = cfg?.nas_enabled ? 'pending' : 'none';
// Private Aufträge: kein Delivery, kein Backup, kein gespeicherter Prompt.
const deliveryStatus = (!isPrivate && (r.delivery === 'picdrop' || r.delivery === 'both')) ? 'pending' : 'none';
const nasStatus = (!isPrivate && cfg?.nas_enabled) ? 'pending' : 'none';
await query(
`UPDATE items SET status='done', result_path=$2, thumb_path=$3, filename=$4, output_px=$5, dpi=$6,
has_alpha=$7, model_used=$8, prompt_used=$9, cost=$10, error_message=NULL,
delivery_status=$11, nas_status=$12 WHERE id=$1`,
[itemId, key, thumbPath, filename, outputPx, dpi, hasAlpha, modelUsed, prompt.slice(0, 1000),
cost, deliveryStatus, nasStatus]);
[itemId, key, thumbPath, filename, outputPx, dpi, hasAlpha, modelUsed,
isPrivate ? null : prompt.slice(0, 1000), cost, deliveryStatus, nasStatus]);
// Quellbilder löschen, wenn nicht behalten (Speicher sparen).
if (cfg?.keep_sources === false) {
// Quellbilder löschen, wenn nicht behalten — bei privaten Aufträgen immer.
if (isPrivate || cfg?.keep_sources === false) {
for (const k of sourceKeys) await deleteObject(k).catch(() => {});
if (isPrivate) await query(`UPDATE items SET source_path=NULL, source_paths=NULL WHERE id=$1`, [itemId]);
}
// Zweite Sicherung auf alle Backup-Ziele (NAS + is_backup-Ziele), best effort.
try {
const { backupItem } = await import('./backup');
await backupItem(itemId);
} catch (e) { console.error('[process] Backup fehlgeschlagen', e); }
// Zweite Sicherung auf alle Backup-Ziele — nicht bei privaten Aufträgen.
if (!isPrivate) {
try {
const { backupItem } = await import('./backup');
await backupItem(itemId);
} catch (e) { console.error('[process] Backup fehlgeschlagen', e); }
}
return { ok: true, cost };
} catch (e: any) {