Skip to content

feat(admin): tableau de bord, lieux, parcours et import CSV - #376

Closed
Nascimento95 wants to merge 23 commits into
issue/53-moderation-queuefrom
issue/55-catalog-dashboard
Closed

Nascimento95 wants to merge 23 commits into
issue/53-moderation-queuefrom
issue/55-catalog-dashboard

Conversation

@Nascimento95

@Nascimento95 Nascimento95 commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Résultat et problème

L'équipe n'avait ni tableau de bord ni moyen de gérer le catalogue. Cette PR ajoute le tableau de bord (A-05) et la section « Lieux et parcours » (A-04) : parcours, lieux et import CSV, côté API et côté admin.

Ce qui est fait :

  • Tableau de bord : GET /admin/counters et GET /admin/stats ; tuiles « à traiter » selon le rôle, chacune ouvre sa liste filtrée ; chiffres du catalogue et des utilisateurs ; liens vers PostHog et RevenueCat.
  • Parcours : GET /admin/routes (recherche, filtres, tri, pages) ; fiche en lecture ; Dépublier et Republier avec motif, Réassigner à l'équipe (admin), chaque action journalisée.
  • Lieux : GET/POST /admin/places, GET/PATCH /admin/places/:id ; liste, fiche dans le panneau de détail, création et édition dans un formulaire (éditeurs et admins ; un modérateur lit). La ville est déduite de la position.
  • Import CSV (admin) : POST /admin/places/import, avec aperçu (dryRun) puis création ; modèle à télécharger ; chaque ligne affichée avec son résultat (prêt, créé, invalide avec les champs à corriger, doublon, hors zone) ; doublon = même nom à moins de 50 m, dans le catalogue ou plus haut dans le fichier ; une ligne au journal par lieu créé.
  • Carte de position (mapbox-gl) : dans le formulaire d'un lieu, une épingle à déplacer ou un clic sur la carte renseigne la latitude et la longitude, et la saisie des champs déplace l'épingle. Sans jeton NEXT_PUBLIC_MAPBOX_TOKEN, le formulaire garde ses deux champs seuls.
  • Ouvrir dans l'app : l'écran du parcours de l'app est encore provisoire (Construire la fiche parcours (E-05) #37) ; le lien n'ouvre pas encore la fiche. Sur ordinateur il ne fait rien.
  • Accès : une page dont le rôle n'ouvre pas la section, le formulaire de lieu pour un modérateur et l'import pour un non-admin affichent un refus, même avec l'adresse tapée à la main ; l'API refuse de son côté (403).

Critères d'acceptation :

  • Import de 50 lieux avec 2 lignes invalides : 48 créés, 2 erreurs explicites (test d'API, et testé à l'écran).
  • Chaque tuile « à traiter » ouvre la liste filtrée. Les tuiles dont la liste n'existe pas encore (signalements, avis, support) affichent 0 et mènent à une page introuvable jusqu'à Admin : signalements de lieux et de parcours, avis (A-03) #54.

Limites, et le ticket qui les lève :

Questions pour Ilan :

  1. Quand un membre de l'équipe n'a pas de prénom, l'historique d'un parcours affiche le début de son e-mail et la pastille du compte son e-mail entier : quel affichage veut-on ?
  2. Le partage des rôles sur le catalogue (éditeur : écrit les lieux ; admin : import, Republier, Réassigner ; modérateur : lit les lieux, Dépublie) suit ma lecture du wiki : à confirmer.
  3. Jeton Mapbox web de Widoo (voir Limites) : qui le crée et où il est stocké.

Fixes #55
Écrans : A-04, A-05
Dépendance : empilée sur #365 (modération, #53), elle-même sur #363.

Comment vérifier

Montage local de #363, base locale, comptes admin, editor et moderator.

