From 331b21ec71dbbc65cc9eec3ae1cb3d481f7cd915 Mon Sep 17 00:00:00 2001 From: borja Date: Tue, 14 Oct 2025 09:50:06 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20a=C3=B1adir=20fallback=20de=20migracione?= =?UTF-8?q?s=20y=20export=20de=20crypto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: aider (openrouter/openai/gpt-5) --- apps/web/src/lib/server/crypto.ts | 20 +------------- src/db.ts | 43 ++++++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 22 deletions(-) diff --git a/apps/web/src/lib/server/crypto.ts b/apps/web/src/lib/server/crypto.ts index 0f3c250..98dd094 100644 --- a/apps/web/src/lib/server/crypto.ts +++ b/apps/web/src/lib/server/crypto.ts @@ -1,19 +1 @@ -/** - * Genera un token aleatorio en base64url (sin padding). - */ -export function randomTokenBase64Url(bytes: number = 32): string { - const arr = new Uint8Array(bytes); - crypto.getRandomValues(arr); - const b64 = Buffer.from(arr).toString('base64'); - return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); -} - -/** - * Calcula SHA-256 en hexadecimal (minúsculas). - */ -export async function sha256Hex(input: string): Promise { - const data = new TextEncoder().encode(input); - const hashBuf = await crypto.subtle.digest('SHA-256', data); - const bytes = new Uint8Array(hashBuf); - return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join(''); -} +export { randomTokenBase64Url, sha256Hex } from '../../../../../src/utils/crypto'; diff --git a/src/db.ts b/src/db.ts index 8258c50..3f5d2d2 100644 --- a/src/db.ts +++ b/src/db.ts @@ -3,6 +3,7 @@ import { normalizeWhatsAppId } from './utils/whatsapp'; import { mkdirSync } from 'fs'; import { join, resolve, dirname } from 'path'; import { Migrator } from './db/migrator'; +import { migrations } from './db/migrations'; function applyDefaultPragmas(instance: Database): void { try { @@ -60,12 +61,48 @@ export function initializeDatabase(instance: Database) { // Aplicar PRAGMAs por defecto (WAL, busy_timeout, FK, etc.) applyDefaultPragmas(instance); - // Ejecutar migraciones up-only (sin baseline por defecto). Evitar backup duplicado aquí. + // Ejecutar migraciones con el Migrator; si no deja el esquema listo, aplicar fallback. + let migratorError: unknown = null; try { Migrator.migrateToLatest(instance, { withBackup: false, allowBaseline: false }); } catch (e) { - console.error('[initializeDatabase] Error al aplicar migraciones:', e); - throw e; + migratorError = e; + console.error('[initializeDatabase] Error al aplicar migraciones con Migrator:', e); + } + + // Verificación mínima: si las tablas base no existen, aplicar fallback secuencial. + const tableExists = (name: string): boolean => { + try { + const row = instance + .query(`SELECT name FROM sqlite_master WHERE type='table' AND name = ?`) + .get(name) as any; + return Boolean(row && row.name === name); + } catch { + return false; + } + }; + + const needsFallback = + !tableExists('users') || + !tableExists('tasks') || + !tableExists('response_queue'); + + if (needsFallback) { + console.warn('[initializeDatabase] Migrator no dejó el esquema listo; aplicando fallback de migraciones secuenciales'); + try { + instance.transaction(() => { + try { instance.exec(`PRAGMA foreign_keys = ON;`); } catch {} + for (const m of migrations) { + m.up(instance); + } + })(); + } catch (fallbackErr) { + console.error('[initializeDatabase] Fallback de migraciones falló:', fallbackErr); + throw fallbackErr; + } + } else if (migratorError) { + // Si el Migrator falló pero el esquema ya está correcto, sólo loggeamos. + console.warn('[initializeDatabase] Migrator reportó error, pero el esquema parece estar correcto. Continuando.'); } }