feat(reels): file d'attente persistante des rendus (reel_jobs)

L'ancienne file comptait les posts « processing » : un redémarrage pendant
un rendu la bloquait définitivement. Les rendus passent désormais par une
table reel_jobs, réservée avec FOR UPDATE SKIP LOCKED, avec heartbeat et
reprise des jobs interrompus au démarrage (2 tentatives maximum).

- Pipeline vidéo et pipeline images (Remotion) extraits des routes vers
  server/services/reels/, les routes ne font que valider (zod) et mettre en file
- Rendus d'images suivis en base au lieu d'une Map en mémoire
- storeName conservé pour les jobs mis en attente
- Publication Facebook en binaire : l'URL relative /uploads/... transmise
  auparavant n'était pas téléchargeable par Facebook
- Job en échec si toutes les pages échouent ou si la voix demandée manque
  (le service Python renseigne enfin tts_error)
- La voix choisie sur la page images est réellement utilisée
- sync-info mesure la voix Gemini avec sa clé
- Timeouts sur les appels au service FFmpeg, vignettes via execFile,
  /debug-ffmpeg protégé par la clé API, plus de voix anglaise en secours

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Ze4bs7tpF1KGWUk6ZZSZ4
This commit is contained in:
Claude committed 2026-09-24 09:31:31 +00:00
1 parent fcbc6b706f
commit d24ab0d4b7
19 files changed
+1071 -901

No files matched your search

+21
View File
@@ -213,6 +213,26 @@ export async function migrate() {
ADD COLUMN IF NOT EXISTS "publish_status" text;
`);
// reel_jobs : file d'attente persistante des rendus de Reels
await client.query(`
CREATE TABLE IF NOT EXISTS "reel_jobs" (
"id" varchar PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" varchar NOT NULL REFERENCES "users"("id") ON DELETE cascade,
"post_id" varchar REFERENCES "posts"("id") ON DELETE cascade,
"kind" text NOT NULL,
"params" jsonb NOT NULL,
"status" text NOT NULL DEFAULT 'pending',
"step" text,
"progress" integer NOT NULL DEFAULT 0,
"attempts" integer NOT NULL DEFAULT 0,
"result" jsonb,
"error" text,
"heartbeat_at" timestamp,
"created_at" timestamp NOT NULL DEFAULT now(),
"updated_at" timestamp NOT NULL DEFAULT now()
);
`);
// 4. Index de performance
//
// Postgres n'indexe pas les clés étrangères tout seul : sans ces index, le
@@ -249,6 +269,7 @@ export async function migrate() {
["idx_scheduled_posts_publish_id", `CREATE INDEX IF NOT EXISTS "idx_scheduled_posts_publish_id" ON "scheduled_posts" ("publish_id") WHERE "publish_id" IS NOT NULL`],
// Reels en cours de génération
["idx_reel_jobs_queue", `CREATE INDEX IF NOT EXISTS "idx_reel_jobs_queue" ON "reel_jobs" ("created_at") WHERE "status" IN ('pending', 'processing')`],
["idx_posts_generation_status", `CREATE INDEX IF NOT EXISTS "idx_posts_generation_status" ON "posts" ("generation_status") WHERE "generation_status" IS NOT NULL`],
];