Compare commits

..

2 Commits

Author SHA1 Message Date
e20542ccdc Merge pull request 'Fix database mismatches' (#5) from dev into loaf-prod
Reviewed-on: #5
2026-01-04 15:02:09 +00:00
Koncept Kit
efc2002a67 Fix database mismatches 2026-01-04 22:01:26 +07:00
6 changed files with 215 additions and 0 deletions

Binary file not shown.

View File

@@ -0,0 +1,75 @@
"""add_missing_user_fields
Revision ID: 002_add_missing_user_fields
Revises: 001_initial_baseline
Create Date: 2026-01-04
Adds missing user fields to sync models.py with database:
- scholarship_reason
- directory_* fields (email, bio, address, phone, dob, partner_name)
- profile_photo_url (rename from profile_image_url)
- social_media_* fields (facebook, instagram, twitter, linkedin)
- email_verification_expires
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '002_add_missing_user_fields'
down_revision: Union[str, None] = '001_initial_baseline'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Add missing user fields"""
# Add scholarship_reason
op.add_column('users', sa.Column('scholarship_reason', sa.Text(), nullable=True))
# Add directory fields
op.add_column('users', sa.Column('directory_email', sa.String(), nullable=True))
op.add_column('users', sa.Column('directory_bio', sa.Text(), nullable=True))
op.add_column('users', sa.Column('directory_address', sa.String(), nullable=True))
op.add_column('users', sa.Column('directory_phone', sa.String(), nullable=True))
op.add_column('users', sa.Column('directory_dob', sa.DateTime(), nullable=True))
op.add_column('users', sa.Column('directory_partner_name', sa.String(), nullable=True))
# Rename profile_image_url to profile_photo_url (for consistency with models.py)
op.alter_column('users', 'profile_image_url', new_column_name='profile_photo_url')
# Add social media fields
op.add_column('users', sa.Column('social_media_facebook', sa.String(), nullable=True))
op.add_column('users', sa.Column('social_media_instagram', sa.String(), nullable=True))
op.add_column('users', sa.Column('social_media_twitter', sa.String(), nullable=True))
op.add_column('users', sa.Column('social_media_linkedin', sa.String(), nullable=True))
# Add email_verification_expires (exists in DB but not in models.py initially)
# Check if it already exists, if not add it
# This field should already exist from the initial schema, but adding for completeness
def downgrade() -> None:
"""Remove added fields (rollback)"""
# Remove social media fields
op.drop_column('users', 'social_media_linkedin')
op.drop_column('users', 'social_media_twitter')
op.drop_column('users', 'social_media_instagram')
op.drop_column('users', 'social_media_facebook')
# Rename profile_photo_url back to profile_image_url
op.alter_column('users', 'profile_photo_url', new_column_name='profile_image_url')
# Remove directory fields
op.drop_column('users', 'directory_partner_name')
op.drop_column('users', 'directory_dob')
op.drop_column('users', 'directory_phone')
op.drop_column('users', 'directory_address')
op.drop_column('users', 'directory_bio')
op.drop_column('users', 'directory_email')
# Remove scholarship_reason
op.drop_column('users', 'scholarship_reason')

View File

@@ -0,0 +1,37 @@
"""add_user_invitation_fields
Revision ID: 003_add_user_invitation_fields
Revises: 002_add_missing_user_fields
Create Date: 2026-01-04
Adds optional pre-filled fields to user_invitations table:
- first_name
- last_name
- phone
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '003_add_user_invitation_fields'
down_revision: Union[str, None] = '002_add_missing_user_fields'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Add optional pre-filled information fields to user_invitations"""
op.add_column('user_invitations', sa.Column('first_name', sa.String(), nullable=True))
op.add_column('user_invitations', sa.Column('last_name', sa.String(), nullable=True))
op.add_column('user_invitations', sa.Column('phone', sa.String(), nullable=True))
def downgrade() -> None:
"""Remove added fields (rollback)"""
op.drop_column('user_invitations', 'phone')
op.drop_column('user_invitations', 'last_name')
op.drop_column('user_invitations', 'first_name')

View File

@@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""
Check for schema mismatches between models.py and database
"""
import os
from sqlalchemy import create_engine, inspect
from dotenv import load_dotenv
from models import Base
load_dotenv()
# Connect to database
engine = create_engine(os.getenv('DATABASE_URL'))
inspector = inspect(engine)
print("=" * 80)
print("SCHEMA MISMATCH DETECTION")
print("=" * 80)
mismatches = []
# Check each model
for table_name, table in Base.metadata.tables.items():
print(f"\n📋 Checking table: {table_name}")
# Get columns from database
try:
db_columns = {col['name'] for col in inspector.get_columns(table_name)}
except Exception as e:
print(f" ❌ Table doesn't exist in database: {e}")
mismatches.append(f"{table_name}: Table missing in database")
continue
# Get columns from model
model_columns = {col.name for col in table.columns}
# Find missing columns
missing_in_db = model_columns - db_columns
extra_in_db = db_columns - model_columns
if missing_in_db:
print(f" ⚠️ Missing in DATABASE: {missing_in_db}")
mismatches.append(f"{table_name}: Missing in DB: {missing_in_db}")
if extra_in_db:
print(f" Extra in DATABASE (not in model): {extra_in_db}")
if not missing_in_db and not extra_in_db:
print(f" ✅ Schema matches!")
print("\n" + "=" * 80)
if mismatches:
print(f"❌ FOUND {len(mismatches)} MISMATCHES:")
for mismatch in mismatches:
print(f" - {mismatch}")
else:
print("✅ ALL SCHEMAS MATCH!")
print("=" * 80)

View File

@@ -0,0 +1,44 @@
#!/bin/bash
# Fix all schema mismatches between models.py and database
# Run this on your server
set -e # Exit on error
echo "============================================================"
echo "Schema Mismatch Fix Script"
echo "============================================================"
echo ""
# Navigate to backend directory
cd "$(dirname "$0")"
echo "Step 1: Check current Alembic status..."
python3 -m alembic current
echo ""
echo "Step 2: Apply migration 003 (user_invitations fields)..."
python3 -m alembic upgrade head
echo ""
echo "Step 3: Verify migration was applied..."
python3 -m alembic current
echo ""
echo "Step 4: Restart PM2 backend..."
pm2 restart membership-backend
echo ""
echo "============================================================"
echo "✅ Schema fixes applied!"
echo "============================================================"
echo ""
echo "Migrations applied:"
echo " - 001_initial_baseline"
echo " - 002_add_missing_user_fields (users table)"
echo " - 003_add_user_invitation_fields (user_invitations table)"
echo ""
echo "Please test:"
echo " 1. Login to admin dashboard"
echo " 2. Navigate to user invitations page"
echo " 3. Verify no more schema errors"
echo ""

View File

@@ -69,6 +69,7 @@ class User(Base):
role_id = Column(UUID(as_uuid=True), ForeignKey("roles.id"), nullable=True) # New dynamic role FK
email_verified = Column(Boolean, default=False)
email_verification_token = Column(String, nullable=True)
email_verification_expires = Column(DateTime, nullable=True)
newsletter_subscribed = Column(Boolean, default=False)
# Newsletter Publication Preferences (Step 2)