Aller au contenu

LearnKorean — architecture (v1)

Stack proposée (ADRs 001 à 004) : app Capacitor (React + TypeScript + Tailwind + Vite)API Laravel 13 (Sanctum)PostgreSQL 17, le tout déployé par la chaîne standard (Forgejo, Woodpecker, Coolify).

Vue d'ensemble

flowchart LR
    subgraph Tel["Téléphone Android"]
        APP["App Capacitor\n(React + Tailwind)"]
    end
    subgraph Infra["Infra (VLAN, Coolify)"]
        TR["Traefik\n(TLS, CrowdSec)"]
        API["API Laravel 13\n(Sanctum, rate limit)"]
        DB[("PostgreSQL 17\n(réseau interne)")]
    end
    APP -- "HTTPS api.korean.leonheu.fr\n(jeton Sanctum)" --> TR --> API --> DB
    DEV["dev-laravel\n(code + contenu)"] -- "git flow / cz" --> FG["Forgejo"]
    FG --> CI["Woodpecker :\ntests PHP + build web\n+ build APK (Gradle)"]
    CI -- "deploy (main)" --> CO["Coolify\n(API + PostgreSQL)"]
    CI -- "artefact AAB signé" --> GP["Google Play"]
    FG -. miroir .-> GH["GitHub"]
  • Sous-domaine API : api.korean.leonheu.fr (couvert par le wildcard ; à confirmer en phase 5).
  • La base n'est jamais exposée : réseau Docker interne de Coolify.
  • Le dépôt est un monorepo : app/ (client) + api/ (Laravel) — versions synchronisées, une PR peut toucher les deux côtés d'une feature.

Points sensibles

Sujet Position v1
Auth Sanctum : jeton par appareil, revocable ; mots de passe argon2 ; rate limiting connexion/inscription (applicatif + CrowdSec)
Données personnelles email + progression uniquement ; suppression de compte = purge SQL + logs anonymisés
E-mails (reset mot de passe) ADR dédié en phase 3 — pas de serveur mail sur l'infra (cf. ADR-003)
Hors-ligne (Should v1.x) non implémenté en v1, mais l'état SRS est conçu côté serveur avec horodatage par révision → une file locale synchronisable pourra s'y greffer sans refonte
YAGNI v1 pas de cache Redis, pas de file de jobs, pas de websockets — à instruire si un besoin réel apparaît

Modèle de données (cœur v1)

erDiagram
    USER ||--o{ USER_DECK : active
    USER ||--o{ CARD : "crée (cartes perso)"
    USER ||--o{ REVIEW_STATE : possède
    USER ||--o{ HANGUL_PROGRESS : avance
    DECK ||--o{ CARD : contient
    USER_DECK }o--|| DECK : ""
    CARD ||--o{ REVIEW_STATE : "état par utilisateur"
    HANGUL_LESSON ||--o{ HANGUL_PROGRESS : ""

    USER {
        uuid id PK
        string email UK
        string password_hash
        datetime created_at
    }
    DECK {
        uuid id PK
        string nom
        string source "attribution licence, null si perso"
        bool officiel
    }
    CARD {
        uuid id PK
        uuid deck_id FK
        uuid user_id FK "null = carte officielle"
        string coreen
        string francais
        string romanisation
        string note "optionnel"
    }
    REVIEW_STATE {
        uuid user_id FK
        uuid card_id FK
        float ease "facteur SM-2"
        int interval_jours
        int repetitions
        datetime due_at "prochaine revue"
        datetime last_reviewed_at
    }
    HANGUL_LESSON {
        uuid id PK
        int ordre
        string titre
        json lettres
    }
    HANGUL_PROGRESS {
        uuid user_id FK
        uuid lesson_id FK
        datetime completed_at
    }
  • Répétition espacée : algorithme SM-2 (celui popularisé par Anki), implémenté et testé côté API (REVIEW_STATE) — les réponses (raté/difficile/correct/facile) ajustent ease, interval_jours et due_at. Les quiz réinjectent les erreurs en remettant due_at à aujourd'hui.
  • La progression et les statistiques (streak, compteurs) se calculent depuis REVIEW_STATE et HANGUL_PROGRESS — pas de table de stats à maintenir (YAGNI).