Testé par Marco les 2026-10-12, 2026-10-13 et 2026-10-14, en local (base locale, Firebase de staging pour la connexion), face aux planches A-05 et aux modèles de Ecrans pour A-04 :

  • le tableau de bord, ses tuiles et leurs liens ;
  • la liste et la fiche des parcours, Dépublier, Republier, Réassigner ;
  • la liste des lieux, la fiche, la création et l'édition ; le refus du formulaire pour un modérateur ;
  • la carte de position, avec un jeton Mapbox public de test sur le compte de Marco : clic sur la carte (épingle posée, latitude et longitude remplies), saisie des coordonnées (épingle déplacée), création d'un lieu avec sa position ; le déplacement de l'épingle à la souris et la réouverture d'un lieu enregistré n'ont pas été confirmés ;
  • « Ouvrir dans l'app » : bouton affiché dans la carte « Actions » d'un parcours ; l'ouverture sur un téléphone n'a pas été testée ;
  • l'import : fichier de 50 lignes dont 2 invalides (aperçu « 48 lieux prêts, 2 lignes écartées », puis 48 créés) ; fichier avec doublons du catalogue, doublon dans le fichier et lieu hors zone ; fichier sans les colonnes obligatoires.

La carte n'a pas de planche : elle suit la phrase du wiki (« épingle déplaçable »). Rien n'a été testé contre un environnement déployé. Pas de test à 150 % de texte ni au lecteur d'écran sur ces écrans.

  • pnpm lint && pnpm typecheck && pnpm test verts en local
  • Testé sur iOS / Android (préciser) — sans objet

Formatage mécanique : non

Risque

Niveau : 2 — nouveaux endpoints authentifiés qui écrivent dans le catalogue (lieux, statut des parcours, import en lot) ; aucune migration.

Taille : deux commits au-dessus de 400 lignes. 9b37d88 (451) : la liste des parcours, ses filtres, son tableau, sa configuration et ses textes. b06b72e (427) : les composants du tableau de bord, son conteneur et ses textes.

Revue OWASP (Top 10) :

  • Contrôle d'accès : lecture par requireAdminSection('catalog') ; écriture d'un lieu par requireRole('editor') ; import, Republier et Réassigner par requireRole('admin'). Refus testés par rôle (403, rien d'écrit). Les gardes de l'admin ne sont qu'un confort : l'API décide.
  • Entrées non fiables : chaque ligne importée passe par le même schéma strict que la création à la main (texte brut borné, site en https, téléphone, coordonnées) ; le CSV est lu dans le navigateur, l'API ne reçoit que du JSON ; 500 lignes au plus par appel. Les textes s'affichent sans HTML.
  • Injection : requêtes paramétrées ; recherche échappée ; filtres et tri validés par schéma strict.
  • Intégrité : un lieu et ses horaires s'écrivent avec leur ligne de journal dans la même transaction ; les actions sur un parcours verrouillent sa ligne.
  • Journalisation : place.create, place.import, place.update et les actions de parcours gardent l'auteur et l'état.
  • Limites : un import fait au plus 500 créations ; pas de quota par admin.

Nouvelle dépendance : mapbox-gl 3.32, chargée avec le formulaire de lieu seulement ; choisie parce que le wiki fixe Mapbox pour les cartes de Widoo (Architecture-Technique). Écartée : maplibre-gl, qui aurait demandé un autre fournisseur de tuiles. CSP de l'admin : api.mapbox.com, events.mapbox.com et un worker blob: ne sont autorisés que lorsqu'un jeton est configuré ; un jeton secret (sk.) est refusé au démarrage.

Retour arrière

Le revert de la PR suffit pour le code, sans migration à annuler. Les lieux créés ou importés restent en base ; un import fait par erreur se retrouve par le journal (admin_actions, action place.import).

🤖 Generated with Claude Code

Nascimento95 and others added 23 commits October 13, 2026 15:01
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…users (A-05)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…first name

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…to the team

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eason dialog

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sort (A-04)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… (A-04)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…urnal

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rnal

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es address

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s (A-04)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… API

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-04)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s (A-04)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Nascimento95 Nascimento95 added type:feature Nouvelle fonctionnalité area:admin apps/admin level:2 Risque niveau 2 (SECURITY.md) labels Oct 13, 2026
@Nascimento95

Copy link
Copy Markdown
Collaborator Author

Fermée pour l'instant à la demande de Marco : il reste trois points demandés par le ticket (position d'un lieu sur une carte, lien partenaire, « Ouvrir dans l'app »). Elle sera rouverte quand ils seront traités.

@Nascimento95

Copy link
Copy Markdown
Collaborator Author

Remplacée par #398 : GitHub ne permet pas de rouvrir une PR dont la branche a été réécrite après sa fermeture (rebase sur la modération à jour). Même description, mise à jour.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:admin apps/admin level:2 Risque niveau 2 (SECURITY.md) type:feature Nouvelle fonctionnalité

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant