Files
Socialflow/server/migrate.ts
T
Claude ac8b494f24 fix: vignettes vidéo persistantes et remise à zéro des erreurs de types
Deux corrections demandées ensemble.

1. Vignettes vidéo

Le planificateur supprime la vidéo locale une fois publiée, alors que la
ligne `media` survit pour l'historique. L'interface tentait donc de lire
un fichier disparu pour en extraire une image : d'où les avertissements
« Video thumbnail generation failed » et les tuiles vides.

Une vignette JPEG est désormais extraite à la création du reel, dans les
deux flux de génération, et stockée à part : elle reste disponible quand
la vidéo ne l'est plus. La purge quotidienne la nettoie en même temps que
la ligne qu'elle illustre. L'extraction est tolérante à l'échec — une
vignette manquante dégrade l'affichage mais ne fait jamais échouer une
publication — et le composant retombe sur son icône de remplacement pour
les médias créés avant ce changement. La fonction d'extraction, jusque-là
privée à la route Remotion, devient un service partagé.

2. Erreurs de compilation préexistantes (27 → 0)

- remotion-video.tsx (bureau et mobile) : les composants Select étaient
  utilisés sans être importés, ce qui faisait planter le sélecteur de voix
  à l'exécution.
- external.ts : fileSize et aiGenerated étaient transmis dans un type que
  le schéma ne déclare pas, et ne fonctionnaient que par conversion
  implicite de PostgreSQL.
- reels.ts : l'interface MusicTrack, référencée mais jamais déclarée dans
  ce module.
- remotion.ts : chromiumOptions.args retiré. Remotion ne lit jamais cette
  clé, les drapeaux Chrome qu'elle contenait n'atteignaient donc pas le
  navigateur.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018uUPZ1GNpEkYB1dpXHYPek
2026-08-12 08:00:48 +00:00

206 lines
6.7 KiB
TypeScript

