Rebuild legacy users table to add email and drop phone NOT NULL
The migration ALTER TABLE users ADD COLUMN email TEXT UNIQUE always
failed ("Cannot add a UNIQUE column" — SQLite doesn't support it), so
phone-era DBs never got an email column and verify 500'd with "no such
column: email". Even with the column added, phone NOT NULL would reject
email-only users. SQLite can't ALTER either change, so detect the legacy
shape (missing email column) and rebuild the table, preserving user ids
and sessions (FKs off so DROP TABLE doesn't cascade).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+52
-3
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user