Charge les avatars depuis cdn.intra.42.fr avec le même access token,
parse image.versions, et documente le flux pour la correction.
Co-authored-by: Cursor <cursoragent@cursor.com>
**Confidentialité :** certains champs peuvent être masqués par l’API selon le profil consulté — afficher `—` si absent (sujet OAuth).
### Photo de profil
1.**URL** : extraite du JSON user (`_imageUrl` dans `profile_screen.dart`) — `image.link`, puis `image.versions`, puis `image_url`.
2.**Fichier** : `GET` sur `cdn.intra.42.fr` avec `Authorization: Bearer` (`IntraAuthService.cdnImageHeaders`) — le CDN refuse souvent les requêtes sans token.
3.**UI** : widget `_IntraProfileAvatar` (bytes en mémoire + `CircleAvatar`) ; sinon initiale du login.
Détail oral : [fiche-soutenance-orale.md](fiche-soutenance-orale.md) §5 « Photo de profil ».
> « 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. »
> « Swifty Companion : Flutter, OAuth2 Intra une fois, puis recherche par login et profil (photo, champs, skills avec %, projets validés et failed). Secrets dans `.env` local, ignoré par Git. Deux écrans, un seul access token pour toutes les requêtes API. »
| Point | Détail à mentionner si demandé |
|-------|--------------------------------|
| Sujet | Mobile Initiation v4 — API Intra **v2** |
2. **`authorizeAndExchangeCode`** (`flutter_appauth`) : navigateur Intra → tu te connectes → redirect `com.example.swiftycompanion://callback` → l’app reçoit les tokens.
**Scopes** (cochés sur l’Intra, pas dans `.env`) : **`public`** + **`projects`** — sans `projects`, les projets du profil renvoient **403**.
**À dire :** « Le bonus n’est pas un second OAuth à chaque fois : c’est le refresh automatique sur 401, puis la même requête est relancée. »
**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. »
### Le bouton « DEBUG : simuler token expiré » — qu’est-ce que ça fait **vraiment** ?
| Question | Réponse courte |
|----------|----------------|
| C’est demandé par le sujet ? | **Non.** C’est un **raccourci de démo** en développement. |
| C’est de la triche ? | **Non.** Il ne contourne pas l’API : il force la même situation qu’un access token périmé, pour tester le **vrai** flux refresh. |
| Visible en soutenance release ? | **Non.** Compilé seulement si `kDebugMode` (ex. `flutter run`). Absent en `flutter build apk --release`. |
2. « Une recherche fonctionne : un seul access token, pas de nouvelle fenêtre OAuth. »
3. « En build debug j’ai un bouton qui **simule** un access token expiré — il ne supprime pas le refresh token. » *(montrer le bouton)*
4. « Je recherche à nouveau : l’API renvoie 401, mon `intra_http` déclenche le refresh, puis relance le GET — le profil s’ouvre sans repasser par le navigateur. »
5. « En production le bouton n’existe pas ; le mécanisme reste le même quand le token expire vraiment. »
**Si pas de téléphone / pas de debug :** `flutter test` sur le parse token
### Photo de profil (critère sujet n°4) — à savoir expliquer
1.**Une fois** : `authorizeAndExchangeCode` → access (+ refresh en bonus).
2.**Ensuite** : chaque appel API utilise `authHeaders()` — **pas** de nouvelle fenêtre OAuth par recherche.
Le PDF demande **quatre détails + profile picture**. L’encadré confidentialité permet de ne pas afficher une donnée **si l’API ne la fournit pas** ; ton code **tente** d’afficher la photo dès qu’une URL existe.
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`, …). |
| En-tête profil | `_ProfileHeader` passe `imageUrl` | cherche `_ProfileHeader` dans `profile_screen.dart` |
**Limite assumée :** tokens en **mémoire** — kill app = nouvelle connexion OAuth (acceptable si tu le dis).
**Pourquoi pas `NetworkImage(url)` seul ?** Le CDN 42 renvoie souvent **403** sans `Authorization: Bearer`. Ce n’est **pas** un nouveau OAuth : c’est le **même** access token que pour `intraApiGet`.
### Démo DEBUG (build debug uniquement)
**Pourquoi pas `authHeaders()` sur l’image ?** Ce helper envoie `Accept: application/json`, inadapté à un JPEG — d’où `cdnImageHeaders()` (Bearer seul).
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.
#### Script oral photo (~30 s)
**Si évaluateur en release :** montrer `flutter test` sur `intra_oauth_tokens_test.dart` + expliquer le flux §7.
> « Le sujet demande la photo de profil. L’API me donne une URL sur `cdn.intra.42.fr` dans le champ `image`. Je parse d’abord `link` puis les `versions`. Ensuite je télécharge le fichier avec le même access token que pour l’API, via `cdnImageHeaders`. Si ça échoue ou s’il n’y a pas d’URL, j’affiche l’initiale du login — c’est prévu par l’encadré confidentialité du sujet. »
**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
#### FAQ correcteur
| 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. |
@@ -53,6 +53,8 @@ Documentation API : [api.intra.42.fr/apidoc](https://api.intra.42.fr/apidoc)
Certaines données peuvent s’afficher comme « — » : l’API peut masquer des champs selon la confidentialité.
**Photo :** l’URL est fournie par l’API (`image` sur la fiche user) ; le fichier JPEG est chargé depuis `cdn.intra.42.fr` avec le **même** access token OAuth (pas un second login).
/// En-têtes pour télécharger la photo sur `cdn.intra.42.fr`.
///
/// L’URL vient du JSON user (`image.link` / `versions`). Le CDN refuse souvent
/// les requêtes sans Bearer (403). On réutilise le **même** access token que pour
/// l’API — pas un second OAuth. On n’envoie pas `Accept: application/json` ici.
Map<String,String>cdnImageHeaders(){
if(!isAuthenticated)returnconst{};
return{'Authorization':'Bearer $_accessToken'};
}
Future<Map<String,dynamic>>fetchMe()async{
finalresponse=awaitintraApiGet(
Uri.parse('${IntraConfig.apiBaseUrl}/me'),
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.