guidesoutenance
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Reprise du projet — où nous en sommes
|
||||
|
||||
> Dernière mise à jour : étape 4 codée (profil complet).
|
||||
> Dernière mise à jour : étape 7 bonus (refresh token) codée.
|
||||
|
||||
## En bref
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
| **Fait** | Structure dépôt perso, sujet FR, paquet rendu, **projet Flutter** (`swifty_companion/`) |
|
||||
| **Fait** | OAuth + token + `/v2/me` (login connecté) |
|
||||
| **Fait** | Vue 1 recherche + vue 2 profil (skills, projets, ≥ 4 champs) |
|
||||
| **Prochaine action** | **Étape 5** — retour / layout + tests manuels sur device |
|
||||
| **Prochaine action** | **Rendu Vogosphere** + test manuel bonus (token invalide) |
|
||||
|
||||
## Arborescence actuelle
|
||||
|
||||
@@ -49,21 +49,23 @@ swifty_companion/ ← dépôt Git de TRAVAIL (perso)
|
||||
| **2** | OAuth2 (`IntraAuthService`, `/v2/me`) | ✅ **Validé** |
|
||||
| **3** | Vue 1 : `SearchScreen` + `IntraUserService` + navigation profil | ✅ **Codé** — tester sur téléphone |
|
||||
| **4** | Vue 2 : `IntraProfileService` + profil (skills, projets, champs) | ✅ **Codé** — tester sur téléphone |
|
||||
| **5** | Retour vue 1 + layout adaptatif (polish) | ⏳ **Prochaine** |
|
||||
| **6** | Passage checklist [swiftyCompanion/README.md](../../swiftyCompanion/README.md) | ⏳ |
|
||||
| **7** | Bonus : refresh token à expiration | ⏳ |
|
||||
| **5** | Retour vue 1 + layout adaptatif (`swifty_ui.dart`) | ✅ **Codé** — [etape-05-navigation-layout.md](etape-05-navigation-layout.md) |
|
||||
| **6** | Checklist soutenance + rendu école | ✅ **Guide** — [etape-06-checklist-soutenance.md](etape-06-checklist-soutenance.md) |
|
||||
| **7** | Bonus : refresh token à expiration | ✅ **Codé** — [etape-07-bonus-token.md](etape-07-bonus-token.md) |
|
||||
|
||||
## Code applicatif (`swifty_companion/lib/`)
|
||||
|
||||
| Fichier | Rôle |
|
||||
|---------|------|
|
||||
| `config/intra_config.dart` | `.env`, URLs OAuth / API |
|
||||
| `services/intra_auth_service.dart` | OAuth + token unique + `GET /v2/me` |
|
||||
| `services/intra_auth_service.dart` | OAuth + refresh token + `GET /v2/me` |
|
||||
| `services/intra_oauth_tokens.dart` | Parse réponse `/oauth/token` |
|
||||
| `services/intra_user_service.dart` | `GET /v2/users?filter[login]=` |
|
||||
| `services/intra_profile_service.dart` | Profil : user, cursus, experiences, projects_users |
|
||||
| `services/intra_profile_service.dart` | Profil : user, cursus_users, projects_users |
|
||||
| `services/intra_api_exception.dart` | Erreurs métier |
|
||||
| `screens/search_screen.dart` | **Vue 1** |
|
||||
| `screens/profile_screen.dart` | **Vue 2** |
|
||||
| `theme/swifty_ui.dart` | Layout responsive partagé |
|
||||
| `main.dart` | `home: SearchScreen()` |
|
||||
|
||||
## Reprendre le travail (checklist)
|
||||
@@ -82,6 +84,7 @@ swifty_companion/ ← dépôt Git de TRAVAIL (perso)
|
||||
|
||||
| Tu veux… | Fichier |
|
||||
|----------|---------|
|
||||
| **Préparer la correction (sujet ↔ fichiers)** | **[guide-soutenance-realisation.md](guide-soutenance-realisation.md)** |
|
||||
| Comprendre le sujet | [docs/sujets/swifty-companion.md](../sujets/swifty-companion.md) |
|
||||
| Plan complet des étapes | [docs/pedagogie/README.md](README.md) |
|
||||
| Vue profil (étape 4) | [etape-04-vue-profil.md](etape-04-vue-profil.md) |
|
||||
@@ -95,11 +98,12 @@ swifty_companion/ ← dépôt Git de TRAVAIL (perso)
|
||||
| [etape-02-oauth-intra.md](etape-02-oauth-intra.md) | ✅ |
|
||||
| [etape-03-vue-recherche.md](etape-03-vue-recherche.md) | ✅ |
|
||||
| [etape-04-vue-profil.md](etape-04-vue-profil.md) | ✅ |
|
||||
| `etape-05-navigation-layout.md` | 🔜 |
|
||||
| `etape-06-bonus-token.md` | 🔜 |
|
||||
| [etape-05-navigation-layout.md](etape-05-navigation-layout.md) | ✅ |
|
||||
| [etape-06-checklist-soutenance.md](etape-06-checklist-soutenance.md) | ✅ |
|
||||
| [etape-07-bonus-token.md](etape-07-bonus-token.md) | ✅ piste bonus |
|
||||
|
||||
## Notes pour la session suivante
|
||||
|
||||
- Valider étapes 3–4 sur **device réel** (recherche, profil, mode avion sur vue 2).
|
||||
- Étape 5 : harmoniser marges / contraintes sur les deux écrans si besoin en soutenance.
|
||||
- Le dépôt perso peut versionner le code dans `swiftyCompanion/` ; le remote **école** pointe uniquement sur ce sous-dossier (voir README racine).
|
||||
- Partie obligatoire : prête pour peer-eval si la checklist étape 6 est OK sur device.
|
||||
- `git init` dans `swiftyCompanion/` quand tu pousses sur Vogosphere (voir README racine).
|
||||
- Bonus refresh token : étape 7 si tu as le temps après rendu.
|
||||
|
||||
@@ -4,7 +4,9 @@ Parcours pour construire le projet **étape par étape**.
|
||||
|
||||
## Reprise rapide
|
||||
|
||||
→ **[00-reprise-et-etat.md](00-reprise-et-etat.md)** — état actuel, arborescence, prochaine action (**étape 5 : layout / tests**).
|
||||
→ **[00-reprise-et-etat.md](00-reprise-et-etat.md)** — état du projet.
|
||||
|
||||
→ **[guide-soutenance-realisation.md](guide-soutenance-realisation.md)** — **cartographie sujet ↔ code** pour la correction (ne rien oublier).
|
||||
|
||||
## Prérequis
|
||||
|
||||
@@ -22,9 +24,9 @@ Base via la [piscine mobile](../../../piscine_mobile/README.md) sur le même dis
|
||||
| **2** | OAuth2 Intra : token + `/v2/me` | ✅ **Fait** — [etape-02-oauth-intra.md](etape-02-oauth-intra.md) |
|
||||
| **3** | **Vue 1** : recherche par login + erreurs | ✅ **Codé** — [etape-03-vue-recherche.md](etape-03-vue-recherche.md) |
|
||||
| **4** | **Vue 2** : profil (≥ 4 champs, skills, projets failed) | ✅ **Codé** — [etape-04-vue-profil.md](etape-04-vue-profil.md) |
|
||||
| **5** | Navigation retour + layout responsive (polish) | ⏳ **Prochaine** |
|
||||
| **6** | Tests manuels + checklist peer-eval | ⏳ |
|
||||
| **7** | **Bonus** : refresh token à l’expiration | ⏳ |
|
||||
| **5** | Navigation retour + layout responsive | ✅ **Codé** — [etape-05-navigation-layout.md](etape-05-navigation-layout.md) |
|
||||
| **6** | Tests + checklist peer-eval + rendu Git école | ✅ **Guide** — [etape-06-checklist-soutenance.md](etape-06-checklist-soutenance.md) |
|
||||
| **7** | **Bonus** : refresh token à l’expiration | ✅ **Codé** — [etape-07-bonus-token.md](etape-07-bonus-token.md) |
|
||||
|
||||
README correcteurs (dépôt école) : [swiftyCompanion/README.md](../../swiftyCompanion/README.md)
|
||||
|
||||
@@ -40,7 +42,9 @@ README correcteurs (dépôt école) : [swiftyCompanion/README.md](../../swiftyCo
|
||||
| [etape-02-oauth-intra.md](etape-02-oauth-intra.md) | ✅ Guide étape 2 |
|
||||
| [etape-03-vue-recherche.md](etape-03-vue-recherche.md) | ✅ Guide étape 3 |
|
||||
| [etape-04-vue-profil.md](etape-04-vue-profil.md) | ✅ Guide étape 4 |
|
||||
| `etape-05-navigation-layout.md` | 🔜 |
|
||||
| `etape-06-bonus-token.md` | 🔜 |
|
||||
| [etape-05-navigation-layout.md](etape-05-navigation-layout.md) | ✅ Guide étape 5 |
|
||||
| [etape-06-checklist-soutenance.md](etape-06-checklist-soutenance.md) | ✅ Guide étape 6 |
|
||||
| [etape-07-bonus-token.md](etape-07-bonus-token.md) | ✅ Bonus refresh |
|
||||
| **[guide-soutenance-realisation.md](guide-soutenance-realisation.md)** | ✅ **Guide correction / soutenance** |
|
||||
|
||||
Demande dans le chat l’étape à ouvrir : le guide correspondant sera rédigé au moment où tu l’attaques.
|
||||
|
||||
@@ -50,7 +50,6 @@ lib/
|
||||
│ ├── intra_auth_service.dart # existant
|
||||
│ └── intra_user_service.dart # GET user(s) avec token
|
||||
├── screens/
|
||||
│ ├── auth_screen.dart # option : fusionner ou garder login au démarrage
|
||||
│ ├── search_screen.dart # VUE 1
|
||||
│ └── profile_screen.dart # VUE 2 (squelette → étape 4)
|
||||
└── main.dart # home → SearchScreen si token OK
|
||||
|
||||
74
docs/pedagogie/etape-05-navigation-layout.md
Normal file
74
docs/pedagogie/etape-05-navigation-layout.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Étape 5 — Navigation et layout adaptatif
|
||||
|
||||
> **Prérequis :** [étape 4 — profil](etape-04-vue-profil.md) validée sur téléphone (OAuth `public` + `projects`).
|
||||
|
||||
## Objectif sujet
|
||||
|
||||
| Critère | Cette étape |
|
||||
|---------|-------------|
|
||||
| Retour vue 1 | `Navigator.push` / `pop` — flèche AppBar sur la vue 2 |
|
||||
| Layout flexible | Scroll, marges, largeur max, 2 colonnes profil si écran large |
|
||||
| Lisibilité | Rotation, clavier ouvert, petits écrans sans overflow |
|
||||
|
||||
---
|
||||
|
||||
## Navigation
|
||||
|
||||
```text
|
||||
SearchScreen --push--> ProfileScreen
|
||||
ProfileScreen --pop--> SearchScreen (session OAuth conservée)
|
||||
```
|
||||
|
||||
- Pas de `go_router` obligatoire : **Navigator 1.0** suffit.
|
||||
- La vue 1 garde le token ; une nouvelle recherche ne refait pas OAuth.
|
||||
|
||||
---
|
||||
|
||||
## Fichiers ajoutés / modifiés
|
||||
|
||||
```
|
||||
lib/
|
||||
├── theme/
|
||||
│ └── swifty_ui.dart # couleur 42, padding, AppBar, scroll responsive
|
||||
├── screens/
|
||||
│ ├── search_screen.dart # formulaire scrollable, centré
|
||||
│ └── profile_screen.dart # lignes détail adaptatives, UI partagée
|
||||
└── main.dart # thème Material 3 (seed 42)
|
||||
```
|
||||
|
||||
### `swifty_ui.dart`
|
||||
|
||||
| Widget / helper | Rôle |
|
||||
|-----------------|------|
|
||||
| `SwiftyUi.appBar` | AppBar turquoise identique sur les 2 vues |
|
||||
| `ResponsiveScrollBody` | `SingleChildScrollView` + padding + `maxWidth` |
|
||||
| `ErrorMessage` | Texte d’erreur sélectionnable, centré |
|
||||
| `LoadingFilledButton` | Bouton avec spinner (connexion / recherche) |
|
||||
|
||||
### Vue recherche
|
||||
|
||||
- Contenu dans `ResponsiveScrollBody` (`maxWidth` ~480 px).
|
||||
- Évite l’overflow quand le **clavier** est ouvert.
|
||||
|
||||
### Vue profil
|
||||
|
||||
- Breakpoint **720 px** : 1 colonne (téléphone) / 2 colonnes (tablette, paysage).
|
||||
- `_DetailRow` : label + valeur en **colonne** si la ligne est trop étroite (< 400 px).
|
||||
|
||||
---
|
||||
|
||||
## Critères « étape 5 terminée »
|
||||
|
||||
| # | Test |
|
||||
|---|------|
|
||||
| 1 | Profil → retour → recherche sans déconnexion |
|
||||
| 2 | Rotation paysage : pas de bande jaune / overflow |
|
||||
| 3 | Clavier ouvert sur la recherche : boutons accessibles (scroll) |
|
||||
| 4 | Tablette ou fenêtre large : profil en 2 colonnes lisible |
|
||||
| 5 | Erreurs toujours visibles (couleur + message lisible) |
|
||||
|
||||
---
|
||||
|
||||
## Ensuite — étape 6
|
||||
|
||||
→ [etape-06-checklist-soutenance.md](etape-06-checklist-soutenance.md) : `flutter test`, démo peer-eval, préparation dépôt école.
|
||||
118
docs/pedagogie/etape-06-checklist-soutenance.md
Normal file
118
docs/pedagogie/etape-06-checklist-soutenance.md
Normal file
@@ -0,0 +1,118 @@
|
||||
# Étape 6 — Tests, soutenance et rendu école
|
||||
|
||||
> **Prérequis :** étapes 2–5 OK sur device (OAuth `public` + `projects`, recherche, profil, retour, layout).
|
||||
|
||||
## Objectif
|
||||
|
||||
1. Valider la **partie obligatoire** du sujet avant peer-eval.
|
||||
2. Préparer le **dépôt Vogosphere** (sans `docs/`, sans `.env`).
|
||||
3. Savoir **quoi montrer** à l’évaluateur et dans quel ordre.
|
||||
|
||||
Référence correcteurs : [swiftyCompanion/README.md](../../swiftyCompanion/README.md).
|
||||
|
||||
---
|
||||
|
||||
## Commandes avant soutenance
|
||||
|
||||
Depuis le package Flutter :
|
||||
|
||||
```bash
|
||||
cd ~/projets/42School/swifty_companion/swiftyCompanion/swifty_companion
|
||||
flutter pub get
|
||||
flutter analyze
|
||||
flutter test
|
||||
flutter doctor
|
||||
```
|
||||
|
||||
Sur téléphone (USB ou Wi‑Fi ADB) :
|
||||
|
||||
```bash
|
||||
flutter devices
|
||||
flutter run -d <device>
|
||||
```
|
||||
|
||||
Après changement de `.env` ou de scopes OAuth : **arrêter** `flutter run` et relancer ; **reconnexion** 42 dans l’app.
|
||||
|
||||
---
|
||||
|
||||
## Checklist peer-eval (détaillée)
|
||||
|
||||
| # | Test | Comment le montrer | Où c’est géré dans l’app |
|
||||
|---|------|--------------------|---------------------------|
|
||||
| 1 | `flutter test` | Terminal sur la machine de dev | `test/widget_test.dart` |
|
||||
| 2 | Parcours nominal | Connexion → login valide → profil | `SearchScreen` → `ProfileScreen` |
|
||||
| 3 | Profil complet | Photo, ≥ 4 champs, skills (niveau + %), projets validés **et** failed | `IntraProfileService` + `profile_screen.dart` |
|
||||
| 4 | Login invalide | `zzzzinvalid` → message, pas de crash | `IntraUserNotFoundException` |
|
||||
| 5 | Hors ligne | Mode avion sur recherche **ou** sur profil (erreur + réessai) | `IntraNetworkException`, retry profil |
|
||||
| 6 | Navigation | Retour AppBar → nouvelle recherche, session conservée | `Navigator.pop` |
|
||||
| 7 | Petit écran / rotation | Paysage, clavier ouvert sur la recherche | `ResponsiveScrollBody`, `swifty_ui.dart` |
|
||||
| 8 | Bonus | Access invalide → refresh → recherche OK | [étape 7](etape-07-bonus-token.md) |
|
||||
|
||||
**Ordre de démo suggéré :** connexion → profil riche (skills + failed) → login faux → mode avion → retour → `flutter test`.
|
||||
|
||||
---
|
||||
|
||||
## Correspondance sujet v4 ↔ app
|
||||
|
||||
| Critère sujet | Statut attendu |
|
||||
|---------------|----------------|
|
||||
| 2 vues | `SearchScreen` + `ProfileScreen` |
|
||||
| Erreurs | Login vide, inconnu, réseau, 401, 403, 429 |
|
||||
| Token unique | `IntraAuthService.instance` partout |
|
||||
| OAuth Intra | `flutter_appauth` + `.env` |
|
||||
| Layout adaptatif | Étape 5 (`wideBreakpoint` 720 px) |
|
||||
|
||||
Champs masqués par l’API (`—`) : normal (confidentialité sujet).
|
||||
|
||||
---
|
||||
|
||||
## Rendu Git école
|
||||
|
||||
### Ce qui part sur Vogosphere
|
||||
|
||||
Racine du dépôt Intra = contenu de **`swiftyCompanion/`** :
|
||||
|
||||
- `README.md`, `.gitignore`, `.env.example`
|
||||
- `swifty_companion/` (code Flutter)
|
||||
|
||||
### Ce qui ne part **pas**
|
||||
|
||||
- `docs/` (dépôt perso uniquement)
|
||||
- `.env` (secrets)
|
||||
- Fichiers de backup type `.env copy`
|
||||
|
||||
### Vérifier avant push
|
||||
|
||||
```bash
|
||||
cd ~/projets/42School/swifty_companion/swiftyCompanion
|
||||
git status # si repo école initialisé ici
|
||||
```
|
||||
|
||||
- Aucun `.env` listé
|
||||
- `REDIRECT_URI` dans `.env.example` = URI enregistrée sur l’Intra
|
||||
- README école à jour pour les correcteurs
|
||||
|
||||
Workflow détaillé : [README racine](../../README.md) (section « Pousser vers l’école »).
|
||||
|
||||
---
|
||||
|
||||
## Parler en soutenance (30 s)
|
||||
|
||||
> « Swifty Companion : OAuth2 Intra une fois, puis recherche par login. Le profil charge cursus, skills et projects_users avec le même token. Erreurs réseau et API gérées ; layout scrollable et deux colonnes sur grand écran. Secrets dans `.env` local, non versionné. »
|
||||
|
||||
---
|
||||
|
||||
## Critères « étape 6 terminée »
|
||||
|
||||
| # | Fait ? |
|
||||
|---|--------|
|
||||
| 1 | `flutter analyze` + `flutter test` verts |
|
||||
| 2 | Checklist peer-eval cochée sur device |
|
||||
| 3 | `.env` absent du futur commit école |
|
||||
| 4 | Tu sais lancer la démo sans notes |
|
||||
|
||||
---
|
||||
|
||||
## Ensuite — étape 7 (bonus)
|
||||
|
||||
→ [etape-07-bonus-token.md](etape-07-bonus-token.md) : refresh token à l’expiration (optionnel si obligatoire parfaite).
|
||||
93
docs/pedagogie/etape-07-bonus-token.md
Normal file
93
docs/pedagogie/etape-07-bonus-token.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# Étape 7 — Bonus : refresh token
|
||||
|
||||
> **Prérequis :** partie obligatoire validée ([étape 6](etape-06-checklist-soutenance.md)).
|
||||
|
||||
## Objectif sujet
|
||||
|
||||
Quand l’**access token** expire, l’app **rafraîchit** la session via le **refresh token** et continue à fonctionner **sans** relancer l’écran OAuth à chaque requête.
|
||||
|
||||
---
|
||||
|
||||
## Implémentation (ce dépôt)
|
||||
|
||||
| Élément | Fichier |
|
||||
|---------|---------|
|
||||
| Stockage `access` + `refresh` après login | `intra_auth_service.dart` |
|
||||
| `POST /oauth/token` `grant_type=refresh_token` | `IntraAuthService.refreshAccessToken()` |
|
||||
| Un seul refresh en parallèle | `_refreshInFlight` (évite 4 refresh si 4 GET en 401) |
|
||||
| Sur **401** API → refresh → **rejouer** le GET | `intra_http.dart` → `intraApiGet(..., auth:)` |
|
||||
| Parsing JSON token | `intra_oauth_tokens.dart` |
|
||||
|
||||
Les scopes OAuth restent `public` + `projects` à la connexion initiale.
|
||||
|
||||
---
|
||||
|
||||
## Flux
|
||||
|
||||
```text
|
||||
signIn() → access + refresh en mémoire
|
||||
GET /v2/users/... → 401
|
||||
→ refreshAccessToken() → nouveaux tokens
|
||||
→ même GET avec nouveau Bearer
|
||||
sinon → IntraUnauthorizedException (« Reconnecte-toi »)
|
||||
```
|
||||
|
||||
`signOut()` efface les deux tokens.
|
||||
|
||||
**Persistance** (secure storage) : non fait — le bonus du sujet vise surtout l’expiration **pendant** une session ; tu peux l’ajouter plus tard.
|
||||
|
||||
---
|
||||
|
||||
## Tests automatisés
|
||||
|
||||
```bash
|
||||
cd swiftyCompanion/swifty_companion
|
||||
flutter test
|
||||
```
|
||||
|
||||
- `test/intra_oauth_tokens_test.dart` — parsing réponse `/oauth/token`
|
||||
- `test/widget_test.dart` — démarrage app
|
||||
|
||||
---
|
||||
|
||||
## Test manuel (soutenance bonus)
|
||||
|
||||
1. Connexion 42 → recherche OK.
|
||||
2. **Simuler** un access token expiré :
|
||||
- en debug, appeler une recherche après avoir invalidé le token en mémoire **sans** effacer le refresh (voir ci‑dessous), **ou**
|
||||
- attendre l’expiration réelle (souvent long).
|
||||
3. Nouvelle recherche → refresh silencieux → profil OK **sans** repasser par le navigateur OAuth.
|
||||
|
||||
### Test sur device (build debug)
|
||||
|
||||
Sur l’écran recherche, bouton **« DEBUG : simuler token expiré »** (`kDebugMode` uniquement — absent en release).
|
||||
|
||||
1. Connexion 42 OK.
|
||||
2. Appuyer sur le bouton debug.
|
||||
3. **Rechercher** un login → refresh silencieux → profil OK sans repasser par OAuth.
|
||||
|
||||
En release / soutenance APK release : expliquer le flux ou montrer `flutter test`.
|
||||
|
||||
---
|
||||
|
||||
## Critères « bonus OK »
|
||||
|
||||
| # | Test |
|
||||
|---|------|
|
||||
| 1 | 401 sur API → refresh → requête réussie |
|
||||
| 2 | Refresh impossible → message « session expirée », pas de crash |
|
||||
| 3 | `flutter test` vert |
|
||||
|
||||
---
|
||||
|
||||
## Fichiers modifiés (récap)
|
||||
|
||||
```
|
||||
lib/services/
|
||||
├── intra_auth_service.dart # refresh + stockage refresh_token
|
||||
├── intra_http.dart # 401 → refresh → retry
|
||||
├── intra_oauth_tokens.dart # parse JSON token
|
||||
└── … (user / profile passent auth à intraApiGet)
|
||||
|
||||
test/intra_oauth_tokens_test.dart
|
||||
```
|
||||
288
docs/pedagogie/guide-soutenance-realisation.md
Normal file
288
docs/pedagogie/guide-soutenance-realisation.md
Normal file
@@ -0,0 +1,288 @@
|
||||
# Guide de correction / soutenance — ce qui a été réalisé
|
||||
|
||||
> **Objectif :** suivre le [sujet Swifty Companion v4](../sujets/swifty-companion.md) point par point, avec **fichiers**, **comportement** et **démo** à ne pas oublier en peer-eval.
|
||||
> **Code évalué :** dépôt école = dossier [`swiftyCompanion/`](../../swiftyCompanion/) (pas `docs/`).
|
||||
|
||||
---
|
||||
|
||||
## 1. Vue d’ensemble
|
||||
|
||||
| Élément | Détail |
|
||||
|---------|--------|
|
||||
| Framework | **Flutter** (Dart), Material 3 |
|
||||
| API | **42 Intra v2** — `https://api.intra.42.fr/v2` |
|
||||
| Auth | **OAuth2** — `flutter_appauth` 12.x |
|
||||
| Secrets | **`.env`** dans `swiftyCompanion/swifty_companion/.env` (asset, non versionné) |
|
||||
| Vues | **2** — recherche (`SearchScreen`) + profil (`ProfileScreen`) |
|
||||
| Bonus | **Refresh token** sur 401 + retry des requêtes API |
|
||||
|
||||
**Point d’entrée :** [`swiftyCompanion/swifty_companion/lib/main.dart`](../../swiftyCompanion/swifty_companion/lib/main.dart) → `home: SearchScreen()`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Chapitre IV — Instructions générales & sécurité
|
||||
|
||||
| Exigence sujet | Réalisation | Fichiers |
|
||||
|----------------|-------------|----------|
|
||||
| Langage mobile (Flutter cité) | Projet Flutter standard | `swifty_companion/pubspec.yaml` |
|
||||
| Dernière API 42 | Base URL v2 | [`lib/config/intra_config.dart`](../../swiftyCompanion/swifty_companion/lib/config/intra_config.dart) — `apiBaseUrl` |
|
||||
| Secrets en **`.env`**, **pas dans Git** | `CLIENT_ID`, `CLIENT_SECRET`, `REDIRECT_URI` | `.env` (local) · modèle [`swiftyCompanion/.env.example`](../../swiftyCompanion/.env.example) · ignore [`swiftyCompanion/.gitignore`](../../swiftyCompanion/.gitignore) et [`.gitignore` racine travail](../../.gitignore) |
|
||||
| Chargement `.env` au démarrage | `dotenv.load` | [`lib/main.dart`](../../swiftyCompanion/swifty_companion/lib/main.dart) + asset dans `pubspec.yaml` (`assets: - .env`) |
|
||||
|
||||
**À dire en correction :** les clés OAuth ne sont jamais dans le code source ; l’évaluateur configure son propre `.env` à partir de `.env.example`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Chapitre V.1 — Partie obligatoire (9 critères)
|
||||
|
||||
### Critère 1 — Au moins 2 vues
|
||||
|
||||
| Vue | Rôle | Fichier |
|
||||
|-----|------|---------|
|
||||
| **Vue 1** | Connexion API + recherche par login | [`lib/screens/search_screen.dart`](../../swiftyCompanion/swifty_companion/lib/screens/search_screen.dart) |
|
||||
| **Vue 2** | Profil étudiant | [`lib/screens/profile_screen.dart`](../../swiftyCompanion/swifty_companion/lib/screens/profile_screen.dart) |
|
||||
|
||||
**Démo :** montrer explicitement les deux écrans après une recherche réussie.
|
||||
|
||||
---
|
||||
|
||||
### Critère 2 — Gérer tous les cas d’erreur
|
||||
|
||||
| Situation | Message utilisateur (ex.) | Où c’est géré |
|
||||
|-----------|---------------------------|---------------|
|
||||
| Pas connecté OAuth | « Connecte-toi d’abord à l’API 42 » | [`intra_api_exception.dart`](../../swiftyCompanion/swifty_companion/lib/services/intra_api_exception.dart) — `IntraNotAuthenticatedException` · [`intra_user_service.dart`](../../swiftyCompanion/swifty_companion/lib/services/intra_user_service.dart) |
|
||||
| Login vide | « Saisis un login » | `IntraUserService.fetchUserByLogin` |
|
||||
| Login inconnu | « Aucun utilisateur pour ce login » | `IntraUserNotFoundException` |
|
||||
| Pas de réseau | « Connexion impossible. Vérifie le réseau. » | `IntraNetworkException` · `SocketException` / `ClientException` dans user + profile services |
|
||||
| OAuth annulé / échec | Messages OAuth / plateforme | [`search_screen.dart`](../../swiftyCompanion/swifty_companion/lib/screens/search_screen.dart) — `_connect()` |
|
||||
| Session expirée (401) | « Session expirée. Reconnecte-toi… » | `IntraUnauthorizedException` · [`intra_http.dart`](../../swiftyCompanion/swifty_companion/lib/services/intra_http.dart) |
|
||||
| Accès refusé (403) | Scopes `public` + `projects` | `IntraForbiddenException` |
|
||||
| Trop de requêtes (429) | Message + attente / retry | `IntraRateLimitException` · retry dans `intra_http.dart` |
|
||||
| Erreur profil (réseau, API) | Message + bouton **Réessayer** | `profile_screen.dart` — `_ErrorBody` |
|
||||
| `.env` manquant | Texte explicite au lancement | `search_screen.dart` — branche `!IntraConfig.isConfigured` |
|
||||
|
||||
**Démo suggérée :** login `zzzzinvalid` → mode avion → (optionnel) profil en erreur + réessai.
|
||||
|
||||
---
|
||||
|
||||
### Critère 3 — Vue 2 : infos du login s’il existe
|
||||
|
||||
- Navigation **uniquement** si l’API renvoie un utilisateur : [`search_screen.dart`](../../swiftyCompanion/swifty_companion/lib/screens/search_screen.dart) — `_search()` → `Navigator.push(ProfileScreen(user: …))`.
|
||||
- Si liste vide → exception, **pas** d’ouverture du profil.
|
||||
|
||||
---
|
||||
|
||||
### Critère 4 — ≥ 4 détails + photo de profil
|
||||
|
||||
| Donnée affichée | Source API | Fichier UI |
|
||||
|-----------------|------------|------------|
|
||||
| **Login** | `user['login']` | AppBar + en-tête profil — `profile_screen.dart` |
|
||||
| **Photo** | `image.link` ou `image_url` | `CircleAvatar` + `NetworkImage` — `_ProfileHeader` |
|
||||
| **Email** | `user['email']` | `_DetailsCard` |
|
||||
| **Téléphone** | `user['phone']` | `_DetailsCard` |
|
||||
| **Niveau cursus** | `cursus_users` (slug `42`) | `IntraProfileService` + `_DetailsCard` |
|
||||
| **Localisation** | `user['location']` | `_DetailsCard` |
|
||||
| **Wallet** | `user['wallet']` | `_DetailsCard` |
|
||||
|
||||
Chargement profil : [`lib/services/intra_profile_service.dart`](../../swiftyCompanion/swifty_companion/lib/services/intra_profile_service.dart) — `GET /users/{login}`, puis `projects_users`.
|
||||
|
||||
**Note sujet :** champs masqués par l’API → affichage `—` (confidentialité OAuth).
|
||||
|
||||
**Non affiché (optionnel sujet) :** `evaluation_points` — tu as déjà **> 4** champs sans lui.
|
||||
|
||||
---
|
||||
|
||||
### Critère 5 — Skills : niveau + pourcentage
|
||||
|
||||
- **Pas** `/experiences` (souvent 403).
|
||||
- Skills depuis **`cursus_users[].skills`** sur la fiche user.
|
||||
- Niveau entier = partie entière du `level` API ; **%** = partie décimale × 100.
|
||||
- Parsing : `IntraProfileService._parseSkillsFromCursus` · affichage : `_SkillsCard` + barre de progression.
|
||||
|
||||
**Démo :** lire une ligne du type `Algorithms — niveau 8 (42 %)`.
|
||||
|
||||
---
|
||||
|
||||
### Critère 6 — Projets réalisés, y compris failed
|
||||
|
||||
- `GET /users/{id}/projects_users?page[size]=100`
|
||||
- Filtre app : statuts **`finished`** et **`failed`** — `_parseProjects` dans `intra_profile_service.dart`.
|
||||
- Liste : `_ProjectsCard` (libellés Validé / Échec + note si présente).
|
||||
|
||||
**Scope OAuth requis :** `projects` (sinon 403).
|
||||
|
||||
---
|
||||
|
||||
### Critère 7 — Revenir à la vue 1
|
||||
|
||||
- `MaterialPageRoute` + **retour système** / flèche AppBar sur le profil.
|
||||
- `ProfileScreen` : `SwiftyUi.appBar(..., showBack: true)`.
|
||||
- Session OAuth **conservée** sur `SearchScreen` (pas de nouveau login).
|
||||
|
||||
**Démo :** retour → nouvelle recherche sans « Se connecter avec 42 ».
|
||||
|
||||
---
|
||||
|
||||
### Critère 8 — Layout flexible / moderne
|
||||
|
||||
| Mécanisme | Fichier |
|
||||
|-----------|---------|
|
||||
| Scroll + clavier | `ResponsiveScrollBody` — [`lib/theme/swifty_ui.dart`](../../swiftyCompanion/swifty_companion/lib/theme/swifty_ui.dart) |
|
||||
| Formulaire centré (vue 1) | `searchMaxWidth` 480 px |
|
||||
| Profil 2 colonnes si large | `wideBreakpoint` 720 px — `profile_screen.dart` `LayoutBuilder` |
|
||||
| Lignes détail empilées si étroit | `_DetailRow` si largeur < 400 px |
|
||||
| Thème 42 turquoise | `SwiftyUi.brand` · `main.dart` `ColorScheme.fromSeed` |
|
||||
|
||||
**Démo :** rotation paysage ou grand écran → 2 colonnes sur le profil.
|
||||
|
||||
---
|
||||
|
||||
### Critère 9 — Un token OAuth, pas un token par requête
|
||||
|
||||
| Règle | Implémentation |
|
||||
|-------|----------------|
|
||||
| Un seul login OAuth | `IntraAuthService.instance` — [`intra_auth_service.dart`](../../swiftyCompanion/swifty_companion/lib/services/intra_auth_service.dart) |
|
||||
| Bearer sur chaque GET | `authHeaders()` |
|
||||
| Pas de OAuth à chaque recherche | `IntraUserService` / `IntraProfileService` réutilisent le même service |
|
||||
|
||||
**À dire :** un flux `authorizeAndExchangeCode` au début ; ensuite uniquement des `GET` API avec le même access token.
|
||||
|
||||
---
|
||||
|
||||
## 4. OAuth2 Intra (encadrés sujet)
|
||||
|
||||
| Élément | Valeur / choix | Fichiers |
|
||||
|---------|----------------|----------|
|
||||
| Authorize | `https://api.intra.42.fr/oauth/authorize` | `intra_config.dart` |
|
||||
| Token | `https://api.intra.42.fr/oauth/token` | `intra_config.dart` + refresh dans `intra_auth_service.dart` |
|
||||
| Scopes | `public`, `projects` | `intra_auth_service.dart` — `AuthorizationTokenRequest` |
|
||||
| Redirect (device) | `com.example.swiftycompanion://callback` | `.env` · [`intra-oauth-formulaire.md`](intra-oauth-formulaire.md) |
|
||||
| Android redirect | `RedirectUriReceiverActivity` | [`android/app/src/main/AndroidManifest.xml`](../../swiftyCompanion/swifty_companion/android/app/src/main/AndroidManifest.xml) |
|
||||
| Gradle scheme | `appAuthRedirectScheme` | [`android/app/build.gradle.kts`](../../swiftyCompanion/swifty_companion/android/app/build.gradle.kts) |
|
||||
|
||||
**Doc perso (non rendue Intra) :** [etape-02-oauth-intra.md](etape-02-oauth-intra.md), [oauth-test-manuel.md](oauth-test-manuel.md).
|
||||
|
||||
---
|
||||
|
||||
## 5. Chapitre V.2 — Bonus (refresh token)
|
||||
|
||||
| Exigence | Réalisation | Fichiers |
|
||||
|----------|-------------|----------|
|
||||
| Stocker refresh au login | `result.refreshToken` | `intra_auth_service.dart` — `signIn()` |
|
||||
| Rafraîchir à expiration | `POST` `grant_type=refresh_token` | `refreshAccessToken()` |
|
||||
| App continue | Sur **401** → refresh → **rejouer** le GET | `intra_http.dart` — `intraApiGet(..., auth:)` |
|
||||
| Parsing JSON token | | `intra_oauth_tokens.dart` + tests |
|
||||
| Test manuel debug | Bouton « DEBUG : simuler token expiré » | `search_screen.dart` (`kDebugMode` uniquement) puis **Rechercher** |
|
||||
|
||||
**Limite assumée :** tokens en **mémoire** seulement — après kill de l’app, reconnexion OAuth (acceptable si tu l’expliques).
|
||||
|
||||
**Guide détaillé :** [etape-07-bonus-token.md](etape-07-bonus-token.md).
|
||||
|
||||
---
|
||||
|
||||
## 6. Inventaire des fichiers applicatifs (lib/)
|
||||
|
||||
Fichiers **métier** à connaître pour la soutenance :
|
||||
|
||||
```
|
||||
swiftyCompanion/swifty_companion/lib/
|
||||
├── main.dart # App + thème + dotenv
|
||||
├── config/
|
||||
│ └── intra_config.dart # URLs + variables .env
|
||||
├── services/
|
||||
│ ├── intra_auth_service.dart # OAuth, access + refresh
|
||||
│ ├── intra_user_service.dart # Recherche login
|
||||
│ ├── intra_profile_service.dart # Profil agrégé
|
||||
│ ├── intra_http.dart # GET + 401/429
|
||||
│ ├── intra_oauth_tokens.dart # Parse token JSON (bonus)
|
||||
│ └── intra_api_exception.dart # Erreurs métier
|
||||
├── screens/
|
||||
│ ├── search_screen.dart # Vue 1
|
||||
│ └── profile_screen.dart # Vue 2
|
||||
└── theme/
|
||||
└── swifty_ui.dart # Layout partagé (étape 5)
|
||||
```
|
||||
|
||||
**Tests :**
|
||||
|
||||
```
|
||||
test/
|
||||
├── widget_test.dart # Démarre app + bouton connexion
|
||||
└── intra_oauth_tokens_test.dart # Parse refresh (bonus)
|
||||
```
|
||||
|
||||
**Fichier supprimé (nettoyage) :** `auth_screen.dart` (écran test obsolète, non utilisé).
|
||||
|
||||
**Plateforme Android (OAuth physique) :** `AndroidManifest.xml`, `build.gradle.kts` (voir §4).
|
||||
|
||||
---
|
||||
|
||||
## 7. Endpoints API utilisés
|
||||
|
||||
| Usage | Méthode | Appelé depuis |
|
||||
|-------|---------|---------------|
|
||||
| Session courante | `GET /v2/me` | `IntraAuthService.fetchMe` (après login) |
|
||||
| Recherche | `GET /v2/users?filter[login]=` | `IntraUserService` |
|
||||
| Fiche complète | `GET /v2/users/{login}` | `IntraProfileService` |
|
||||
| Cursus (secours) | `GET /v2/users/{id}/cursus_users` | `IntraProfileService` si pas embarqué |
|
||||
| Projets | `GET /v2/users/{id}/projects_users` | `IntraProfileService` |
|
||||
| Refresh token | `POST /oauth/token` | `IntraAuthService.refreshAccessToken` |
|
||||
|
||||
Requêtes **séquentielles** + cache profil 2 min + retry 429 : voir [etape-04-vue-profil.md](etape-04-vue-profil.md).
|
||||
|
||||
---
|
||||
|
||||
## 8. Chapitre VI — Rendu Git
|
||||
|
||||
| À pousser sur Vogosphere | À ne **pas** pousser |
|
||||
|--------------------------|----------------------|
|
||||
| Contenu de `swiftyCompanion/` | Dossier `docs/` (dépôt perso) |
|
||||
| `README.md`, `.gitignore`, `.env.example` | Fichier `.env` |
|
||||
| `swifty_companion/` (code Flutter) | `.env copy`, secrets dans l’historique Git |
|
||||
|
||||
Checklist : [etape-06-checklist-soutenance.md](etape-06-checklist-soutenance.md) · [swiftyCompanion/README.md](../../swiftyCompanion/README.md).
|
||||
|
||||
---
|
||||
|
||||
## 9. Script de démo (5–8 min)
|
||||
|
||||
1. `cd swiftyCompanion/swifty_companion` → `flutter test` (rapide).
|
||||
2. `flutter run -d <device>` — montrer `.env` configuré **sans** afficher les secrets.
|
||||
3. **Se connecter avec 42** — une fois.
|
||||
4. Recherche login valide → **profil** : photo, champs, skills, projet failed si dispo.
|
||||
5. Retour → **deuxième** recherche (même session).
|
||||
6. Login invalide → message.
|
||||
7. Mode avion → erreur réseau.
|
||||
8. **Bonus :** DEBUG simuler token expiré → **Rechercher** → OK sans OAuth navigateur.
|
||||
9. Rappeler : 2 vues, 1 token, `.env` ignoré par git.
|
||||
|
||||
---
|
||||
|
||||
## 10. Parcours pédagogique (docs perso uniquement)
|
||||
|
||||
| Étape | Guide |
|
||||
|-------|--------|
|
||||
| 0–1 | [00-reprise-et-etat.md](00-reprise-et-etat.md) |
|
||||
| 2 OAuth | [etape-02-oauth-intra.md](etape-02-oauth-intra.md) |
|
||||
| 3 Recherche | [etape-03-vue-recherche.md](etape-03-vue-recherche.md) |
|
||||
| 4 Profil | [etape-04-vue-profil.md](etape-04-vue-profil.md) |
|
||||
| 5 Layout | [etape-05-navigation-layout.md](etape-05-navigation-layout.md) |
|
||||
| 6 Soutenance | [etape-06-checklist-soutenance.md](etape-06-checklist-soutenance.md) |
|
||||
| 7 Bonus | [etape-07-bonus-token.md](etape-07-bonus-token.md) |
|
||||
| **Ce document** | **Cartographie sujet ↔ code pour la correction** |
|
||||
|
||||
---
|
||||
|
||||
## 11. Checklist « je n’ai rien oublié » (avant peer-eval)
|
||||
|
||||
- [ ] App OAuth Intra : scopes **public** + **projects**, redirect = `.env`
|
||||
- [ ] `.env` présent sur la machine de démo, **absent** du Git école
|
||||
- [ ] `flutter analyze` / `flutter test` OK
|
||||
- [ ] Parcours nominal + erreurs + retour testés sur **device**
|
||||
- [ ] Bonus refresh testé (DEBUG → Rechercher)
|
||||
- [ ] Prêt à expliquer **où** est chaque critère du sujet (ce guide §3)
|
||||
- [ ] SECRET régénéré si jamais exposé (chat, commit accidentel)
|
||||
|
||||
---
|
||||
|
||||
*Dernière alignement : sujet PDF v4 — traduction [swifty-companion.md](../sujets/swifty-companion.md).*
|
||||
@@ -145,6 +145,8 @@ Déposez le travail dans votre **dépôt Git** comme d’habitude. **Seul le con
|
||||
|
||||
→ Checklist soutenance (dépôt école) : [../../swiftyCompanion/README.md](../../swiftyCompanion/README.md) — dossier **non** inclus dans le rendu Intra si tu ne pousses que ce sous-répertoire.
|
||||
|
||||
→ Guide détaillé **sujet ↔ code** (dépôt perso) : [../pedagogie/guide-soutenance-realisation.md](../pedagogie/guide-soutenance-realisation.md)
|
||||
|
||||
---
|
||||
|
||||
## Éléments visuels du PDF
|
||||
|
||||
Reference in New Issue
Block a user