#!/usr/bin/env node /** * Migration: Fix microchip UNIQUE constraint * * Problem: The microchip field had a UNIQUE constraint which prevents multiple NULL values. * Solution: Remove the constraint and create a partial unique index that only applies to non-NULL values. * * This script can be run safely multiple times. */ const Database = require('better-sqlite3'); const path = require('path'); const fs = require('fs'); function migrateMicrochip() { const dbPath = process.env.DB_PATH || path.join(__dirname, '../../data/breedr.db'); if (!fs.existsSync(dbPath)) { console.log('No database found at', dbPath); console.log('Skipping migration - database will be created with correct schema.'); return; } console.log('Migrating database:', dbPath); const db = new Database(dbPath); db.pragma('foreign_keys = OFF'); // Temporarily disable for migration try { console.log('Starting microchip field migration...'); // Check if the old unique constraint exists const tableInfo = db.pragma('table_info(dogs)'); const microchipField = tableInfo.find(col => col.name === 'microchip'); if (!microchipField) { console.log('Microchip field not found. Skipping migration.'); return; } // SQLite doesn't support ALTER COLUMN, so we need to recreate the table console.log('Step 1: Creating new dogs table with correct schema...'); db.exec(` -- Create new table with correct schema CREATE TABLE IF NOT EXISTS dogs_new ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, registration_number TEXT UNIQUE, breed TEXT NOT NULL, sex TEXT NOT NULL CHECK(sex IN ('male', 'female')), birth_date DATE, color TEXT, microchip TEXT, photo_urls TEXT, notes TEXT, is_active INTEGER DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); `); console.log('Step 2: Copying data from old table...'); // Copy all data from old table to new table db.exec(` INSERT INTO dogs_new SELECT * FROM dogs; `); console.log('Step 3: Dropping old table and renaming new table...'); // Drop old table and rename new table db.exec(` DROP TABLE dogs; ALTER TABLE dogs_new RENAME TO dogs; `); console.log('Step 4: Creating partial unique index for microchip...'); // Create partial unique index (only applies to non-NULL values) db.exec(` CREATE UNIQUE INDEX IF NOT EXISTS idx_dogs_microchip ON dogs(microchip) WHERE microchip IS NOT NULL; `); console.log('Step 5: Recreating other indexes...'); // Recreate other indexes db.exec(` CREATE INDEX IF NOT EXISTS idx_dogs_name ON dogs(name); CREATE INDEX IF NOT EXISTS idx_dogs_registration ON dogs(registration_number); `); console.log('Step 6: Recreating triggers...'); // Recreate the updated_at trigger db.exec(` DROP TRIGGER IF EXISTS update_dogs_timestamp; CREATE TRIGGER update_dogs_timestamp AFTER UPDATE ON dogs FOR EACH ROW BEGIN UPDATE dogs SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id; END; `); console.log('✓ Migration completed successfully!'); console.log('✓ Microchip field is now optional and can be left empty.'); console.log('✓ Multiple dogs can have no microchip (NULL value).'); console.log('✓ Unique constraint still prevents duplicate microchip numbers.'); } catch (error) { console.error('Migration failed:', error.message); console.error('\nYou may need to restore from backup or manually fix the database.'); throw error; } finally { db.pragma('foreign_keys = ON'); // Re-enable foreign keys db.close(); } } if (require.main === module) { console.log('='.repeat(60)); console.log('BREEDR Database Migration: Microchip Field Fix'); console.log('='.repeat(60)); console.log(''); migrateMicrochip(); console.log(''); console.log('='.repeat(60)); console.log('Migration Complete'); console.log('='.repeat(60)); } module.exports = { migrateMicrochip };