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) ajustentease,interval_joursetdue_at. Les quiz réinjectent les erreurs en remettantdue_atà aujourd'hui. - La progression et les statistiques (streak, compteurs) se calculent
depuis
REVIEW_STATEetHANGUL_PROGRESS— pas de table de stats à maintenir (YAGNI).