Merge pull request 'Fix database mismatches' (#5) from dev into loaf-prod
Reviewed-on: #5
This commit was merged in pull request #5.
This commit is contained in:
Binary file not shown.
75
alembic/versions/002_add_missing_user_fields.py
Normal file
75
alembic/versions/002_add_missing_user_fields.py
Normal 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')
|
||||||
37
alembic/versions/003_add_user_invitation_fields.py
Normal file
37
alembic/versions/003_add_user_invitation_fields.py
Normal 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')
|
||||||
58
check_schema_mismatches.py
Normal file
58
check_schema_mismatches.py
Normal 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)
|
||||||
44
fix_all_schema_mismatches.sh
Normal file
44
fix_all_schema_mismatches.sh
Normal 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 ""
|
||||||
@@ -69,6 +69,7 @@ class User(Base):
|
|||||||
role_id = Column(UUID(as_uuid=True), ForeignKey("roles.id"), nullable=True) # New dynamic role FK
|
role_id = Column(UUID(as_uuid=True), ForeignKey("roles.id"), nullable=True) # New dynamic role FK
|
||||||
email_verified = Column(Boolean, default=False)
|
email_verified = Column(Boolean, default=False)
|
||||||
email_verification_token = Column(String, nullable=True)
|
email_verification_token = Column(String, nullable=True)
|
||||||
|
email_verification_expires = Column(DateTime, nullable=True)
|
||||||
newsletter_subscribed = Column(Boolean, default=False)
|
newsletter_subscribed = Column(Boolean, default=False)
|
||||||
|
|
||||||
# Newsletter Publication Preferences (Step 2)
|
# Newsletter Publication Preferences (Step 2)
|
||||||
|
|||||||
Reference in New Issue
Block a user