# Scripts `bin/`

Point d'entrée en ligne de commande de Hydrogen. Trois familles :

- **Crons / workers** — à planifier (Task Scheduler en local, cron en prod). Ils
  vidangent des tampons, recalculent des agrégats ou régénèrent des fichiers
  statiques.
- **Opérations ponctuelles** — déploiement, bundling des migrations, application
  des réglages Meilisearch. Lancés à la main au moment voulu.
- **Outils de dev / debug** — harnais de test et diagnostic, jamais en prod.

> **Invocation PHP.** Le binaire n'est pas dans le `PATH` sur la machine de dev :
> utiliser le chemin complet, p. ex.
> `/c/laragon/bin/php/php-8.4.4-nts-Win32-vs17-x64/php.exe bin/<script>.php`.
> Sur le serveur, `php bin/<script>.php` suffit.

Tous les scripts chargent `.env` via `Hydrogen\Config\Env::load()` puis
construisent le conteneur PHP-DI (`useAutowiring(true)` + `useAttributes(true)`),
sauf `robots-generate.php` (pas besoin du conteneur).

**Convention de codes de sortie** : `0` succès · `1` échec de bootstrap / IO ·
`2` échec d'exécution (worker à relancer) ou arguments invalides.

---

## Crons / workers

À cadencer périodiquement. Sauf mention contraire, un **seul** worker suffit :
les tampons sont vidangés FIFO par id auto-incrémenté et chaque lot est commité
atomiquement, donc deux workers concurrents ne feraient que se disputer les mêmes
lignes (le perdant noop).

### `notifications-flush.php`

Pousse les notifications en file vers OneSignal (1 ligne → 1 push, N lignes → un
digest par destinataire).

```bash
php bin/notifications-flush.php
```

- **Cadence** : `NOTIFICATION_DIGEST_INTERVAL_MINUTES` (2 en dev, 5 en prod).
- **Sortie** : `recipients`, `pushed`, `skipped`, `failed`.
- **Service** : `NotificationFlushService`.

### `media-counters-flush.php`

Vidange le tampon de compteurs média, met à jour `media_stats` + `media_view_daily`,
puis GC la table de déduplication.

```bash
php bin/media-counters-flush.php
```

- **Cadence** : `MEDIA_COUNTERS_FLUSH_TICK_SECONDS` (défaut 30 s en prod).
- **Sortie** : `drained`, `bumped`, `deletedEvents`, `gcRows`.
- **Service** : `MediaCountersFlushService`.

### `user-counters-flush.php`

Identique au précédent pour les profils utilisateur : `user_stats` +
`user_view_daily`.

```bash
php bin/user-counters-flush.php
```

- **Cadence** : `USER_COUNTERS_FLUSH_TICK_SECONDS` (défaut 30 s en prod).
- **Sortie** : `drained`, `bumped`, `deletedEvents`, `gcRows`.
- **Service** : `UserCountersFlushService`.

### `tracking-flush.php`

