forked from cartsnitch/api
5c8fe9a62b
- 001: guard has_table check; skip if session_data already TEXT - 002: guard each ADD COLUMN / CREATE TABLE; guard password migration - 003: guard has_table; guard nullable check - 004: guard has_table; skip if users.id already TEXT - env.py: add Base.metadata.create_all after run_migrations to bootstrap fresh DBs - api/user.py: make hashed_password nullable; add email_verified, image, email_inbound_token fields Co-Authored-By: Paperclip <noreply@paperclip.ing>
137 lines
4.1 KiB
Python
137 lines
4.1 KiB
Python
"""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"
|
|
)
|
|
)
|