Skip to content

Commit 22ed3ee

Browse files
committed
(debug) Fix broken users table migration and enable auto-migrations
1 parent dfb159d commit 22ed3ee

2 files changed

Lines changed: 104 additions & 2 deletions

File tree

migrations/versions/108020504ecb_fix_users_table_schema.py

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,93 @@
2020

2121
def upgrade() -> None:
2222
"""Upgrade schema."""
23-
pass
23+
conn = op.get_bind()
24+
inspector = sa.inspect(conn)
25+
columns = [col['name'] for col in inspector.get_columns('users')]
26+
27+
if 'id' not in columns:
28+
# 1. Create a temporary table with the correct current schema
29+
op.create_table('users_fix',
30+
sa.Column('id', sa.Integer(), primary_key=True),
31+
sa.Column('name', sa.String(), nullable=True),
32+
sa.Column('phone_number', sa.String(), unique=True, nullable=True),
33+
sa.Column('email', sa.String(), unique=True, nullable=True),
34+
sa.Column('business_id', sa.Integer(), sa.ForeignKey('businesses.id'), nullable=False),
35+
sa.Column('role', sa.String(), nullable=False),
36+
sa.Column('created_at', sa.DateTime(), nullable=False),
37+
sa.Column('preferred_channel', sa.String(), nullable=False, server_default='WHATSAPP'),
38+
sa.Column('preferences', sa.JSON(), nullable=False),
39+
sa.Column('timezone', sa.String(), nullable=False, server_default='UTC'),
40+
sa.Column('default_start_location_lat', sa.Float(), nullable=True),
41+
sa.Column('default_start_location_lng', sa.Float(), nullable=True),
42+
sa.Column('google_calendar_credentials', sa.JSON(), nullable=True),
43+
sa.Column('google_calendar_sync_enabled', sa.Boolean(), nullable=False, server_default='0'),
44+
sa.Column('clerk_id', sa.String(), unique=True, nullable=True),
45+
sa.Column('current_latitude', sa.Float(), nullable=True),
46+
sa.Column('current_longitude', sa.Float(), nullable=True),
47+
sa.Column('location_updated_at', sa.DateTime(), nullable=True),
48+
sa.Column('current_shift_start', sa.DateTime(), nullable=True),
49+
sa.Column('geocoding_count', sa.Integer(), nullable=False, server_default='0'),
50+
)
51+
52+
# 2. Copy data from old users table to users_fix
53+
# We map columns that we know exist in the old schema
54+
old_cols = [c for c in columns if c in [
55+
'name', 'phone_number', 'email', 'business_id', 'role', 'created_at',
56+
'preferred_channel', 'preferences', 'timezone', 'default_start_location_lat',
57+
'default_start_location_lng', 'google_calendar_credentials',
58+
'google_calendar_sync_enabled', 'clerk_id', 'current_latitude',
59+
'current_longitude', 'location_updated_at', 'current_shift_start', 'geocoding_count'
60+
]]
61+
col_list = ", ".join(old_cols)
62+
op.execute(f"INSERT INTO users_fix ({col_list}) SELECT {col_list} FROM users")
63+
64+
# 3. Swap tables
65+
op.drop_table('users')
66+
op.rename_table('users_fix', 'users')
67+
68+
# 4. Re-create indexes
69+
op.create_index(op.f('ix_users_business_id'), 'users', ['business_id'], unique=False)
70+
71+
# 5. Fix messages table if it has user_id but it's null/incorrect
72+
msg_cols = [col['name'] for col in inspector.get_columns('messages')]
73+
if 'user_id' in msg_cols:
74+
op.execute("""
75+
UPDATE messages
76+
SET user_id = (SELECT u.id FROM users u WHERE u.phone_number = messages.from_number)
77+
WHERE role = 'USER' AND user_id IS NULL
78+
""")
79+
op.execute("""
80+
UPDATE messages
81+
SET user_id = (SELECT u.id FROM users u WHERE u.phone_number = messages.to_number)
82+
WHERE role = 'ASSISTANT' AND user_id IS NULL
83+
""")
84+
85+
# 6. Fix conversation_states table if it uses phone_number instead of user_id
86+
cs_cols = [col['name'] for col in inspector.get_columns('conversation_states')]
87+
if 'user_id' not in cs_cols and 'phone_number' in cs_cols:
88+
op.create_table('cs_fix',
89+
sa.Column('user_id', sa.Integer(), sa.ForeignKey('users.id'), primary_key=True),
90+
sa.Column('state', sa.String(), nullable=False),
91+
sa.Column('draft_data', sa.JSON(), nullable=True),
92+
sa.Column('last_action_metadata', sa.JSON(), nullable=True),
93+
sa.Column('last_updated', sa.DateTime(), nullable=False),
94+
sa.Column('pending_action_timestamp', sa.DateTime(), nullable=True),
95+
sa.Column('pending_action_payload', sa.JSON(), nullable=True),
96+
sa.Column('active_channel', sa.String(), nullable=False, server_default='WHATSAPP'),
97+
)
98+
op.execute("""
99+
INSERT INTO cs_fix (user_id, state, draft_data, last_action_metadata, last_updated)
100+
SELECT u.id, cs.state, cs.draft_data, cs.last_action_metadata, cs.last_updated
101+
FROM conversation_states cs
102+
JOIN users u ON cs.phone_number = u.phone_number
103+
""")
104+
op.drop_table('conversation_states')
105+
op.rename_table('cs_fix', 'conversation_states')
106+
107+
# Also check if expenses needs fixing due to previous broken states
108+
if 'expenses' in inspector.get_table_names():
109+
pass
24110

25111

26112
def downgrade() -> None:

src/main.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,23 @@ async def lifespan(app: FastAPI):
2222
# Initialize logging configuration
2323
setup_logging()
2424

25-
# Startup: Create tables
25+
# Run migrations automatically
26+
if ":memory:" not in str(engine.url):
27+
logger = logging.getLogger("src.main")
28+
logger.info("Running database migrations...")
29+
try:
30+
import alembic.config
31+
import alembic.command
32+
alembic_cfg = alembic.config.Config("alembic.ini")
33+
# Ensure we are in the correct directory for alembic.ini if needed,
34+
# but usually it's in the root
35+
alembic.command.upgrade(alembic_cfg, "head")
36+
logger.info("Migrations completed successfully.")
37+
except Exception as e:
38+
logger.error(f"Failed to run migrations: {e}")
39+
# Continue anyway, as validation might still pass if it was already at head
40+
41+
# Startup: Create tables (as a fallback/for new tables not in migrations yet)
2642
async with engine.begin() as conn:
2743
await conn.run_sync(Base.metadata.create_all)
2844

0 commit comments

Comments
 (0)