"""Fix users.id UUID->text type mismatch for Better-Auth compatibility. Better-Auth generates nanoid-style text IDs (e.g. pGud2ln2WAFHC0KYjBVKR4Rc7mM8OcTI), but the users table was using PostgreSQL uuid type. When Better-Auth tries to INSERT a new user, Postgres throws: ERROR: invalid input syntax for type uuid: "pGud2ln2WAFHC0KYjBVKR4Rc7mM8OcTI" The sessions, accounts, and verifications tables already use text IDs — only users, user_store_accounts.user_id, and purchases.user_id needed fixing. Revision ID: 004_fix_user_id_text Revises: 003_make_users_hashed_password_nullable Create Date: 2026-03-31 """ import sqlalchemy as sa from sqlalchemy import text from alembic import op revision = "004_fix_user_id_text" down_revision = "003_make_users_hashed_password_nullable" branch_labels = None depends_on = None def upgrade() -> None: conn = op.get_bind() inspector = sa.inspect(conn) # Fresh DB — no tables yet, nothing to convert if not inspector.has_table("users"): return # Check if already TEXT (Base.metadata.create_all uses TEXT for fresh DB) users_cols = {c["name"]: c for c in inspector.get_columns("users")} if "id" in users_cols: id_type = str(users_cols["id"]["type"]).lower() if "text" in id_type and "uuid" not in id_type: return # already TEXT — nothing to do # Step 1: Drop existing FK constraints (ignore if they don't exist) op.execute( text( "ALTER TABLE user_store_accounts " "DROP CONSTRAINT IF EXISTS user_store_accounts_user_id_fkey" ) ) op.execute(text("ALTER TABLE purchases DROP CONSTRAINT IF EXISTS purchases_user_id_fkey")) # Step 2: Alter users.id from uuid to text op.alter_column( "users", "id", type_=sa.Text(), existing_type=sa.UUID(), postgresql_using="id::text", ) # Step 3: Alter user_store_accounts.user_id from uuid to text op.alter_column( "user_store_accounts", "user_id", type_=sa.Text(), existing_type=sa.UUID(), postgresql_using="user_id::text", ) # Step 4: Alter purchases.user_id from uuid to text op.alter_column( "purchases", "user_id", type_=sa.Text(), existing_type=sa.UUID(), postgresql_using="user_id::text", ) # Step 5: Re-add FK constraints op.execute( text( "ALTER TABLE user_store_accounts " "ADD CONSTRAINT user_store_accounts_user_id_fkey " "FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE" ) ) op.execute( text( "ALTER TABLE purchases " "ADD CONSTRAINT purchases_user_id_fkey " "FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE" ) ) def downgrade() -> None: # Drop FK constraints op.execute( text( "ALTER TABLE user_store_accounts " "DROP CONSTRAINT IF EXISTS user_store_accounts_user_id_fkey" ) ) op.execute(text("ALTER TABLE purchases DROP CONSTRAINT IF EXISTS purchases_user_id_fkey")) # Revert users.id from text to uuid op.alter_column( "users", "id", type_=sa.UUID(), existing_type=sa.Text(), postgresql_using="id::uuid", ) # Revert user_store_accounts.user_id from text to uuid op.alter_column( "user_store_accounts", "user_id", type_=sa.UUID(), existing_type=sa.Text(), postgresql_using="user_id::uuid", ) # Revert purchases.user_id from text to uuid op.alter_column( "purchases", "user_id", type_=sa.UUID(), existing_type=sa.Text(), postgresql_using="user_id::uuid", ) # Re-add FK constraints (PostgreSQL will auto-name them) op.execute( text( "ALTER TABLE user_store_accounts " "ADD CONSTRAINT user_store_accounts_user_id_fkey " "FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE" ) ) op.execute( text( "ALTER TABLE purchases " "ADD CONSTRAINT purchases_user_id_fkey " "FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE" ) )