Přeskočit obsah

Datový Model (Supabase / PostgreSQL)

Tento dokument popisuje schéma DB na vyšší úrovni. Kanonický zdroj pravdy je supabase/migrations/20260422000001_init.sql — pokud něco kolize, věřit SQL souboru.

Aktuální stav (2026-05-11): jediná migrace, 15 tabulek, 7 enum typů. RLS enabled na všech tabulkách, policy v rolích (is_staff(), is_admin()), nikdy ne hardcoded auth.uid() v business logice.

Disciplíny (must)

  1. Relační model, žádný JSONB pro hlavní entity.
  2. RLS přes role helpery (is_staff, is_admin), ne přes uživatelské ID.
  3. M:N přes join tabulky (např. slot_topicsstudents přes student_slot_topics).
  4. Schema připravené na Fázi 2+ (day_overrides, taught_by, arrived_at/left_at, afterschool class_type) — UI bude doplněno až bude potřeba.

1. Enums (7)

Enum Hodnoty Použití
user_role pending, director, teacher, office, parent profiles.role
class_type school, afterschool classes.class_type (afterschool = družina)
student_status active, inactive students.status
staff_role lead_teacher, co_teacher, assistant class_staff.role
guardian_relationship mother, father, grandparent, guardian, other student_guardians.relationship
attendance_status present, absent, late, excused attendance_records.status
day_override_type excursion, project_day, school_holiday, other day_overrides.type

2. Tabulky — přehled (15)

Identity & Auth

Tabulka Účel Klíčové sloupce
profiles Uživatelské účty (1:1 s auth.users) id (FK → auth.users), email, role (default pending)

profiles.id je FK na auth.users(id). Při registraci přes Supabase Auth se profil vytvoří automaticky triggerem handle_new_user s role = 'pending' (čeká na schválení).

Referenční data

Tabulka Účel Unique
school_years Školní roky ("2025/2026") Jen 1 active naráz (partial unique index)
classes Třídy (Kentaur, Phenix, afterschool) name
subjects Předměty (Matematika, ČJ, Kosmická) name

Žáci a zákonní zástupci

Tabulka Účel Vazby
students Žáci grade (ročník), status
students_classes Zápis žáka do třídy per rok M:N studentsclasses × school_years
guardians Zákonní zástupci nullable user_id (link na auth.users až po registraci rodiče)
student_guardians Zástupce ↔ žák M:N s typem vztahu + is_primary flag

Personál (učitelé)

Tabulka Účel Vazby
class_staff Učitel přidělen do třídy v ročníku M:N profilesclasses × school_years s rolí (lead_teacher / co_teacher / assistant)

Rozvrh a třídnice

Tabulka Účel Klíčové
schedule_slots Týdenní opakující se rozvrh per třída day_of_week 1–5, slot_order 1–10, default_teacher_id (slot-level ownership)
classbook_entries Skutečný záznam hodiny per slot per datum taught_by (zastupování — Fáze 2), attendance_done_at (kompletnost docházky)
slot_topics Témata probraná v hodině (M:N s žáky) grade_hint (rychlý "přiřaď ročníku X" shortcut)
student_slot_topics Který žák pracoval na kterém tématu Unique per (student_id, slot_topic_id)
attendance_records Docházka per slot arrived_at / left_at nullable (rezerva pro Fázi 2 MŠMT export)

Speciální dny

Tabulka Účel Klíčové
day_overrides Mimořádné dny (výlet, projektový den, prázdniny) class_id NULL = celoškolní; typ z day_override_type

day_overrides.class_id může být NULL pro celoškolní akce. Unique constrainty (partial indexes) řeší kolize: 1 override per (datum, třída) nebo 1 celoškolní override per datum.


3. ER diagram

erDiagram
    auth_users ||--|| profiles : "1:1"
    profiles ||--o{ class_staff : "učí v"
    profiles ||--o{ schedule_slots : "default teacher"
    profiles ||--o{ classbook_entries : "taught_by"
    profiles ||--o{ guardians : "user_id (nullable)"

    school_years ||--o{ students_classes : "zápisy roku"
    school_years ||--o{ class_staff : "personál roku"
    school_years ||--o{ schedule_slots : "rozvrh roku"

    classes ||--o{ students_classes : ""
    classes ||--o{ class_staff : ""
    classes ||--o{ schedule_slots : ""
    classes ||--o{ day_overrides : ""

    students ||--o{ students_classes : ""
    students ||--o{ student_guardians : ""
    students ||--o{ student_slot_topics : ""
    students ||--o{ attendance_records : ""

    guardians ||--o{ student_guardians : ""

    subjects ||--o{ schedule_slots : ""

    schedule_slots ||--o{ classbook_entries : ""
    classbook_entries ||--o{ slot_topics : ""
    classbook_entries ||--o{ attendance_records : ""
    slot_topics ||--o{ student_slot_topics : ""

4. Triggers

Trigger Tabulka Co dělá
*_updated_at profiles, students, guardians, classbook_entries, attendance_records Auto-update updated_at při každém UPDATE
on_auth_user_created auth.users (insert) Vytvoří profil s role='pending'

5. Helper funkce (RLS)

is_staff()  boolean   -- role IN ('teacher', 'director', 'office')
is_admin()  boolean   -- role IN ('director', 'office')

Obě jsou security definer, stable, set search_path = public. Volají se v using / with check policies.


6. RLS pattern

Všechny tabulky mají RLS enabled. Policies se řídí třemi vzory:

Vzor Použito na Pravidlo
Authenticated read school_years, classes, subjects Každý přihlášený uživatel může číst
Staff read + admin write students, students_classes, guardians, student_guardians, class_staff is_staff() čte vše, is_admin() zapisuje
Staff full + parent read of own children attendance_records, schedule_slots, students (parent path) Personál plný přístup, rodiče čtou jen data svých dětí přes student_guardiansguardians.user_id

Self-access: profiles_self_update umožňuje uživateli editovat vlastní profil. guardians_self_read umožňuje rodiči číst svůj záznam zástupce.

Classbook write: classbook_entries, slot_topics, student_slot_topics, attendance_records (write) má každý člen personálu — v MVP žádné per-class omezení (učitelé v Sofii učí napříč třídami).

Detailní policies viz init.sql řádky 320–537. Tento dokument popisuje vzory, ne každou jednotlivou policy.


7. Co schéma neobsahuje (záměrně)

  • Feed / events / posts — Fáze 3+. Reálná data pro rodiče jdou přes Sofii jako orchestrátora, ne přes vlastní entity.
  • Tasks / PM modul — Fáze 2+. Plánováno jako organická náhrada přes BookStack tagy + Sofie.
  • Omluvenky, vyzvedávání — Fáze 2+. Schema bude doplněno, až přijde UI.
  • Audit log — TBD před přechodem na produkční data (GDPR gate v AGENTS.md).

8. Migrace

Aktuálně 1 migrace (20260422000001_init.sql). Předchozí 2 fix-up migrace byly squashnuty do init při bootstrap cleanupu 2026-05-11 (default_teacher_id, attendance_done_at). Squash je legitimní strategie, dokud nemáme produkční data — viz DEV-LOG.

# Lokálně
supabase db reset

# Cloud
supabase db push --linked

Pro úplnou strukturu (všechny indexy, FK constraints, RLS policies) viz init.sql. Tento dokument je rychlá orientace — udržuje se přes /curate-docs.