Skip to content

feat(admin): affiliation et clics de réservation (A-09) - #501

Open
Nascimento95 wants to merge 11 commits into
issue/348-settings-and-logfrom
issue/344-affiliation
Open

Nascimento95 wants to merge 11 commits into
issue/348-settings-and-logfrom
issue/344-affiliation

Conversation

@Nascimento95

Copy link
Copy Markdown
Collaborator

Résultat et problème

Widoo n'avait aucun moyen de compter les clics sur « Réserver », ni de gérer ses programmes d'affiliation, ni de savoir quelles étapes réservables n'ont pas encore de lien partenaire. A-09 « Affiliation » le fait, pour l'éditeur et l'admin.

  • GET /v1/go/:stepId (public) : compte le clic dans booking_clicks, puis redirige (302) vers le lien que l'étape affiche, celui du créateur d'abord, sinon le lien partenaire de Widoo (D-048). Il ne redirige que vers un lien déjà enregistré en https pour une étape d'un parcours publié, jamais vers une adresse donnée par l'appelant ; tout le reste est un 404 qui ne compte rien. Limité à 60 par minute et par adresse.
  • Programmes : liste, création, modification, activation et désactivation (nom unique, réseau, note de commission, cookie de 0 à 365 jours). Un programme inactif ne prête plus ses liens.
  • Lien partenaire par lieu : deux champs dans la fiche d'un lieu (PATCH /admin/places/:id), le lien en https et son programme, ensemble ou pas du tout ; un programme demande un lien.
  • Étapes réservables : les étapes de parcours publiés qui demandent une réservation ou portent un lien, avec le lien affiché, son origine (créateur, créateur affilié, partenaire de Widoo), ses clics sur 30 jours, et le filtre « lieux sans lien partenaire ».
  • Une seule règle du lien affiché : resolveBookingLink (packages/shared), que /go et la liste utilisent.
  • Chaque écriture de programme et de lieu écrit admin_actions.

