Feature #124
closedRécupérer automatiquement la photo de profil LinkedIn à la connexion
100%
## Spec — Récupération auto de la photo de profil LinkedIn à la connexion
### Décision de découpage
**Pas de découpage (unité de dev unique, fullstack).** C'est une correction de bug à surface unique sur GetYourJob (Next.js) : on reproduit pour le provider LinkedIn la logique d'avatar déjà en place pour Google, plus un fallback d'initiales. Le callback OAuth `profile` (`src/lib/auth.ts`) et le composant d'affichage d'avatar partagent une même chaîne de priorité (upload manuel > provider > initiales) ; les scinder créerait des sous-tâches triviales et redondantes.
### Problème
À la connexion LinkedIn, la photo de profil du candidat n'est pas récupérée automatiquement (contrairement à Google) → profil incomplet / icône vide tant que l'utilisateur n'uploade pas manuellement une photo.
### Approche
1. **Auth / mapping provider (NextAuth — `src/lib/auth.ts`)** : dans le provider LinkedIn (OIDC "Sign In with LinkedIn using OpenID Connect"), mapper la claim `picture` du profil vers `image`, comme le fait déjà le provider Google. Vérifier que le scope `profile` (OpenID Connect) est bien demandé — sinon `picture` est absent côté API (à valider avant de conclure à un bug de code).
2. **Persistance** : à la création/màj de l'utilisateur, renseigner l'URL d'avatar provider uniquement si l'utilisateur n'a pas déjà une photo uploadée manuellement (l'upload manuel prime toujours). Réutiliser la même logique de priorité que Google.
3. **Affichage / fallback (composant avatar)** : si aucune photo (ni manuelle, ni provider) n'est disponible, afficher une icône générique ronde avec les initiales "DP" sur fond coloré (ex. bleu clair). Comportement identique quel que soit le provider.
4. **Aucune étape de confirmation** : récupération et affichage automatiques, sans prompt utilisateur.
### Critères d'acceptation
- [ ] La photo LinkedIn est récupérée automatiquement à la connexion et affichée en haut à droite du profil candidat.
- [ ] Sans photo LinkedIn, une icône ronde avec initiales "DP" sur fond coloré s'affiche.
- [ ] Une photo uploadée manuellement dans GetYourJob prime toujours sur la photo automatique (LinkedIn ou Google).
- [ ] Comportement cohérent avec Google (même logique de fallback et de priorité).
- [ ] Aucun message de confirmation n'est demandé à l'utilisateur.
### Routage
Fullstack (modif config NextAuth + persistance utilisateur côté serveur, plus composant avatar côté présentation). Petite surface, à traiter en TDD.
### Estimation
S
Description
### Problème
Actuellement, lors de la connexion via LinkedIn, la photo de profil du candidat n'est pas récupérée automatiquement, contrairement à la connexion via Google. Cela entraîne un affichage incomplet du profil (icône vide ou par défaut) si l'utilisateur n'a pas uploadé manuellement une photo dans son profil GetYourJob.
### Contexte
- Pour les connexions Google, la photo de profil est bien récupérée automatiquement (ou remplacée par une photo manuelle si l'utilisateur en ajoute une).
- Pour les connexions LinkedIn, aucune photo n'est récupérée, ce qui crée une incohérence dans l'expérience utilisateur.
- Le comportement attendu est une récupération automatique de la photo LinkedIn, sans étape de confirmation, avec un fallback vers une icône générique si aucune photo n'est disponible.
### Comportement proposé
1. Récupérer automatiquement la photo de profil LinkedIn dès la connexion (comme pour Google).
2. Si aucune photo n'est disponible, afficher une icône générique ronde avec les initiales "DP" et un fond coloré (ex. : bleu clair).
3. La photo manuelle uploadée par l'utilisateur dans son profil GetYourJob prime toujours sur la photo automatique (LinkedIn/Google).
## Acceptance criteria
['- [ ] La photo de profil LinkedIn est récupérée automatiquement lors de la connexion et affichée en haut à droite du profil candidat.', '- [ ] Si aucune photo LinkedIn n\'est disponible, une icône générique ronde avec les initiales "DP" et un fond coloré est affichée.', "- [ ] La photo manuelle uploadée par l'utilisateur dans son profil GetYourJob remplace toujours la photo automatique (LinkedIn ou Google).", '- [ ] Le comportement est cohérent avec celui de la connexion Google (même logique de fallback et de priorité).', "- [ ] Aucun message de confirmation n'est demandé à l'utilisateur pour l'affichage de la photo LinkedIn."]
## Classification
- bug
## Complexity
- 5/10 — L'intégration avec l'API LinkedIn pour récupérer la photo nécessite une gestion des permissions et des erreurs, mais le fallback et la logique de priorité sont déjà partiellement implémentés pour Google.
Files
RA Updated by Redmine Admin about 2 months ago
- Status changed from Submitted to Spec
RA Updated by Redmine Admin about 2 months ago
- % Done changed from 0 to 100
- spec_ref updated (diff)
RA Updated by Redmine Admin about 2 months ago
- Status changed from Spec to In development
RA Updated by Redmine Admin about 2 months ago
- Status changed from In development to QA
- branch set to feat/124-linkedin-profile-picture
- pr_url set to https://github.com/omdev-tech/GetYourJob/pull/255
PR ouverte vers `dev` : https://github.com/omdev-tech/GetYourJob/pull/255
Implémentation (fullstack, TDD test-first) :
- Auth (`src/lib/auth.ts` + nouveau `src/lib/auth-providers.ts`) : la claim OIDC `picture` de LinkedIn est mappée vers `image`, exactement comme Google. Vérifié au préalable : le scope `profile` était **déjà demandé** et le mapping `picture`→`image` déjà présent — pas de bug de scope. Le seam pur `mapLinkedInProfileToUser()` rend le mapping testable.
- Priorité : la photo provider n'est écrite qu'à la création du compte (PrismaAdapter), jamais réécrite à la reconnexion → une photo uploadée manuellement (DeveloperProfile.profileImageUrl) prime toujours. Aucune confirmation demandée.
- Affichage (`src/components/atoms/UserAvatar.tsx`, intégré dans `ConnectionButton.tsx` — avatar en haut à droite) : avatar rond, chaîne upload manuel > photo provider > initiales « DP » sur fond bleu clair quand aucune photo.
Tests verts : `auth-providers.test.ts` (4) + `UserAvatar.test.tsx` (7). tsc : aucune nouvelle erreur sur les fichiers touchés (erreurs pré-existantes hors périmètre). `npm run build` : Compiled successfully.
Statut : In development → QA.
RA Updated by Redmine Admin about 2 months ago
RA Updated by Redmine Admin about 2 months ago
- File 124-avatar-states.png 124-avatar-states.png added
RA Updated by Redmine Admin about 2 months ago
## Rapport QA — VERDICT : PASS (5/5 critères OK)
Smoke test sur un worktree QA dédié (`.worktrees/qa-124`, depuis `origin/feat/124-linkedin-profile-picture`, commit `b99ff5d`, PR #255). Le ticket reste en **QA** (promotion preprod = autre agent).
### Critères d'acceptation
| # | Critère | Résultat | Preuve |
|---|---------|----------|--------|
| AC1 | Photo LinkedIn récupérée auto à la connexion, affichée en haut à droite | **OK** | `auth.ts` provider LinkedIn (scope `profile` déjà demandé) → `profile()` délègue à `mapLinkedInProfileToUser()` qui mappe `picture`→`image` (`auth-providers.ts` l.41) ; `ConnectionButton.tsx` rend `<UserAvatar image={user.image}>` dans l'avatar coin haut-droit |
| AC2 | Sans photo : icône ronde, initiales « DP », fond coloré | **OK** | `UserAvatar.tsx` l.70-81 `div rounded-full bg-sky-100 text-sky-700`, `getAvatarInitials` défaut « DP ». Preuve visuelle jointe (`124-avatar-states.png`) |
| AC3 | Photo manuelle GetYourJob prime toujours sur l'auto (LinkedIn/Google) | **OK** | Double garantie : (a) l'image provider n'est écrite qu'à la création du compte, jamais réécrite à la reconnexion ; (b) `UserAvatar.tsx` l.53 `resolvedImageUrl = profileImageUrl \|\| image` → l'upload manuel passe avant le provider |
| AC4 | Comportement cohérent avec Google (même fallback + priorité) | **OK** | Même forme de retour `profile()` que GoogleProvider, même composant `UserAvatar` et même chaîne de priorité quel que soit le provider |
| AC5 | Aucun message de confirmation demandé | **OK** | Aucun prompt/modal sur le chemin récupération/affichage ; mapping + rendu purement automatiques |
### Tests exécutés
- **Vitest (suites touchées)** : `auth-providers.test.ts` (4) + `UserAvatar.test.tsx` (7) → **11/11 verts**.
- **tsc --noEmit (fichiers touchés)** : `auth-providers.ts`, `UserAvatar.tsx`, `ConnectionButton.tsx` → **0 erreur**. Une (1) erreur subsiste dans `auth.ts:46` (typage Prisma `subscription.createMany`) — **vérifiée pré-existante** : bloc identique sur `origin/dev`, **non touché** par le diff #124 → hors périmètre, non bloquant.
- **Inspection source** : chaîne de priorité et persistance confirmées (voir tableau).
### Pièces jointes
- `124-rapport-technique-qa.txt` — rapport technique détaillé (secrets filtrés).
- `124-avatar-states.png` — preuve visuelle des 3 états (manuel / provider / initiales « DP »).
Aucun secret/token n'a été imprimé ni joint. Statut conservé : **QA**.
RA Updated by Redmine Admin about 2 months ago
- Status changed from QA to Preprod
- preprod_url set to https://github.com/omdev-tech/GetYourJob/pull/267
RA Updated by Redmine Admin about 2 months ago
- Status changed from Preprod to Shipped
Livré en production ✅ — déployé sur master/prod (App/master #26 SUCCESS, prod-app à jour 2026-06-16). Clôturé.