diff --git a/docs/superpowers/plans/2026-05-19-mail-integration-master-plan.md b/docs/superpowers/plans/2026-05-19-mail-integration-master-plan.md new file mode 100644 index 0000000..089a708 --- /dev/null +++ b/docs/superpowers/plans/2026-05-19-mail-integration-master-plan.md @@ -0,0 +1,264 @@ +# Mail Integration — Master Plan + +> **Master plan** : ce document décrit le découpage en phases. Chaque phase aura son propre plan détaillé (rédigé par un subagent rédacteur) puis sera implémentée par un subagent codeur, en cycle. + +**Spec source** : `docs/superpowers/specs/2026-05-19-mail-integration-design.md` + +**Goal** : Ajouter à Lesstime un client mail intégré pour une boîte partagée OVH (IMAP/SMTP), avec lecture inbox/dossiers et création/lien tâche depuis un mail. + +**Stratégie** : 7 phases séquentielles, dépendances claires, chaque phase = working software testable. Cycle par phase : rédacteur → codeur → review humaine → phase suivante. + +--- + +## Cartographie des phases + +``` +Phase 1 (Backend foundations) ──┐ + ├─→ Phase 2 (IMAP provider + sync) ──┐ + │ ├─→ Phase 3 (API backend) ──┐ + │ │ │ + └─→─────────────────────────────────────────────────────────────────┤ + │ +Phase 4 (Frontend services + store) ←──────────────────────────────────────────────────────────────┘ + │ + ├─→ Phase 5 (UI principale 3 colonnes) + │ + ├─→ Phase 6 (Intégration tâches : modals, onglet TaskDrawer) + │ + └─→ Phase 7 (Admin config + sidebar + polish) +``` + +Chaque phase produit du logiciel fonctionnel (testable, mergeable) sans casser les précédentes. + +--- + +## Phase 1 — Backend Foundations + +**Plan détaillé attendu** : `docs/superpowers/plans/2026-05-19-mail-phase1-foundations.md` + +**Scope** : +- Entité `MailConfiguration` (singleton, fields complets de la spec, `encryptedPassword` via `TokenEncryptor`) +- Entité `MailFolder` +- Entité `MailMessage` +- Entité `TaskMailLink` (avec unique constraint) +- Repositories : `MailConfigurationRepository::findSingleton()`, `MailFolderRepository`, `MailMessageRepository`, `TaskMailLinkRepository` +- Migration Doctrine unique créant les 4 tables (raw SQL) +- DTOs sous `src/Mail/Dto/` : `MailFolderDto`, `MailMessageHeaderDto`, `MailMessageDetailDto`, `MailAttachmentDto` +- Interface `App\Mail\MailProviderInterface` (signatures uniquement, pas d'impl) +- Exception `App\Mail\Exception\MailProviderException` +- Tests unitaires repositories (au moins le pattern singleton) + +**Critère d'acceptation** : +- `make migration-migrate` passe sans erreur +- `php bin/console doctrine:schema:validate` OK +- `make test` vert (au moins les tests créés) +- Fixture `MailConfiguration` désactivée (OVH defaults) ajoutée + +**Dépendances** : aucune (point d'entrée). + +--- + +## Phase 2 — IMAP Provider + Sync + +**Plan détaillé attendu** : `docs/superpowers/plans/2026-05-19-mail-phase2-imap-sync.md` + +**Scope** : +- Ajout dépendance Composer `webklex/php-imap` (vérifier compat PHP 8.4) +- Implémentation `App\Mail\ImapMailProvider implements MailProviderInterface` + - Lecture config via `MailConfigurationRepository::findSingleton()` + - Déchiffrement password via `TokenEncryptor` + - `listFolders`, `listMessages`, `fetchMessage`, `markRead`, `markFlagged`, `moveMessage`, `fetchAttachment` + - Wrapping erreurs en `MailProviderException` +- `App\Service\MailSyncService` + - `syncAll(): MailSyncReport` + - `syncFolder(string $folderPath): MailSyncReport` + - `syncFolderStructure(): void` + - Algorithme exact de la spec (UID FETCH lastUid+1:*, resync flags N=200 derniers, detect suppressions avec garde 50%) +- DTO `MailSyncReport` (count créés / mis à jour / supprimés / errors) +- Symfony Lock (`mail.sync`, TTL 10 min) +- Commande console `app:mail:sync` (avec option `--folder=...`) +- Documentation cron OS + cible Makefile `make mail-sync` +- Tests : ImapMailProvider mocké via fixture serveur ou interface, MailSyncService avec provider mocké + +**Critère d'acceptation** : +- `php bin/console app:mail:sync --dry-run` fonctionne contre une fake config +- Tests `make test` verts +- `make mail-sync` documentée dans Makefile + +**Dépendances** : Phase 1. + +--- + +## Phase 3 — API Backend + +**Plan détaillé attendu** : `docs/superpowers/plans/2026-05-19-mail-phase3-api.md` + +**Scope** : +- API Platform ressources : + - `GET /api/mail/configuration` (ROLE_ADMIN) — singleton provider + - `PATCH /api/mail/configuration` (ROLE_ADMIN) — processor (jamais retourner password en clair, accepter nouveau password à chiffrer) +- Custom controllers (priority: 1) : + - `POST /api/mail/configuration/test` (ROLE_ADMIN) — test connexion + - `GET /api/mail/folders` (ROLE_USER, refus ROLE_CLIENT explicite) — arbre + unreadCount depuis BDD + - `GET /api/mail/folders/{path}/messages?page&limit` — pagination cursor `sentAt DESC, id DESC` + - `GET /api/mail/messages/{id}` — fetch live IMAP + cache Symfony `mail_body_{messageId}` TTL 5 min + - `POST /api/mail/messages/{id}/read` (body `{ read: bool }`) + - `POST /api/mail/messages/{id}/flag` + - `POST /api/mail/messages/{id}/create-task` (body `{ projectId, taskGroupId?, priority? }`) + - `POST /api/mail/messages/{id}/link-task` (body `{ taskId }`) + - `DELETE /api/mail/messages/{id}/link-task/{taskId}` + - `GET /api/tasks/{id}/mails` + - `GET /api/mail/attachments/{id}` — stream, `Content-Disposition: attachment`, jamais inline + - `POST /api/mail/sync` — async via Messenger +- Message + Handler Symfony Messenger `MailSyncRequested` +- Sécurité : `#[IsGranted('IS_AUTHENTICATED_FULLY')]` + check `ROLE_USER && !ROLE_CLIENT` explicite +- Tests fonctionnels endpoints (auth, format réponses, ROLE_CLIENT refusé) + +**Critère d'acceptation** : +- Tous endpoints répondent corrects status/format +- Tests `make test` verts +- ROLE_CLIENT refusé sur 100% des endpoints mail +- Password jamais leak dans les réponses + +**Dépendances** : Phase 1, Phase 2. + +--- + +## Phase 4 — Frontend Services + Store + +**Plan détaillé attendu** : `docs/superpowers/plans/2026-05-19-mail-phase4-frontend-services.md` + +**Scope** : +- Install npm `dompurify` + types +- `frontend/services/dto/mail.ts` : tous les types TS +- `frontend/services/mail.ts` : méthodes API (suivre pattern `tasks.ts`) + - `listFolders`, `listMessages`, `getMessage`, `markRead`, `markFlagged` + - `createTaskFromMail`, `linkTask`, `unlinkTask`, `listMailsForTask` + - `triggerSync` + - `getConfiguration`, `updateConfiguration`, `testConfiguration` + - `downloadAttachment` (retourne Blob) +- Store Pinia `frontend/stores/useMailStore.ts` + - State : `folders`, `selectedFolderPath`, `messages[]`, `selectedMessageId`, `selectedMessageDetail`, `loading`, `syncing`, `globalUnreadCount` + - Actions correspondantes + - Polling `pollUnreadCount()` toutes les 30s (start/stop) +- Sanitization helper `frontend/utils/sanitizeMailHtml.ts` (DOMPurify avec config bloquante : script/iframe/object/embed/on*/javascript:, strip ou placeholder pour `` distants) + +**Critère d'acceptation** : +- `cd frontend && npx tsc --noEmit` OK +- Test manuel d'un appel `mail.listFolders()` depuis devtools renvoie 401 si pas authentifié, 200 sinon + +**Dépendances** : Phase 3 (les endpoints doivent exister). + +--- + +## Phase 5 — UI principale (page /mail) + +**Plan détaillé attendu** : `docs/superpowers/plans/2026-05-19-mail-phase5-ui-main.md` + +**Scope** : +- Page `frontend/pages/mail.vue` — layout 3 colonnes (dossiers / liste / lecteur), responsive +- Composants `frontend/components/mail/` : + - `MailFolderTree.vue` — arbre récursif avec badges unread, sélection + - `MailMessageList.vue` — liste paginée (infinite scroll), indicateurs lu/étoilé/PJ, formatage relatif des dates + - `MailMessageViewer.vue` — header (de/à/cc/date) + body sanitizé via DOMPurify + liste PJ téléchargeables + actions (Créer tâche / Lier / Marquer lu/non-lu / Étoiler) + - `MailRefreshButton.vue` — bouton sync manuel, désactivé pendant `syncing` +- i18n clés `mail.*` dans `frontend/i18n/locales/fr.json` (et `en.json` si présent) : titres, vides, actions, erreurs +- Mapping noms dossiers système (`INBOX`, `Sent`, `Drafts`, `Archive`, `Trash`, `Junk`) → labels traduits +- Gestion query param `?messageId=X` pour deep-link vers un mail (selection auto à l'ouverture) +- Refus visuel pour ROLE_CLIENT (le middleware backend bloque déjà, mais ajouter check côté router/middleware Nuxt) + +**Critère d'acceptation** : +- Page accessible à `/mail` pour ROLE_USER/ROLE_ADMIN +- ROLE_CLIENT redirigé vers `/portal` +- Pas d'XSS via body mail (test manuel avec un mail contenant ``) +- Pixels tracking distants remplacés par placeholder + +**Dépendances** : Phase 4. + +--- + +## Phase 6 — Intégration Tâches + +**Plan détaillé attendu** : `docs/superpowers/plans/2026-05-19-mail-phase6-task-integration.md` + +**Scope** : +- `frontend/components/mail/MailCreateTaskModal.vue` — wrapper du `TaskDrawer` existant pré-rempli : + - Titre = subject + - Description = body plain text + - Picker projet + groupe + priorité + - À la création : appelle `POST /api/mail/messages/{id}/create-task`, ferme modal, redirige ou affiche succès +- `frontend/components/mail/MailLinkTaskModal.vue` — autocomplete sur tâches existantes (filter par projet, statut non-archivé) +- Onglet **"Mails"** sur `TaskDrawer.vue` : + - Nouvelle section affichée à côté Documents / Time tracking / etc. + - Liste `MailMessage` liés à la tâche (via `GET /api/tasks/{id}/mails`) + - Item cliquable → `router.push('/mail?messageId=' + id)` + - Bouton "Lier un mail" → ouvre un picker mail (TBD selon ergonomie : modal recherche ou redirige vers /mail) +- Tests manuels : créer tâche depuis mail, lier mail à tâche existante, voir mail depuis onglet tâche + +**Critère d'acceptation** : +- Workflow complet : mail → "Créer tâche" → tâche créée et liée → visible dans onglet "Mails" du TaskDrawer +- Workflow : tâche existante → "Lier mail" → mail apparaît dans onglet + +**Dépendances** : Phase 5. + +--- + +## Phase 7 — Admin Config + Sidebar + Polish + +**Plan détaillé attendu** : `docs/superpowers/plans/2026-05-19-mail-phase7-admin-polish.md` + +**Scope** : +- `frontend/components/admin/AdminMailTab.vue` (calqué sur `AdminZimbraTab.vue`) : + - Form : protocol (imap pour MVP), imapHost/Port/Encryption, smtpHost/Port/Encryption, username, password (write-only, `hasPassword: true` côté GET), sentFolderPath, enabled toggle + - Bouton "Tester la connexion" → `POST /api/mail/configuration/test` + - Indicateur OVH defaults pré-remplis (`ssl0.ovh.net:993/465`) +- Ajout onglet `AdminMailTab` dans la page admin (selon pattern existant) +- Lien sidebar dans le layout default : + - Icône `material-symbols:mail-outline` + - Label traduit + - Badge unread (count `useMailStore.globalUnreadCount`) + - Visible uniquement pour `ROLE_USER && !ROLE_CLIENT` +- Lifecycle polling 30s : start dans `app.vue` ou layout default, stop au logout +- Documentation finale : + - README ou `docs/` : section "Mail integration" (cron OS, variables config, sécurité) + - Makefile : `make mail-sync` documentée +- Vérification finale tracking pixels (relire `sanitizeMailHtml.ts` + tester) +- QA passe : workflow end-to-end depuis vraie boîte OVH (si dispo) ou IMAP test (greenmail/dovecot local) + +**Critère d'acceptation** : +- Admin peut configurer la boîte, tester, activer +- Sidebar affiche badge unread temps réel (30s polling) +- Doc d'install à jour +- Aucun warning console front, aucun ERROR PHP dans `make logs-dev` + +**Dépendances** : Phase 5 (sidebar utilise le store), Phase 3 (admin API). + +--- + +## Conventions communes à toutes les phases + +- **TDD** : test rouge → code → test vert → commit +- **Strict types** PHP (`declare(strict_types=1)`) en tête de chaque fichier +- **PHP CS Fixer** : `make php-cs-fixer-allow-risky` avant chaque commit +- **Commits** : format `(mail) : ` (espace avant `:`) +- **Branche** : `feat/mail-integration` (créée au début de Phase 1) +- **Pas de jamais logger** : bodies, password, attachments +- **Review humaine entre chaque phase** : le user valide avant lancement phase suivante + +--- + +## Cycle d'exécution + +Pour chaque phase N : + +1. **Spawn subagent rédacteur** (`feature-dev:code-architect`) + - Input : ce master plan + spec + scope phase N + - Output : `docs/superpowers/plans/2026-05-19-mail-phaseN-*.md` au format `writing-plans` (tasks bite-sized, fichiers exacts, code complet, commandes test) + +2. **Spawn subagent codeur** (`ruflo-core:coder`) + - Input : plan détaillé phase N + - Output : code + tests + commits (TDD strict) + +3. **Review humaine** : user valide ou demande corrections + +4. **Phase suivante** uniquement si OK