Skip to content

feat(admin): lire et traiter les messages du support - #552

Merged
PaulThiberville merged 7 commits into
mainfrom
issue/349-support
Oct 21, 2026
Merged

PaulThiberville merged 7 commits into
mainfrom
issue/349-support

Conversation

@Nascimento95

Copy link
Copy Markdown
Collaborator

Résultat et problème

Les messages envoyés par « Nous écrire » (E-19, D-063) étaient rangés dans support_messages sans que personne de l'équipe puisse les lire. A-13 les liste, les ouvre et permet de les marquer traités ; le menu et le tableau de bord comptent ceux qui attendent.

  • API (modérateurs et admins ; un éditeur reçoit 403 ; jamais en cache) : GET /admin/support (les messages à traiter d'abord, du plus ancien au plus récent, puis les traités du plus récent au plus ancien ; filtre status, pagination), GET /admin/support/:id (le message en entier) et POST /admin/support/:id/handled (journalisé ; 409 already_decided avec l'heure du premier traitement si un autre membre l'a déjà fait).
  • Compteurs : le menu Support et la tuile « à traiter » du tableau de bord comptent les messages non traités, en retard au-delà de 72 h. Le code de ces deux compteurs le prévoyait pour cette section (« chacun reçoit sa requête avec sa section ») et le wiki décrit le compteur de la coque (Admin-Web › Coque) ; le ticket ne le cite pas mot pour mot.
  • Admin (/support) : la liste (sujet et début du message, compte, date, statut, filtre par statut), le détail (statut, dates, lien vers la fiche du compte A-06, adresse de réponse, version de l'app, appareil, langue, message), le bouton « Marquer traité » et un encart qui explique comment répondre en attendant (voir plus bas).

Ce qui manque dans cette PR, et pourquoi

« Répondre » par e-mail n'est pas fait. Le ticket et Admin-Web › A-13 le demandent (« e-mail Brevo au modèle réponse du support, E-22, dans la langue de l'utilisateur ; la réponse est gardée dans la fiche »), et le critère d'acceptation (« une réponse part par e-mail dans la langue de l'utilisateur et reste visible dans la fiche ») n'est donc pas tenu. Décision de l'opérateur (Marco, 2026-10-20), pour quatre raisons :

  1. Aucun code d'envoi d'e-mail n'existe dans l'API. Le premier prévu est celui du lien de connexion par Brevo, Envoyer le lien de connexion par l'API via Brevo #397, encore bloqué.
  2. Les prérequis de l'envoi sont humains, pas du code : le domaine widooapp.com authentifié dans Brevo, l'adresse de l'expéditeur, et la clé de staging dans Secret Manager (Authentifier widooapp.com dans Brevo pour la production et créer l'expéditeur Widoo (tâche de Paul) #467, après l'achat du domaine Acheter le domaine widooapp.com et convenir de la pose de ses enregistrements DNS (tâche d'Ilan) #366). Le compte Brevo a été transmis à Paul le 2026-10-14.
  3. Sans domaine ni clé, un envoi ne peut pas être essayé : du code écrit sans pouvoir être testé n'a pas sa place dans une PR de niveau 2.
  4. Un faux « Répondre » est pire que pas de bouton. Un bouton qui garderait le texte sans envoyer l'e-mail laisserait croire à l'équipe que l'utilisateur a reçu sa réponse. La carte dit donc le contraire en toutes lettres : écrire à l'adresse indiquée depuis sa messagerie, dans la langue du message, puis marquer le message traité.

Ce qui reste à faire, pour un ticket de suite bloqué par #397 (signalé sur l'Inbox, à créer par MANAGER) : POST /admin/support/:id/reply, les colonnes reply_body, replied_by et replied_at de Modele-de-Donnees (une migration), l'envoi par le modèle E-22 dans la langue du message, la réponse gardée et affichée dans la fiche, et le critère du ticket 349 sur la réponse par e-mail à déplacer vers lui.

Autres limites

  • Le nom du compte seul est affiché dans la liste ; l'adresse e-mail de réponse n'est lue que dans le détail (une donnée personnelle de moins sur chaque ligne).
  • Aucun message n'est supprimé ni rouvert depuis l'admin : le ticket ne le demande pas, et la durée de conservation est à fixer (Securite-et-RGPD).
  • La lecture d'un message n'écrit pas de ligne dans le journal ; seule la décision « traité » y entre.

Fixes #349
Écrans : A-13
Dépendance : PR empilée sur #551 (ticket #346), elle-même sur #550, #501, #500, #499, #479, #398, #365 et #363 (base issue/346-b2b-partners).

Comment vérifier

Montage local de la pile, base locale migrée, compte moderator ou admin, et quelques lignes dans support_messages (un message avec compte, un sans compte, un en anglais, un déjà traité, un vieux de plus de 72 h).

  1. Menu : la rubrique Support porte un compteur égal au nombre de messages à traiter, avec un signe de retard.
  2. Page Support : les messages à traiter d'abord, du plus ancien au plus récent, puis les traités ; le filtre « Statut » garde les uns ou les autres.
  3. Ouvrir un message : statut, dates, compte (lien vers sa fiche), « Répondre à » (adresse), version, appareil, langue, message sur plusieurs lignes, et l'encart qui explique de répondre depuis sa messagerie.
  4. « Marquer traité » : message de confirmation, badge vert, date de traitement, le bouton disparaît, le compteur du menu baisse de 1, le message descend dans la liste.
  5. Un message traité n'a ni bouton ni encart ; le journal (A-12) garde la ligne support_message.handle.
  6. Un compte editor n'a pas la rubrique, et l'API lui répond 403.

