From efc2002a67d3076b3045b2517e122ad47efb1564 Mon Sep 17 00:00:00 2001 From: Koncept Kit <63216427+konceptkit@users.noreply.github.com> Date: Sun, 4 Jan 2026 22:01:26 +0700 Subject: [PATCH] Fix database mismatches --- __pycache__/models.cpython-312.pyc | Bin 30129 -> 30190 bytes .../versions/002_add_missing_user_fields.py | 75 ++++++++++++++++++ .../003_add_user_invitation_fields.py | 37 +++++++++ check_schema_mismatches.py | 58 ++++++++++++++ fix_all_schema_mismatches.sh | 44 ++++++++++ models.py | 1 + 6 files changed, 215 insertions(+) create mode 100644 alembic/versions/002_add_missing_user_fields.py create mode 100644 alembic/versions/003_add_user_invitation_fields.py create mode 100644 check_schema_mismatches.py create mode 100644 fix_all_schema_mismatches.sh diff --git a/__pycache__/models.cpython-312.pyc b/__pycache__/models.cpython-312.pyc index aa1ad192e1f97f557d40b7a1cc50093434988e40..173f7c11351c4aa199545c0f399c2868263453e8 100644 GIT binary patch delta 2402 zcmZux3rv$&6z2Rufj(%Z6li$}Ey9Yttwm%M0mnmLRiM0tiu}biAjl6a(_}GDw=8pN z*l~+)(a0V~q)zb9)CEcQOpKdRj5>7>vSgbkCW~%N%^q(5xn)=hP5SlRbH4N4bI<9$ z<9FfLyP*0?rIPzeAFiX>JuO zr_|`uIX_o`OXrI41kW)tuturVWwJ=tQ@Pm`fn_))*9KXf}Q z)Q7#uhg~+u4kPyVS&?!dy4HujW{w{2BVOUd*7>k2=hzWG>?-D!TFFQHc&o|F;V+?a zP=mt?J;te{z>YcU1X#<~f;uX49ZRj1QsaG6>u^|YimadA%(%tPe~Kf{|nDRc8~X1=9-s*kUQ`O^4QKHVpKy;!07#~)tD^6(R|2Qy83 z@lJTCWbN0DK&xnrI0(@O*XDM2xAS?I;O=yHv=6v@dz`KpwzvgX|9jUX147!QvF-A^ z5gv^{W*TtX9wqtYISBpfztW%<&igp+vN zG^6v7d4fPqu#sR4?XUF2DpHER2WqING}p%Znl)88;L~ z!I!wTs2IM+$)b7%(_uq7gR#Z2@(B_nh{e^#rz8Q=(yTUe_+80yE*CJ%vosMF`T+QT7pp;Z&7gTv8sw;q{7R zT)9_#vO*8#l6a$;3F|4xKrl~NHcvc;I0QCKt{Q3To`L8)V)}k%~+t9fE2+Yab6Jt7B-9PTaY+1;B;V>)w$sB?^HPch%OyQg)8} zwKo6;@Irlt>?{d?jQ`e`#oi>-+Z@TdCEMJ~4|cl*Z}G@GQsmhrM$m-^94|vQ6U%8x z<51Qdiq_??;G?EcZl||7lIh$@3C*NyB4{C~CSVutL&@5vtnYUT=|XR}%jxEYHVRrP z%UhVV906g2^iyp%2#YBiPmn;c0zcuCHMzw0sU34$;u5`+B+`@VvcHave0h|zk^b|z zwYeE0mx_u%M;wG$7t>Gb&gESnY)vH@Fq$M2{in8pbT6HNpGX z)1C-rtnZ%o91bk_Q^#291jQSO&)V!QQ1Vrq;}_~#k)+`78j@F18o_b#uqz6iG%p+)~WedT2f>u^lBbfx}@qPbRZSCfp% zHQ^Usm*h^$Czuisbyom5nKai^&q*&vQ ccYiFrjD~?B4G*vxUbW2lF=M6@Ck7t;2XHuK?EnA( delta 2350 zcmZuxdrVVj6z{o6v4uXM6k2EplnNBF$Wu{XfB%A8JJ=H`-Re;8-7G3LySyXPyzE8!;hm-C&+@0{;D z=lg!Y1844l>Y_@el!@Q@VdvH@XH;(}xD#@0Sl%GqSI9ZM7Oe&wrp4$nHO8UQ`x8QQ z2RL$<$-H*b>vK7oe~y14&xN9td>mKm^aZon3(0~LF-aQT6LE`}exYinehK4S z%B=fTZp?_$>X$LzC*!LyR;@QyGs|*b#Ygh$kY-@cRJ|*(Lal$HMq-VXSZn|1FQ6WX;8Bj6COj~|4?__O^L{5M`LN`3l%SSPGW zI0izL;ZcO5QEbD0+4cBj?5ax=Y^M1jbeMNU!n-Wyyk#)yJ@L!~UV?N2A3+(xNwiug z^aCV@2s8v62~OdHc{y-Y@Xo7D7$$k#}HZl z*dx~|c@hai!btXW5pYE$>%(GuKYWJ=b30)K@86I!g0kPElv|P>Qp93{5`w)1(F9|N zDi*R>MKklmeQ2ZnDSFZ*yt(N1DwT02=D2PfAjG)P}RI3(i3vX?dlmqyc z%aRyNN`f5m@BTnE&{W!JD$&#!gGc7Y;J1!F5Q~>PjKap13XZD%51dBa=IjFp z-uCLmq${i}p@9gE1T*r54~~*5lgUg_hJ0N=Bs1>JdLF=xha299RxEHi;2b{hvS=Uk zDX3~So^&P2N5dQV#${Hf6BmK_P`J)SlGNE7s~0|N^a1c_YI;+dO)dm&INH<%*({xX z?R9`|jB@A7KPI+Mu+;4^UnenqW}bw<-&5A`j8Q#o z97joFBT-J$5(?Q!GmjU23V2qy>TBVk2<>foab#N?s35mxBGd#5u&ZqdUd60-OEjyW zy(5*_++M>fu;8cdM&}Bmd_3Nx(+LMjDkdv?WmL43)6zl`d!4K#vCR>@jFt_z#A2Xh zUzUR$O=Mm6kr;ETD&Zc+jNFX(O7Rw4Wtl7DnS|nJ2tsa7+J;6W)3FtwcVph4_Gp) zj2$HNpK8zUu7q%VMm2=1XP@HqrAzWc^%_VUu>$KE6MUa+gp zt{S@|>=v-;4Xh_W)-l!tc7CjGRzVO;f@au{o?x%G6JQ6tR5&4H!o(`PADsLTGyY3; diff --git a/alembic/versions/002_add_missing_user_fields.py b/alembic/versions/002_add_missing_user_fields.py new file mode 100644 index 0000000..0e9b2bc --- /dev/null +++ b/alembic/versions/002_add_missing_user_fields.py @@ -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') diff --git a/alembic/versions/003_add_user_invitation_fields.py b/alembic/versions/003_add_user_invitation_fields.py new file mode 100644 index 0000000..06a0ad2 --- /dev/null +++ b/alembic/versions/003_add_user_invitation_fields.py @@ -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') diff --git a/check_schema_mismatches.py b/check_schema_mismatches.py new file mode 100644 index 0000000..2110be4 --- /dev/null +++ b/check_schema_mismatches.py @@ -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) diff --git a/fix_all_schema_mismatches.sh b/fix_all_schema_mismatches.sh new file mode 100644 index 0000000..8c6c864 --- /dev/null +++ b/fix_all_schema_mismatches.sh @@ -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 "" diff --git a/models.py b/models.py index a48761a..a8d8d30 100644 --- a/models.py +++ b/models.py @@ -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) -- 2.39.5