docs(db): add ER and ORM diagrams (PlantUML) #452

Merged
marcel merged 3 commits from docs/db-diagrams into main 2026-05-07 07:12:31 +02:00
3 changed files with 575 additions and 0 deletions

View File

@@ -572,3 +572,14 @@ sequenceDiagram
Backend-->>Frontend: 200 OK — Document JSON
Frontend-->>User: Refreshed document view
```
---
## Database
Entity-relationship and full column reference for the PostgreSQL schema (30 tables, 7 domain groups). Source files in `docs/architecture/db/`.
- **[db-relationships.puml](db/db-relationships.puml)** — Entity relationships: all tables and foreign-key connections, grouped by domain. Start here for an overview.
- **[db-orm.puml](db/db-orm.puml)** — Full schema reference: all columns and types for all 30 tables. Use this when mapping Java entities to database columns.
> Schema as of Flyway V60 (2026-05-06). Open in VS Code with the PlantUML extension (server: `http://heim-nas:8500`).

View File

@@ -0,0 +1,432 @@
@startuml db-orm
' Schema source: Flyway V1V60 (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 <<PK>>
--
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 <<PK>>
--
name : VARCHAR(255) NOT NULL UNIQUE
}
entity app_users_groups {
app_user_id : UUID <<FK>>
group_id : UUID <<FK>>
}
entity group_permissions {
group_id : UUID <<FK>>
--
permission : VARCHAR(255)
}
entity password_reset_tokens {
id : UUID <<PK>>
--
app_user_id : UUID <<FK>>
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 <<PK>>
--
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 <<FK>>
created_at : TIMESTAMP NOT NULL
revoked : BOOLEAN NOT NULL
}
entity invite_token_group_ids {
invite_token_id : UUID <<FK>>
group_id : UUID <<FK>>
}
}
' ── Documents ──
package "Documents" {
entity documents {
id : UUID <<PK>>
--
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 <<FK>>
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 <<computed>>
created_at : TIMESTAMP
updated_at : TIMESTAMP
}
entity document_receivers {
document_id : UUID <<FK>>
person_id : UUID <<FK>>
}
entity document_tags {
document_id : UUID <<FK>>
tag_id : UUID <<FK>>
}
entity document_versions {
id : UUID <<PK>>
--
document_id : UUID <<FK>>
editor_id : UUID <<FK>>
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 <<PK>>
--
document_id : UUID <<FK>>
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 <<FK>>
created_at : TIMESTAMP NOT NULL
}
entity document_comments {
id : UUID <<PK>>
--
document_id : UUID <<FK>>
annotation_id : UUID <<FK>>
block_id : UUID <<FK>>
parent_id : UUID <<FK>>
author_id : UUID <<FK>>
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 <<FK>>
--
label : VARCHAR(50) NOT NULL
}
entity comment_mentions {
comment_id : UUID <<FK>>
app_user_id : UUID <<FK>>
}
}
' ── Persons ──
package "Persons" {
entity persons {
id : UUID <<PK>>
--
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 <<PK>>
--
person_id : UUID <<FK>>
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 <<PK>>
--
person_id : UUID <<FK>>
related_person_id : UUID <<FK>>
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 <<PK>>
--
name : VARCHAR(255) NOT NULL UNIQUE
parent_id : UUID <<FK>>
color : VARCHAR(20)
}
}
' ── Transcription ──
package "Transcription" {
entity transcription_blocks {
id : UUID <<PK>>
--
annotation_id : UUID <<FK>>
document_id : UUID <<FK>>
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 <<FK>>
updated_by : UUID <<FK>>
created_at : TIMESTAMP NOT NULL
updated_at : TIMESTAMP NOT NULL
}
entity transcription_block_versions {
id : UUID <<PK>>
--
block_id : UUID <<FK>>
text : TEXT NOT NULL
changed_by : UUID <<FK>>
changed_at : TIMESTAMP NOT NULL
}
entity transcription_block_mentioned_persons {
block_id : UUID <<FK>>
person_id : UUID NOT NULL
--
display_name : VARCHAR(200) NOT NULL
}
}
' ── OCR ──
package "OCR" {
entity ocr_jobs {
id : UUID <<PK>>
--
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 <<PK>>
--
job_id : UUID <<FK>>
document_id : UUID <<FK>>
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 <<PK>>
--
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 <<FK>>
person_id : UUID <<FK>>
cer : DOUBLE PRECISION
loss : DOUBLE PRECISION
accuracy : DOUBLE PRECISION
epochs : INT
created_at : TIMESTAMPTZ NOT NULL
completed_at : TIMESTAMPTZ
}
entity sender_models {
id : UUID <<PK>>
--
person_id : UUID <<FK>> 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 <<PK>>
--
recipient_id : UUID <<FK>>
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 <<PK>>
--
happened_at : TIMESTAMPTZ NOT NULL
actor_id : UUID <<FK>>
kind : VARCHAR(50) NOT NULL
document_id : UUID <<FK>>
payload : JSONB
}
entity geschichten {
id : UUID <<PK>>
--
title : VARCHAR(255) NOT NULL
body : TEXT
status : VARCHAR(32) NOT NULL
author_id : UUID <<FK>>
created_at : TIMESTAMP NOT NULL
updated_at : TIMESTAMP NOT NULL
published_at : TIMESTAMP
}
entity geschichten_persons {
geschichte_id : UUID <<FK>>
person_id : UUID <<FK>>
}
entity geschichten_documents {
geschichte_id : UUID <<FK>>
document_id : UUID <<FK>>
}
}
' 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

View File

@@ -0,0 +1,132 @@
@startuml db-relationships
' Schema source: Flyway V1V60 (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
left to right direction
' ── Auth ──
package "Auth" {
entity app_users
entity user_groups
entity app_users_groups
entity group_permissions
entity password_reset_tokens
entity invite_tokens
entity invite_token_group_ids
}
' ── Documents ──
package "Documents" {
entity documents
entity document_receivers
entity document_tags
entity document_versions
entity document_annotations
entity document_comments
entity document_training_labels
entity comment_mentions
}
' ── Persons ──
package "Persons" {
entity persons
entity person_name_aliases
entity person_relationships
}
' ── Tags ──
package "Tags" {
entity tag
}
' ── Transcription ──
package "Transcription" {
entity transcription_blocks
entity transcription_block_versions
entity transcription_block_mentioned_persons
}
' ── OCR ──
package "OCR" {
entity ocr_jobs
entity ocr_job_documents
entity ocr_training_runs
entity sender_models
}
' ── Supporting ──
package "Supporting" {
entity notifications
entity audit_log
entity geschichten
entity geschichten_persons
entity geschichten_documents
}
' 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