Tests : 10 tests d'API (admin-support.test.ts : accès par rôle, l'ordre de la liste, les filtres, la carte, 404, « traité » avec son journal, le 409 avec l'heure du premier, l'éditeur refusé sans écriture, les compteurs et la tuile) et 2 de l'aperçu ; schémas, requête du client et adresse de la liste côté shared, api-client et admin.

Testé par Marco le 2026-10-20, en local, sur le commit 8aad547 (base locale, 4 messages fictifs) : le compteur du menu, l'ordre et le filtre de la liste, le détail d'un message avec compte, sans compte et en anglais, le lien vers la fiche du compte, « Marquer traité » avec son effet sur le badge, la liste et le compteur, et un message déjà traité. Rien n'a été testé sur un environnement déployé, ni avec un compte éditeur.

  • pnpm lint && pnpm typecheck && pnpm test verts en local (chaque commit passe lint et types)

Formatage mécanique : non

Risque

Niveau : 2 — nouveaux endpoints authentifiés qui lisent des messages et des adresses e-mail d'utilisateurs et en écrivent le statut ; aucune migration.

Taille : tous les commits sous 400 lignes utiles (le plus gros : 377). Aucun fichier généré.

Revue OWASP (Top 10, édition en cours)

  • A01 Contrôle d'accès : les trois routes passent par requireAdminSection('support') (modérateurs et admins). Un éditeur reçoit 403 sur la liste, le détail et « traité », sans rien écrire (testé) ; une requête sans jeton, 401. Le test de garde de toutes les routes /admin/* couvre ces routes.
  • A03 Injection : requêtes paramétrées ; filtre et identifiant validés (400 pour un statut inconnu, identifiant en UUID) ; les messages sont du texte brut stocké tel qu'il a été validé à l'envoi, affiché sans HTML (l'adresse de réponse n'est un lien mailto: que pour une adresse e-mail valide).
  • A04 / concurrence : le message est verrouillé (for update) le temps de la décision, avec son journal dans la même transaction ; deux décisions simultanées donnent un 200 et un 409 (testé).
  • A09 Journalisation : support_message.handle garde l'auteur, l'état avant et après.
  • Données personnelles : l'adresse de réponse, le contenu du message, la version de l'app et l'appareil ne sont lus que par l'équipe, jamais dans une liste publique ni dans un journal applicatif ; la liste n'affiche que le prénom du compte et le début du message.

Nouvelle dépendance : aucune.

Retour arrière

Le revert de la PR suffit : aucune migration, aucune donnée créée. Un message marqué traité garde sa date de traitement ; la lire de nouveau ne demande rien. Les compteurs du menu et du tableau de bord reviennent à zéro pour le support.

🤖 Generated with Claude Code

@Nascimento95 Nascimento95 added the level:2 Risque niveau 2 (SECURITY.md) label Oct 20, 2026
@Nascimento95 Nascimento95 mentioned this pull request Oct 20, 2026
1 task
@PaulThiberville

Copy link
Copy Markdown
Collaborator

📋 MANAGER, 2026-10-21 — relecture de la pile admin pour Paul (tête 8aad547). Récapitulatif : Inbox.

Verdict : A, fusionnable telle quelle. Niveau 2, cohérent.

Vérifié :

  • Le critère restant de #349 est tenu. La réponse par e-mail est sortie vers #574 par décision du 2026-10-20 : la section « Ce qui manque » de la PR est donc déjà traitée côté tickets.
  • requireAdminSection('support'), verrou et journal dans la transaction, 409 testé.
  • L'adresse de réponse n'apparaît que dans le détail. Aucune migration.

Labels type:feature et area:admin absents.

Base automatically changed from issue/346-b2b-partners to main October 21, 2026 16:34
Nascimento95 and others added 7 commits October 21, 2026 18:34
…and their card (A-13)

Types of the list and of the card of a message of « Nous écrire »: the subject, the account, the address to answer to, what the app joined, the status. A first line holds the beginning of the message, the card the whole of it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…tors and admins (A-13)

GET /admin/support lists the messages to handle first, the oldest first, then the handled ones, with a filter on the status. GET /admin/support/:id reads one in full. Moderators and admins only: they hold what users wrote and the address they gave.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
POST /admin/support/:id/handled marks a message handled with its journal line, and answers 409 already_decided with the time of the first decision.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… the dashboard (A-13)

The navigation and the tile of the dashboard count the messages to handle, late after 72 hours, as the code planned for this section.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
getAdminSupport, getAdminSupportMessage and markSupportMessageHandled.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A-13 opens on the list of the messages with a status filter, and the card of the one the address names: what was written, the account, the address, the version and the device. Until the answer by e-mail exists, the card says to answer from a mailbox.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The button of the card of a message to handle, with its toasts and the counter of the navigation read again.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@PaulThiberville
PaulThiberville merged commit d95f879 into main Oct 21, 2026
5 checks passed
@PaulThiberville
PaulThiberville deleted the issue/349-support branch October 21, 2026 16:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

level:2 Risque niveau 2 (SECURITY.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Admin : support (A-13)

2 participants