Compare commits
1 Commits
d8f2235e87
...
maintenanc
| Author | SHA1 | Date | |
|---|---|---|---|
| 7a06d891b4 |
55
README.md
55
README.md
@@ -1,55 +0,0 @@
|
||||
# Swifty Companion — dépôt de travail
|
||||
|
||||
Espace **perso** : sujet traduit, pédagogie, notes. Le rendu à l’école ne contient **pas** tout ce dépôt.
|
||||
|
||||
## Deux dépôts Git distincts
|
||||
|
||||
| Rôle | Où | Contenu typique |
|
||||
|------|-----|-----------------|
|
||||
| **Travail** (ce repo) | Racine `swifty_companion/` | `docs/`, historique, éventuellement une copie du code |
|
||||
| **École** (Intra / Vogosphere) | Racine = contenu de **`swiftyCompanion/`** | App mobile + `README.md` + `.gitignore` + `.env.example` uniquement |
|
||||
|
||||
Le sous-dossier [`swiftyCompanion/`](swiftyCompanion/) est le **paquet de rendu** : README et `.gitignore` **autonomes**, prêts pour le futur dépôt école.
|
||||
|
||||
### Pousser vers l’école (quand le code est prêt)
|
||||
|
||||
**Option A — Git dans le sous-dossier** (recommandé en local) :
|
||||
|
||||
```bash
|
||||
cd swiftyCompanion
|
||||
git init
|
||||
git remote add origin <url-vogosphere-swifty_companion>
|
||||
# premier push : uniquement les fichiers de ce dossier
|
||||
```
|
||||
|
||||
Dans le dépôt de **travail**, ignorer le `.git` imbriqué (déjà dans `.gitignore` racine) pour éviter un repo dans un repo par accident.
|
||||
|
||||
**Option B — Clone Vogosphere vide** puis copier le contenu de `swiftyCompanion/` (sans `docs/`) à la racine du clone.
|
||||
|
||||
Ne pas pousser : `docs/`, traductions, PDF, dépôt perso.
|
||||
|
||||
## Documentation (travail uniquement)
|
||||
|
||||
| Document | Lien |
|
||||
|----------|------|
|
||||
| Sujet FR (v4) | [docs/sujets/swifty-companion.md](docs/sujets/swifty-companion.md) |
|
||||
| PDF officiel | [docs/sujets/originaux/swifty_companion_subject.pdf](docs/sujets/originaux/swifty_companion_subject.pdf) |
|
||||
| Plan pas à pas | [docs/pedagogie/README.md](docs/pedagogie/README.md) |
|
||||
| Index doc | [docs/README.md](docs/README.md) |
|
||||
| Piscine mobile (référence) | [../piscine_mobile/README.md](../piscine_mobile/README.md) |
|
||||
|
||||
## Rendu école
|
||||
|
||||
Tout ce qui concerne correcteurs et soutenance : **[swiftyCompanion/README.md](swiftyCompanion/README.md)** (à lire depuis le dépôt Intra une fois poussé).
|
||||
|
||||
## Reprise du projet
|
||||
|
||||
**État actuel :** app Flutter dans `swiftyCompanion/swifty_companion/`, partie obligatoire codée.
|
||||
|
||||
→ [docs/pedagogie/00-reprise-et-etat.md](docs/pedagogie/00-reprise-et-etat.md) · **correction :** [guide-soutenance-realisation.md](docs/pedagogie/guide-soutenance-realisation.md)
|
||||
|
||||
## Démarrage
|
||||
|
||||
1. Lire le [sujet traduit](docs/sujets/swifty-companion.md).
|
||||
2. Suivre [00-reprise-et-etat.md](docs/pedagogie/00-reprise-et-etat.md) puis le [plan pédagogique](docs/pedagogie/README.md).
|
||||
3. Coder uniquement dans `swiftyCompanion/` ([README rendu](swiftyCompanion/README.md)).
|
||||
@@ -1,25 +0,0 @@
|
||||
# Documentation — Swifty Companion
|
||||
|
||||
Documentation du projet, sur le modèle de [piscine_mobile/docs](../piscine_mobile/docs/README.md).
|
||||
|
||||
## Sujet officiel
|
||||
|
||||
→ [sujets/README.md](sujets/README.md)
|
||||
|
||||
| Document | Lien |
|
||||
|----------|------|
|
||||
| Traduction française (v4) | [sujets/swifty-companion.md](sujets/swifty-companion.md) |
|
||||
| PDF Intra | [sujets/originaux/swifty_companion_subject.pdf](sujets/originaux/swifty_companion_subject.pdf) |
|
||||
|
||||
## Pédagogie
|
||||
|
||||
| Document | Lien |
|
||||
|----------|------|
|
||||
| **Reprise / état du projet** | [pedagogie/00-reprise-et-etat.md](pedagogie/00-reprise-et-etat.md) |
|
||||
| Plan des étapes | [pedagogie/README.md](pedagogie/README.md) |
|
||||
|
||||
## Dépôt de rendu 42 (hors dépôt de travail)
|
||||
|
||||
Le code et le README correcteurs vivent dans **`swiftyCompanion/`** — racine du futur dépôt Intra, séparé de ce dépôt perso.
|
||||
|
||||
→ [../swiftyCompanion/README.md](../swiftyCompanion/README.md) · workflow : [../README.md](../README.md#deux-dépôts-git-distincts)
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 112 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 10 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 36 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 112 KiB |
@@ -1,73 +0,0 @@
|
||||
# Plan de préparation — dépôt soutenance, doc et code
|
||||
|
||||
> **Méthode :** pour chaque lot, l’agent décrit ce qu’il compte faire → **tu valides ou ajustes** → on exécute → on coche.
|
||||
|
||||
**Dernière mise à jour :** lot 4 livré (commentaires `lib/` + DEBUG conservé).
|
||||
|
||||
---
|
||||
|
||||
## Décisions validées (lot 0)
|
||||
|
||||
| Point | Décision |
|
||||
|-------|----------|
|
||||
| Branche `soutenance` | Même contenu que rendu école ; `main` = travail perso ; présentation portable sur `soutenance` |
|
||||
| PC école | Clone perso → copie `swiftyCompanion/` → supprimer repo perso → `git init` rendu → push Vogosphere (détail §6 fiche dépôt) |
|
||||
| `.env` | **Uniquement** `swifty_companion/.env` en local ; **jamais** dans Git ; `.env.example` versionné (sujet : échec direct si secrets publics) |
|
||||
| Fiche orale (lot 3) | **Déroulé de soutenance** détaillé (sujet, points critiques, réalisation, commandes expliquées) — un fichier structuré logiquement |
|
||||
| Commentaires code | **Français** — sujet ne l’interdit pas |
|
||||
| Bouton DEBUG | **Garder** — aide à démontrer le bonus refresh en `flutter run` ; absent en release (`kDebugMode`) — [fiche-depot-soutenance.md §8](fiche-depot-soutenance.md) |
|
||||
|
||||
---
|
||||
|
||||
## Où vit quoi
|
||||
|
||||
| Zone | Chemin | Git |
|
||||
|------|--------|-----|
|
||||
| Travail | racine `swifty_companion/` | Gitea privé (`main`) |
|
||||
| Rendu / soutenance | `swiftyCompanion/` | Vogosphere + branche `soutenance` |
|
||||
| Oral long | `docs/pedagogie/` | perso uniquement |
|
||||
|
||||
---
|
||||
|
||||
## Ordre des lots
|
||||
|
||||
| Phase | Lot | Statut |
|
||||
|-------|-----|--------|
|
||||
| A | 0 Cadrage | ✅ Validé |
|
||||
| B | 1 Dépôt + branche | ✅ [fiche-depot-soutenance.md](fiche-depot-soutenance.md) |
|
||||
| C | 2 README rendu autonome | ✅ fait |
|
||||
| D | 3 Fiche soutenance orale | ✅ [fiche-soutenance-orale.md](fiche-soutenance-orale.md) |
|
||||
| E | 4 Commentaires `lib/` | ✅ fait |
|
||||
|
||||
---
|
||||
|
||||
## Lot 2 — Livré
|
||||
|
||||
- `swiftyCompanion/README.md` réécrit (autonome, sans lien `docs/`).
|
||||
- `.env.example` commenté (racine + `swifty_companion/.env.example`).
|
||||
- `swifty_companion/README.md` : pointeur court vers le README racine.
|
||||
|
||||
## Lot 3 — Livré
|
||||
|
||||
- [fiche-soutenance-orale.md](fiche-soutenance-orale.md) — déroulé oral, commandes commentées, démo, FAQ.
|
||||
|
||||
---
|
||||
|
||||
## Lot 4 — Livré
|
||||
|
||||
- Commentaires / dartdoc en **français** dans `swiftyCompanion/swifty_companion/lib/`.
|
||||
- Bouton **DEBUG** conservé (`kDebugMode`) pour la démo du bonus refresh — pas présent en APK release.
|
||||
|
||||
**Plan préparation :** tous les lots 0–4 sont cochés ; prochaine action opérationnelle = branche `soutenance` + push Vogosphere ([fiche-depot-soutenance.md](fiche-depot-soutenance.md)).
|
||||
|
||||
---
|
||||
|
||||
## Suivi
|
||||
|
||||
| Lot | Fait ? |
|
||||
|-----|--------|
|
||||
| 0 | ✅ |
|
||||
| 1 | ✅ |
|
||||
| 2 | ✅ |
|
||||
| 3 | ✅ |
|
||||
| 4 | ✅ |
|
||||
@@ -1,109 +0,0 @@
|
||||
# Reprise du projet — où nous en sommes
|
||||
|
||||
> Dernière mise à jour : étape 7 bonus (refresh token) codée.
|
||||
|
||||
## En bref
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **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** | **Rendu Vogosphere** + test manuel bonus (token invalide) |
|
||||
|
||||
## Arborescence actuelle
|
||||
|
||||
```
|
||||
swifty_companion/ ← dépôt Git de TRAVAIL (perso)
|
||||
├── README.md ← explique les 2 dépôts Git
|
||||
├── .gitignore ← ignore docs + swiftyCompanion/.git/
|
||||
├── docs/
|
||||
│ ├── README.md
|
||||
│ ├── sujets/
|
||||
│ │ ├── swifty-companion.md ← sujet traduit (référence)
|
||||
│ │ ├── originaux/swifty_companion_subject.pdf
|
||||
│ │ └── captures/ ← visuels PDF (couverture uniquement)
|
||||
│ └── pedagogie/
|
||||
│ ├── README.md ← plan des étapes
|
||||
│ └── 00-reprise-et-etat.md ← ce fichier
|
||||
└── swiftyCompanion/ ← futur dépôt ÉCOLE (racine Vogosphere)
|
||||
├── README.md
|
||||
├── .env.example
|
||||
└── swifty_companion/ ← projet Flutter (lib/, android/, pubspec.yaml)
|
||||
```
|
||||
|
||||
## Décisions déjà prises
|
||||
|
||||
1. **Deux dépôts Git** : tout `docs/` et la traduction restent dans le repo perso ; l’Intra ne reçoit que le contenu de `swiftyCompanion/`.
|
||||
2. **Stack** : Flutter, `flutter_dotenv`, `flutter_appauth`, `http`.
|
||||
3. **Secrets** : `.env` local uniquement, jamais commité (modèle dans `swiftyCompanion/.env.example`).
|
||||
4. **Redirect OAuth mobile** : `com.example.swiftycompanion://callback` (pas `localhost` sur téléphone physique).
|
||||
5. **Sujet PDF** : pas de maquettes d’écrans — l’UI suit la liste des critères obligatoires ([sujet FR](../sujets/swifty-companion.md)).
|
||||
|
||||
## Avancement par étape
|
||||
|
||||
| Étape | Contenu | Statut |
|
||||
|-------|---------|--------|
|
||||
| **0** | Structure, traduction sujet, séparation travail / école, README rendu | ✅ **Terminé** |
|
||||
| **1** | Projet Flutter `swiftyCompanion/swifty_companion/` | ✅ **Terminé** |
|
||||
| **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 (`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 + 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_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)
|
||||
|
||||
1. Ouvrir le dépôt : `~/projets/42School/swifty_companion`
|
||||
2. Relire le [sujet traduit](../sujets/swifty-companion.md) (section obligatoire).
|
||||
3. Vérifier Flutter : `flutter doctor` et `flutter devices`.
|
||||
4. Lancer l’app (après modification `.env`, relancer `flutter run`) :
|
||||
```bash
|
||||
cd swiftyCompanion/swifty_companion
|
||||
flutter run -d <device>
|
||||
```
|
||||
5. Tester le parcours : OAuth → recherche → profil → retour → erreurs réseau.
|
||||
|
||||
## Fichiers utiles selon le contexte
|
||||
|
||||
| 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) |
|
||||
| Workflow Git travail / école | [README racine](../../README.md) |
|
||||
| Soutenance / peer-eval | [swiftyCompanion/README.md](../../swiftyCompanion/README.md) |
|
||||
|
||||
## Guides détaillés
|
||||
|
||||
| Fichier | Statut |
|
||||
|---------|--------|
|
||||
| [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-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
|
||||
|
||||
- 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.
|
||||
@@ -1,56 +0,0 @@
|
||||
# Pédagogie — Swifty Companion
|
||||
|
||||
Parcours pour construire le projet **étape par étape**.
|
||||
|
||||
## Reprise rapide
|
||||
|
||||
→ **[00-reprise-et-etat.md](00-reprise-et-etat.md)** — état du projet.
|
||||
|
||||
→ **[00-plan-preparation-soutenance.md](00-plan-preparation-soutenance.md)** — plan (valider lot par lot).
|
||||
|
||||
→ **[fiche-depot-soutenance.md](fiche-depot-soutenance.md)** — branche `soutenance`, `.env`, push PC école, bouton DEBUG.
|
||||
|
||||
→ **[fiche-soutenance-orale.md](fiche-soutenance-orale.md)** — déroulé soutenance, commandes, démo, FAQ.
|
||||
|
||||
→ **[guide-soutenance-realisation.md](guide-soutenance-realisation.md)** — cartographie sujet ↔ code pour la correction.
|
||||
|
||||
## Prérequis
|
||||
|
||||
Base via la [piscine mobile](../../../piscine_mobile/README.md) sur le même disque :
|
||||
|
||||
- [Flutter — Ubuntu natif](../../../piscine_mobile/docs/pedagogie/flutter/09-ubuntu-natif-dev-android.md)
|
||||
- [Stratégie VM école / correction](../../../piscine_mobile/docs/pedagogie/flutter/07-strategie-wsl-vm-ecole-correction.md)
|
||||
|
||||
## Étapes du projet
|
||||
|
||||
| Étape | Objectif | Statut |
|
||||
|-------|----------|--------|
|
||||
| **0** | Dépôt travail + `docs/`, sujet FR, paquet `swiftyCompanion/` | ✅ **Fait** — [00-reprise-et-etat.md](00-reprise-et-etat.md) |
|
||||
| **1** | Projet `swiftyCompanion/swifty_companion/` + `flutter run` | ✅ **Fait** |
|
||||
| **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 | ✅ **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)
|
||||
|
||||
## Sujet de référence
|
||||
|
||||
→ [Traduction FR v4](../sujets/swifty-companion.md) · PDF : [originaux/swifty_companion_subject.pdf](../sujets/originaux/swifty_companion_subject.pdf)
|
||||
|
||||
## Guides détaillés
|
||||
|
||||
| Fichier | Statut |
|
||||
|---------|--------|
|
||||
| [00-reprise-et-etat.md](00-reprise-et-etat.md) | ✅ À jour |
|
||||
| [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-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.
|
||||
@@ -1,109 +0,0 @@
|
||||
# Audit conformité usage API 42 — Swifty Companion
|
||||
|
||||
> **Date :** audit local dépôt de travail
|
||||
> **Contraintes respectées :** aucune lecture du fichier `.env` local actuel ; **aucune requête** vers `api.intra.42.fr`.
|
||||
|
||||
Objectif : vérifier si le **code et le dépôt** peuvent expliquer une **extraction massive** (~140k profils) du type décrit par le mail 42 — ou seulement un usage **pédagogique ponctuel**.
|
||||
|
||||
---
|
||||
|
||||
## 1. Verdict global
|
||||
|
||||
| Question | Réponse |
|
||||
|----------|---------|
|
||||
| Le **code Flutter** ressemble-t-il à un scraper massif ? | **Non** |
|
||||
| Usage prévu du sujet Swifty Companion ? | **Oui** (1 login → 1 profil) |
|
||||
| Risque d’être l’**auteur principal** de la fuite nationale décrite ? | **Très improbable** si tu n’as pas lancé d’autres scripts |
|
||||
| Point **sécurité** à corriger dans **ton** Git ? | **Oui** — voir §4 |
|
||||
|
||||
---
|
||||
|
||||
## 2. Analyse du code (`swiftyCompanion/swifty_companion/lib/`)
|
||||
|
||||
### Appels API présents
|
||||
|
||||
| Fichier | Comportement | Volume typique |
|
||||
|---------|--------------|----------------|
|
||||
| `intra_user_service.dart` | `GET /v2/users?filter[login]=<un login>` | **1 utilisateur** par recherche |
|
||||
| `intra_profile_service.dart` | `GET /users/{login}`, évent. `cursus_users`, `projects_users` (page 100) | **1 profil** par navigation |
|
||||
| `intra_auth_service.dart` | OAuth + `GET /me` + refresh token | Session |
|
||||
| `intra_http.dart` | GET avec retry 429, refresh sur 401 | Par requête ci-dessus |
|
||||
|
||||
### Absent du dépôt (recherché)
|
||||
|
||||
- Pas de script `sh` / `py` d’export massif
|
||||
- Pas de boucle sur campus / pagination `page[number]` sur tous les users
|
||||
- Pas de `filter[campus]`, pas de dump CSV/JSON de milliers de profils
|
||||
- Pas de stockage fichier des profils (cache mémoire **2 min**, **1 id** à la fois)
|
||||
|
||||
**Conclusion technique :** l’application ne peut pas, **seule**, extraire 140k profils sans modification majeure du code.
|
||||
|
||||
---
|
||||
|
||||
## 3. Lien avec le mail école
|
||||
|
||||
Le mail décrit :
|
||||
|
||||
- extraction **automatisée** à très grande échelle ;
|
||||
- **multi-campus**, candidats + étudiants ;
|
||||
- via un **compte légitime** (pas forcément ton app OAuth).
|
||||
|
||||
Ton projet :
|
||||
|
||||
- recherche **manuelle** par login ;
|
||||
- quelques requêtes par profil ;
|
||||
- aligné sur le sujet (« consulter un pair »).
|
||||
|
||||
**Ce n’est pas le même profil d’usage**, sauf si tu as **en plus** utilisé un autre outil, un script perso, ou partagé des données.
|
||||
|
||||
---
|
||||
|
||||
## 4. Risque sécurité — dépôt Git (hors fuite massive)
|
||||
|
||||
Audit Git **sans afficher les valeurs** des secrets :
|
||||
|
||||
| Élément | État |
|
||||
|---------|------|
|
||||
| `swifty_companion/.env` | **Ignoré** par `.gitignore` (OK) |
|
||||
| `swifty_companion/.env copy` | **Versionné dans Git** (commit `06fe43b`) |
|
||||
| Contenu versionné | Présence de `CLIENT_ID` et `CLIENT_SECRET` dans ce fichier (valeurs **non** recopiées dans ce document) |
|
||||
| Remote | `origin` → **Gitea privé** perso (pas Vogosphere 42) |
|
||||
|
||||
**Risque :** si le dépôt est **privé** et que **toi seul** y as accès, l’exposition des clés via `.env copy` est **limitée** (pas équivalent à un repo public). Ça ne te relie **pas** à l’incident ~140k profils. Reste une **mauvaise pratique** (historique Git + sujet 42) : régénérer le SECRET si tu as déjà poussé ce fichier, retirer `.env copy` du suivi Git pour l’avenir.
|
||||
|
||||
### Actions recommandées (à faire de ton côté)
|
||||
|
||||
1. **Régénérer le SECRET** sur [Applications OAuth Intra](https://profile.intra.42.fr/oauth/applications) (même si déjà fait après le chat).
|
||||
2. **Retirer** `swifty_companion/.env copy` du suivi Git (`git rm --cached`, supprimer le fichier, commit).
|
||||
3. Si le dépôt distant a déjà reçu ce fichier : considérer **réécriture d’historique** ou invalidation des anciens secrets (SECRET régénéré = ancien jeton mort).
|
||||
4. Ne jamais committer `.env`, captures avec clés, ou dumps de profils.
|
||||
|
||||
---
|
||||
|
||||
## 5. Ce que cet audit n’a pas fait
|
||||
|
||||
- Pas d’appel à l’API 42 (pas de logs côté 42 consultés).
|
||||
- Pas de lecture du `.env` actuel sur disque.
|
||||
- Pas d’accès aux serveurs 42 / pas de corrélation avec l’enquête interne.
|
||||
|
||||
Seul **42** ou la police pourrait lier un compte OAuth à un volume de requêtes précis.
|
||||
|
||||
---
|
||||
|
||||
## 6. En cas de contact DPO / campus
|
||||
|
||||
Tu peux décrire factuellement :
|
||||
|
||||
- projet **Swifty Companion** (Flutter) ;
|
||||
- recherche **par login** uniquement ;
|
||||
- pas d’export massif dans le code livré ;
|
||||
- app OAuth **pédagogique** avec scopes `public` + `projects`.
|
||||
|
||||
Contact mail incident : **dpo@42.tech** (comme indiqué par l’école).
|
||||
|
||||
---
|
||||
|
||||
## 7. Références
|
||||
|
||||
- [guide-soutenance-realisation.md](guide-soutenance-realisation.md) — cartographie sujet ↔ code
|
||||
- [sujet traduit](../sujets/swifty-companion.md) — usage autorisé vs extraction massive (CGU 42)
|
||||
@@ -1,178 +0,0 @@
|
||||
# Étape 2 — OAuth2 Intra + token unique
|
||||
|
||||
> **Prérequis :** étape 1 OK (`swiftyCompanion/swifty_companion/` créé, `flutter run` fonctionne).
|
||||
|
||||
## Objectif de l’étape
|
||||
|
||||
1. Obtenir un **access token** via **OAuth2 de l’Intra** (pas un token par requête API).
|
||||
2. Stocker les secrets dans **`.env`** (racine `swiftyCompanion/` ou dossier `swifty_companion/` — un seul endroit, documenté dans le README).
|
||||
3. Avoir un **service d’auth** réutilisable pour toutes les futures requêtes `api.intra.42.fr`.
|
||||
|
||||
Les **vues** (recherche login, profil) viennent aux étapes 3–4 ; ici on pose l’authentification API.
|
||||
|
||||
---
|
||||
|
||||
## 2.1 — Vérifier l’app
|
||||
|
||||
```bash
|
||||
cd ~/projets/42School/swifty_companion/swiftyCompanion/swifty_companion
|
||||
flutter pub get
|
||||
flutter run -d <device>
|
||||
```
|
||||
|
||||
L’écran compteur par défaut suffit pour valider l’étape 1.
|
||||
|
||||
---
|
||||
|
||||
## 2.2 — Créer l’application OAuth sur l’Intra
|
||||
|
||||
1. [profile.intra.42.fr/oauth/applications](https://profile.intra.42.fr/oauth/applications) → **New application**.
|
||||
2. Renseigner :
|
||||
- **Name** : ex. `swifty_companion_dev`
|
||||
- **Website** : URL factice ou repo perso (souvent requis)
|
||||
- **Redirect URI** : doit correspondre **exactement** à ce que l’app utilisera (voir 2.3)
|
||||
3. Noter **UID** (= `CLIENT_ID`) et **SECRET** (= `CLIENT_SECRET`).
|
||||
|
||||
Copier `swiftyCompanion/.env.example` → `.env` (même niveau que le README rendu ou dans `swifty_companion/`) :
|
||||
|
||||
```env
|
||||
CLIENT_ID=...
|
||||
CLIENT_SECRET=...
|
||||
REDIRECT_URI=...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2.3 — Choisir le `REDIRECT_URI` (mobile)
|
||||
|
||||
### Erreur Intra : « must be a valid URI »
|
||||
|
||||
Le formulaire **42 Intra** accepte en pratique surtout des URI **`http://` ou `https://`**.
|
||||
Les schémas du type `com.example.swifty_companion://callback` sont souvent **refusés** (validateur strict + `_` dans le schéma).
|
||||
|
||||
**Type d’application Intra :** `42 pedagogic project` (normal pour Swifty Companion — pas de type « mobile »).
|
||||
|
||||
**URI de redirection** — format **http(s)://host:port/chemin** uniquement (pas de `com.example://…`).
|
||||
|
||||
Essayer **dans cet ordre** (une seule ligne dans le champ, sans guillemets ni espace) :
|
||||
|
||||
1. `http://localhost:8080/callback` ← le plus courant sur l’Intra 42
|
||||
2. `http://localhost:3000/callback`
|
||||
3. `http://127.0.0.1:8080/callback`
|
||||
|
||||
Exemple tiré de la [doc 42](https://api.intra.42.fr/apidoc/guides/web_application_flow) :
|
||||
`http://localhost:1919/users/auth/ft/callback` (tu peux t’en inspirer, mais garde une URI simple type `/callback`).
|
||||
|
||||
Copier **exactement** la ligne acceptée dans `.env` et dans Flutter.
|
||||
|
||||
Avec `flutter_appauth`, Android/iOS interceptent cette URL de boucle locale après le login dans le navigateur — pas besoin d’un vrai serveur web.
|
||||
|
||||
| Approche | Redirect URI | Intra 42 |
|
||||
|----------|--------------|----------|
|
||||
| **Recommandé (Flutter + 42)** | `http://127.0.0.1:42800/callback` | ✅ en général accepté |
|
||||
| Schéma custom | `com.example.swifty_companion://callback` | ❌ souvent refusé |
|
||||
| Site web classique | `https://ton-domaine.com/callback` | ✅ si tu as un backend |
|
||||
|
||||
Le même URI doit être : formulaire Intra, `.env`, et paramètre `redirect_uri` dans l’app.
|
||||
|
||||
**Android** : `applicationId` = `com.example.swifty_companion` (`android/app/build.gradle.kts`) — indépendant du `REDIRECT_URI` loopback.
|
||||
|
||||
Endpoints 42 (référence) :
|
||||
|
||||
| Étape | URL |
|
||||
|-------|-----|
|
||||
| Autorisation | `GET https://api.intra.42.fr/oauth/authorize` |
|
||||
| Token | `POST https://api.intra.42.fr/oauth/token` |
|
||||
| API | `GET https://api.intra.42.fr/v2/...` avec header `Authorization: Bearer <token>` |
|
||||
|
||||
Paramètres authorize usuels : `client_id`, `redirect_uri`, `response_type=code`, `scope=public projects` (voir [intra-oauth-formulaire.md](intra-oauth-formulaire.md)).
|
||||
|
||||
---
|
||||
|
||||
## 2.4 — Dépendances Flutter suggérées
|
||||
|
||||
Dans `swifty_companion/` :
|
||||
|
||||
```bash
|
||||
flutter pub add flutter_dotenv http
|
||||
# selon le flux choisi, par ex. :
|
||||
# flutter pub add flutter_appauth
|
||||
```
|
||||
|
||||
| Package | Rôle |
|
||||
|---------|------|
|
||||
| `flutter_dotenv` | Charger `.env` au démarrage |
|
||||
| `http` | Échange code → token + appels API |
|
||||
| `flutter_appauth` | Flux OAuth2 + redirect natif (optionnel mais pratique) |
|
||||
|
||||
---
|
||||
|
||||
## 2.5 — Architecture minimale (fichiers à créer)
|
||||
|
||||
```
|
||||
lib/
|
||||
├── main.dart # dotenv.load + MaterialApp
|
||||
├── config/
|
||||
│ └── env.dart # lecture CLIENT_ID, etc.
|
||||
└── services/
|
||||
└── intra_auth_service.dart # token en mémoire (singleton)
|
||||
```
|
||||
|
||||
Règles sujet :
|
||||
|
||||
- **Un seul token** : le service le garde après le login OAuth ; les futures requêtes réutilisent `getAccessToken()`.
|
||||
- **Ne pas** appeler `/oauth/token` avant chaque `GET /v2/users/...`.
|
||||
|
||||
Pseudo-contrat du service :
|
||||
|
||||
```dart
|
||||
class IntraAuthService {
|
||||
String? _accessToken;
|
||||
String? get accessToken => _accessToken;
|
||||
bool get isAuthenticated => _accessToken != null;
|
||||
|
||||
Future<void> signIn(); // ouvre le flux OAuth, stocke le token
|
||||
Future<String> getValidToken(); // retourne le token (bonus : refresh plus tard)
|
||||
}
|
||||
```
|
||||
|
||||
Test manuel étape 2 : bouton « Se connecter à l’Intra » → navigateur / WebView → retour app → afficher temporairement « Connecté » ou le login `/v2/me`.
|
||||
|
||||
---
|
||||
|
||||
## 2.6 — `.env` et assets
|
||||
|
||||
Si `flutter_dotenv` charge depuis la racine du package Flutter :
|
||||
|
||||
1. Placer `.env` dans `swifty_companion/` (ignoré par git).
|
||||
2. Déclarer dans `pubspec.yaml` :
|
||||
|
||||
```yaml
|
||||
flutter:
|
||||
assets:
|
||||
- .env
|
||||
```
|
||||
|
||||
> En production soutenance : les correcteurs créent **leur** `.env` avec **leur** app OAuth ; ne jamais committer le fichier.
|
||||
|
||||
---
|
||||
|
||||
## 2.7 — Critère « terminé » pour l’étape 2
|
||||
|
||||
| Check | OK si |
|
||||
|-------|--------|
|
||||
| OAuth Intra | Connexion obtient un access token |
|
||||
| Token unique | Plusieurs appels API utilisent le **même** token sans re-login |
|
||||
| Secrets | Uniquement dans `.env`, pas dans le code en dur |
|
||||
| Doc | README rendu rappelle où mettre `.env` et comment lancer l’app |
|
||||
|
||||
---
|
||||
|
||||
## Ensuite — étape 3
|
||||
|
||||
→ [etape-03-vue-recherche.md](etape-03-vue-recherche.md) (à rédiger quand l’étape 2 est validée) : **vue 1** — champ login, appel API, erreurs 404 / réseau.
|
||||
|
||||
Documentation API utile :
|
||||
|
||||
- [apidoc](https://api.intra.42.fr/apidoc)
|
||||
- User : `GET /v2/users/:id` ou recherche par login (voir doc)
|
||||
@@ -1,106 +0,0 @@
|
||||
# Étape 3 — Vue 1 : recherche par login
|
||||
|
||||
> **Prérequis :** étape 2 OK — OAuth actif, token unique (`IntraAuthService`).
|
||||
|
||||
## Objectif sujet
|
||||
|
||||
| Exigence | Cette étape |
|
||||
|----------|-------------|
|
||||
| Au moins 2 vues | **Vue 1** : recherche · **Vue 2** : profil (étape 4) |
|
||||
| Erreurs gérées | Login introuvable, réseau, non connecté |
|
||||
| Token unique | Réutiliser `IntraAuthService.instance` (ne pas refaire OAuth) |
|
||||
|
||||
---
|
||||
|
||||
## Parcours utilisateur (vue 1)
|
||||
|
||||
1. L’utilisateur est déjà connecté à l’API (étape 2) — ou bouton « Se connecter » si token absent.
|
||||
2. **Champ texte** + bouton **Rechercher** (login 42, ex. `jdoe`).
|
||||
3. Appel API avec `Authorization: Bearer <token>`.
|
||||
4. **Succès** → navigation vers **vue 2** (profil).
|
||||
5. **Échec** → message clair, pas de crash.
|
||||
|
||||
---
|
||||
|
||||
## API 42 utile
|
||||
|
||||
Doc : [api.intra.42.fr/apidoc](https://api.intra.42.fr/apidoc)
|
||||
|
||||
| Action | Requête typique |
|
||||
|--------|-----------------|
|
||||
| Utilisateur par login | `GET /v2/users/{login}` ou `GET /v2/users?filter[login]=<login>` |
|
||||
| Toi (déjà fait) | `GET /v2/me` |
|
||||
|
||||
Vérifier dans l’apidoc la forme exacte supportée ; beaucoup de projets utilisent :
|
||||
|
||||
```http
|
||||
GET https://api.intra.42.fr/v2/users?filter[login]=jdoe
|
||||
Authorization: Bearer <access_token>
|
||||
```
|
||||
|
||||
Réponse : tableau JSON ; **vide** = login inconnu.
|
||||
|
||||
---
|
||||
|
||||
## Fichiers à ajouter (étape 3)
|
||||
|
||||
```
|
||||
lib/
|
||||
├── services/
|
||||
│ ├── intra_auth_service.dart # existant
|
||||
│ └── intra_user_service.dart # GET user(s) avec token
|
||||
├── screens/
|
||||
│ ├── search_screen.dart # VUE 1
|
||||
│ └── profile_screen.dart # VUE 2 (squelette → étape 4)
|
||||
└── main.dart # home → SearchScreen si token OK
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `IntraUserService` (idée)
|
||||
|
||||
- Méthode `Future<Map<String, dynamic>> fetchUserByLogin(String login)`.
|
||||
- Utilise `IntraAuthService.instance.authHeaders()`.
|
||||
- **404 / liste vide** → exception métier `UserNotFoundException`.
|
||||
- **Socket / timeout** → `NetworkException`.
|
||||
- **401** → token expiré → message « reconnecte-toi ».
|
||||
|
||||
---
|
||||
|
||||
## Gestion des erreurs (sujet)
|
||||
|
||||
| Situation | Message utilisateur (exemple) |
|
||||
|-----------|-------------------------------|
|
||||
| Login inconnu | `Aucun utilisateur pour ce login` |
|
||||
| Pas de réseau | `Connexion impossible. Vérifie le réseau.` |
|
||||
| Non connecté OAuth | `Connecte-toi d’abord à l’API 42` |
|
||||
| Champ vide | `Saisis un login` |
|
||||
|
||||
---
|
||||
|
||||
## Navigation
|
||||
|
||||
```text
|
||||
SearchScreen --[login trouvé]--> ProfileScreen(login)
|
||||
ProfileScreen --[retour]--> SearchScreen
|
||||
```
|
||||
|
||||
`Navigator.push` / `pop` ou `go_router` — rester simple (Navigator 1.0 suffit).
|
||||
|
||||
---
|
||||
|
||||
## Critères « étape 3 terminée »
|
||||
|
||||
| # | Test |
|
||||
|---|------|
|
||||
| 1 | Login valide (ex. `fgras-ca`) → ouvre vue 2 |
|
||||
| 2 | Login `zzzzzzzz` → message erreur |
|
||||
| 3 | Mode avion → message réseau |
|
||||
| 4 | Retour depuis vue 2 → nouvelle recherche possible |
|
||||
| 5 | Un seul OAuth ; les recherches réutilisent le même token |
|
||||
|
||||
---
|
||||
|
||||
## Ensuite — étape 4
|
||||
|
||||
→ [etape-04-vue-profil.md](etape-04-vue-profil.md) : photo, ≥ 4 champs, skills, projets (dont failed), layout adaptatif.
|
||||
@@ -1,113 +0,0 @@
|
||||
# Étape 4 — Vue 2 : profil complet
|
||||
|
||||
> **Prérequis :** [étape 3 — recherche](etape-03-vue-recherche.md) (navigation vers vue 2 avec un login valide).
|
||||
|
||||
## Objectif sujet
|
||||
|
||||
| Exigence | Cette étape |
|
||||
|----------|-------------|
|
||||
| ≥ 4 infos + photo | login (titre / avatar), email, téléphone, niveau cursus, localisation, wallet |
|
||||
| Skills | Niveau entier + **pourcentage** (partie décimale du `level` API) |
|
||||
| Projets | Statuts **`finished`** et **`failed`** |
|
||||
| Erreurs | Réseau, 401, réponse API inattendue — message + réessai |
|
||||
| Token | Un seul OAuth : `IntraAuthService` pour tous les `GET` |
|
||||
| Retour vue 1 | `AppBar` + bouton retour système (`Navigator.pop`) |
|
||||
| Layout | `LayoutBuilder` : colonne sur téléphone, 2 colonnes si largeur ≥ 720 px |
|
||||
|
||||
---
|
||||
|
||||
## Parcours utilisateur (vue 2)
|
||||
|
||||
1. Arrivée depuis la recherche avec un objet `user` minimal (`id`, `login`, parfois `image`).
|
||||
2. **Chargement** : appels API en parallèle (spinner plein écran).
|
||||
3. **Succès** : photo, carte « Informations », liste skills, liste projets.
|
||||
4. **Échec** : message rouge + bouton **Réessayer**.
|
||||
5. **Retour** : flèche AppBar → vue 1, nouvelle recherche possible.
|
||||
|
||||
---
|
||||
|
||||
## API 42 utilisée
|
||||
|
||||
Doc : [api.intra.42.fr/apidoc](https://api.intra.42.fr/apidoc)
|
||||
|
||||
| Donnée | Endpoint | Notes |
|
||||
|--------|----------|--------|
|
||||
| Fiche user | *(déjà renvoyée par la recherche étape 3)* | email, phone, location, wallet, image — pas de 2ᵉ `GET /users/{id}` pour limiter le quota |
|
||||
| Fiche complète | `GET /v2/users/{login}` | inclut souvent `cursus_users` + `skills` par cursus |
|
||||
| Niveau / skills | `cursus_users[].skills` | pas `/experiences` (souvent **403** avec token étudiant) |
|
||||
| Projets | `GET /v2/users/{id}/projects_users?page[size]=100` | scope OAuth **`projects`** ; filtrer côté app : `finished` / `failed` |
|
||||
|
||||
Tous les appels :
|
||||
|
||||
```http
|
||||
Authorization: Bearer <access_token>
|
||||
Accept: application/json
|
||||
```
|
||||
|
||||
**Confidentialité :** certains champs peuvent être masqués par l’API selon le profil consulté — afficher `—` si absent (sujet OAuth).
|
||||
|
||||
### Skills — niveau et pourcentage
|
||||
|
||||
L’API renvoie un `level` flottant (ex. `8.42`) :
|
||||
|
||||
- **Niveau affiché** : partie entière (`8`)
|
||||
- **Pourcentage** : partie décimale × 100 (`42 %`)
|
||||
|
||||
---
|
||||
|
||||
## Fichiers (étape 4)
|
||||
|
||||
```
|
||||
lib/
|
||||
├── services/
|
||||
│ ├── intra_auth_service.dart # existant — headers Bearer
|
||||
│ ├── intra_user_service.dart # existant — recherche
|
||||
│ └── intra_profile_service.dart # NEW — agrégation profil
|
||||
└── screens/
|
||||
└── profile_screen.dart # StatefulWidget — chargement + UI
|
||||
```
|
||||
|
||||
### `IntraProfileService`
|
||||
|
||||
- `fetchProfile(Map<String, dynamic> summaryUser)` → `IntraUserProfile`
|
||||
- Requêtes **séquentielles** (pause ~450 ms) + retry sur 429 (`intra_http.dart`)
|
||||
- Cache profil 2 min par `user_id` pour éviter de re-taper l’API au retour arrière
|
||||
- Mêmes exceptions que l’étape 3 : `IntraNetworkException`, `IntraUnauthorizedException`, etc.
|
||||
|
||||
### `ProfileScreen`
|
||||
|
||||
- Reçoit le `user` de l’étape 3
|
||||
- `initState` → `_load()`
|
||||
- Pas de second OAuth
|
||||
|
||||
---
|
||||
|
||||
## Gestion des erreurs (vue 2)
|
||||
|
||||
| Situation | Comportement |
|
||||
|-----------|----------------|
|
||||
| Pas de réseau | Message + **Réessayer** |
|
||||
| Token expiré (401) | « Session expirée… » — reconnecter depuis vue 1 |
|
||||
| Trop de requêtes (429) | Message dédié ; retry automatique côté client + attendre avant de réessayer |
|
||||
| Accès refusé (403) | Scopes `public` + `projects` sur l’app OAuth, puis **reconnexion** dans l’app |
|
||||
| ID manquant | Erreur métier explicite |
|
||||
| Listes vides skills / projets | Texte informatif, pas de crash |
|
||||
|
||||
---
|
||||
|
||||
## Critères « étape 4 terminée »
|
||||
|
||||
| # | Test |
|
||||
|---|------|
|
||||
| 1 | Login valide → photo + au moins 4 champs remplis ou `—` |
|
||||
| 2 | Section skills avec niveau **et** pourcentage |
|
||||
| 3 | Au moins un projet `finished` ou `failed` visible (si l’étudiant en a) |
|
||||
| 4 | Mode avion sur vue 2 → erreur + réessai |
|
||||
| 5 | Retour → vue 1 sans perdre la session OAuth |
|
||||
| 6 | Largeur tablette / fenêtre large → mise en page 2 colonnes |
|
||||
|
||||
---
|
||||
|
||||
## Ensuite — étape 5
|
||||
|
||||
→ Affiner navigation, contraintes et polish responsive sur **les deux vues** (`etape-05-navigation-layout.md` à rédiger).
|
||||
@@ -1,74 +0,0 @@
|
||||
# É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.
|
||||
@@ -1,118 +0,0 @@
|
||||
# É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).
|
||||
@@ -1,93 +0,0 @@
|
||||
# É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
|
||||
```
|
||||
@@ -1,233 +0,0 @@
|
||||
# Fiche — dépôt de soutenance, branche `soutenance`, PC école
|
||||
|
||||
> **Lot 1** du [plan de préparation](00-plan-preparation-soutenance.md).
|
||||
> **Validé :** branche `soutenance` = même contenu que Vogosphere ; travail quotidien sur `main` ; `.env` local uniquement ; `.env.example` dans le rendu Git.
|
||||
|
||||
---
|
||||
|
||||
## 1. Ce que dit le sujet (sécurité — **éliminatoire**)
|
||||
|
||||
Traduction du PDF v4, chapitre IV (encadré page 5) :
|
||||
|
||||
> Les identifiants, clés API, variables d’environnement, etc. doivent être stockés **localement** dans un fichier **`.env`** et **ignorés par git**. Des identifiants exposés **publiquement** entraînent **l’échec direct** du projet.
|
||||
|
||||
**À retenir pour le rendu :**
|
||||
|
||||
| Fichier | Dans le Git école (Vogosphere) ? |
|
||||
|---------|----------------------------------|
|
||||
| `.env` (avec vrais secrets) | **NON — jamais** |
|
||||
| `.env.example` (modèle vide + explications) | **OUI** |
|
||||
| `.gitignore` qui ignore `.env` | **OUI** |
|
||||
|
||||
Le correcteur crée **son** `.env` sur sa machine à partir de `.env.example` + **son** app OAuth Intra (ou la tienne en démo, **sans** committer les valeurs).
|
||||
|
||||
---
|
||||
|
||||
## 2. Où mettre le `.env` pour que l’app tourne
|
||||
|
||||
Flutter charge le fichier déclaré dans `pubspec.yaml` :
|
||||
|
||||
```yaml
|
||||
assets:
|
||||
- .env
|
||||
```
|
||||
|
||||
→ chemin **obligatoire pour l’exécution** :
|
||||
|
||||
```text
|
||||
swiftyCompanion/swifty_companion/.env
|
||||
```
|
||||
|
||||
| Fichier | Rôle |
|
||||
|---------|------|
|
||||
| `swifty_companion/.env` | **Secrets réels** — machine locale (portable, école en démo) — **gitignore** |
|
||||
| `swiftyCompanion/.env.example` | **Modèle** à la racine du paquet rendu — **versionné** |
|
||||
| `swiftyCompanion/.env` (racine paquet) | **Inutile** pour Flutter — à **ne pas** utiliser pour éviter la confusion (tu peux le supprimer en local) |
|
||||
|
||||
**Procédure correcteur / toi :**
|
||||
|
||||
```bash
|
||||
cd swiftyCompanion/swifty_companion
|
||||
cp ../.env.example .env
|
||||
# Éditer .env : CLIENT_ID, CLIENT_SECRET, REDIRECT_URI
|
||||
flutter pub get
|
||||
flutter run -d <device>
|
||||
```
|
||||
|
||||
*(Le lot 2 enrichira `.env.example` avec commentaires détaillés par variable.)*
|
||||
|
||||
---
|
||||
|
||||
## 3. Contenu exact du dépôt école (Vogosphere)
|
||||
|
||||
**Racine du dépôt Intra** = contenu du dossier `swiftyCompanion/` (pas le repo perso entier).
|
||||
|
||||
```text
|
||||
swiftyCompanion/ ← racine Git Vogosphere
|
||||
├── README.md
|
||||
├── .gitignore
|
||||
├── .env.example
|
||||
└── swifty_companion/ ← projet Flutter
|
||||
├── pubspec.yaml
|
||||
├── lib/
|
||||
├── android/
|
||||
├── test/
|
||||
└── …
|
||||
```
|
||||
|
||||
**Ne doit pas apparaître dans Vogosphere :**
|
||||
|
||||
- `docs/` (sujet traduit, pédagogie, fiches orale)
|
||||
- tout `.env` avec secrets
|
||||
- dépôt Gitea perso, liens vers guides internes
|
||||
|
||||
---
|
||||
|
||||
## 4. Deux Git, deux rôles
|
||||
|
||||
| Dépôt | Remote typique | Contenu |
|
||||
|-------|----------------|---------|
|
||||
| **Perso** | Gitea privé | `docs/` + `swiftyCompanion/` (travail) |
|
||||
| **École** | Vogosphere | **uniquement** `swiftyCompanion/` à la racine |
|
||||
|
||||
Sur le **portable**, tu continues à coder sur **`main`** (perso). Quand le rendu est prêt, tu mets à jour la branche **`soutenance`** (voir §5) — c’est la référence « propre » pour présenter et pour préparer le push école.
|
||||
|
||||
---
|
||||
|
||||
## 5. Branche `soutenance` (portable)
|
||||
|
||||
### Objectif
|
||||
|
||||
- Même **arbre utile** que Vogosphere : tout sous `swiftyCompanion/` à jour et fonctionnel.
|
||||
- Tu **peux** continuer à développer sur `main` ; `soutenance` ne bouge que quand tu **décides** de figer une version pour correction.
|
||||
- En soutenance sur **ton PC** : tu ouvres le projet dans `swiftyCompanion/swifty_companion/` (pas besoin d’afficher `docs/` à l’écran).
|
||||
|
||||
### Méthode recommandée (monorepo perso)
|
||||
|
||||
```bash
|
||||
cd ~/projets/42School/swifty_companion
|
||||
|
||||
# 1) Finir les modifs sur main (swiftyCompanion + éventuellement docs)
|
||||
git checkout main
|
||||
git status # rien de secret à committer
|
||||
|
||||
# 2) Mettre à jour soutenance (merge depuis main)
|
||||
git checkout soutenance # créer une fois : git checkout -b soutenance
|
||||
git merge main # ou cherry-pick des commits rendu uniquement
|
||||
|
||||
# 3) Vérifier que seul le paquet rendu est cohérent
|
||||
cd swiftyCompanion/swifty_companion
|
||||
flutter analyze && flutter test
|
||||
# .env présent en local, absent du git :
|
||||
git -C ../.. ls-files | rg '\.env' # doit montrer seulement .env.example
|
||||
```
|
||||
|
||||
**Présentation :** `git checkout soutenance`, lancer `flutter run` depuis `swifty_companion/`.
|
||||
|
||||
**Travail après soutenance :** retour sur `main`, nouvelles features ; avant rendu final, re-merge vers `soutenance`.
|
||||
|
||||
### Alternative (si tu préfères un repo Git **uniquement** dans `swiftyCompanion/`)
|
||||
|
||||
- `git init` dans `swiftyCompanion/`, remote Vogosphere.
|
||||
- Branche `main` = école, branche `dev` optionnelle.
|
||||
- Le monorepo perso ne versionne plus le code Flutter (plus lourd à maintenir). **Tu as choisi le monorepo + branche `soutenance`** — la méthode ci-dessus suffit.
|
||||
|
||||
---
|
||||
|
||||
## 6. Ton scénario **PC école** (push Vogosphere)
|
||||
|
||||
C’est le flux que tu as décrit — formalisé pour ne rien oublier.
|
||||
|
||||
### Prérequis
|
||||
|
||||
- Branche `soutenance` à jour sur Gitea **ou** dossier `swiftyCompanion/` prêt sur clé USB / copie.
|
||||
- `.env` **sur la clé ou la machine école** pour la démo (fichier local, **pas** dans le commit).
|
||||
- URL du dépôt **Vogosphere** (Intra 42).
|
||||
|
||||
### Étapes sur le PC école
|
||||
|
||||
```bash
|
||||
# A — Récupérer le travail (une fois)
|
||||
cd ~
|
||||
git clone https://ton-gitea/.../swifty_companion.git
|
||||
cd swifty_companion
|
||||
git checkout soutenance
|
||||
|
||||
# B — Préparer le dossier de rendu (contenu école pur)
|
||||
# Option simple : le rendu EST le sous-dossier swiftyCompanion
|
||||
cd ~/chemin/attendu/par/l/ecole # répertoire de rendu du sujet
|
||||
rm -rf ./* # seulement si dossier vide / dédié — attention !
|
||||
cp -a ~/swifty_companion/swiftyCompanion/. .
|
||||
|
||||
# C — Retirer le dépôt perso de la machine école (optionnel mais ton choix)
|
||||
rm -rf ~/swifty_companion
|
||||
|
||||
# D — Git ÉCOLE uniquement dans le répertoire de rendu
|
||||
cd ~/chemin/attendu/par/l/ecole
|
||||
git init
|
||||
git remote add origin <url-vogosphere-swifty_companion>
|
||||
git add .
|
||||
git status # VÉRIFIER : pas de .env, pas de docs/
|
||||
|
||||
git commit -m "Rendu Swifty Companion"
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
### Contrôles avant `git push` (checklist)
|
||||
|
||||
- [ ] `git status` ne liste **aucun** `.env`
|
||||
- [ ] `.env.example` est présent et **sans** vraies clés
|
||||
- [ ] Pas de dossier `docs/` du dépôt perso
|
||||
- [ ] `flutter test` OK dans `swifty_companion/`
|
||||
- [ ] README à la **racine** du clone Vogosphere (pas un niveau au-dessus)
|
||||
|
||||
---
|
||||
|
||||
## 7. Portable = démo = même binaire « logique » que l’école
|
||||
|
||||
| Action | Portable (`soutenance`) | Vogosphere |
|
||||
|--------|-------------------------|------------|
|
||||
| Code Flutter | `swiftyCompanion/swifty_companion/` | idem |
|
||||
| Secrets | `.env` local | jamais poussé |
|
||||
| Doc longue oral | `docs/pedagogie/` (perso) | absent |
|
||||
| README correcteur | `swiftyCompanion/README.md` | idem |
|
||||
|
||||
---
|
||||
|
||||
## 8. Point « DEBUG » dans l’app (éclaircissement)
|
||||
|
||||
Ce n’est **pas** un fichier `.env` ni un secret.
|
||||
|
||||
Dans `search_screen.dart`, un bouton **« DEBUG : simuler token expiré »** n’apparaît que si `kDebugMode` est vrai (build **debug**, ex. `flutter run`). Il **n’existe pas** dans un APK **release** (`flutter build apk --release`).
|
||||
|
||||
| Question | Réponse |
|
||||
|----------|---------|
|
||||
| Le sujet l’interdit ? | **Non** — rien dans le PDF sur les boutons de debug. |
|
||||
| Risque en soutenance ? | L’évaluateur peut le voir en `flutter run` ; tu peux l’expliquer pour le **bonus refresh**, ou le retirer avant rendu final si tu veux une UI plus « propre ». |
|
||||
| Décision (lot 4) | **Garder** — le bonus sujet = refresh token ; le bouton ne fait qu’**invalider l’access** en debug pour tester le refresh sans attendre l’expiration. Absent en `flutter build apk --release`. |
|
||||
|
||||
→ Démo orale : [fiche-soutenance-orale.md](fiche-soutenance-orale.md) §7.
|
||||
|
||||
---
|
||||
|
||||
## 9. Commentaires en français dans le code
|
||||
|
||||
Le sujet n’impose pas la langue des commentaires. **Français = OK** (lot 4).
|
||||
|
||||
---
|
||||
|
||||
## 10. Prochaines étapes (autres lots)
|
||||
|
||||
| Lot | Document / action |
|
||||
|-----|-------------------|
|
||||
| 2 | README rendu + `.env.example` commenté (sans ton « IA ») |
|
||||
| 3 | `fiche-soutenance-orale.md` — déroulé minute par minute + commandes |
|
||||
| 4 | Commentaires `lib/` + décision finale bouton DEBUG |
|
||||
|
||||
---
|
||||
|
||||
## 11. Liens
|
||||
|
||||
- Plan global : [00-plan-preparation-soutenance.md](00-plan-preparation-soutenance.md)
|
||||
- Sujet sécurité : [swifty-companion.md](../sujets/swifty-companion.md) § IV
|
||||
- PDF : [originaux/swifty_companion_subject.pdf](../sujets/originaux/swifty_companion_subject.pdf)
|
||||
@@ -1,257 +0,0 @@
|
||||
# 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é.*
|
||||
@@ -1,289 +0,0 @@
|
||||
# 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)
|
||||
- [ ] Fichier **`.env copy`** retiré du Git — voir [audit-conformite-usage-api.md](audit-conformite-usage-api.md)
|
||||
|
||||
---
|
||||
|
||||
*Dernière alignement : sujet PDF v4 — traduction [swifty-companion.md](../sujets/swifty-companion.md).*
|
||||
@@ -1,59 +0,0 @@
|
||||
# Formulaire OAuth Intra — valeurs pour Swifty Companion
|
||||
|
||||
À copier-coller sur [Nouvelle application API](https://profile.intra.42.fr/oauth/applications/new) (ou édition).
|
||||
|
||||
## Paramètres obligatoires
|
||||
|
||||
| Champ (FR) | Valeur proposée |
|
||||
|------------|-----------------|
|
||||
| **Nom** * | `Swifty Companion` |
|
||||
| **Image** | `docs/assets/swifty_companion_oauth_icon_256.png` ou `swifty_companion_oauth_icon_256.jpg` (vrai PNG/JPEG, 256×256). Ne pas uploader l’ancien fichier mal nommé (`.png` qui était en réalité du JPEG). |
|
||||
| **Description** * | `Projet pédagogique Mobile Initiation : application Flutter pour afficher le profil d’un étudiant 42 via l’API Intra.` |
|
||||
| **Type d’application** * | **`42 pedagogic project`** (choix correct ; il n’y a pas de type « mobile ») |
|
||||
| **Site web** | `https://www.42.fr` ou l’URL de ton dépôt GitHub / Git perso (placeholder accepté) |
|
||||
| **Publique** | **Décoché** (app perso de projet) |
|
||||
|
||||
## URI de redirection
|
||||
|
||||
**Téléphone physique (recommandé Swifty)** — une ligne :
|
||||
|
||||
```text
|
||||
com.example.swiftycompanion://callback
|
||||
```
|
||||
|
||||
(Sans `_` dans le schéma — souvent accepté par l’Intra ; sinon voir [oauth-test-manuel.md](oauth-test-manuel.md).)
|
||||
|
||||
**Émulateur seulement** (optionnel) :
|
||||
|
||||
```text
|
||||
http://localhost:8080/callback
|
||||
```
|
||||
|
||||
**À ne pas mettre :**
|
||||
|
||||
- `com.example.swifty_companion://callback`
|
||||
- lignes vides ou plusieurs URI mélangées
|
||||
- espaces avant/après
|
||||
|
||||
## Champs d’application (scopes)
|
||||
|
||||
Cocher :
|
||||
|
||||
- [x] **Accéder aux données publiques de l'utilisateur** (`public`)
|
||||
- [x] **Accéder aux données des projets de l'utilisateur** (`projects`) — requis pour `projects_users` (sinon **403** sur le profil)
|
||||
|
||||
Laisser **décoché** le reste sauf besoin explicite (équipes, médias, forum, etc.).
|
||||
|
||||
Après ajout du scope `projects`, **déconnexion puis nouvelle connexion** dans l’app pour obtenir un token à jour.
|
||||
|
||||
## Après création
|
||||
|
||||
Sur la page de l’application :
|
||||
|
||||
| Intra | `.env` |
|
||||
|-------|--------|
|
||||
| **UID** | `CLIENT_ID` |
|
||||
| **SECRET** | `CLIENT_SECRET` |
|
||||
| URI enregistrée | `REDIRECT_URI` (caractère pour caractère identique) |
|
||||
|
||||
Fichier : `swiftyCompanion/.env` (ne pas committer).
|
||||
@@ -1,78 +0,0 @@
|
||||
# OAuth 42 — tests manuels des URLs
|
||||
|
||||
## Pourquoi pas `http://localhost` sur téléphone ?
|
||||
|
||||
| Environnement | `http://localhost:8080/callback` |
|
||||
|---------------|----------------------------------|
|
||||
| Émulateur Android | Parfois OK (localhost = l’émulateur) |
|
||||
| **Téléphone physique** | ❌ « Site inaccessible » — localhost = le téléphone, pas ton PC |
|
||||
|
||||
**Swifty Companion (mobile réel)** : utiliser un **schéma d’app** :
|
||||
|
||||
```text
|
||||
com.example.swiftycompanion://callback
|
||||
```
|
||||
|
||||
À enregistrer sur l’Intra **et** dans `.env` (`REDIRECT_URI`).
|
||||
|
||||
---
|
||||
|
||||
## Endpoints 42 (référence sujet)
|
||||
|
||||
| Rôle | URL |
|
||||
|------|-----|
|
||||
| Autorisation | `https://api.intra.42.fr/oauth/authorize` |
|
||||
| Token | `https://api.intra.42.fr/oauth/token` |
|
||||
| API (ex. moi) | `https://api.intra.42.fr/v2/me` |
|
||||
|
||||
Doc : [api.intra.42.fr/apidoc](https://api.intra.42.fr/apidoc)
|
||||
|
||||
---
|
||||
|
||||
## 1. Tester l’API (sans app)
|
||||
|
||||
Dans un navigateur (connecté à l’Intra ou non pour cette étape) :
|
||||
|
||||
```text
|
||||
https://api.intra.42.fr/
|
||||
```
|
||||
|
||||
Réponse JSON attendue (message API) → l’API est joignable.
|
||||
|
||||
---
|
||||
|
||||
## 2. Tester la page d’autorisation (navigateur)
|
||||
|
||||
Remplace `TON_CLIENT_ID` par l’**UID** de ton app OAuth.
|
||||
|
||||
**Redirect actuel (mobile)** :
|
||||
|
||||
```text
|
||||
https://api.intra.42.fr/oauth/authorize?client_id=TON_CLIENT_ID&redirect_uri=com.example.swiftycompanion%3A%2F%2Fcallback&response_type=code&scope=public
|
||||
```
|
||||
|
||||
| Résultat | Signification |
|
||||
|----------|----------------|
|
||||
| Page login / autorisation 42 | ✅ URL d’auth OK |
|
||||
| Erreur `redirect_uri` | ❌ URI pas enregistrée sur l’Intra ou différente de `.env` |
|
||||
| Erreur `invalid_client` | ❌ `client_id` incorrect |
|
||||
|
||||
Après validation, le navigateur tente d’ouvrir `com.example.swiftycompanion://callback?code=...` → sur PC ça échoue (normal) ; **sur le téléphone avec l’app installée**, l’app doit s’ouvrir.
|
||||
|
||||
---
|
||||
|
||||
## 3. Alignement obligatoire (3 endroits identiques)
|
||||
|
||||
1. [Application OAuth Intra](https://profile.intra.42.fr/oauth/applications) → **URI de redirection**
|
||||
2. `swifty_companion/.env` → `REDIRECT_URI=...`
|
||||
3. Code Flutter → même valeur via `IntraConfig.redirectUri`
|
||||
|
||||
---
|
||||
|
||||
## 4. Ancien redirect (à retirer sur l’Intra si présent)
|
||||
|
||||
```text
|
||||
http://localhost:8080/callback
|
||||
```
|
||||
|
||||
Remplacer par `com.example.swiftycompanion://callback` (une ligne dans le formulaire).
|
||||
@@ -1,7 +0,0 @@
|
||||
# Sujets — Swifty Companion
|
||||
|
||||
| Document | Description |
|
||||
|----------|-------------|
|
||||
| [swifty-companion.md](swifty-companion.md) | Traduction FR complète (v4) + interprétation des visuels |
|
||||
| [originaux/swifty_companion_subject.pdf](originaux/swifty_companion_subject.pdf) | PDF officiel Intra |
|
||||
| [captures/](captures/) | Images extraites du PDF (couverture) |
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 870 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 204 KiB |
Binary file not shown.
@@ -1,164 +0,0 @@
|
||||
# Mobile Initiation — Swifty Companion
|
||||
|
||||
> **Version :** 4
|
||||
> **Résumé :** Ce projet vise à vous initier au développement d’applications mobiles.
|
||||
> **Traduction française** — source : [swifty_companion_subject.pdf](originaux/swifty_companion_subject.pdf)
|
||||
|
||||
---
|
||||
|
||||
## Table des matières
|
||||
|
||||
1. [Chapitre I — Avant-propos](#chapitre-i--avant-propos)
|
||||
2. [Chapitre II — Introduction](#chapitre-ii--introduction)
|
||||
3. [Chapitre III — Objectifs](#chapitre-iii--objectifs)
|
||||
4. [Chapitre IV — Instructions générales](#chapitre-iv--instructions-générales)
|
||||
5. [Chapitre V — Le projet](#chapitre-v--le-projet)
|
||||
- [V.1 Partie obligatoire](#v1-partie-obligatoire)
|
||||
- [V.2 Bonus](#v2-bonus)
|
||||
6. [Chapitre VI — Rendu et évaluation par les pairs](#chapitre-vi--rendu-et-évaluation-par-les-pairs)
|
||||
7. [Éléments visuels du PDF](#éléments-visuels-du-pdf)
|
||||
|
||||
---
|
||||
|
||||
## Page de garde (PDF page 1)
|
||||
|
||||
| Champ | Texte officiel (EN) | Traduction |
|
||||
|-------|---------------------|------------|
|
||||
| Série | Mobile Initiation | Initiation mobile |
|
||||
| Titre | Swifty Companion | Swifty Companion |
|
||||
| Résumé | This project aims to introduce you to the development of mobile application. | Ce projet vise à vous initier au développement d’applications mobiles. |
|
||||
| Version | 4 | 4 |
|
||||
|
||||
### Référence visuelle — couverture
|
||||
|
||||

|
||||
|
||||
| Zone | Description |
|
||||
|------|-------------|
|
||||
| Illustration centrale | Motif vertical en facettes (style low-poly), tons gris/noir — **élément graphique 42**, pas une maquette d’application |
|
||||
| Fond | Motif géométrique léger (triangles) sur toute la page |
|
||||
| Texte | « Mobile Initiation » puis « Swifty Companion », résumé en italique, « Version: 4 » |
|
||||
|
||||
Extrait haute résolution de l’illustration : [illustration-motif-42.png](captures/illustration-motif-42.png)
|
||||
|
||||
> **Note :** le sujet PDF (9 pages) **ne contient pas de captures d’écran de l’app à reproduire**. Les pages 2 à 9 reprennent le même fond géométrique que la couverture, avec le texte des chapitres. L’UI de votre application est définie **uniquement par la liste de critères** de la partie obligatoire.
|
||||
|
||||
---
|
||||
|
||||
## Chapitre I — Avant-propos
|
||||
|
||||
*(Traduction du texte Wikipedia sur le dodo — page 2 du PDF.)*
|
||||
|
||||
Voici ce que Wikipedia dit du **dodo** :
|
||||
|
||||
Le dodo (*Raphus cucullatus*) est un oiseau incapable de voler, aujourd’hui éteint, endémique de l’île Maurice, à l’est de Madagascar dans l’océan Indien. Son plus proche parent génétique était le solitaire de Rodrigues, également éteint ; les deux formaient la sous-famille des Raphinae, au sein de la famille des columbidés. Le plus proche parent encore vivant du dodo est le pigeon de Nicobar. On a un temps cru à tort qu’un dodo blanc existait sur l’île voisine de La Réunion.
|
||||
|
||||
Les restes subfossiles montrent que le dodo mesurait environ 1 mètre et pouvait peser 10,6 à 21,1 kg à l’état sauvage. Son apparence réelle n’est connue que par des dessins, peintures et récits du XVIIe siècle. Ces sources varient beaucoup et peu d’illustrations proviennent de spécimens vivants : l’apparence exacte reste débattue. On le représente souvent avec un plumage gris-brun, des pattes jaunes, une touffe de plumes à la queue, une tête nue grise et un bec noir, jaune et vert. Il utilisait des gastrolithes pour digérer, mangeait probablement des fruits, et vivait dans les bois des zones côtières plus sèches de Maurice. Une source mentionne un seul œuf par couvée. Il est supposé qu’il a perdu le vol face à l’abondance de nourriture et au peu de prédateurs.
|
||||
|
||||
Les Hollandais le mentionnent pour la première fois en 1598. Chasse, animaux domestiques et espèces invasives ont contribué à sa disparition ; la dernière observation largement acceptée date de 1662. Son extinction ne fut pas tout de suite remarquée ; certains le considéraient comme mythique. Au XIXe siècle, des recherches portèrent sur quelques spécimens rapportés en Europe au début du XVIIe siècle, dont une tête séchée — seul tissu mou encore conservé. Depuis, beaucoup de matériel subfossile a été extrait de Maurice (notamment du marais Mare aux Songes). La rapidité de l’extinction a mis en lumière le rôle de l’homme dans la disparition d’espèces entières. Le dodo est célèbre via *Alice au pays des merveilles* et est devenu un symbole d’extinction et d’obsolescence, ainsi qu’une mascotte de Maurice.
|
||||
|
||||
---
|
||||
|
||||
## Chapitre II — Introduction
|
||||
|
||||
Dans ce projet, vous découvrirez comment développer une **application mobile**.
|
||||
|
||||
L’objectif est de construire une application qui récupère les **informations des étudiants de 42** via l’**API 42**.
|
||||
|
||||
Une **API** (*Application Programming Interface*) est un ensemble de routines, protocoles et outils pour construire des applications. Souvent, une API se présente comme une bibliothèque avec des spécifications pour routines, structures de données, classes et variables. Dans d’autres cas — notamment les services **SOAP** et **REST** — une API est simplement une spécification d’appels distants exposés aux consommateurs.
|
||||
|
||||
---
|
||||
|
||||
## Chapitre III — Objectifs
|
||||
|
||||
Ce projet vise à vous familiariser avec :
|
||||
|
||||
- un **langage mobile** (Dart, Swift, Kotlin, Java, etc.) ;
|
||||
- un **IDE** de votre choix (Android Studio est un bon candidat) ;
|
||||
- des **frameworks mobiles** (le sujet cite **Flutter**) ;
|
||||
- l’**API 42**.
|
||||
|
||||
---
|
||||
|
||||
## Chapitre IV — Instructions générales
|
||||
|
||||
- Le projet est évalué **uniquement par des humains** (soutenance).
|
||||
- Vous pouvez utiliser **n’importe quel langage mobile**, en gardant à l’esprit la **dernière version de l’IDE** disponible sur les machines de l’école.
|
||||
- Frameworks et bibliothèques compatibles autorisés si leur usage est **justifié** en soutenance — soyez pertinent.
|
||||
- Le projet doit utiliser la **dernière version disponible de l’API 42**.
|
||||
- Vos choix techniques peuvent vous rendre plus attractif sur le marché du travail.
|
||||
|
||||
### ⚠️ Sécurité — encadré page 5
|
||||
|
||||
> Pour des raisons de sécurité évidentes, identifiants, clés API, variables d’environnement, etc. doivent être stockés **localement** dans un fichier **`.env`** et **ignorés par git**. Des identifiants exposés publiquement entraînent **l’échec direct** du projet.
|
||||
|
||||
---
|
||||
|
||||
## Chapitre V — Le projet
|
||||
|
||||
### V.1 Partie obligatoire
|
||||
|
||||
Pour valider la partie obligatoire, vous devez respecter **tous** les critères suivants :
|
||||
|
||||
| # | Critère (EN) | Exigence en français |
|
||||
|---|--------------|----------------------|
|
||||
| 1 | At least 2 views | Au moins **2 vues** (écrans) |
|
||||
| 2 | Handle all cases of errors | Gérer **tous** les cas d’erreur (login introuvable, erreur réseau, etc.) |
|
||||
| 3 | Second view shows login info | La **2ᵉ vue** affiche les infos du login **s’il existe** |
|
||||
| 4 | Four details + profile picture | Afficher **au moins 4 détails** parmi : login, email, mobile, level, location, wallet, evaluations, etc. **+ photo de profil** |
|
||||
| 5 | Skills with level and percentage | Afficher les **compétences (skills)** avec **niveau** et **pourcentage** |
|
||||
| 6 | Completed projects including failed | Afficher les **projets réalisés**, **y compris les échecs** |
|
||||
| 7 | Navigate back to first view | Permettre de **revenir à la 1ʳᵉ vue** |
|
||||
| 8 | Flexible / modern layout | Mise en page **flexible ou moderne** (contraintes, etc.) pour **plusieurs tailles d’écran** et plateformes |
|
||||
| 9 | Do not create a token for each query | **Ne pas créer un token à chaque requête** — réutiliser le flux OAuth2 |
|
||||
|
||||
### OAuth2 — encadrés page 6
|
||||
|
||||
> Se référer à la **documentation OAuth2**.
|
||||
|
||||
> Vous devez utiliser **OAuth2 de l’Intra**. Pour des raisons de **confidentialité** et conformément aux lois de certains pays, il peut être **nécessaire de ne pas afficher** certaines informations.
|
||||
|
||||
**Liens utiles (hors PDF, à consulter pendant le projet) :**
|
||||
|
||||
- [Documentation développeur API 42](https://api.intra.42.fr/apidoc)
|
||||
- [Applications OAuth sur l’Intra](https://profile.intra.42.fr/oauth/applications)
|
||||
|
||||
---
|
||||
|
||||
### V.2 Bonus
|
||||
|
||||
Bonus disponible :
|
||||
|
||||
| Bonus | Description |
|
||||
|-------|-------------|
|
||||
| Rafraîchissement du token | Recréer / **rafraîchir le token à l’expiration**. Si le token expire, l’app doit le renouveler et **continuer à fonctionner** dans tous les cas. |
|
||||
|
||||
### ⚠️ Règle bonus — encadré page 7
|
||||
|
||||
> La partie bonus n’est évaluée **que si la partie obligatoire est PARFAITE**. « Parfait » signifie : partie obligatoire **entièrement** réalisée et **sans dysfonctionnement**. Si **une seule** exigence obligatoire manque, le bonus **n’est pas évalué**.
|
||||
|
||||
---
|
||||
|
||||
## Chapitre VI — Rendu et évaluation par les pairs
|
||||
|
||||
Déposez le travail dans votre **dépôt Git** comme d’habitude. **Seul le contenu du dépôt** sera évalué en soutenance. Vérifiez les **noms de dossiers et de fichiers**.
|
||||
|
||||
→ 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
|
||||
|
||||
| Page PDF | Fichier extrait | Contenu visuel |
|
||||
|----------|-----------------|----------------|
|
||||
| 1 | [page-01-couverture.png](captures/page-01-couverture.png) | Page de garde complète |
|
||||
| 1 (détail) | [illustration-motif-42.png](captures/illustration-motif-42.png) | Illustration centrale seule |
|
||||
| 2–9 | *(pas d’image métier)* | Fond géométrique répété + texte des chapitres |
|
||||
|
||||
---
|
||||
|
||||
## Prochaine étape pédagogique
|
||||
|
||||
→ [Plan de réalisation pas à pas](../pedagogie/README.md)
|
||||
Reference in New Issue
Block a user