Limites

  • La fiche d'un parcours côté app n'affiche pas encore le lien partenaire : GET /routes/:id n'existe pas (ticket Exposer GET /routes/:id et /routes/:id/timeline, stocker computed #36). Il devra appeler resolveBookingLink et joindre le lieu et son programme. Tant qu'il n'existe pas, aucun lien partenaire ne s'affiche dans l'app.
  • Le bouton « Réserver » de l'app qui appelle /go est le ticket mobile Passer « Réserver » par /go/:stepId pour compter les clics #345 (Paul). L'adresse est /v1/go/:stepId, comme toutes les routes de l'API (le ticket écrit https://<api>/go/:stepId).
  • booking_clicks.user_id reste vide : la redirection s'ouvre dans le navigateur, sans session. La colonne est prévue par le wiki, et son lien est coupé à la purge d'un compte.
  • Le suivi des inscriptions des programmes (S'inscrire aux programmes d'affiliation (activités, billetterie) et fournir les liens à Sarah #212, cité par le wiki A-09) n'est pas dans la liste du ticket : non fait.
  • À l'enregistrement d'un lieu, une erreur de champ (422) est maintenant dite comme telle (« un champ est refusé… ») au lieu d'un échec général, et la page défile jusqu'à l'erreur, le bouton étant en bas d'un long formulaire.

Fixes #344
Écrans : A-09, A-04 (champs du lien partenaire)
Dépendance : PR empilée sur #500 (#348, base issue/348-settings-and-log), elle-même sur #499 (#341), #479, #398, #365 et #363.

Comment vérifier

  1. pnpm install, docker-compose up -d, pnpm --filter @widoo/api db:migrate && pnpm --filter @widoo/api db:seed, pnpm dev.
  2. En admin, Affiliation › Programmes : ajouter un programme (un nom déjà pris est refusé), le modifier, le désactiver.
  3. Dans la fiche d'un lieu (Lieux et parcours), saisir un lien partenaire https://… : le champ « Programme du lien » apparaît ; un lien en http:// est refusé dès l'enregistrement.
  4. Affiliation › Étapes réservables : l'origine du lien (une étape sans lien de créateur montre « Partenaire Widoo »), les clics sur 30 jours, et le filtre « Lieux sans lien partenaire ».
  5. Ouvrir http://localhost:8080/v1/go/<identifiant d'une étape avec un lien> dans le navigateur : redirection vers le lien, et le compteur de l'étape augmente de 1. Un identifiant inconnu donne un 404 sans redirection.
  6. Connecté en moderator : « Affiliation » n'est pas dans le menu.

Tests : l'ordre créateur puis partenaire, le programme inactif, un lien partenaire sans programme, le 404 sans clic pour une étape sans lien, d'un parcours non publié ou inconnue, le délai de moins de 200 ms, les droits (moderator refusé), la liste des étapes (origines, filtre, clics sur 30 jours), les programmes (unicité du nom sans tenir compte de la casse, patch partiel, journal), le lien partenaire d'un lieu (ensemble, conservé si absent, refusé hors https ou sans lien).

  • pnpm lint && pnpm typecheck && pnpm test verts en local (chaque commit passe lint et types) ; seuls les tests chronométrés de l'API échouent parfois en suite complète (délai de 5 s) et passent seuls

Formatage mécanique : non

Risque

Niveau : 3 — nouvelle table et migration, endpoint public non authentifié qui écrit et redirige.

Taille : tous les commits sous 400 lignes utiles (le plus gros : 394). Le snapshot de la migration (meta/0018_snapshot.json) est généré par drizzle-kit.

Revue OWASP

  • A01 Contrôle d'accès : les routes admin sont fermées au moderator et à l'utilisateur par le garde de section (rôle lu en base à chaque appel) ; PATCH /admin/places/:id reste réservé à l'éditeur et à l'admin. /go est public par nature : il ne lit rien du compte et n'expose que le lien déjà affiché de l'étape.
  • A01 Redirection non validée (open redirect) : /go ne prend aucune adresse en entrée, il lit le lien stocké de l'étape ou du lieu, et le refuse s'il n'est pas en https. Les liens sont déjà tenus à https à l'écriture, par le schéma et par une contrainte de la base (places_partner_link_check).
  • A04 Conception non sécurisée : un lien partenaire n'est utilisé que si son programme est actif ; un programme exige un lien ; le lien du créateur passe toujours avant. Seule une étape d'un parcours publié est joignable.
  • A04 / A05 Abus et déni de service : une redirection est une écriture ; elle est limitée à 60 par minute et par adresse (429 au-delà), et un identifiant mal formé est refusé avant la base.
  • A03 Injection : requêtes paramétrées ; la date de la fenêtre de 30 jours est passée en paramètre typé ; textes des programmes en texte brut sans balises.
  • A09 Journalisation : création et modification d'un programme et d'un lieu écrivent admin_actions (avant, après) dans la transaction du changement.
  • Données personnelles : un clic ne porte ni compte, ni adresse IP, ni navigateur ; user_id reste vide. Réponses no-store, noindex, referrer-policy: no-referrer.
  • Aucune nouvelle dépendance.

Retour arrière

Revert de la PR. Migration 0018 additive : deux tables et deux colonnes nullables sur places. Un revert du code laisse les tables et colonnes sans effet. Pour les supprimer, retirer d'abord les liens partenaires des lieux (partner_program_id, puis partner_booking_url) avant de supprimer affiliate_programs. Les clics comptés sont des statistiques sans donnée personnelle : les garder ne coûte rien.

🤖 Generated with Claude Code

Nascimento95 and others added 11 commits October 16, 2026 17:51
…tner link of a place (A-09)

Migration 0018, additive: affiliate_programs (name, network, commission, cookie, active),
booking_clicks (step, origin of the link; no foreign key to the step, as editing a route rewrites
its steps and a cascade would erase the clicks), and places.partner_booking_url (https only) and
partner_program_id (needs a link). user_id of a click stays null: the redirect opens in the
browser, without a session.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…link a step shows (A-09)

Schemas of the programs and of the bookable steps, and resolveBookingLink, the one place of the
rule (D-048): the creator's link first, the partner link of Widoo only when the creator set none
and unless its program is inactive.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
GET, POST and PATCH /admin/affiliate-programs, for editors and admins: name (unique, whatever the
case), network, commission note, cookie days (0 to 365), active. A patch changes only the fields
sent; each write is journaled with its before and after. An inactive program no longer lends its
links.

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

PATCH /admin/places/:id takes partnerBookingUrl (https) and partnerProgramId, sent together or
not at all: left out, the place keeps its link. A program needs a link, and must exist. The sheet
of a place reads both, and the journal keeps them before and after.

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

GET /v1/go/:stepId, public: counts the click in booking_clicks, then redirects (302) to the link
the step shows, the creator's before the partner's of Widoo. It only redirects to a link stored
for a step of a published route (https), never to an address the caller gives; anything else is a
404 that counts nothing. Limited to 60 a minute and per address, never cached nor indexed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… the clicks of 30 days (A-09)

GET /admin/bookable-steps, for editors and admins: the steps of published routes that ask for a
booking or carry a link, most clicked first, each with the link it shows and where it comes from
(creator, creator affiliate, partner of Widoo). withoutPartnerLink keeps the places with no
partner link: the ones to sign up for first.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Affiliation, for editors and admins: the programs as a table (network, commission, cookie, places
that name it, status) and the detail of one next to it, with the warning that an inactive program
no longer lends its links.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…A-09)

« Ajouter un programme » and « Modifier » open a window with the name, network, commission note
and cookie days; « Désactiver » and « Activer » switch a program. A name already taken, a refused
field and any other failure are told apart in a toast.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…nd its clicks (A-09)

The second tab of Affiliation: each bookable step with the link it shows and where it comes from
(creator, creator affiliate, partner of Widoo), its clicks of the last 30 days, and a link to the
sheet of its place. « Lieux sans lien partenaire » keeps the places to sign up for first.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Two fields in the sheet of a place: the partner link (https, checked in the browser at once) and,
once there is a link, the program it belongs to (an inactive one is marked). A field the API
refuses (422) is now told as a refused field instead of a general failure, and the page scrolls
to the error, as the button is at the bottom of a long form.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

This branch has not been deployed

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

Labels

level:3 Risque niveau 3 (SECURITY.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant