System Architektur

Zurück

Technischer Aufbau der SVT Regatta Applikation. Die Plattform laeuft auf zwei Raspberry Pi im Docker Swarm mit einer PostgreSQL Datenbank auf Linode. Alle Verbindungen sind via Cloudflare Tunnel verschluesselt.

Infrastruktur
graph TB
    subgraph Internet
        USER["Browser / Mobile"]
        CF["Cloudflare<br/>CDN + DNS + Tunnel"]
        CFA["Cloudflare Access<br/>Zero Trust"]
    end

    subgraph RaspberryPi["Docker Swarm (2x Raspberry Pi)"]
        subgraph Services
            FE["Frontend<br/>Flask :5001<br/>Resultate, Termine, News"]
            ADMIN["Admin<br/>Flask :5002<br/>CRUD, Upload, Staff"]
            API["API<br/>FastAPI :5004<br/>REST Endpoints"]
        end
        subgraph TestServices["Test Services"]
            FE_T["Frontend Test<br/>Flask :5003"]
            ADMIN_T["Admin Test<br/>Flask :5005"]
            API_T["API Test<br/>FastAPI :5007"]
        end
        CRON["systemd Timer<br/>Staff Reminders<br/>taeglich 09:00"]
    end

    subgraph External["Externe Dienste"]
        DB[("PostgreSQL<br/>Linode<br/>svt-regatta-db")]
        TWILIO["Twilio<br/>SMS"]
        SMTP["SMTP2GO<br/>E-Mail"]
        CLAUDE["Claude AI<br/>News Generator"]
        GHCR["GitHub Container<br/>Registry (GHCR)"]
    end

    USER -->|HTTPS| CF
    CF -->|Tunnel| FE
    CF -->|Tunnel| FE_T
    CF -->|Tunnel + Access| ADMIN
    CF -->|Tunnel + Access| ADMIN_T
    CF -->|Tunnel| API

    FE --> DB
    ADMIN --> DB
    API --> DB
    FE_T --> DB
    ADMIN_T --> DB
    API_T --> DB

    ADMIN --> TWILIO
    ADMIN --> SMTP
    ADMIN --> CLAUDE
    ADMIN_T --> TWILIO
    ADMIN_T --> SMTP
    FE_T --> TWILIO

    CRON -->|curl| ADMIN_T

    GHCR -.->|docker pull| Services

    style FE fill:#152B55,color:#fff
    style ADMIN fill:#1a5d3a,color:#fff
    style API fill:#0d6efd,color:#fff
    style FE_T fill:#152B55,color:#fff,stroke-dasharray: 5 5
    style ADMIN_T fill:#1a5d3a,color:#fff,stroke-dasharray: 5 5
    style API_T fill:#0d6efd,color:#fff,stroke-dasharray: 5 5
    style DB fill:#336791,color:#fff
    style CF fill:#f38020,color:#fff
    style CFA fill:#f38020,color:#fff
    style TWILIO fill:#F22F46,color:#fff
    style SMTP fill:#2ecc71,color:#fff
    style CLAUDE fill:#7c3aed,color:#fff
    style GHCR fill:#333,color:#fff
    style CRON fill:#ffc107,color:#000
Datenfluss - Resultate Upload
sequenceDiagram
    participant RL as Regattaleiter
    participant ST as Sailrace Timer App
    participant AD as Admin Portal
    participant DB as PostgreSQL
    participant FE as Frontend

    RL->>ST: Zeitmessung Rennen
    ST->>ST: CSV Export
    RL->>AD: CSV Upload
    AD->>DB: Results INSERT (Draft)
    AD->>AD: Review & Edit
    AD->>DB: UPDATE is_published=TRUE
    AD->>DB: sync DNS + Ranking
    FE->>DB: SELECT published results
    FE->>FE: Anzeige Resultate + Gesamtwertung
Staff Benachrichtigung
sequenceDiagram
    participant CRON as systemd Timer
    participant AD as Admin Portal
    participant DB as PostgreSQL
    participant SMS as Twilio SMS
    participant STAFF as Regattahelfer
    participant PRES as Praesident
    participant FE as Frontend

    CRON->>AD: POST /api/staff-reminders/run
    AD->>DB: Regatten in N Tagen?
    DB-->>AD: Staff-Zuweisungen

    alt Staff zugewiesen
        AD->>SMS: Erinnerung + Confirm-Link
        SMS->>STAFF: SMS
        STAFF->>FE: Klick Confirm-Link
        FE->>DB: SET confirmed_at
    end

    alt Kein Helfer / Kein Leiter
        AD->>SMS: Missing-Staff Alert
        SMS->>PRES: ACHTUNG SMS
    end

    alt Nicht bestaetigt (letzter Reminder)
        AD->>SMS: Eskalation
        SMS->>PRES: Eskalations-SMS
    end
Datenbank Schema (Haupttabellen)
erDiagram
    events ||--o{ regattas : "hat Termine"
    events ||--o{ event_seasons : "pro Saison"
    events ||--o{ race_days : "Renntage"
    events ||--o{ event_entries : "Anmeldungen"
    race_days ||--o{ races : "Rennen"
    races ||--o{ results : "Resultate"
    regattas ||--o{ regatta_staff : "Staff"
    persons ||--o{ regatta_staff : "zugewiesen"
    sailors ||--o{ event_entries : "Skipper"
    registered_boats ||--o{ event_entries : "Boot"
    boat_classes ||--o{ registered_boats : "Klasse"

    events {
        int id PK
        string name
        string slug
        boolean has_season_ranking
    }
    regattas {
        int id PK
        date date
        int event_id FK
        time regatta_start
        time regatta_end
    }
    races {
        int id PK
        int race_day_id FK
        int race_number
        boolean is_published
    }
    results {
        int id PK
        int race_id FK
        string sailno
        string boat_class
        interval corrected_time
        string penalty
    }
    sailors {
        int id PK
        string name
        string email_hash
    }
    registered_boats {
        int id PK
        string sailno
        boolean is_pool
    }
Tech Stack
Backend
  • Python 3.11 Flask + FastAPI
  • PostgreSQL Linode Server
  • Docker Swarm 2x Raspberry Pi
  • Gunicorn WSGI Server
Frontend
  • Bootstrap 5.3 CSS Framework
  • Jinja2 Template Engine
  • Vanilla JS Kein Framework
  • Mermaid.js Diagramme
Security & DevOps
  • Cloudflare Tunnel + Access
  • SOPS + age Config Encryption
  • Fernet PII Encryption
  • GitHub Actions CI/CD

Entwickelt mit Claude Code (Anthropic Claude Opus). Diagramme via Mermaid.js.