import { sql } from 'drizzle-orm'; import { db } from './db.js'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Table pour suivre les migrations appliquées const createMigrationsTable = async () => { await db.execute(sql` CREATE TABLE IF NOT EXISTS migrations ( id SERIAL PRIMARY KEY, filename VARCHAR(255) UNIQUE NOT NULL, executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) `); }; interface MigrationFile { filename: string; sql: string; } export async function runMigrations() { console.log('🔄 Vérification des migrations...'); try { // Créer la table des migrations si elle n'existe pas await createMigrationsTable(); // Récupérer les migrations déjà appliquées const appliedMigrations = await db.execute(sql`SELECT filename FROM migrations`); const appliedFilenames = new Set(appliedMigrations.rows.map((row: any) => row.filename)); // Ajouter les migrations hardcodées directement dans le code pour éviter les problèmes de chemins const hardcodedMigrations = [ { filename: '20260402000002_add_notified_fields_to_customer_orders.sql', content: ` ALTER TABLE customer_orders ADD COLUMN IF NOT EXISTS notified_at TIMESTAMP; ALTER TABLE customer_orders ADD COLUMN IF NOT EXISTS notified_comment TEXT; ` }, { filename: '20260402000000_add_codefou_to_suppliers.sql', content: `ALTER TABLE suppliers ADD COLUMN IF NOT EXISTS codefou VARCHAR;` }, { filename: '20250903141000_create_webhook_bap_config.sql', content: `CREATE TABLE IF NOT EXISTS webhook_bap_config ( id SERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL DEFAULT 'Configuration BAP', webhook_url TEXT NOT NULL, description TEXT, is_active BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); INSERT INTO webhook_bap_config (name, webhook_url, description, is_active) SELECT 'Configuration BAP', 'https://workflow.ffnancy.fr/webhook/a3d03176-b72f-412d-8fb9-f920b9fbab4d', 'Configuration par défaut pour envoi des fichiers BAP vers n8n', true WHERE NOT EXISTS (SELECT 1 FROM webhook_bap_config);` }, { filename: '20260814000000_add_store_contact_and_smtp_to_groups.sql', content: ` -- Coordonnées du magasin (signature des mails fournisseurs) ALTER TABLE groups ADD COLUMN IF NOT EXISTS address TEXT; ALTER TABLE groups ADD COLUMN IF NOT EXISTS phone VARCHAR(50); ALTER TABLE groups ADD COLUMN IF NOT EXISTS logo TEXT; -- Configuration SMTP propre à chaque magasin ALTER TABLE groups ADD COLUMN IF NOT EXISTS smtp_enabled BOOLEAN DEFAULT false; ALTER TABLE groups ADD COLUMN IF NOT EXISTS smtp_host VARCHAR(255); ALTER TABLE groups ADD COLUMN IF NOT EXISTS smtp_port INTEGER; ALTER TABLE groups ADD COLUMN IF NOT EXISTS smtp_secure BOOLEAN DEFAULT false; ALTER TABLE groups ADD COLUMN IF NOT EXISTS smtp_user VARCHAR(255); ALTER TABLE groups ADD COLUMN IF NOT EXISTS smtp_password VARCHAR(255); ALTER TABLE groups ADD COLUMN IF NOT EXISTS smtp_sender_email VARCHAR(255); ALTER TABLE groups ADD COLUMN IF NOT EXISTS smtp_sender_name VARCHAR(255); ` }, { filename: '20260814000001_create_supplier_mail_logs.sql', content: ` CREATE TABLE IF NOT EXISTS supplier_mail_logs ( id SERIAL PRIMARY KEY, delivery_id INTEGER NOT NULL, group_id INTEGER NOT NULL, supplier_id INTEGER, supplier_name VARCHAR(255), sent_to VARCHAR(255) NOT NULL, subject TEXT, status VARCHAR(20) NOT NULL, error_message TEXT, message_id VARCHAR(255), sent_by VARCHAR NOT NULL, sent_by_name VARCHAR(255), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_supplier_mail_logs_delivery ON supplier_mail_logs(delivery_id); CREATE INDEX IF NOT EXISTS idx_supplier_mail_logs_group ON supplier_mail_logs(group_id); ` }, { filename: '20261010000000_create_external_api_keys.sql', content: ` CREATE TABLE IF NOT EXISTS external_api_keys ( id SERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL, key_prefix VARCHAR(16) NOT NULL, key_hash VARCHAR(64) NOT NULL UNIQUE, created_by VARCHAR, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, last_used_at TIMESTAMP, revoked_at TIMESTAMP ); ` } ]; // Ignorer les fichiers de migrations pour éviter les erreurs SQL // Utiliser uniquement les migrations hardcodées const allMigrations = hardcodedMigrations; if (allMigrations.length === 0) { console.log('✅ Aucune migration à exécuter'); return; } let executedCount = 0; for (const migration of allMigrations) { if (appliedFilenames.has(migration.filename)) { console.log(`⏭️ Migration déjà appliquée: ${migration.filename}`); continue; } console.log(`🔄 Exécution migration: ${migration.filename}`); try { // Exécuter la migration dans une transaction await db.transaction(async (tx: any) => { // Exécuter le SQL de migration await tx.execute(sql.raw(migration.content)); // Marquer comme appliquée await tx.execute(sql` INSERT INTO migrations (filename) VALUES (${migration.filename}) `); }); console.log(`✅ Migration appliquée avec succès: ${migration.filename}`); executedCount++; } catch (error) { console.error(`❌ Erreur lors de la migration ${migration.filename}:`, error); throw error; } } if (executedCount > 0) { console.log(`✅ ${executedCount} migration(s) appliquée(s) avec succès`); } else { console.log('✅ Toutes les migrations sont déjà appliquées'); } } catch (error) { console.error('❌ Erreur lors de l\'exécution des migrations:', error); throw error; } } // Fonction pour créer une nouvelle migration export function createMigration(name: string): string { const timestamp = new Date().toISOString().replace(/[^\d]/g, '').slice(0, 14); const filename = `${timestamp}_${name.toLowerCase().replace(/\s+/g, '_')}.sql`; const migrationsDir = path.join(__dirname, '..', 'migrations'); if (!fs.existsSync(migrationsDir)) { fs.mkdirSync(migrationsDir, { recursive: true }); } const filePath = path.join(migrationsDir, filename); const template = `-- Migration: ${name} -- Created: ${new Date().toISOString()} -- Add your SQL here `; fs.writeFileSync(filePath, template); console.log(`📝 Migration créée: ${filePath}`); return filePath; }