feat: añadir migrador de migraciones up-only para SQLite
Co-authored-by: aider (openrouter/openai/gpt-5) <aider@aider.chat>pull/1/head
							parent
							
								
									4a305dc007
								
							
						
					
					
						commit
						efe8aaef89
					
				| @ -0,0 +1,119 @@ | |||||||
|  | import type { Database } from 'bun:sqlite'; | ||||||
|  | 
 | ||||||
|  | export type Migration = { | ||||||
|  |   version: number; | ||||||
|  |   name: string; | ||||||
|  |   checksum: string; // estático para trazabilidad básica
 | ||||||
|  |   up: (db: Database) => void | Promise<void>; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | function tableHasColumn(db: Database, table: string, column: string): boolean { | ||||||
|  |   const cols = db.query(`PRAGMA table_info(${table})`).all() as any[]; | ||||||
|  |   return Array.isArray(cols) && cols.some((c: any) => c.name === column); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const migrations: Migration[] = [ | ||||||
|  |   { | ||||||
|  |     version: 1, | ||||||
|  |     name: 'initial-schema', | ||||||
|  |     checksum: 'v1-initial-schema-2025-09-06', | ||||||
|  |     up: (db: Database) => { | ||||||
|  |       // Esquema inicial (equivalente al initializeDatabase actual)
 | ||||||
|  |       db.exec(`PRAGMA foreign_keys = ON;`); | ||||||
|  | 
 | ||||||
|  |       db.exec(` | ||||||
|  |         CREATE TABLE IF NOT EXISTS users ( | ||||||
|  |           id TEXT PRIMARY KEY, | ||||||
|  |           first_seen TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%f', 'now')), | ||||||
|  |           last_seen TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%f', 'now')) | ||||||
|  |         ); | ||||||
|  |       `);
 | ||||||
|  | 
 | ||||||
|  |       db.exec(` | ||||||
|  |         CREATE TABLE IF NOT EXISTS groups ( | ||||||
|  |           id TEXT PRIMARY KEY, | ||||||
|  |           community_id TEXT NOT NULL, | ||||||
|  |           name TEXT, | ||||||
|  |           last_verified TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%f', 'now')), | ||||||
|  |           active BOOLEAN DEFAULT TRUE | ||||||
|  |         ); | ||||||
|  |       `);
 | ||||||
|  | 
 | ||||||
|  |       db.exec(` | ||||||
|  |         CREATE TABLE IF NOT EXISTS tasks ( | ||||||
|  |           id INTEGER PRIMARY KEY AUTOINCREMENT, | ||||||
|  |           description TEXT NOT NULL, | ||||||
|  |           created_at TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%f', 'now')), | ||||||
|  |           due_date TEXT NULL, | ||||||
|  |           completed BOOLEAN DEFAULT FALSE, | ||||||
|  |           completed_at TEXT NULL, | ||||||
|  |           group_id TEXT NULL, | ||||||
|  |           created_by TEXT NOT NULL, | ||||||
|  |           completed_by TEXT NULL, | ||||||
|  |           FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE CASCADE, | ||||||
|  |           FOREIGN KEY (completed_by) REFERENCES users(id) ON DELETE SET NULL, | ||||||
|  |           FOREIGN KEY (group_id) REFERENCES groups(id) ON DELETE SET NULL | ||||||
|  |         ); | ||||||
|  |       `);
 | ||||||
|  | 
 | ||||||
|  |       db.exec(` | ||||||
|  |         CREATE TABLE IF NOT EXISTS task_assignments ( | ||||||
|  |           task_id INTEGER NOT NULL, | ||||||
|  |           user_id TEXT NOT NULL, | ||||||
|  |           assigned_by TEXT NOT NULL, | ||||||
|  |           assigned_at TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%f', 'now')), | ||||||
|  |           PRIMARY KEY (task_id, user_id), | ||||||
|  |           FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE, | ||||||
|  |           FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, | ||||||
|  |           FOREIGN KEY (assigned_by) REFERENCES users(id) ON DELETE CASCADE | ||||||
|  |         ); | ||||||
|  |       `);
 | ||||||
|  | 
 | ||||||
|  |       db.exec(` | ||||||
|  |         CREATE TABLE IF NOT EXISTS response_queue ( | ||||||
|  |           id INTEGER PRIMARY KEY AUTOINCREMENT, | ||||||
|  |           recipient TEXT NOT NULL, | ||||||
|  |           message TEXT NOT NULL, | ||||||
|  |           status TEXT NOT NULL DEFAULT 'queued' CHECK (status IN ('queued','processing','sent','failed')), | ||||||
|  |           attempts INTEGER NOT NULL DEFAULT 0, | ||||||
|  |           last_error TEXT NULL, | ||||||
|  |           metadata TEXT NULL, | ||||||
|  |           created_at TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%f', 'now')), | ||||||
|  |           updated_at TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%f', 'now')) | ||||||
|  |         ); | ||||||
|  |       `);
 | ||||||
|  | 
 | ||||||
|  |       db.exec(` | ||||||
|  |         CREATE INDEX IF NOT EXISTS idx_response_queue_status_created_at | ||||||
|  |         ON response_queue (status, created_at); | ||||||
|  |       `);
 | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     version: 2, | ||||||
|  |     name: 'response-queue-reliability', | ||||||
|  |     checksum: 'v2-rq-reliability-2025-09-06', | ||||||
|  |     up: (db: Database) => { | ||||||
|  |       // Añadir columnas necesarias si no existen (idempotente)
 | ||||||
|  |       if (!tableHasColumn(db, 'response_queue', 'next_attempt_at')) { | ||||||
|  |         db.exec(`ALTER TABLE response_queue ADD COLUMN next_attempt_at TEXT NULL;`); | ||||||
|  |       } | ||||||
|  |       if (!tableHasColumn(db, 'response_queue', 'lease_until')) { | ||||||
|  |         db.exec(`ALTER TABLE response_queue ADD COLUMN lease_until TEXT NULL;`); | ||||||
|  |       } | ||||||
|  |       if (!tableHasColumn(db, 'response_queue', 'last_status_code')) { | ||||||
|  |         db.exec(`ALTER TABLE response_queue ADD COLUMN last_status_code INTEGER NULL;`); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // Índices complementarios
 | ||||||
|  |       db.exec(` | ||||||
|  |         CREATE INDEX IF NOT EXISTS idx_response_queue_status_next_attempt | ||||||
|  |         ON response_queue (status, next_attempt_at); | ||||||
|  |       `);
 | ||||||
|  |       db.exec(` | ||||||
|  |         CREATE INDEX IF NOT EXISTS idx_response_queue_status_lease_until | ||||||
|  |         ON response_queue (status, lease_until); | ||||||
|  |       `);
 | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | ]; | ||||||
					Loading…
					
					
				
		Reference in New Issue