A tool for tailing a labelers' firehose, rehydrating, and storing records for future analysis of moderation decisions.

fix: add migration for existing profiles tables

Handles case where profiles table exists but lacks avatar_cid/banner_cid columns by checking schema and running ALTER TABLE if needed.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Changed files
+54 -2
src
database
+54 -2
src/database/schema.ts
··· 60 60 CREATE INDEX IF NOT EXISTS idx_blobs_phash ON blobs(phash); 61 61 `; 62 62 63 + async function migrateProfilesTable(): Promise<void> { 64 + const db = getDatabase(); 65 + 66 + return new Promise((resolve, reject) => { 67 + db.all( 68 + "SELECT column_name FROM information_schema.columns WHERE table_name = 'profiles'", 69 + (err, rows: any[]) => { 70 + if (err) { 71 + logger.error({ err }, "Failed to check profiles table columns"); 72 + reject(err); 73 + return; 74 + } 75 + 76 + const columnNames = rows.map((row) => row.column_name); 77 + const hasAvatarCid = columnNames.includes("avatar_cid"); 78 + const hasBannerCid = columnNames.includes("banner_cid"); 79 + 80 + if (!hasAvatarCid || !hasBannerCid) { 81 + logger.info("Migrating profiles table to add avatar_cid and banner_cid columns"); 82 + 83 + const migrations: string[] = []; 84 + if (!hasAvatarCid) { 85 + migrations.push("ALTER TABLE profiles ADD COLUMN avatar_cid TEXT"); 86 + } 87 + if (!hasBannerCid) { 88 + migrations.push("ALTER TABLE profiles ADD COLUMN banner_cid TEXT"); 89 + } 90 + 91 + db.exec(migrations.join("; "), (err) => { 92 + if (err) { 93 + logger.error({ err }, "Failed to migrate profiles table"); 94 + reject(err); 95 + return; 96 + } 97 + logger.info("Profiles table migration completed"); 98 + resolve(); 99 + }); 100 + } else { 101 + logger.debug("Profiles table already has avatar_cid and banner_cid columns"); 102 + resolve(); 103 + } 104 + } 105 + ); 106 + }); 107 + } 108 + 63 109 export async function initializeSchema(): Promise<void> { 64 110 const db = getDatabase(); 65 111 66 112 return new Promise((resolve, reject) => { 67 - db.exec(SCHEMA_SQL, (err) => { 113 + db.exec(SCHEMA_SQL, async (err) => { 68 114 if (err) { 69 115 logger.error({ err }, "Failed to initialize schema"); 70 116 reject(err); 71 117 return; 72 118 } 73 119 logger.info("Database schema initialized"); 74 - resolve(); 120 + 121 + try { 122 + await migrateProfilesTable(); 123 + resolve(); 124 + } catch (migrationErr) { 125 + reject(migrationErr); 126 + } 75 127 }); 76 128 }); 77 129 }