From d4b5c14a2649c011d360457e213fc5c09d3e0fb0 Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 6 May 2026 23:44:00 +0200 Subject: [PATCH] docs(db): add full ORM diagram (db-orm.puml) Co-Authored-By: Claude Sonnet 4.6 --- docs/architecture/db/db-orm.puml | 432 +++++++++++++++++++++++++++++++ 1 file changed, 432 insertions(+) create mode 100644 docs/architecture/db/db-orm.puml diff --git a/docs/architecture/db/db-orm.puml b/docs/architecture/db/db-orm.puml new file mode 100644 index 00000000..a6e64aa3 --- /dev/null +++ b/docs/architecture/db/db-orm.puml @@ -0,0 +1,432 @@ +@startuml db-orm +' Schema source: Flyway V1–V60 (excl. V37, V43 — intentionally removed) +' Schema as of: V60 (2026-05-06) +' ⚠ This is a versioned snapshot. Update when the schema changes significantly. + +hide circle +skinparam linetype ortho + +' ── Auth ── +package "Auth" { + + entity app_users { + id : UUID <> + -- + email : VARCHAR(255) NOT NULL UNIQUE + password : VARCHAR(255) NOT NULL + first_name : VARCHAR(100) + last_name : VARCHAR(100) + birth_date : DATE + contact : TEXT + enabled : BOOLEAN NOT NULL + color : VARCHAR(20) NOT NULL + notify_on_reply : BOOLEAN NOT NULL + notify_on_mention : BOOLEAN NOT NULL + created_at : TIMESTAMP + } + + entity user_groups { + id : UUID <> + -- + name : VARCHAR(255) NOT NULL UNIQUE + } + + entity app_users_groups { + app_user_id : UUID <> + group_id : UUID <> + } + + entity group_permissions { + group_id : UUID <> + -- + permission : VARCHAR(255) + } + + entity password_reset_tokens { + id : UUID <> + -- + app_user_id : UUID <> + token : VARCHAR(64) NOT NULL UNIQUE + expires_at : TIMESTAMP NOT NULL + used : BOOLEAN NOT NULL + created_at : TIMESTAMP NOT NULL + } + + entity invite_tokens { + id : UUID <> + -- + code : VARCHAR(10) NOT NULL UNIQUE + label : VARCHAR(255) + max_uses : INTEGER + use_count : INTEGER NOT NULL + prefill_first_name : VARCHAR(255) + prefill_last_name : VARCHAR(255) + prefill_email : VARCHAR(255) + expires_at : TIMESTAMP + created_by : UUID <> + created_at : TIMESTAMP NOT NULL + revoked : BOOLEAN NOT NULL + } + + entity invite_token_group_ids { + invite_token_id : UUID <> + group_id : UUID <> + } +} + +' ── Documents ── +package "Documents" { + + entity documents { + id : UUID <> + -- + title : VARCHAR(255) NOT NULL + original_filename : VARCHAR(255) NOT NULL + status : VARCHAR(255) NOT NULL + file_path : VARCHAR(255) + file_hash : VARCHAR(64) + summary : TEXT + transcription : TEXT + meta_date : DATE + meta_location : VARCHAR(255) + meta_document_location : VARCHAR(255) + archive_box : VARCHAR(255) + archive_folder : VARCHAR(255) + sender_id : UUID <> + metadata_complete : BOOLEAN NOT NULL + script_type : VARCHAR(30) NOT NULL + thumbnail_key : VARCHAR(255) + thumbnail_generated_at : TIMESTAMP + thumbnail_aspect : VARCHAR(16) + page_count : INTEGER + search_vector : tsvector <> + created_at : TIMESTAMP + updated_at : TIMESTAMP + } + + entity document_receivers { + document_id : UUID <> + person_id : UUID <> + } + + entity document_tags { + document_id : UUID <> + tag_id : UUID <> + } + + entity document_versions { + id : UUID <> + -- + document_id : UUID <> + editor_id : UUID <> + editor_name : VARCHAR(200) NOT NULL + saved_at : TIMESTAMP NOT NULL + snapshot : JSONB NOT NULL + changed_fields : JSONB NOT NULL + } + + entity document_annotations { + id : UUID <> + -- + document_id : UUID <> + page_number : INTEGER NOT NULL + x : DOUBLE PRECISION NOT NULL + y : DOUBLE PRECISION NOT NULL + width : DOUBLE PRECISION NOT NULL + height : DOUBLE PRECISION NOT NULL + color : VARCHAR(20) NOT NULL + polygon : JSONB + file_hash : VARCHAR(64) + created_by : UUID <> + created_at : TIMESTAMP NOT NULL + } + + entity document_comments { + id : UUID <> + -- + document_id : UUID <> + annotation_id : UUID <> + block_id : UUID <> + parent_id : UUID <> + author_id : UUID <> + author_name : VARCHAR(200) NOT NULL + content : TEXT NOT NULL + created_at : TIMESTAMP NOT NULL + updated_at : TIMESTAMP NOT NULL + } + + entity document_training_labels { + document_id : UUID <> + -- + label : VARCHAR(50) NOT NULL + } + + entity comment_mentions { + comment_id : UUID <> + app_user_id : UUID <> + } +} + +' ── Persons ── +package "Persons" { + + entity persons { + id : UUID <> + -- + first_name : VARCHAR(255) + last_name : VARCHAR(255) NOT NULL + alias : VARCHAR(255) + title : VARCHAR(50) + person_type : VARCHAR(20) NOT NULL + notes : TEXT + birth_year : INTEGER + death_year : INTEGER + family_member : BOOLEAN NOT NULL + } + + entity person_name_aliases { + id : UUID <> + -- + person_id : UUID <> + last_name : VARCHAR(255) NOT NULL + first_name : VARCHAR(255) + type : VARCHAR(50) NOT NULL + sort_order : INTEGER NOT NULL + created_at : TIMESTAMPTZ + } + + entity person_relationships { + id : UUID <> + -- + person_id : UUID <> + related_person_id : UUID <> + relation_type : VARCHAR(30) NOT NULL + from_year : INTEGER + to_year : INTEGER + notes : VARCHAR(2000) + created_at : TIMESTAMPTZ NOT NULL + } +} + +' ── Tags ── +package "Tags" { + + entity tag { + id : UUID <> + -- + name : VARCHAR(255) NOT NULL UNIQUE + parent_id : UUID <> + color : VARCHAR(20) + } +} + +' ── Transcription ── +package "Transcription" { + + entity transcription_blocks { + id : UUID <> + -- + annotation_id : UUID <> + document_id : UUID <> + text : TEXT + label : VARCHAR(200) + sort_order : INTEGER NOT NULL + version : INTEGER NOT NULL + source : VARCHAR(10) NOT NULL + reviewed : BOOLEAN NOT NULL + created_by : UUID <> + updated_by : UUID <> + created_at : TIMESTAMP NOT NULL + updated_at : TIMESTAMP NOT NULL + } + + entity transcription_block_versions { + id : UUID <> + -- + block_id : UUID <> + text : TEXT NOT NULL + changed_by : UUID <> + changed_at : TIMESTAMP NOT NULL + } + + entity transcription_block_mentioned_persons { + block_id : UUID <> + person_id : UUID NOT NULL + -- + display_name : VARCHAR(200) NOT NULL + } +} + +' ── OCR ── +package "OCR" { + + entity ocr_jobs { + id : UUID <> + -- + status : VARCHAR(20) NOT NULL + total_documents : INT NOT NULL + processed_documents : INT NOT NULL + error_count : INT NOT NULL + skipped_count : INT NOT NULL + created_by : UUID + progress_message : TEXT + created_at : TIMESTAMPTZ NOT NULL + updated_at : TIMESTAMPTZ NOT NULL + } + + entity ocr_job_documents { + id : UUID <> + -- + job_id : UUID <> + document_id : UUID <> + status : VARCHAR(20) NOT NULL + error_message : TEXT + current_page : INT + total_pages : INT + created_at : TIMESTAMPTZ NOT NULL + updated_at : TIMESTAMPTZ NOT NULL + } + + entity ocr_training_runs { + id : UUID <> + -- + status : VARCHAR(20) NOT NULL + block_count : INT NOT NULL + document_count : INT NOT NULL + model_name : VARCHAR(100) NOT NULL + error_message : TEXT + triggered_by : UUID <> + person_id : UUID <> + cer : DOUBLE PRECISION + loss : DOUBLE PRECISION + accuracy : DOUBLE PRECISION + epochs : INT + created_at : TIMESTAMPTZ NOT NULL + completed_at : TIMESTAMPTZ + } + + entity sender_models { + id : UUID <> + -- + person_id : UUID <> UNIQUE + model_path : TEXT NOT NULL + accuracy : DOUBLE PRECISION + cer : DOUBLE PRECISION + corrected_lines_at_training : INT NOT NULL + created_at : TIMESTAMPTZ NOT NULL + updated_at : TIMESTAMPTZ NOT NULL + } +} + +' ── Supporting ── +package "Supporting" { + + entity notifications { + id : UUID <> + -- + recipient_id : UUID <> + type : VARCHAR(32) NOT NULL + document_id : UUID + reference_id : UUID + annotation_id : UUID + actor_name : VARCHAR(255) + read : BOOLEAN NOT NULL + created_at : TIMESTAMP NOT NULL + } + + entity audit_log { + id : UUID <> + -- + happened_at : TIMESTAMPTZ NOT NULL + actor_id : UUID <> + kind : VARCHAR(50) NOT NULL + document_id : UUID <> + payload : JSONB + } + + entity geschichten { + id : UUID <> + -- + title : VARCHAR(255) NOT NULL + body : TEXT + status : VARCHAR(32) NOT NULL + author_id : UUID <> + created_at : TIMESTAMP NOT NULL + updated_at : TIMESTAMP NOT NULL + published_at : TIMESTAMP + } + + entity geschichten_persons { + geschichte_id : UUID <> + person_id : UUID <> + } + + entity geschichten_documents { + geschichte_id : UUID <> + document_id : UUID <> + } +} + +' Auth relationships +app_users_groups }o--|| app_users : app_user_id +app_users_groups }o--|| user_groups : group_id +group_permissions }o--|| user_groups : group_id +password_reset_tokens }o--|| app_users : app_user_id +invite_tokens }o--|| app_users : created_by +invite_token_group_ids }o--|| invite_tokens : invite_token_id +invite_token_group_ids }o--|| user_groups : group_id + +' Document relationships +documents }o--o| persons : sender_id +document_receivers }o--|| documents : document_id +document_receivers }o--|| persons : person_id +document_tags }o--|| documents : document_id +document_tags }o--|| tag : tag_id +document_versions }o--|| documents : document_id +document_versions }o--o| app_users : editor_id +document_annotations }o--|| documents : document_id +document_annotations }o--o| app_users : created_by +document_comments }o--|| documents : document_id +document_comments }o--o| document_annotations : annotation_id +document_comments }o--o| transcription_blocks : block_id +document_comments }o--o| app_users : author_id +document_comments }o--o| document_comments : parent_id +document_training_labels }o--|| documents : document_id +comment_mentions }o--|| document_comments : comment_id +comment_mentions }o--|| app_users : app_user_id + +' Person relationships +person_name_aliases }o--|| persons : person_id +person_relationships }o--|| persons : person_id +person_relationships }o--|| persons : related_person_id + +' Tag self-reference +tag }o--o| tag : parent_id + +' Transcription relationships +transcription_blocks }o--|| document_annotations : annotation_id +transcription_blocks }o--|| documents : document_id +transcription_blocks }o--o| app_users : created_by +transcription_blocks }o--o| app_users : updated_by +transcription_block_versions }o--|| transcription_blocks : block_id +transcription_block_versions }o--o| app_users : changed_by +transcription_block_mentioned_persons }o--|| transcription_blocks : block_id + +' OCR relationships +ocr_job_documents }o--|| ocr_jobs : job_id +ocr_job_documents }o--|| documents : document_id +ocr_training_runs }o--o| app_users : triggered_by +ocr_training_runs }o--o| persons : person_id +sender_models ||--|| persons : person_id + +' Supporting relationships +notifications }o--|| app_users : recipient_id +audit_log }o--o| app_users : actor_id +audit_log }o--o| documents : document_id +geschichten }o--o| app_users : author_id +geschichten_persons }o--|| geschichten : geschichte_id +geschichten_persons }o--|| persons : person_id +geschichten_documents }o--|| geschichten : geschichte_id +geschichten_documents }o--|| documents : document_id + +@enduml