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,168 @@
|
||||
-- Klarbild — Initial schema (users, recipes, jobs, items, delivery, sharing, Telegram).
|
||||
-- Applied automatically at startup (numbered SQL files, no ORM magic).
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
||||
|
||||
-- Users ---------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
username text UNIQUE NOT NULL,
|
||||
password_hash text NOT NULL,
|
||||
role text NOT NULL DEFAULT 'user' CHECK (role IN ('user','admin')),
|
||||
display_name text,
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Preset recipes (saved presets) -------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS recipes (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name text NOT NULL,
|
||||
tasks jsonb NOT NULL DEFAULT '[]'::jsonb, -- e.g. ["clean","format","deliver"]
|
||||
output_format text,
|
||||
orientation text,
|
||||
crop_mode text,
|
||||
dpi int NOT NULL DEFAULT 300,
|
||||
contour_mm numeric,
|
||||
model_key text,
|
||||
delivery text NOT NULL DEFAULT 'library' CHECK (delivery IN ('library','remote','both')),
|
||||
delivery_folder text,
|
||||
custom_instruction text,
|
||||
is_default bool NOT NULL DEFAULT false,
|
||||
created_by uuid REFERENCES users(id) ON DELETE SET NULL,
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Jobs (a batch) --------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS jobs (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
created_by uuid REFERENCES users(id) ON DELETE SET NULL,
|
||||
origin text NOT NULL DEFAULT 'web' CHECK (origin IN ('web','telegram')),
|
||||
recipe_snapshot jsonb NOT NULL,
|
||||
status text NOT NULL DEFAULT 'queued'
|
||||
CHECK (status IN ('queued','running','paused','done','cancelled')),
|
||||
total int NOT NULL DEFAULT 0,
|
||||
done_count int NOT NULL DEFAULT 0,
|
||||
failed_count int NOT NULL DEFAULT 0,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
finished_at timestamptz
|
||||
);
|
||||
|
||||
-- Folders ---------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS folders (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name text NOT NULL,
|
||||
delivery_folder text,
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Items (one image in a job) -----------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS items (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
job_id uuid REFERENCES jobs(id) ON DELETE CASCADE,
|
||||
position int NOT NULL DEFAULT 0,
|
||||
status text NOT NULL DEFAULT 'queued'
|
||||
CHECK (status IN ('queued','running','done','failed','skipped')),
|
||||
attempts int NOT NULL DEFAULT 0,
|
||||
error_message text,
|
||||
source_path text,
|
||||
result_path text,
|
||||
filename text,
|
||||
output_px text,
|
||||
source_quality text CHECK (source_quality IN ('original','compressed')),
|
||||
dpi int,
|
||||
has_alpha bool NOT NULL DEFAULT false,
|
||||
model_used text,
|
||||
prompt_used text,
|
||||
cost numeric, -- actual cost, from usage.cost
|
||||
folder_id uuid REFERENCES folders(id) ON DELETE SET NULL,
|
||||
delivery_status text NOT NULL DEFAULT 'none'
|
||||
CHECK (delivery_status IN ('none','pending','delivered','failed')),
|
||||
delivered_at timestamptz,
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS items_job_idx ON items(job_id);
|
||||
CREATE INDEX IF NOT EXISTS items_created_idx ON items(created_at DESC);
|
||||
|
||||
-- Print jobs ------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS print_jobs (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
created_by uuid REFERENCES users(id) ON DELETE SET NULL,
|
||||
note text,
|
||||
status text NOT NULL DEFAULT 'open' CHECK (status IN ('open','done')),
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS print_job_items (
|
||||
print_job_id uuid REFERENCES print_jobs(id) ON DELETE CASCADE,
|
||||
item_id uuid REFERENCES items(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (print_job_id, item_id)
|
||||
);
|
||||
|
||||
-- Share links -------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS share_links (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
token text UNIQUE NOT NULL,
|
||||
scope_type text NOT NULL CHECK (scope_type IN ('selection','folder')),
|
||||
scope_ref jsonb NOT NULL,
|
||||
expires_at timestamptz,
|
||||
password_hash text,
|
||||
revoked bool NOT NULL DEFAULT false,
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Telegram ---------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS telegram_links (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
chat_id bigint UNIQUE NOT NULL,
|
||||
user_id uuid REFERENCES users(id) ON DELETE CASCADE,
|
||||
default_recipe_id uuid REFERENCES recipes(id) ON DELETE SET NULL,
|
||||
active bool NOT NULL DEFAULT true,
|
||||
linked_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS telegram_pairing_codes (
|
||||
code text PRIMARY KEY,
|
||||
user_id uuid REFERENCES users(id) ON DELETE CASCADE,
|
||||
expires_at timestamptz NOT NULL,
|
||||
used_at timestamptz
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS telegram_drafts (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
chat_id bigint NOT NULL,
|
||||
media_group_id text,
|
||||
file_refs jsonb NOT NULL DEFAULT '[]'::jsonb,
|
||||
last_received_at timestamptz NOT NULL DEFAULT now(),
|
||||
status text NOT NULL DEFAULT 'collecting'
|
||||
CHECK (status IN ('collecting','awaiting_recipe','dispatched','discarded')),
|
||||
notice_message_id bigint
|
||||
);
|
||||
|
||||
-- Settings (single row) -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
id int PRIMARY KEY DEFAULT 1 CHECK (id = 1),
|
||||
openrouter_key_enc text,
|
||||
delivery_host text,
|
||||
delivery_protocol text CHECK (delivery_protocol IN ('ftps','sftp')),
|
||||
delivery_port int,
|
||||
delivery_user text,
|
||||
delivery_password_enc text,
|
||||
delivery_base_path text,
|
||||
default_dpi int NOT NULL DEFAULT 300,
|
||||
default_crop_mode text NOT NULL DEFAULT 'crop',
|
||||
concurrency int NOT NULL DEFAULT 2,
|
||||
cricut_sheet_cm text NOT NULL DEFAULT '17.1x23.5',
|
||||
monthly_budget numeric,
|
||||
n8n_webhook_url text,
|
||||
telegram_bot_token_enc text,
|
||||
telegram_webhook_secret text
|
||||
);
|
||||
|
||||
-- Models ------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS models (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
model_id text NOT NULL,
|
||||
label text NOT NULL,
|
||||
description text,
|
||||
active bool NOT NULL DEFAULT true,
|
||||
is_default bool NOT NULL DEFAULT false,
|
||||
supports_alpha bool NOT NULL DEFAULT false,
|
||||
sort int NOT NULL DEFAULT 0
|
||||
);
|
||||
Reference in new issue
Block a user