Boutons "Créer une tâche" / "Lier à une tâche" réduits (text-xs, padding compact),
suppression des actions "Marquer comme non lu" et "Marquer important".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Trois causes racines révélées par une vraie synchro complète (139 dossiers) :
- contrainte UNIQUE globale sur message_id : fausse pour IMAP (un même Message-ID
existe dans plusieurs dossiers) → violation → fermeture de l'EntityManager →
cascade qui tuait tous les dossiers suivants. Migration : index simple à la place.
- 139 connexions IMAP (une par dossier) → throttling OVH (failed to authenticate) :
réutilisation d'une seule connexion (closeConnection() ajouté à l'interface).
- état de connexion corrompu après un dossier en erreur (must be in SELECTED state) :
reconnexion ciblée après chaque dossier en échec.
- garde anti-cascade : reset du ManagerRegistry + arrêt propre si l'EM se ferme.
Résultat : 456 messages sur 57 dossiers (avant : 188/30 puis crash). Les rares
dossiers à encodage spécial sont skippés proprement et réessayés au cycle suivant.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Seuls les dossiers racine sont affichés au départ ; chevron pour déplier/replier
chaque dossier ayant des sous-dossiers. Le clic sur le nom sélectionne toujours le dossier.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Quatre bugs révélés en testant contre une vraie boîte OVH (les tests mockaient
le provider, donc jamais exercés) :
- requête sans critère → "BAD parse error: zero-length content" : ajout de whereAll()
- getDate()/getSubject() renvoient des Attribute webklex v6, pas des scalaires : casts explicites
- séquence par défaut ST_MSGN → le peek() de webklex faisait un STORE par numéro de
séquence rejeté par OVH ("flag could not be removed") : force ST_UID sur toutes les requêtes
- snippet via getTextBody() forçait un fetch de corps par mail (sync 179s + peek) :
setFetchBody(false) au listing, snippet désormais optionnel
Sync INBOX : 9 messages en 1,6s (avant : échec en 179s).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Le service appelait GET /mail/messages?folder=X (404) au lieu de la vraie route
/mail/folders/{path}/messages. Ajoute aussi une couche de mapping backend→DTO
(messages→items, fromAddress→fromEmail, toAddresses→toRecipients, détail plat→imbriqué)
pour réconcilier la dérive de contrat entre Phase 3 (API) et Phase 4 (DTOs front).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Le guard enabled dans getClient() bloquait le test de connexion alors que le
workflow naturel est configurer → tester → activer. getClient(requireEnabled)
permet au nouveau testConnection() de se connecter sans exiger enabled=true.
Le controller (ROLE_ADMIN) renvoie désormais le détail de l'erreur pour faciliter
le diagnostic.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- ajoute la regle ^/api/mail avant ^/api pour expliciter l'authentification requise
- les checks fins ROLE_USER vs ROLE_CLIENT restent dans MailAccessChecker (chaque controller)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- dispatch MailSyncRequested au bus Messenger, retourne 202 immediat
- folderPath optionnel via body JSON pour sync ciblee
- en test : transport in-memory route le message en sync
- securite via MailAccessChecker
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- App\Message\MailSyncRequested (optionnel folderPath)
- App\MessageHandler\MailSyncRequestedHandler delegue a MailSyncService::syncFolder ou syncAll
- messenger.yaml : transport async via Doctrine DSN, retry 3x exponentiel, failure transport
- en test : transport in-memory (sync immediat)
- migration Version20260519220000 : cree messenger_messages table (idempotente, IF NOT EXISTS)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- downloadId = base64url(messageDbId:partNumber)
- Content-Disposition: attachment systematique (jamais inline pour eviter XSS via HTML attachments)
- X-Content-Type-Options: nosniff
- filename sanitise via basename pour eviter path traversal
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- POST /api/mail/messages/{id}/link-task body {taskId} : cree TaskMailLink (idempotent)
- DELETE /api/mail/messages/{id}/link-task/{taskId} : supprime le lien (204)
- GET /api/tasks/{id}/mails : liste les mails lies a une tache
- securite via MailAccessChecker, tests fonctionnels 401/403
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- cree une Task avec titre = subject du mail (max 255 chars)
- utilise findMaxNumberByProjectForUpdate pour numero (advisory lock PG)
- transaction wrapInTransaction pour eviter race conditions
- taskGroupId et priorityId optionnels via body JSON
- cree automatiquement le TaskMailLink (mail <-> tache)
- retourne 201 + taskId/taskNumber/taskTitle/messageId
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- POST /api/mail/messages/{id}/read body {read: bool} - synchro IMAP + BDD
- POST /api/mail/messages/{id}/flag body {flagged: bool} - synchro IMAP + BDD
- IMAP-side non bloquant : BDD est mise a jour meme si IMAP fail (resync au prochain cycle)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- recupere headers + body + attachments via ImapMailProvider::fetchMessage
- cache Symfony pool cache.app, cle mail_body_{md5(messageId)}, TTL 300s
- attachments serialises sans contenu binaire, avec downloadId base64url(messageDbId:partNumber)
- 503 si IMAP indisponible, 404 si message inconnu
- les tests read/flag ROLE_CLIENT/auth seront ajoutes en Task 10 (route deja existante)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- ensureCanAccessMail : refuse ROLE_CLIENT pur (sans ROLE_ADMIN)
- ensureIsAdmin : helper pour endpoints config
- service utilise par tous les controllers metier mail
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- endpoint ROLE_ADMIN qui teste la connexion IMAP via listFolders
- retourne ok:bool + foldersCount ou error sanitise (pas de leak interne)
- priority:1 obligatoire pour eviter conflit avec route API Platform {id}
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>