# Phase 2 — Cartographie pré-plan (parking)

> Date : 2026-05-25
> Statut : exploration faite, plan d'implémentation **différé** (l'utilisateur a pivoté vers la validation des rôles Phase 1 via 3 users de test + le catalogue Stripe).
> Reprendre quand on revient sur la migration multi-tenant des tables domaine.

## TL;DR

26 fichiers à toucher (schema, 11 API routes, 2 dashboard pages, 4 admin pages, 1 lib helper, 1 cron, 1 client hook, 5 scripts à vérifier). Un nouveau script `backfill-sites-orgs.ts` à créer pour adosser les rows existantes à `organization_id` via membership.

## Schema (1)

- `lib/db/schema.ts` — `es_site` (+ `organization_id` NOT NULL FK cascade, rename `user_id → created_by_user_id`, indexes `idx_site_org`, drop `idx_site_user`). Idem `es_site_test` avec `triggered_by_user_id`. Complexité : Moyen.

## API routes (10)

### Sites (5)

- `app/api/sites/route.ts` GET — swap `eq(sites.userId, ...)` → `eq(sites.organizationId, session.active_organization_id)`. Trivial.
- `app/api/sites/route.ts` POST — idem + ajout `organizationId` + rename `userId → createdByUserId` à l'insert. Trivial.
- `app/api/sites/[siteId]/route.ts` GET — swap filter. Trivial.
- `app/api/sites/[siteId]/route.ts` DELETE — swap filter. Trivial.
- `app/api/sites/[siteId]/har/route.ts` — swap filter. Trivial.

### Site tests (2)

- `app/api/sites/[siteId]/test/route.ts` POST — swap filter + ajouter `organizationId` + rename `userId → triggeredByUserId` à l'insert. Trivial.
- `app/api/sites/[siteId]/test/route.ts` GET — update direct par id, **vérifier qu'on valide bien l'appartenance org** (l'audit a noté qu'il n'y a pas de check actuel). **Moyen — sécurité**.

### Integration API (2) — **risqué**

- `app/api/integrations/audit/route.ts` — audits anonymes (`userId IS NULL`). Décision archi nécessaire : `organizationId` reste NULL (column nullable) ou créer une "Anonymous Org" globale ? **Recommandation** : column nullable pour les audits anonymes.
- `app/api/integrations/audit/[id]/route.ts` — swap `userId IS NULL` → `triggeredByUserId IS NULL`. Trivial une fois la décision prise.

### Cron (1)

- `app/api/cron/run-audits/route.ts` — passer `organizationId: site.organizationId` à `launchSitespeedTest`. Trivial.

## Pages (6)

### Dashboard (2)

- `app/[locale]/dashboard/sites/[siteId]/page.tsx` — swap filter. Trivial.
- `app/[locale]/dashboard/sites/[siteId]/actions.ts` — swap filter. Trivial.

### Admin (4)

- `app/[locale]/admin/sites/page.tsx` — change `.leftJoin(user, sites.userId)` → `.leftJoin(organization, sites.organizationId)`. Décision UX : afficher l'org plutôt que l'owner email ? Si on veut quand même l'owner email, subquery sur `member` WHERE `role='owner'`. **Moyen**.
- `app/[locale]/admin/tests/page.tsx` — vérifier les joins (probable no-op).
- `app/[locale]/admin/page.tsx` — no-op (count global, pas de filtre).
- `app/[locale]/admin/users/page.tsx` — subquery `sitesCount` / `testsCount` par user doit changer : count via `member` JOIN. **Moyen**.

## Lib & client (2)

- `lib/sitespeed-launch.ts` — ajouter `organizationId` au type `LaunchTestArgs` + à l'insert. Renommer `userId → triggeredByUserId`. Trivial.
- `data/sites/hooks.ts` — ajouter `organizationId` au type `SiteWithLatest` (et probablement drop `userId` côté client). Trivial.

## Scripts (5)

- `scripts/make-admin.ts` — no-op (touche `user.role`).
- `scripts/backfill-orgs.ts` — no-op (Phase 1).
- `scripts/seed-user.ts` — no-op.
- `scripts/verify-org-signup.ts` — no-op (test signup → org, pas de sites).
- `scripts/test-email-validation.ts` — no-op.

## Nouveau script (1)

- `scripts/backfill-sites-orgs.ts` — pour chaque `es_site` / `es_site_test` avec `user_id` historique, lookup membership de ce user, set `organization_id` correspondant, et populate `created_by_user_id` / `triggered_by_user_id` depuis l'ancien `user_id`.

## Décisions d'archi à trancher avant le plan

1. **Audits anonymes (Integration API)** : `organizationId` NULLABLE ou "Anonymous Org" globale ? Recommandation : NULLABLE.
2. **`triggered_by_user_id` pour cron** : NULL (pas de user) ou créer un user système ? Recommandation : NULL.
3. **`/admin/sites` join** : afficher org name ou owner email ? Recommandation : afficher org name (simpler), garder l'option de drill-down vers le détail org.
4. **`/admin/users` sitesCount** : sites créés par l'user (direct, via `createdByUserId`) ou sites dans toutes les orgs de l'user (via membership) ? Recommandation : "sites dans orgs de l'user" (matche mieux le mental model multi-tenant).

## Pré-requis avant d'attaquer le plan

- Phase 1 ✅ déjà mergée dans `dev`.
- Valider les 3 décisions ci-dessus avec l'utilisateur.
- Décider de la stratégie de migration sur DB peuplée (1 user, N sites existants chez `bastien@elevaseo.com`).
