docs(mail) : master plan d'intégration mail OVH IMAP — 7 phases (foundations, sync, API, services front, UI, intégration tâches, admin)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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 `<img src="http(s)://...">` 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 `<script>alert(1)</script>`)
|
||||
- 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 `<type>(mail) : <message>` (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
|
||||
Reference in New Issue
Block a user