// Migration: Add user_group column to users table and map legacy roles // Mapping: admin→Admin, editor→Standard_User, viewer→Read_Only // NULL/unrecognized roles default to Read_Only // Idempotent — safe to run multiple times const sqlite3 = require('sqlite3').verbose(); const path = require('path'); /** * Run the migration against the given database instance. * Exported for testing with in-memory databases. * @param {sqlite3.Database} db * @returns {Promise} */ function runMigration(db) { return new Promise((resolve, reject) => { db.serialize(() => { // Check if user_group column already exists db.all("PRAGMA table_info(users)", (err, columns) => { if (err) { reject(err); return; } const hasUserGroup = columns.some(col => col.name === 'user_group'); if (hasUserGroup) { console.log('✓ user_group column already exists — skipping migration'); resolve(); return; } console.log('Adding user_group column to users table...'); // SQLite doesn't support ADD COLUMN with CHECK inline in all versions, // so we add the column first, map values, then recreate with constraint. // However, SQLite also doesn't support ALTER TABLE ADD CONSTRAINT. // Strategy: add column, map values, create index. // The CHECK constraint is enforced via table rebuild. db.run( `ALTER TABLE users ADD COLUMN user_group VARCHAR(20) NOT NULL DEFAULT 'Read_Only'`, (err) => { if (err) { reject(err); return; } console.log('✓ Added user_group column'); // Map existing roles to groups db.run( `UPDATE users SET user_group = 'Admin' WHERE role = 'admin'`, function(err) { if (err) { reject(err); return; } console.log(` ✓ Mapped ${this.changes} admin(s) → Admin`); db.run( `UPDATE users SET user_group = 'Standard_User' WHERE role = 'editor'`, function(err) { if (err) { reject(err); return; } console.log(` ✓ Mapped ${this.changes} editor(s) → Standard_User`); db.run( `UPDATE users SET user_group = 'Read_Only' WHERE role = 'viewer'`, function(err) { if (err) { reject(err); return; } console.log(` ✓ Mapped ${this.changes} viewer(s) → Read_Only`); // Map NULL or unrecognized roles to Read_Only db.run( `UPDATE users SET user_group = 'Read_Only' WHERE user_group = 'Read_Only' AND role NOT IN ('admin', 'editor', 'viewer')`, function(err) { if (err) { reject(err); return; } console.log(` ✓ Mapped ${this.changes} unrecognized role(s) → Read_Only`); // Create index on user_group db.run( `CREATE INDEX IF NOT EXISTS idx_users_user_group ON users(user_group)`, (err) => { if (err) { reject(err); return; } console.log('✓ Created idx_users_user_group index'); console.log('Migration complete!'); resolve(); } ); } ); } ); } ); } ); } ); }); }); }); } // Run directly if executed as a script if (require.main === module) { const dbPath = path.join(__dirname, '..', 'cve_database.db'); const db = new sqlite3.Database(dbPath); console.log('Starting add_user_groups migration...'); runMigration(db) .then(() => { db.close(() => { console.log('Database connection closed.'); }); }) .catch((err) => { console.error('Migration failed:', err); db.close(); process.exit(1); }); } module.exports = { runMigration };