import { pool } from "./db";
export async function migrate() {
const client = await pool.connect();
try {
console.log("[Migration] Starting safe migration Check...");
// 1. Create Enums if not exist and Update Enums
await client.query(`
DO $$ BEGIN
CREATE TYPE "token_status" AS ENUM('valid', 'expiring', 'expired', 'error');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
`);
// Add 'reel' to post_type enum if not exists
await client.query(`ALTER TYPE "post_type" ADD VALUE IF NOT EXISTS 'reel';`);
// Add 'tiktok' to platform enum if not exists
await client.query(`ALTER TYPE "platform" ADD VALUE IF NOT EXISTS 'tiktok';`);
// 2. Create Tables if not exist
// post_analytics
await client.query(`
CREATE TABLE IF NOT EXISTS "post_analytics" (
"id" varchar PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"post_id" varchar NOT NULL REFERENCES "posts"("id") ON DELETE cascade,
"fetched_at" timestamp DEFAULT now(),
"impressions" integer DEFAULT 0,
"reach" integer DEFAULT 0,
"engagement" integer DEFAULT 0,
"reactions" integer DEFAULT 0,
"comments" integer DEFAULT 0,
"shares" integer DEFAULT 0,
"clicks" integer DEFAULT 0,
"raw_data" jsonb
);
`);
// page_analytics_history
await client.query(`
CREATE TABLE IF NOT EXISTS "page_analytics_history" (
"id" varchar PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"page_id" varchar NOT NULL REFERENCES "social_pages"("id") ON DELETE cascade,
"date" timestamp NOT NULL,
"followers_count" integer DEFAULT 0,
"page_views" integer DEFAULT 0,
"page_reach" integer DEFAULT 0,
"created_at" timestamp DEFAULT now()
);
`);
// 3. Add Columns to existing tables if not exist
// social_pages.token_status
await client.query(`
ALTER TABLE "social_pages"
ADD COLUMN IF NOT EXISTS "token_status" "token_status" DEFAULT 'valid';
`);
// social_pages.last_token_check
await client.query(`
ALTER TABLE "social_pages"
ADD COLUMN IF NOT EXISTS "last_token_check" timestamp;
`);
// music_favorites
await client.query(`
CREATE TABLE IF NOT EXISTS "music_favorites" (
"id" varchar PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" varchar NOT NULL REFERENCES "users"("id") ON DELETE cascade,
"track_id" text NOT NULL,
"title" text NOT NULL,
"artist" text NOT NULL,
"album_name" text,
"duration" integer NOT NULL,
"preview_url" text NOT NULL,
"download_url" text NOT NULL,
"image_url" text,
"license" text,
"created_at" timestamp DEFAULT now()
);
`);
// freesound_config
await client.query(`
CREATE TABLE IF NOT EXISTS "freesound_config" (
"id" varchar PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" varchar NOT NULL REFERENCES "users"("id") ON DELETE cascade,
"client_id" text NOT NULL,
"client_secret" text NOT NULL,
"created_at" timestamp DEFAULT now(),
"updated_at" timestamp DEFAULT now(),
CONSTRAINT freesound_config_user_id_unique UNIQUE ("user_id")
);
`);
// posts.generation_status (Reel progress tracking)
await client.query(`
ALTER TABLE "posts"
ADD COLUMN IF NOT EXISTS "generation_status" text;
`);
// posts.generation_progress
await client.query(`
ALTER TABLE "posts"
ADD COLUMN IF NOT EXISTS "generation_progress" integer DEFAULT 0;
`);
// posts.generation_error
await client.query(`
ALTER TABLE "posts"
ADD COLUMN IF NOT EXISTS "generation_error" text;
`);
// cloudinary_config.endpoint_url (MinIO internal endpoint URL)
await client.query(`
ALTER TABLE "cloudinary_config"
ADD COLUMN IF NOT EXISTS "endpoint_url" text;
`);
// cloudinary_config.public_url (MinIO public-facing base URL)
await client.query(`
ALTER TABLE "cloudinary_config"
ADD COLUMN IF NOT EXISTS "public_url" text;
`);
// page_analytics_history.page_engagement
await client.query(`
ALTER TABLE "page_analytics_history"
ADD COLUMN IF NOT EXISTS "page_engagement" integer DEFAULT 0;
`);
// audio_tracks (bibliothèque audio interne)
await client.query(`
CREATE TABLE IF NOT EXISTS "audio_tracks" (
"id" varchar PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" varchar REFERENCES "users"("id") ON DELETE cascade,
"title" text NOT NULL,
"file_name" text NOT NULL,
"url" text NOT NULL,
"duration" integer DEFAULT 0,
"created_at" timestamp DEFAULT now()
);
`);
// app_config (global application settings)
await client.query(`
CREATE TABLE IF NOT EXISTS "app_config" (
"id" varchar PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"external_api_key" text,
"created_at" timestamp DEFAULT now(),
"updated_at" timestamp DEFAULT now()
);
`);
// app_config.gemini_api_key
await client.query(`
ALTER TABLE "app_config"
ADD COLUMN IF NOT EXISTS "gemini_api_key" text;
`);
// media.thumbnail_url (vignette conservée après suppression de la vidéo)
await client.query(`
ALTER TABLE "media"
ADD COLUMN IF NOT EXISTS "thumbnail_url" text;
`);
// tiktok_config (application développeur TikTok, globale)
await client.query(`
CREATE TABLE IF NOT EXISTS "tiktok_config" (
"id" varchar PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"client_key" text NOT NULL,
"client_secret" text NOT NULL,
"created_at" timestamp DEFAULT now(),
"updated_at" timestamp DEFAULT now()
);
`);
// social_pages : champs OAuth TikTok
await client.query(`
ALTER TABLE "social_pages"
ADD COLUMN IF NOT EXISTS "refresh_token" text,
ADD COLUMN IF NOT EXISTS "refresh_token_expires_at" timestamp,
ADD COLUMN IF NOT EXISTS "scopes" text,
ADD COLUMN IF NOT EXISTS "avatar_url" text;
`);
// scheduled_posts : suivi de la publication asynchrone TikTok
await client.query(`
ALTER TABLE "scheduled_posts"
ADD COLUMN IF NOT EXISTS "publish_id" text,
ADD COLUMN IF NOT EXISTS "publish_status" text;
`);
console.log("[Migration] Safe migration completed.");
} catch (error) {
console.error("[Migration] Error during migration:", error);
// Don't kill the process, let the app try to start even if migration failed
// (It might have failed because tables already existed in a different state)
} finally {
client.release();
}
}