diff --git a/migrate.go b/migrate.go index e7119c7..89bec4b 100644 --- a/migrate.go +++ b/migrate.go @@ -10,9 +10,6 @@ func runMigrations(database *sql.DB) { migrations := []string{ `ALTER TABLE events ADD COLUMN description TEXT DEFAULT ''`, `ALTER TABLE events ADD COLUMN user_id INTEGER REFERENCES users(id)`, - // Users may have email, phone, or both. Add whichever column is missing. - `ALTER TABLE users ADD COLUMN phone TEXT UNIQUE`, - `ALTER TABLE users ADD COLUMN email TEXT UNIQUE`, // Verification codes use a generic identifier column. `ALTER TABLE verification_codes ADD COLUMN identifier TEXT NOT NULL DEFAULT ''`, // Indexes for auth tables (created here so they run after column migrations). @@ -47,4 +44,56 @@ func runMigrations(database *sql.DB) { log.Printf("migration warning: %v", err) } } + + rebuildLegacyUsersTable(database) +} + +// rebuildLegacyUsersTable migrates users tables from the phone-auth era +// (phone NOT NULL, no email column). SQLite can't add a UNIQUE column or +// drop NOT NULL via ALTER TABLE, so the table has to be rebuilt. Detected +// by the missing email column. +func rebuildLegacyUsersTable(database *sql.DB) { + var hasEmail int + err := database.QueryRow( + `SELECT COUNT(*) FROM pragma_table_info('users') WHERE name = 'email'`, + ).Scan(&hasEmail) + if err != nil || hasEmail > 0 { + return + } + + // Disable FK enforcement so DROP TABLE users doesn't cascade-delete + // sessions. main.go sets MaxOpenConns(1), so these pragmas apply to + // the same connection the transaction runs on. + database.Exec(`PRAGMA foreign_keys=OFF`) + defer database.Exec(`PRAGMA foreign_keys=ON`) + + tx, err := database.Begin() + if err != nil { + log.Printf("users rebuild: %v", err) + return + } + defer tx.Rollback() + + stmts := []string{ + `CREATE TABLE users_new ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + phone TEXT UNIQUE, + email TEXT UNIQUE, + name TEXT NOT NULL DEFAULT '', + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP + )`, + `INSERT INTO users_new (id, phone, name, created_at) + SELECT id, phone, name, created_at FROM users`, + `DROP TABLE users`, + `ALTER TABLE users_new RENAME TO users`, + } + for _, stmt := range stmts { + if _, err := tx.Exec(stmt); err != nil { + log.Printf("users rebuild: %v", err) + return + } + } + if err := tx.Commit(); err != nil { + log.Printf("users rebuild: %v", err) + } }