Jobs & files
POST /admin/jobs/cleanup-orphan-files
Section titled “POST /admin/jobs/cleanup-orphan-files”Récupère les fichiers media publiés dont la row DB a disparu (média hard-deleté hors-bande, upload avorté ayant écrit le WebP avant la row, purge SQL manuelle…). Scanne le root publié (MEDIA_STORAGE_PATH), ne retient que les WebP primaires <hex>.webp, batch les ids et interroge la DB : un fichier sans row est un orphelin. Le supprimer retire aussi son compagnon -blurhash.webp.
Sécurité — dry-run par défaut. Rien n’est supprimé sans ?delete=1. Un dry-run rapporte exactement ce qu’une vraie passe supprimerait.
Le scan est plafonné (?limit, défaut 1000, max 50000 fichiers examinés par appel) pour rester borné sur un arbre à plusieurs millions de fichiers. Quand capped = true, la borne a été atteinte — relancer pour continuer (en mode delete, l’ensemble des orphelins rétrécit au fil des suppressions).
Les originaux (root séparé, extension source) ne sont pas touchés : leur extension n’est pas déductible de l’id seul, et ce sont des archives froides — hors périmètre du nettoyage du root publié.
Query params
limit(int, optionnel) : 1..50000 fichiers à examiner. Défaut1000.delete(bool, optionnel) :1/truepour réellement supprimer. Défaut off (dry-run).
Réponse (200)
{ "status": "ok", "dryRun": true, "scanned": 1000, "orphansFound": 7, "deleted": 0, "capped": true, "sample": ["01a3471992e44c60a8f08321f713635a", "..."], "durationMs": 142}| Champ | Sens |
|---|---|
dryRun | true tant que ?delete=1 n’est pas passé |
scanned | nombre de WebP primaires examinés |
orphansFound | fichiers sans row DB |
deleted | fichiers réellement supprimés (0 en dry-run) |
capped | true si la borne limit a été atteinte (il peut rester des orphelins) |
sample | aperçu des ids orphelins (max 100) |
Erreurs
| Status | Body | Sens |
|---|---|---|
400 | { "error": "Invalid limit." } | limit non entier ou hors 1..50000 |
403 | { "error": "..." } | auth KO |
Exemple curl
# Dry-run (audit)curl -s -X POST -H "Authorization: Bearer $ADMIN_API_TOKEN" \ "http://hydrogen.dev.com/admin/jobs/cleanup-orphan-files?limit=5000"
# Suppression effectivecurl -s -X POST -H "Authorization: Bearer $ADMIN_API_TOKEN" \ "http://hydrogen.dev.com/admin/jobs/cleanup-orphan-files?limit=5000&delete=1"POST /admin/jobs/flush/{job}
Section titled “POST /admin/jobs/flush/{job}”Force un drain immédiat d’un tampon habituellement vidé par cron, sans accès shell (Postman / Talend). L’endpoint n’est qu’un déclencheur HTTP : il appelle exactement le même FlushService::flush() que les entry-points bin/*-flush.php (aucune logique dupliquée).
{job} (la route restreint déjà aux valeurs valides) :
job | Service(s) | Équivaut au cron |
|---|---|---|
counters | media + user (les deux en un appel) | bin/media-counters-flush.php + bin/user-counters-flush.php |
notifications | digest OneSignal | bin/notifications-flush.php |
tracking | tampon de clics d’affiliation | bin/tracking-flush.php |
Idempotent par nature : un flush sur un tampon vide renvoie des compteurs à zéro. Un échec de transaction laisse remonter l’exception → 500 JSON plat (l’opérateur relance), comme un cron qui sortirait en code 2.
Réponse (200)
{ "job": "counters", "summary": { "media": { "drained": 0, "bumped": 0, "deletedEvents": 0, "gcRows": 0 }, "user": { "drained": 0, "bumped": 0, "deletedEvents": 0, "gcRows": 0 } }}{ "job": "notifications", "summary": { "recipients": 3, "pushed": 3, "skipped": 0, "failed": 0 } }{ "job": "tracking", "summary": { "drained": 42, "bumped": 12, "deletedEvents": 42 } }Erreurs
| Status | Body | Sens |
|---|---|---|
400 | { "error": "Unknown job '…'. Expected: counters, notifications, tracking." } | garde défensive (la regex de route 404 avant, en principe) |
403 | { "error": "..." } | auth KO |
500 | { "error": "..." } | échec de flush (transaction) — relancer |
Exemples curl
curl -s -X POST -H "Authorization: Bearer $ADMIN_API_TOKEN" \ "http://hydrogen.dev.com/admin/jobs/flush/counters"
curl -s -X POST -H "Authorization: Bearer $ADMIN_API_TOKEN" \ "http://hydrogen.dev.com/admin/jobs/flush/tracking"GET /admin/jobs/describe-queue
Section titled “GET /admin/jobs/describe-queue”Observabilité de la file IA work.media_to_describe (FIFO des médias en attente de description, consommée par un worker hors-bande). Jusqu’ici l’admin était aveugle sur ce backlog. Lecture seule, un seul round-trip vers la base work.
Réponse (200)
{ "size": 42, "oldestEnqueuedAt": "2026-06-19T08:12:00+00:00", "oldestAgeSeconds": 1834, "enqueuedLastHour": 7, "enqueuedLast24h": 120}| Champ | Sens |
|---|---|
size | profondeur de la file (médias en attente) |
oldestEnqueuedAt | timestamp de la tête de file (null si vide) |
oldestAgeSeconds | âge de la tête en secondes — grandit ⇒ le worker décroche |
enqueuedLastHour / enqueuedLast24h | taux d’arrivée récent, à comparer au débit du worker |
curl -s -H "Authorization: Bearer $ADMIN_API_TOKEN" \ "http://hydrogen.dev.com/admin/jobs/describe-queue"POST /admin/jobs/describe-queue/requeue
Section titled “POST /admin/jobs/describe-queue/requeue”Réinjecte un média bloqué dans la file IA (worker mort en plein processing, ligne de queue perdue, retry après failed). INSERT IGNORE idempotent + remise de media.status à pending (sauf s’il l’est déjà) pour que le cycle de vie reste cohérent et qu’un futur claim soit légal.
Refuse un média en état terminal (published / rejected) : il a déjà un verdict, le redécrire serait une régression (409).
Body
| Champ | Type | Requis | Sens |
|---|---|---|---|
mediaId | string (32 hex) | oui | id du média à réinjecter |
Réponse (200)
{ "status": "ok", "mediaId": "d26d1600cde54bd095e09f8b68ace05f", "previousStatus": "failed", "mediaStatus": "pending", "alreadyQueued": false}| Champ | Sens |
|---|---|
previousStatus | état du média avant requeue |
mediaStatus | toujours pending après requeue |
alreadyQueued | true si la ligne était déjà dans la file (réinjection no-op) |
Erreurs
| Status | Body | Sens |
|---|---|---|
400 | { "error": "Body must be JSON object with 'mediaId' 32-hex string." } | corps absent / mediaId manquant ou mal formé |
404 | { "error": "Media not found." } | id inconnu |
409 | { "error": "Cannot requeue a media in terminal state '<state>'." } | média published / rejected |
403 | { "error": "..." } | auth KO |
curl -s -X POST -H "Authorization: Bearer $ADMIN_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "mediaId": "d26d1600cde54bd095e09f8b68ace05f" }' \ "http://hydrogen.dev.com/admin/jobs/describe-queue/requeue"