Vidange le tampon de clics (`tracking_event`) et met à jour `tracking_stats` +
`tracking_daily`. Les **conversions** ne passent pas par ici (écrites en synchrone
par l'endpoint postback admin).

```bash
php bin/tracking-flush.php
```

- **Cadence** : `TRACKING_FLUSH_TICK_SECONDS` (défaut 30 s en prod).
- **Sortie** : `drained`, `bumped`, `deletedEvents`.
- **Service** : `TrackingFlushService`.

### `account-purge.php`

Efface **irréversiblement** les comptes dont la fenêtre de grâce RGPD est écoulée :
suppression des médias possédés (fichiers + DB + index), anonymisation de la ligne
`user`, retrait du document de recherche et du token de suppression.

```bash
php bin/account-purge.php [limit]
```

- **Argument** : `limit` plafonne le nombre de comptes traités ce tick (défaut
  `ACCOUNT_PURGE_BATCH_SIZE`, fallback 50).
- **Cadence** : quotidienne. Idempotent — un crash retente au tick suivant.
- **Sortie** : `purgedUsers`, `erasedMedias`.
- **Service** : `AccountPurgeService`.

### `platform-metrics-rollup.php`

Recalcule le rollup KPI quotidien (`hxa_bo.platform_metric_daily`) lu par les
dashboards de tendances du back-office.

```bash
php bin/platform-metrics-rollup.php
```

- **Cadence** : une fois par jour, hors pointe (p. ex. 03:00). La fenêtre
  `PLATFORM_METRICS_LOOKBACK_DAYS` (défaut 7) re-plie les derniers jours pour
  corriger les lignes arrivées en retard. UPSERT → rattrapage manuel sûr.
- **Sortie** : `days`, `flowRows`, `snapshotRows`, `countryRows`.
- **Service** : `PlatformMetricsRollupService`.

### `sitemap-generate.php`

Régénère `public/sitemap.xml` à partir des providers de sitemap enregistrés.
Écriture atomique (`.tmp` + `rename()`) pour qu'un crawler ne voie jamais un
document à moitié écrit.

```bash
php bin/sitemap-generate.php
```

- **Cadence** : périodique, pour rester en phase avec le catalogue (nouveaux pays
  indexés, nouvelles pages du registry…).
- **Sortie** : nombre d'URLs, octets, durée.
- **Fichier statique** : servi directement par le serveur web, hors hot path PHP.

### `robots-generate.php`

Régénère `public/robots.txt` à partir de `APP_URL`. Même écriture atomique.

```bash
php bin/robots-generate.php
```

- **Cadence** : à chaque changement de `APP_URL` (typiquement au déploiement) ou
  dans le même tick que `sitemap-generate.php`.
- **Prérequis** : `APP_URL` non vide (sinon code `1`).
- **Sortie** : `Disallow: /api/` + `/admin/`, ligne `Sitemap:` vers `sitemap.xml`.

---

## Opérations ponctuelles

### `deploy.php`

Déploiement FTP/FTPS à la demande, push du **delta** uniquement (cf.
[docs/deploy.md](deploy.md) pour la configuration et les détails).

```bash
php bin/deploy.php [--config=PATH] [--dry-run] [--mirror] [--help]
php bin/deploy.php --rollback [--backup=ID] [--dry-run] [--config=PATH]
```

| Option | Effet |
|---|---|
| `--config=PATH` | Chemin du fichier de config (défaut `./deploy.config.php`). |
| `--dry-run` | Affiche le plan sans contacter le serveur. |
| `--mirror` | Supprime aussi les fichiers distants absents en local. |
| `--rollback` | Annule le dernier déploiement depuis sa sauvegarde. |
| `--backup=ID` | Sauvegarde précise à restaurer (défaut : la plus récente). |
| `--help` | Affiche l'aide. |

- **Codes** : `0` ok / dry-run · `1` config ou transfert · `2` arguments invalides.

### `migrations-bundle.php`

Regroupe les migrations SQL **non encore déployées** en **un fichier par base**
(`database/deploy/<clé>.sql`), à exécuter sur le serveur puis supprimer. Pas de
runner automatique : le schéma est appliqué à la main.

```bash
php bin/migrations-bundle.php             # génère les bundles (sûr, relançable)
php bin/migrations-bundle.php --dry-run   # liste le delta sans écrire
php bin/migrations-bundle.php --mark      # avance le watermark (APRÈS un déploiement réussi)
```

- **Delta** = migrations dont le préfixe `YYYY_MM_DD_HHMMSS` est supérieur au
  watermark de `database/migrations/.bundle-state.json`.
- **Base cible** = directive d'en-tête `-- @database: <clé>` sur sa propre ligne.
  Clés : `main` (défaut → `DB_NAME`), `bo` (`BO_DB_NAME`), `work` (`WORK_DB_NAME`).
  Le fichier généré n'émet **pas** de `USE` : l'opérateur choisit la base à
  l'exécution.
- **Flux** : générer → exécuter les `.sql` → `--mark` → supprimer
  `database/deploy/`.
- **Codes** : `0` ok · `1` IO · `2` arguments invalides.

### `migrate.php`

Runner de migrations : applique les `database/migrations/*.sql` **non encore
appliquées**, dans l'ordre chronologique, chacune sur sa base cible, et trace
l'état dans une table `schema_migration` **par base** (créée au besoin) — donc
jamais de double application.

```bash
php bin/migrate.php                    # applique les migrations en attente
php bin/migrate.php --dry-run          # liste les pending, n'applique rien (lecture seule)
php bin/migrate.php --status           # appliquées vs en attente, par base (lecture seule)
php bin/migrate.php --baseline         # marque TOUTES les migrations comme appliquées, sans les jouer
php bin/migrate.php --baseline=<ver>   # marque celles dont le nom <= <ver> comme appliquées, sans les jouer
php bin/migrate.php --mark=<fichier>   # marque UNE migration comme appliquée, sans la jouer
```

- **Exécution via le client `mysql`** (env `MYSQL_BIN`, défaut `mysql`) — pas
  PDO, car certaines migrations utilisent `DELIMITER` + triggers. Le suivi
  (`schema_migration`) passe, lui, par PDO.
- **Base cible** = même directive `-- @database: <clé>` que le bundler. Chaque
  base a **ses propres** identifiants (`DB_*`, `BO_DB_*`, `WORK_DB_*`,
  `CONTENT_DB_*`).
- **Adoption sur une base déjà peuplée** (aucun tracking initial) : lancer
  **une fois** `--baseline=<dernière migration réellement appliquée>` pour
  initialiser `schema_migration` sans rejouer l'existant, puis `migrate.php`
  n'appliquera plus que les nouvelles. `--mark` sert au cas d'une migration
  appliquée à la main.
- **Arrêt sur erreur** : une migration qui échoue stoppe le runner ; les
  précédentes restent appliquées (MySQL committe le DDL implicitement — pas de
  rollback transactionnel possible). Corriger puis relancer.
- **Codes** : `0` ok · `1` runtime/IO · `2` arguments invalides.

> Coexistence avec `migrations-bundle.php` : le bundler reste utile pour
> produire un fichier consolidé à exécuter à la main sur un serveur sans PHP ;
> `migrate.php` est le chemin automatisé quand PHP + le client `mysql` sont
> disponibles.

### `media-meili-apply-settings.php`

Pousse les réglages d'index média + synonymes dont dépend le pipeline hashtags.
Idempotent (`updateSettings` / `updateSynonyms` sont des upserts côté serveur).

```bash
php bin/media-meili-apply-settings.php
```

À lancer :

- après le premier déploiement (l'index refuse les filtres `hashtags=` et les
  `facetSearch` tant que `filterableAttributes` ne liste pas `hashtags`),
- après édition de `config/hashtag_synonyms.php` (Meili remplace la map en entier),
- après modification de la forme des réglages dans
  `MeilisearchMediaSync::applySettings()`.

- **Sortie** : confirmation settings + nombre de synonymes.
- **Codes** : `0` ok · `1` settings KO · `2` settings ok mais synonymes KO
  (relance sûre).

---

## Outils de dev / debug

> **Non destinés à la production.** Ces scripts écrivent en base et sur disque ;
> ne jamais les lancer sur un environnement réel.

### `debug_google_token.php`

Diagnostique pourquoi un `id_token` Google échoue à la vérification : header JOSE
décodé (kid, alg), payload non vérifié (iss, aud, exp, email…), puis résultat de
la vraie vérification signature + claims.

```bash
php bin/debug_google_token.php "<id_token>"
```

### `test-media-upload.php` · `test-media-http.php` · `test-media-glide.php` · `test-media-crud.php`

Harnais de test ad-hoc du pipeline média. Ils fabriquent un JPEG synthétique via
Imagick, ciblent le **premier utilisateur confirmé** trouvé en base, et exercent
respectivement :

- **`test-media-upload.php`** — l'upload via le service domaine (pHash, etc.).
- **`test-media-http.php`** — l'upload via une requête PSR-7 (3 slots de fichier).
- **`test-media-glide.php`** — l'upload puis la transformation Glide.
- **`test-media-crud.php`** — le cycle CRUD complet, y compris le cas interdit
  (média d'un autre utilisateur). ⚠️ **Purge les médias existants** de
  l'utilisateur de test pour être déterministe.

```bash
php bin/test-media-crud.php
```

Aucun argument ni flag — tout est câblé en dur dans le script.
