Files
swift_companion/docs/pedagogie/fiche-soutenance-orale.md
2026-10-21 08:54:03 +02:00

258 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Fiche soutenance orale — Swifty Companion
> **Lot 3** du [plan de préparation](00-plan-preparation-soutenance.md).
> **Usage :** répétition à voix haute, ordre de démo, réponses courtes.
> **Cartographie détaillée sujet ↔ code :** [guide-soutenance-realisation.md](guide-soutenance-realisation.md).
> **Dépôt / `.env` / PC école :** [fiche-depot-soutenance.md](fiche-depot-soutenance.md).
**Durée type peer-eval :** 5 à 15 min selon l’évaluateur — viser **8 min** de démo + questions.
---
## Avant d’entrer en soutenance (5 min)
| Check | Action |
|-------|--------|
| Device | Téléphone chargé, USB débogage ou Wi‑Fi ADB OK |
| `.env` | Présent dans `swiftyCompanion/swifty_companion/.env`, **pas** affiché à l’écran |
| OAuth Intra | App avec scopes **public** + **projects**, redirect = `REDIRECT_URI` du `.env` |
| Terminal prêt | Dossier `swiftyCompanion/swifty_companion` |
| Git école | Pas de `.env` dans `git status` si on montre le repo |
---
## 1. Introduction (~30 s)
**À dire (modèle) :**
> « Swifty Companion est une app Flutter qui se connecte une fois à l’API 42 en OAuth2, puis permet de chercher un login et d’afficher son profil : photo, coordonnées, niveau cursus, skills avec pourcentage, et projets validés ou en échec. Les secrets sont dans un `.env` local, ignoré par Git. J’ai deux écrans et un seul token réutilisé pour toutes les requêtes API. »
| Point | Détail à mentionner si demandé |
|-------|--------------------------------|
| Sujet | Mobile Initiation v4 — API Intra **v2** |
| Stack | Flutter / Dart, `flutter_appauth`, `http`, `flutter_dotenv` |
| Entrée app | `lib/main.dart` → `SearchScreen` |
---
## 2. Sécurité et fichier `.env` (~1 min)
### Ce que le sujet impose (éliminatoire)
- Identifiants **uniquement** dans `.env` **local**.
- `.env` **ignoré par Git** — exposition publique = **échec direct**.
- Le rendu Vogosphere contient **`.env.example`**, pas de vrais secrets.
### Où est le fichier qui tourne
```text
swiftyCompanion/swifty_companion/.env
```
Chargé au démarrage : `dotenv.load()` dans `main.dart`, déclaré en asset dans `pubspec.yaml`.
### Les trois variables (à expliquer sans lire les valeurs à l’écran)
| Variable | Rôle | Où la trouver sur l’Intra |
|----------|------|---------------------------|
| `CLIENT_ID` | Identifiant de l’app OAuth | Champ **UID** |
| `CLIENT_SECRET` | Échange code → tokens | Champ **SECRET** (privé) |
| `REDIRECT_URI` | Retour navigateur → app | **URI de redirection** (ex. `com.example.swiftycompanion://callback`) |
**Scopes** (cochés sur l’Intra, pas dans `.env`) : **`public`** + **`projects`** — sans `projects`, les projets du profil renvoient **403**.
**Phrase clé :** « Le correcteur copie `.env.example` vers `.env` et met ses propres clés ; mon dépôt Git ne contient jamais le SECRET. »
---
## 3. Architecture (~1–2 min)
### Les deux vues
| Vue | Fichier | Rôle |
|-----|---------|------|
| **1 — Recherche** | `lib/screens/search_screen.dart` | Connexion OAuth, champ login, navigation vers profil |
| **2 — Profil** | `lib/screens/profile_screen.dart` | Données agrégées, erreur + Réessayer |
### Couche services (flux données)
```text
SearchScreen / ProfileScreen
↓
IntraUserService (recherche) IntraProfileService (profil)
↓ ↓
intra_http.dart (GET + gestion 401 / 429)
↓
IntraAuthService.instance (un seul access + refresh)
```
| Fichier | Responsabilité |
|---------|----------------|
| `config/intra_config.dart` | URLs API v2 + lecture `.env` |
| `services/intra_auth_service.dart` | OAuth, headers Bearer, refresh |
| `services/intra_user_service.dart` | `GET /users?filter[login]=` |
| `services/intra_profile_service.dart` | User + skills (cursus) + `projects_users` |
| `services/intra_api_exception.dart` | Erreurs métier → messages UI |
| `theme/swifty_ui.dart` | Scroll, largeurs max, AppBar commune |
### Un seul token OAuth (critère sujet)
1. **Une fois** : `authorizeAndExchangeCode` → access (+ refresh en bonus).
2. **Ensuite** : chaque appel API utilise `authHeaders()` — **pas** de nouvelle fenêtre OAuth par recherche.
Singleton : `IntraAuthService.instance`.
---
## 4. Démo guidée — commandes et parcours (~4–6 min)
### 4.1 Terminal — ce que fait chaque commande
Toujours depuis le package Flutter :
```bash
cd ~/chemin/vers/swiftyCompanion/swifty_companion
```
| Commande | À dire à l’oral |
|----------|-----------------|
| `flutter doctor` | Vérifie SDK Flutter, Android toolchain, device branché — preuve que l’environnement est prêt. |
| `flutter pub get` | Télécharge les dépendances du `pubspec.yaml` (`flutter_appauth`, `http`, `dotenv`, …). |
| `flutter analyze` | Analyse statique Dart — pas d’erreurs bloquantes. |
| `flutter test` | Lance les tests unitaires / widget (`test/widget_test.dart`, `test/intra_oauth_tokens_test.dart` pour le bonus). |
| `flutter devices` | Liste émulateur ou téléphone — on note l’id pour `-d`. |
| `flutter run -d <device>` | Compile, installe et lance l’app en mode debug sur l’appareil. |
**Ordre recommandé devant l’évaluateur :** `flutter test` (rapide, visible) → `flutter run` si l’app n’est pas déjà lancée.
### 4.2 Parcours écran par écran
| Étape | Action | Ce que tu commentes |
|-------|--------|---------------------|
| 1 | Lancer l’app | Si `.env` manquant : message explicite sur l’écran recherche (`IntraConfig.isConfigured`). |
| 2 | **Se connecter avec 42** | Flux OAuth : navigateur / Custom Tab → redirect vers l’app → token stocké en mémoire. |
| 3 | Login valide (ex. un camarade ou toi) | `GET` filtre login → si trouvé, `Navigator.push` vers profil. |
| 4 | **Profil** | Photo, ≥ 4 champs, carte skills (niveau + %), liste projets **finished** et **failed**. |
| 5 | **Retour** (flèche) | Retour vue 1 — **session conservée** (pas besoin de reconnecter). |
| 6 | Deuxième recherche | Prouve le token unique. |
| 7 | Login invalide `zzzzinvalid` | Message clair, pas de crash, pas d’ouverture profil vide. |
| 8 | Mode avion (recherche ou profil) | Erreur réseau ; sur profil, bouton **Réessayer**. |
| 9 | (Option) Rotation / grand écran | Profil en 2 colonnes si large (`LayoutBuilder`, breakpoint 720 px). |
**Login de démo :** prévoir un login qui a des skills et au moins un projet failed visible.
---
## 5. Critères obligatoires — réponses « où c’est » (~2 min si questions)
Référence complète : [guide-soutenance-realisation.md §3](guide-soutenance-realisation.md#3-chapitre-v1--partie-obligatoire-9-critères).
| # | Critère | Réponse courte + fichier |
|---|---------|---------------------------|
| 1 | 2 vues | `search_screen.dart` + `profile_screen.dart` |
| 2 | Toutes les erreurs | Exceptions dans `intra_api_exception.dart` ; catch dans services + UI (`search_screen`, `profile_screen` `_ErrorBody`) |
| 3 | Profil seulement si login existe | Pas de navigation si liste users vide — `IntraUserNotFoundException` |
| 4 | ≥ 4 infos + photo | `_ProfileHeader`, `_DetailsCard` — email, phone, location, wallet, niveau cursus, … |
| 5 | Skills niveau + % | Depuis `cursus_users[].skills` (pas `/experiences`) — `IntraProfileService._parseSkillsFromCursus` |
| 6 | Projets dont failed | `projects_users` — statuts `finished` et `failed` — scope `projects` |
| 7 | Retour vue 1 | `Navigator.pop` + AppBar retour |
| 8 | Layout moderne / flexible | `swifty_ui.dart` — scroll, `searchMaxWidth`, 2 colonnes profil |
| 9 | Un token, pas un par requête | `IntraAuthService.instance` + `intra_http.dart` |
**Champs `—` sur le profil :** l’API masque certaines données selon la confidentialité — conforme au sujet.
---
## 6. Erreurs et cas limites — quoi montrer
| Cas | Démo | Message / comportement attendu |
|-----|------|--------------------------------|
| Login vide | Rechercher sans texte | « Saisis un login » |
| Login inconnu | `zzzzinvalid` | Pas de profil |
| Non connecté | (si évaluateur demande) tenter recherche sans OAuth | Invitation à se connecter |
| Réseau | Mode avion | Message réseau |
| 403 scopes | (expliquer) | Scopes `public` + `projects` sur l’app Intra |
| 429 rate limit | (souvent pas en démo) | Retry dans `intra_http.dart` + cache profil 2 min |
| 401 session | Après refresh impossible | « Session expirée… » — voir bonus |
| Erreur chargement profil | (si réseau flaky) | Écran erreur + **Réessayer** |
Tu n’es pas obligé de tout rejouer : **2–3 cas** bien choisis suffisent souvent.
---
## 7. Bonus refresh token (~1–2 min)
### Flux à expliquer
```text
Login → access + refresh en mémoire
GET API → 401 Unauthorized
→ POST /oauth/token (grant_type=refresh_token)
→ nouveau access
→ même GET rejoué une fois
Si refresh échoue → IntraUnauthorizedException, l’utilisateur se reconnecte
```
| Fichier | Rôle |
|---------|------|
| `intra_auth_service.dart` | `refreshAccessToken()`, `_refreshInFlight` |
| `intra_http.dart` | `intraApiGet` avec retry après refresh |
| `intra_oauth_tokens.dart` | Parse JSON token — testé dans `intra_oauth_tokens_test.dart` |
**Limite assumée :** tokens en **mémoire** — kill app = nouvelle connexion OAuth (acceptable si tu le dis).
### Démo DEBUG (build debug uniquement)
1. Connexion OK, une recherche OK.
2. Bouton **« DEBUG : simuler token expiré »** sur `SearchScreen` (`kDebugMode` — absent en release).
3. **Rechercher** à nouveau → refresh silencieux → profil sans repasser par le navigateur.
**Si évaluateur en release :** montrer `flutter test` sur `intra_oauth_tokens_test.dart` + expliquer le flux §7.
**Bouton DEBUG au rendu :** **conservé** — visible uniquement en build debug (`kDebugMode`), utile pour prouver le bonus refresh ; absent en release. Voir [fiche-depot-soutenance.md §8](fiche-depot-soutenance.md).
---
## 8. Questions fréquentes — réponses courtes
| Question | Réponse |
|----------|---------|
| Pourquoi pas `/experiences` pour les skills ? | Souvent **403** ; le sujet permet les données user / cursus — skills dans `cursus_users`. |
| Pourquoi `projects` dans les scopes ? | Endpoint `projects_users` exige ce scope. |
| Où est le redirect Android ? | `AndroidManifest.xml` — `RedirectUriReceiverActivity` ; `appAuthRedirectScheme` dans `build.gradle.kts`. |
| Pourquoi des requêtes séquentielles ? | Éviter **429** Too Many Requests sur l’API 42. |
| Cache profil ? | ~2 minutes en mémoire dans `IntraProfileService` — moins de hits API. |
| Différence dépôt perso / école ? | Perso : `docs/` + travail ; école : **racine** = contenu de `swiftyCompanion/` seul. |
| Où est la traduction du sujet ? | Dépôt perso `docs/sujets/` — pas dans Vogosphere. |
| Tests ? | `widget_test` démarre l’app ; bonus : parse token OAuth. |
| WSL vs téléphone ? | OAuth fiable sur **device/émulateur Android** ; voir stratégie VM école dans la piscine mobile. |
| Que faire si SECRET exposé ? | Régénérer sur l’Intra, mettre à jour `.env` local, vérifier l’historique Git. |
---
## 9. Script condensé (une page — à réciter)
1. **Intro** — Flutter, OAuth une fois, 2 vues, `.env` hors Git.
2. **`flutter test`** — vert.
3. **Connexion 42** — une fois.
4. **Recherche OK** → profil (photo, champs, skills %, failed).
5. **Retour** → 2ᵉ recherche sans reconnecter.
6. **Login faux** + **mode avion**.
7. **Bonus** — DEBUG token expiré → recherche OK.
8. **Rappel** — 9 critères, un token, `public`+`projects`, `.env.example` dans le repo.
---
## 10. Liens utiles (perso)
| Document | Usage |
|----------|--------|
| [guide-soutenance-realisation.md](guide-soutenance-realisation.md) | Tableau sujet ↔ fichiers |
| [etape-06-checklist-soutenance.md](etape-06-checklist-soutenance.md) | Checklist peer-eval |
| [etape-07-bonus-token.md](etape-07-bonus-token.md) | Détail refresh |
| [swiftyCompanion/README.md](../../swiftyCompanion/README.md) | README correcteurs (rendu) |
---
*Aligné sujet Swifty Companion v4 — lot 3 livré.*