Merge pull request 'Merge from dev' (#15) from dev into loaf-prod
Reviewed-on: #15
This commit was merged in pull request #15.
This commit is contained in:
@@ -24,31 +24,48 @@ depends_on: Union[str, Sequence[str], None] = None
|
|||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
"""Add missing user fields"""
|
"""Add missing user fields (skip if already exists)"""
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
|
conn = op.get_bind()
|
||||||
|
inspector = inspect(conn)
|
||||||
|
existing_columns = {col['name'] for col in inspector.get_columns('users')}
|
||||||
|
|
||||||
# Add scholarship_reason
|
# Add scholarship_reason
|
||||||
op.add_column('users', sa.Column('scholarship_reason', sa.Text(), nullable=True))
|
if 'scholarship_reason' not in existing_columns:
|
||||||
|
op.add_column('users', sa.Column('scholarship_reason', sa.Text(), nullable=True))
|
||||||
|
|
||||||
# Add directory fields
|
# Add directory fields
|
||||||
op.add_column('users', sa.Column('directory_email', sa.String(), nullable=True))
|
if 'directory_email' not in existing_columns:
|
||||||
op.add_column('users', sa.Column('directory_bio', sa.Text(), nullable=True))
|
op.add_column('users', sa.Column('directory_email', sa.String(), nullable=True))
|
||||||
op.add_column('users', sa.Column('directory_address', sa.String(), nullable=True))
|
if 'directory_bio' not in existing_columns:
|
||||||
op.add_column('users', sa.Column('directory_phone', sa.String(), nullable=True))
|
op.add_column('users', sa.Column('directory_bio', sa.Text(), nullable=True))
|
||||||
op.add_column('users', sa.Column('directory_dob', sa.DateTime(), nullable=True))
|
if 'directory_address' not in existing_columns:
|
||||||
op.add_column('users', sa.Column('directory_partner_name', sa.String(), nullable=True))
|
op.add_column('users', sa.Column('directory_address', sa.String(), nullable=True))
|
||||||
|
if 'directory_phone' not in existing_columns:
|
||||||
|
op.add_column('users', sa.Column('directory_phone', sa.String(), nullable=True))
|
||||||
|
if 'directory_dob' not in existing_columns:
|
||||||
|
op.add_column('users', sa.Column('directory_dob', sa.DateTime(), nullable=True))
|
||||||
|
if 'directory_partner_name' not in existing_columns:
|
||||||
|
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)
|
# Rename profile_image_url to profile_photo_url (skip if already renamed)
|
||||||
op.alter_column('users', 'profile_image_url', new_column_name='profile_photo_url')
|
if 'profile_image_url' in existing_columns and 'profile_photo_url' not in existing_columns:
|
||||||
|
op.alter_column('users', 'profile_image_url', new_column_name='profile_photo_url')
|
||||||
|
|
||||||
# Add social media fields
|
# Add social media fields
|
||||||
op.add_column('users', sa.Column('social_media_facebook', sa.String(), nullable=True))
|
if 'social_media_facebook' not in existing_columns:
|
||||||
op.add_column('users', sa.Column('social_media_instagram', sa.String(), nullable=True))
|
op.add_column('users', sa.Column('social_media_facebook', sa.String(), nullable=True))
|
||||||
op.add_column('users', sa.Column('social_media_twitter', sa.String(), nullable=True))
|
if 'social_media_instagram' not in existing_columns:
|
||||||
op.add_column('users', sa.Column('social_media_linkedin', sa.String(), nullable=True))
|
op.add_column('users', sa.Column('social_media_instagram', sa.String(), nullable=True))
|
||||||
|
if 'social_media_twitter' not in existing_columns:
|
||||||
|
op.add_column('users', sa.Column('social_media_twitter', sa.String(), nullable=True))
|
||||||
|
if 'social_media_linkedin' not in existing_columns:
|
||||||
|
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)
|
# Add email_verification_expires if missing
|
||||||
# Check if it already exists, if not add it
|
if 'email_verification_expires' not in existing_columns:
|
||||||
# This field should already exist from the initial schema, but adding for completeness
|
op.add_column('users', sa.Column('email_verification_expires', sa.DateTime(), nullable=True))
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
|
|||||||
@@ -22,11 +22,24 @@ depends_on: Union[str, Sequence[str], None] = None
|
|||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
"""Add optional pre-filled information fields to user_invitations"""
|
"""Add optional pre-filled information fields to user_invitations (skip if already exists)"""
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
op.add_column('user_invitations', sa.Column('first_name', sa.String(), nullable=True))
|
conn = op.get_bind()
|
||||||
op.add_column('user_invitations', sa.Column('last_name', sa.String(), nullable=True))
|
inspector = inspect(conn)
|
||||||
op.add_column('user_invitations', sa.Column('phone', sa.String(), nullable=True))
|
existing_columns = {col['name'] for col in inspector.get_columns('user_invitations')}
|
||||||
|
|
||||||
|
# Add first_name if missing
|
||||||
|
if 'first_name' not in existing_columns:
|
||||||
|
op.add_column('user_invitations', sa.Column('first_name', sa.String(), nullable=True))
|
||||||
|
|
||||||
|
# Add last_name if missing
|
||||||
|
if 'last_name' not in existing_columns:
|
||||||
|
op.add_column('user_invitations', sa.Column('last_name', sa.String(), nullable=True))
|
||||||
|
|
||||||
|
# Add phone if missing
|
||||||
|
if 'phone' not in existing_columns:
|
||||||
|
op.add_column('user_invitations', sa.Column('phone', sa.String(), nullable=True))
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
|
|||||||
@@ -22,16 +22,26 @@ depends_on: Union[str, Sequence[str], None] = None
|
|||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
"""Add file_size_bytes column to document tables"""
|
"""Add file_size_bytes column to document tables (skip if already exists)"""
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
# Add to newsletter_archives
|
conn = op.get_bind()
|
||||||
op.add_column('newsletter_archives', sa.Column('file_size_bytes', sa.Integer(), nullable=True))
|
inspector = inspect(conn)
|
||||||
|
|
||||||
# Add to financial_reports
|
# Add to newsletter_archives if missing
|
||||||
op.add_column('financial_reports', sa.Column('file_size_bytes', sa.Integer(), nullable=True))
|
existing_columns = {col['name'] for col in inspector.get_columns('newsletter_archives')}
|
||||||
|
if 'file_size_bytes' not in existing_columns:
|
||||||
|
op.add_column('newsletter_archives', sa.Column('file_size_bytes', sa.Integer(), nullable=True))
|
||||||
|
|
||||||
# Add to bylaws_documents
|
# Add to financial_reports if missing
|
||||||
op.add_column('bylaws_documents', sa.Column('file_size_bytes', sa.Integer(), nullable=True))
|
existing_columns = {col['name'] for col in inspector.get_columns('financial_reports')}
|
||||||
|
if 'file_size_bytes' not in existing_columns:
|
||||||
|
op.add_column('financial_reports', sa.Column('file_size_bytes', sa.Integer(), nullable=True))
|
||||||
|
|
||||||
|
# Add to bylaws_documents if missing
|
||||||
|
existing_columns = {col['name'] for col in inspector.get_columns('bylaws_documents')}
|
||||||
|
if 'file_size_bytes' not in existing_columns:
|
||||||
|
op.add_column('bylaws_documents', sa.Column('file_size_bytes', sa.Integer(), nullable=True))
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
|
|||||||
@@ -22,26 +22,44 @@ depends_on: Union[str, Sequence[str], None] = None
|
|||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
"""Add missing columns and fix naming"""
|
"""Add missing columns and fix naming (skip if already exists)"""
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
# Add missing columns to subscriptions table
|
conn = op.get_bind()
|
||||||
op.add_column('subscriptions', sa.Column('start_date', sa.DateTime(timezone=True), nullable=True))
|
inspector = inspect(conn)
|
||||||
op.add_column('subscriptions', sa.Column('end_date', sa.DateTime(timezone=True), nullable=True))
|
|
||||||
op.add_column('subscriptions', sa.Column('amount_paid_cents', sa.Integer(), nullable=True))
|
|
||||||
op.add_column('subscriptions', sa.Column('manual_payment_notes', sa.Text(), nullable=True))
|
|
||||||
op.add_column('subscriptions', sa.Column('manual_payment_admin_id', UUID(as_uuid=True), nullable=True))
|
|
||||||
op.add_column('subscriptions', sa.Column('manual_payment_date', sa.DateTime(timezone=True), nullable=True))
|
|
||||||
op.add_column('subscriptions', sa.Column('payment_method', sa.String(50), nullable=True))
|
|
||||||
|
|
||||||
# Add foreign key for manual_payment_admin_id
|
# Check existing columns in subscriptions table
|
||||||
op.create_foreign_key(
|
existing_columns = {col['name'] for col in inspector.get_columns('subscriptions')}
|
||||||
'subscriptions_manual_payment_admin_id_fkey',
|
|
||||||
'subscriptions', 'users',
|
|
||||||
['manual_payment_admin_id'], ['id']
|
|
||||||
)
|
|
||||||
|
|
||||||
# Rename storage_usage.last_calculated_at to last_updated
|
# Add missing columns to subscriptions table only if they don't exist
|
||||||
op.alter_column('storage_usage', 'last_calculated_at', new_column_name='last_updated')
|
if 'start_date' not in existing_columns:
|
||||||
|
op.add_column('subscriptions', sa.Column('start_date', sa.DateTime(timezone=True), nullable=True))
|
||||||
|
if 'end_date' not in existing_columns:
|
||||||
|
op.add_column('subscriptions', sa.Column('end_date', sa.DateTime(timezone=True), nullable=True))
|
||||||
|
if 'amount_paid_cents' not in existing_columns:
|
||||||
|
op.add_column('subscriptions', sa.Column('amount_paid_cents', sa.Integer(), nullable=True))
|
||||||
|
if 'manual_payment_notes' not in existing_columns:
|
||||||
|
op.add_column('subscriptions', sa.Column('manual_payment_notes', sa.Text(), nullable=True))
|
||||||
|
if 'manual_payment_admin_id' not in existing_columns:
|
||||||
|
op.add_column('subscriptions', sa.Column('manual_payment_admin_id', UUID(as_uuid=True), nullable=True))
|
||||||
|
if 'manual_payment_date' not in existing_columns:
|
||||||
|
op.add_column('subscriptions', sa.Column('manual_payment_date', sa.DateTime(timezone=True), nullable=True))
|
||||||
|
if 'payment_method' not in existing_columns:
|
||||||
|
op.add_column('subscriptions', sa.Column('payment_method', sa.String(50), nullable=True))
|
||||||
|
|
||||||
|
# Add foreign key for manual_payment_admin_id if it doesn't exist
|
||||||
|
existing_fks = [fk['name'] for fk in inspector.get_foreign_keys('subscriptions')]
|
||||||
|
if 'subscriptions_manual_payment_admin_id_fkey' not in existing_fks:
|
||||||
|
op.create_foreign_key(
|
||||||
|
'subscriptions_manual_payment_admin_id_fkey',
|
||||||
|
'subscriptions', 'users',
|
||||||
|
['manual_payment_admin_id'], ['id']
|
||||||
|
)
|
||||||
|
|
||||||
|
# Rename storage_usage.last_calculated_at to last_updated (only if needed)
|
||||||
|
storage_columns = {col['name'] for col in inspector.get_columns('storage_usage')}
|
||||||
|
if 'last_calculated_at' in storage_columns and 'last_updated' not in storage_columns:
|
||||||
|
op.alter_column('storage_usage', 'last_calculated_at', new_column_name='last_updated')
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
|
|||||||
@@ -20,8 +20,16 @@ depends_on: Union[str, Sequence[str], None] = None
|
|||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
"""Rename is_active to active"""
|
"""Rename is_active to active (skip if already renamed)"""
|
||||||
op.alter_column('subscription_plans', 'is_active', new_column_name='active')
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
|
conn = op.get_bind()
|
||||||
|
inspector = inspect(conn)
|
||||||
|
|
||||||
|
# Check if rename is needed
|
||||||
|
existing_columns = {col['name'] for col in inspector.get_columns('subscription_plans')}
|
||||||
|
if 'is_active' in existing_columns and 'active' not in existing_columns:
|
||||||
|
op.alter_column('subscription_plans', 'is_active', new_column_name='active')
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
|
|||||||
37
alembic/versions/010_add_email_verification_expires.py
Normal file
37
alembic/versions/010_add_email_verification_expires.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
"""add_email_verification_expires
|
||||||
|
|
||||||
|
Revision ID: 010_add_email_exp
|
||||||
|
Revises: 009_add_all_missing
|
||||||
|
Create Date: 2026-01-05
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
- Add missing email_verification_expires column to users table
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = '010_add_email_exp'
|
||||||
|
down_revision: Union[str, None] = '009_add_all_missing'
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
"""Add email_verification_expires column (skip if already exists)"""
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
|
conn = op.get_bind()
|
||||||
|
inspector = inspect(conn)
|
||||||
|
existing_columns = {col['name'] for col in inspector.get_columns('users')}
|
||||||
|
|
||||||
|
# Add email_verification_expires if missing
|
||||||
|
if 'email_verification_expires' not in existing_columns:
|
||||||
|
op.add_column('users', sa.Column('email_verification_expires', sa.DateTime(), nullable=True))
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Remove email_verification_expires column"""
|
||||||
|
op.drop_column('users', 'email_verification_expires')
|
||||||
10
server.py
10
server.py
@@ -2814,8 +2814,9 @@ async def verify_invitation_token(
|
|||||||
if not invitation:
|
if not invitation:
|
||||||
raise HTTPException(status_code=404, detail="Invalid or expired invitation token")
|
raise HTTPException(status_code=404, detail="Invalid or expired invitation token")
|
||||||
|
|
||||||
# Check expiry
|
# Check expiry (handle timezone-naive datetime from DB)
|
||||||
if invitation.expires_at < datetime.now(timezone.utc):
|
expires_at_aware = invitation.expires_at.replace(tzinfo=timezone.utc) if invitation.expires_at.tzinfo is None else invitation.expires_at
|
||||||
|
if expires_at_aware < datetime.now(timezone.utc):
|
||||||
invitation.status = InvitationStatus.expired
|
invitation.status = InvitationStatus.expired
|
||||||
db.commit()
|
db.commit()
|
||||||
raise HTTPException(status_code=400, detail="Invitation has expired")
|
raise HTTPException(status_code=400, detail="Invitation has expired")
|
||||||
@@ -2847,8 +2848,9 @@ async def accept_invitation(
|
|||||||
if not invitation:
|
if not invitation:
|
||||||
raise HTTPException(status_code=404, detail="Invalid or expired invitation token")
|
raise HTTPException(status_code=404, detail="Invalid or expired invitation token")
|
||||||
|
|
||||||
# Check expiry
|
# Check expiry (handle timezone-naive datetime from DB)
|
||||||
if invitation.expires_at < datetime.now(timezone.utc):
|
expires_at_aware = invitation.expires_at.replace(tzinfo=timezone.utc) if invitation.expires_at.tzinfo is None else invitation.expires_at
|
||||||
|
if expires_at_aware < datetime.now(timezone.utc):
|
||||||
invitation.status = InvitationStatus.expired
|
invitation.status = InvitationStatus.expired
|
||||||
db.commit()
|
db.commit()
|
||||||
raise HTTPException(status_code=400, detail="Invitation has expired")
|
raise HTTPException(status_code=400, detail="Invitation has expired")
|
||||||
|
|||||||
Reference in New Issue
Block a user