diff --git a/__pycache__/models.cpython-312.pyc b/__pycache__/models.cpython-312.pyc index 5bbab35..873b1cd 100644 Binary files a/__pycache__/models.cpython-312.pyc and b/__pycache__/models.cpython-312.pyc differ diff --git a/__pycache__/server.cpython-312.pyc b/__pycache__/server.cpython-312.pyc index 3e2abfd..82142e2 100644 Binary files a/__pycache__/server.cpython-312.pyc and b/__pycache__/server.cpython-312.pyc differ diff --git a/migrate_multistep_registration.py b/migrate_multistep_registration.py new file mode 100644 index 0000000..e33fdde --- /dev/null +++ b/migrate_multistep_registration.py @@ -0,0 +1,69 @@ +""" +Migration script to add multi-step registration fields to users table. +Run this once to update the database schema. +""" + +from database import engine +from sqlalchemy import text + +def add_multistep_registration_columns(): + """Add newsletter, volunteer, scholarship, and directory columns to users table""" + + migrations = [ + # Newsletter preferences + "ALTER TABLE users ADD COLUMN IF NOT EXISTS newsletter_publish_name BOOLEAN NOT NULL DEFAULT FALSE", + "ALTER TABLE users ADD COLUMN IF NOT EXISTS newsletter_publish_photo BOOLEAN NOT NULL DEFAULT FALSE", + "ALTER TABLE users ADD COLUMN IF NOT EXISTS newsletter_publish_birthday BOOLEAN NOT NULL DEFAULT FALSE", + "ALTER TABLE users ADD COLUMN IF NOT EXISTS newsletter_publish_none BOOLEAN NOT NULL DEFAULT FALSE", + + # Volunteer interests + "ALTER TABLE users ADD COLUMN IF NOT EXISTS volunteer_interests JSON DEFAULT '[]'::json", + + # Scholarship + "ALTER TABLE users ADD COLUMN IF NOT EXISTS scholarship_requested BOOLEAN NOT NULL DEFAULT FALSE", + "ALTER TABLE users ADD COLUMN IF NOT EXISTS scholarship_reason TEXT", + + # Directory settings + "ALTER TABLE users ADD COLUMN IF NOT EXISTS show_in_directory BOOLEAN NOT NULL DEFAULT FALSE", + "ALTER TABLE users ADD COLUMN IF NOT EXISTS directory_email VARCHAR", + "ALTER TABLE users ADD COLUMN IF NOT EXISTS directory_bio TEXT", + "ALTER TABLE users ADD COLUMN IF NOT EXISTS directory_address VARCHAR", + "ALTER TABLE users ADD COLUMN IF NOT EXISTS directory_phone VARCHAR", + "ALTER TABLE users ADD COLUMN IF NOT EXISTS directory_dob TIMESTAMP", + "ALTER TABLE users ADD COLUMN IF NOT EXISTS directory_partner_name VARCHAR" + ] + + try: + print("Adding multi-step registration columns to users table...") + with engine.connect() as conn: + for sql in migrations: + print(f" Executing: {sql[:70]}...") + conn.execute(text(sql)) + conn.commit() + print("✅ Migration completed successfully!") + print("\nAdded columns:") + print(" Newsletter Preferences:") + print(" - newsletter_publish_name (BOOLEAN)") + print(" - newsletter_publish_photo (BOOLEAN)") + print(" - newsletter_publish_birthday (BOOLEAN)") + print(" - newsletter_publish_none (BOOLEAN)") + print(" Volunteer:") + print(" - volunteer_interests (JSON)") + print(" Scholarship:") + print(" - scholarship_requested (BOOLEAN)") + print(" - scholarship_reason (TEXT)") + print(" Directory:") + print(" - show_in_directory (BOOLEAN)") + print(" - directory_email (VARCHAR)") + print(" - directory_bio (TEXT)") + print(" - directory_address (VARCHAR)") + print(" - directory_phone (VARCHAR)") + print(" - directory_dob (TIMESTAMP)") + print(" - directory_partner_name (VARCHAR)") + + except Exception as e: + print(f"❌ Migration failed: {e}") + raise + +if __name__ == "__main__": + add_multistep_registration_columns() diff --git a/models.py b/models.py index 7837fac..b8e347e 100644 --- a/models.py +++ b/models.py @@ -54,6 +54,29 @@ class User(Base): email_verified = Column(Boolean, default=False) email_verification_token = Column(String, nullable=True) newsletter_subscribed = Column(Boolean, default=False) + + # Newsletter Publication Preferences (Step 2) + newsletter_publish_name = Column(Boolean, default=False, nullable=False) + newsletter_publish_photo = Column(Boolean, default=False, nullable=False) + newsletter_publish_birthday = Column(Boolean, default=False, nullable=False) + newsletter_publish_none = Column(Boolean, default=False, nullable=False) + + # Volunteer Interests (Step 2) + volunteer_interests = Column(JSON, default=list) + + # Scholarship Request (Step 2) + scholarship_requested = Column(Boolean, default=False, nullable=False) + scholarship_reason = Column(Text, nullable=True) + + # Directory Settings (Step 3) + show_in_directory = Column(Boolean, default=False, nullable=False) + directory_email = Column(String, nullable=True) + directory_bio = Column(Text, nullable=True) + directory_address = Column(String, nullable=True) + directory_phone = Column(String, nullable=True) + directory_dob = Column(DateTime, nullable=True) + directory_partner_name = Column(String, nullable=True) + created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc)) diff --git a/server.py b/server.py index cd7066f..04110c8 100644 --- a/server.py +++ b/server.py @@ -59,8 +59,7 @@ logger = logging.getLogger(__name__) # Pydantic Models class RegisterRequest(BaseModel): - email: EmailStr - password: str = Field(min_length=6) + # Step 1: Personal & Partner Information first_name: str last_name: str phone: str @@ -74,7 +73,48 @@ class RegisterRequest(BaseModel): partner_last_name: Optional[str] = None partner_is_member: Optional[bool] = False partner_plan_to_become_member: Optional[bool] = False + + # Step 2: Newsletter, Volunteer & Scholarship referred_by_member_name: Optional[str] = None + newsletter_publish_name: bool + newsletter_publish_photo: bool + newsletter_publish_birthday: bool + newsletter_publish_none: bool + volunteer_interests: List[str] = [] + scholarship_requested: bool = False + scholarship_reason: Optional[str] = None + + # Step 3: Directory Settings + show_in_directory: bool = False + directory_email: Optional[str] = None + directory_bio: Optional[str] = None + directory_address: Optional[str] = None + directory_phone: Optional[str] = None + directory_dob: Optional[datetime] = None + directory_partner_name: Optional[str] = None + + # Step 4: Account Credentials + email: EmailStr + password: str = Field(min_length=6) + + @validator('newsletter_publish_none') + def validate_newsletter_preferences(cls, v, values): + """At least one newsletter preference must be selected""" + name = values.get('newsletter_publish_name', False) + photo = values.get('newsletter_publish_photo', False) + birthday = values.get('newsletter_publish_birthday', False) + + if not (name or photo or birthday or v): + raise ValueError('At least one newsletter publication preference must be selected') + return v + + @validator('scholarship_reason') + def validate_scholarship_reason(cls, v, values): + """If scholarship requested, reason must be provided""" + requested = values.get('scholarship_requested', False) + if requested and not v: + raise ValueError('Scholarship reason is required when requesting scholarship') + return v class LoginRequest(BaseModel): email: EmailStr @@ -179,8 +219,11 @@ async def register(request: RegisterRequest, db: Session = Depends(get_db)): # Create user user = User( + # Account credentials (Step 4) email=request.email, password_hash=get_password_hash(request.password), + + # Personal information (Step 1) first_name=request.first_name, last_name=request.last_name, phone=request.phone, @@ -190,11 +233,39 @@ async def register(request: RegisterRequest, db: Session = Depends(get_db)): zipcode=request.zipcode, date_of_birth=request.date_of_birth, lead_sources=request.lead_sources, + + # Partner information (Step 1) partner_first_name=request.partner_first_name, partner_last_name=request.partner_last_name, partner_is_member=request.partner_is_member, partner_plan_to_become_member=request.partner_plan_to_become_member, + + # Referral (Step 2) referred_by_member_name=request.referred_by_member_name, + + # Newsletter publication preferences (Step 2) + newsletter_publish_name=request.newsletter_publish_name, + newsletter_publish_photo=request.newsletter_publish_photo, + newsletter_publish_birthday=request.newsletter_publish_birthday, + newsletter_publish_none=request.newsletter_publish_none, + + # Volunteer interests (Step 2) + volunteer_interests=request.volunteer_interests, + + # Scholarship (Step 2) + scholarship_requested=request.scholarship_requested, + scholarship_reason=request.scholarship_reason, + + # Directory settings (Step 3) + show_in_directory=request.show_in_directory, + directory_email=request.directory_email, + directory_bio=request.directory_bio, + directory_address=request.directory_address, + directory_phone=request.directory_phone, + directory_dob=request.directory_dob, + directory_partner_name=request.directory_partner_name, + + # Status fields status=UserStatus.pending_email, role=UserRole.guest, email_verified=False,