Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions backend/app/routes/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,19 @@ async def logout(
request: Request,
credentials: HTTPAuthorizationCredentials = Depends(security),
auth_service: AuthService = Depends(get_auth_service),
user_service: UserService = Depends(get_user_service),
):
try:
user_id = request.state.user_id
if not user_id:
auth_id = request.state.user_id # This is actually the Firebase auth_id
if not auth_id:
raise HTTPException(status_code=401, detail="Authentication required")

# Convert Firebase auth_id to database user_id (UUID)
user_id = await user_service.get_user_id_by_auth_id(auth_id)
auth_service.revoke_tokens(user_id)
return {"message": "Successfully logged out"}
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

Expand Down
70 changes: 70 additions & 0 deletions backend/app/routes/contact.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import logging

from fastapi import APIRouter, Depends, HTTPException, Request
from sqlalchemy.orm import Session

from app.middleware.auth import has_roles
from app.models import User
from app.schemas.contact import ContactRequest, ContactResponse
from app.schemas.user import UserRole
from app.utilities.constants import LOGGER_NAME
from app.utilities.db_utils import get_db

log = logging.getLogger(LOGGER_NAME("contact"))

router = APIRouter(
prefix="/contact",
tags=["contact"],
)


@router.post("/submit", response_model=ContactResponse)
async def submit_contact_form(
contact_data: ContactRequest,
request: Request,
db: Session = Depends(get_db),
authorized: bool = has_roles([UserRole.PARTICIPANT, UserRole.VOLUNTEER, UserRole.ADMIN]),
):
"""
Submit a contact form message from a user.

This endpoint receives contact form submissions from participants or volunteers
and sends the message to the admin team.

Args:
contact_data: The contact form data (name, email, message)
request: The FastAPI request object (contains user_id from auth middleware)
db: Database session

Returns:
ContactResponse with success status and message
"""
try:
# Get current user from auth middleware
current_user_auth_id = request.state.user_id
current_user = db.query(User).filter(User.auth_id == current_user_auth_id).first()

if not current_user:
raise HTTPException(status_code=401, detail="User not found")

# Log the contact form submission
log.info(
f"Contact form submission from user {current_user.id} "
f"(name: {contact_data.name}, email: {contact_data.email})"
)
log.info(f"Message: {contact_data.message}")

# TODO: Send email to admin team
# This will be implemented in a future update
# For now, we just log the message and return success

return ContactResponse(
success=True,
message="Your message has been sent successfully. A staff member will get back to you as soon as possible.",
)

except HTTPException:
raise
except Exception as e:
log.error(f"Error submitting contact form: {str(e)}")
raise HTTPException(status_code=500, detail="Failed to submit contact form")
57 changes: 54 additions & 3 deletions backend/app/routes/user_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@

from fastapi import APIRouter, Depends, HTTPException, Request
from pydantic import BaseModel, ConfigDict
from sqlalchemy.orm import Session
from sqlalchemy.orm import Session, joinedload

from app.middleware.auth import has_roles
from app.models import Experience, Treatment, User, UserData
from app.models.User import Language
from app.models.VolunteerData import VolunteerData
from app.schemas.user import UserRole
from app.utilities.db_utils import get_db

Expand Down Expand Up @@ -62,6 +64,7 @@ class UserDataResponse(BaseModel):
has_kids: Optional[str] = None
other_ethnic_group: Optional[str] = None
gender_identity_custom: Optional[str] = None
timezone: Optional[str] = None

# Cancer Experience
diagnosis: Optional[str] = None
Expand All @@ -84,6 +87,9 @@ class UserDataResponse(BaseModel):
# Availability (list of availability templates)
availability: List[AvailabilityTemplateResponse] = []

# Volunteer Data (for volunteers)
volunteer_experience: Optional[str] = None


# ===== Endpoints =====

Expand All @@ -106,7 +112,9 @@ async def get_my_user_data(
try:
# Get current user from auth middleware
current_user_auth_id = request.state.user_id
current_user = db.query(User).filter(User.auth_id == current_user_auth_id).first()
current_user = (
db.query(User).options(joinedload(User.volunteer_data)).filter(User.auth_id == current_user_auth_id).first()
)

if not current_user:
raise HTTPException(status_code=401, detail="User not found")
Expand All @@ -128,6 +136,11 @@ async def get_my_user_data(
if template.is_active
]

# Get volunteer_data.experience if user is a volunteer
volunteer_experience = None
if current_user.volunteer_data:
volunteer_experience = current_user.volunteer_data.experience

# Build response with all fields and resolved relationships
response = UserDataResponse(
# Personal Information
Expand All @@ -147,6 +160,7 @@ async def get_my_user_data(
has_kids=user_data.has_kids,
other_ethnic_group=user_data.other_ethnic_group,
gender_identity_custom=user_data.gender_identity_custom,
timezone=user_data.timezone,
# Cancer Experience
diagnosis=user_data.diagnosis,
date_of_diagnosis=user_data.date_of_diagnosis.isoformat() if user_data.date_of_diagnosis else None,
Expand All @@ -166,6 +180,8 @@ async def get_my_user_data(
caring_for_someone=user_data.caring_for_someone,
# Availability
availability=availability_templates,
# Volunteer Data
volunteer_experience=volunteer_experience,
)

return response
Expand All @@ -192,7 +208,9 @@ async def update_my_user_data(
try:
# Get current user from auth middleware
current_user_auth_id = request.state.user_id
current_user = db.query(User).filter(User.auth_id == current_user_auth_id).first()
current_user = (
db.query(User).options(joinedload(User.volunteer_data)).filter(User.auth_id == current_user_auth_id).first()
)

if not current_user:
raise HTTPException(status_code=401, detail="User not found")
Expand Down Expand Up @@ -300,6 +318,32 @@ async def update_my_user_data(
if experience:
user_data.loved_one_experiences.append(experience)

# Update user language (stored on User model, not UserData)
if "language" in update_data:
try:
language_value = update_data["language"]
if language_value in ["en", "fr"]:
current_user.language = Language(language_value)
except (ValueError, AttributeError):
pass # Invalid language value, skip

# Update user timezone (stored on UserData model)
if "timezone" in update_data:
user_data.timezone = update_data["timezone"]

# Handle volunteer_experience update if provided
if "volunteer_experience" in update_data:
volunteer_data = db.query(VolunteerData).filter(VolunteerData.user_id == current_user.id).first()
if volunteer_data:
volunteer_data.experience = update_data["volunteer_experience"]
else:
# Create volunteer_data if it doesn't exist
volunteer_data = VolunteerData(
user_id=current_user.id,
experience=update_data["volunteer_experience"],
)
db.add(volunteer_data)

db.commit()
db.refresh(user_data)

Expand All @@ -314,6 +358,11 @@ async def update_my_user_data(
if template.is_active
]

# Get volunteer_data.experience if user is a volunteer
volunteer_experience = None
if current_user.volunteer_data:
volunteer_experience = current_user.volunteer_data.experience

response = UserDataResponse(
first_name=user_data.first_name,
last_name=user_data.last_name,
Expand All @@ -330,6 +379,7 @@ async def update_my_user_data(
has_kids=user_data.has_kids,
other_ethnic_group=user_data.other_ethnic_group,
gender_identity_custom=user_data.gender_identity_custom,
timezone=user_data.timezone,
diagnosis=user_data.diagnosis,
date_of_diagnosis=user_data.date_of_diagnosis.isoformat() if user_data.date_of_diagnosis else None,
treatments=[treatment.name for treatment in user_data.treatments],
Expand All @@ -345,6 +395,7 @@ async def update_my_user_data(
has_blood_cancer=user_data.has_blood_cancer,
caring_for_someone=user_data.caring_for_someone,
availability=availability_templates,
volunteer_experience=volunteer_experience,
)

return response
Expand Down
16 changes: 16 additions & 0 deletions backend/app/schemas/contact.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from pydantic import BaseModel


class ContactRequest(BaseModel):
"""Schema for contact form submission"""

name: str
email: str
message: str


class ContactResponse(BaseModel):
"""Schema for contact form response"""

success: bool
message: str
1 change: 1 addition & 0 deletions backend/app/schemas/match.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class MatchVolunteerSummary(BaseModel):
first_name: Optional[str] = None
last_name: Optional[str] = None
email: str
phone: Optional[str] = None
pronouns: Optional[List[str]] = None
diagnosis: Optional[str] = None
age: Optional[int] = None
Expand Down
58 changes: 57 additions & 1 deletion backend/app/seeds/users.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Seed users data for testing matching functionality."""

import uuid
from datetime import date
from datetime import date, time

from sqlalchemy import delete
from sqlalchemy.orm import Session
Expand Down Expand Up @@ -131,6 +131,7 @@ def seed_users(session: Session) -> None:
"date_of_diagnosis": date(2018, 4, 20), # Survivor
"has_blood_cancer": "yes",
"caring_for_someone": "no",
"timezone": "EST",
},
"treatments": [
TreatmentId.CHEMOTHERAPY,
Expand All @@ -143,6 +144,12 @@ def seed_users(session: Session) -> None:
ExperienceId.FATIGUE,
ExperienceId.RETURNING_TO_WORK,
],
"volunteer_experience": "My journey with blood cancer started when I was about twelve years old and getting treatment for the first time was extremely stress-inducing. My journey with blood cancer started when I was about twelve years old and getting treatment for the first time was extremely stress-inducing.",
"availability_templates": [
{"day_of_week": 1, "start_time": time(14, 0), "end_time": time(16, 0)}, # Tuesday 2-4pm
{"day_of_week": 3, "start_time": time(14, 0), "end_time": time(17, 0)}, # Thursday 2-5pm
{"day_of_week": 4, "start_time": time(10, 0), "end_time": time(12, 0)}, # Friday 10am-12pm
],
},
{
"role": "volunteer",
Expand All @@ -165,9 +172,16 @@ def seed_users(session: Session) -> None:
"date_of_diagnosis": date(2020, 8, 15), # Survivor
"has_blood_cancer": "yes",
"caring_for_someone": "no",
"timezone": "PST",
},
"treatments": [3, 6], # Chemotherapy, Radiation (matching Sarah's preferences)
"experiences": [1, 3, 4], # Brain Fog, Feeling Overwhelmed, Fatigue (same as Sarah!)
"volunteer_experience": "I was diagnosed with ALL at 34 and underwent intensive chemotherapy and radiation. The hardest part was balancing treatment with being a mother. I want to help others navigate this difficult journey and share what I learned.",
"availability_templates": [
{"day_of_week": 0, "start_time": time(16, 0), "end_time": time(18, 0)}, # Monday 4-6pm
{"day_of_week": 2, "start_time": time(11, 0), "end_time": time(13, 0)}, # Wednesday 11am-1pm
{"day_of_week": 4, "start_time": time(13, 0), "end_time": time(15, 0)}, # Friday 1-3pm
],
},
{
"role": "volunteer",
Expand All @@ -190,9 +204,16 @@ def seed_users(session: Session) -> None:
"date_of_diagnosis": date(2020, 2, 14),
"has_blood_cancer": "yes",
"caring_for_someone": "no",
"timezone": "EST",
},
"treatments": [3, 6], # Chemotherapy, Radiation
"experiences": [10, 11, 7], # Anxiety/Depression, PTSD, Returning to work
"volunteer_experience": "Fighting Hodgkin Lymphoma taught me resilience I never knew I had. The mental health challenges were just as tough as the physical ones. I'm here to listen and support anyone going through similar struggles.",
"availability_templates": [
{"day_of_week": 1, "start_time": time(9, 0), "end_time": time(11, 0)}, # Tuesday 9-11am
{"day_of_week": 3, "start_time": time(14, 0), "end_time": time(16, 0)}, # Thursday 2-4pm
{"day_of_week": 5, "start_time": time(10, 0), "end_time": time(12, 0)}, # Saturday 10am-12pm
],
},
# High-matching volunteers for Sarah Johnson
{
Expand All @@ -216,9 +237,15 @@ def seed_users(session: Session) -> None:
"date_of_diagnosis": date(2019, 5, 10), # Survivor
"has_blood_cancer": "yes",
"caring_for_someone": "no",
"timezone": "EST",
},
"treatments": [3, 6], # Chemotherapy, Radiation (matching Sarah's preferences)
"experiences": [1, 3, 4], # Brain Fog, Feeling Overwhelmed, Fatigue (same as Sarah!)
"volunteer_experience": "As a working mother of two, being diagnosed with leukemia turned my world upside down. Brain fog and fatigue made everyday tasks feel impossible. Now in remission, I'm passionate about helping others find their strength during treatment.",
"availability_templates": [
{"day_of_week": 2, "start_time": time(14, 0), "end_time": time(17, 0)}, # Wednesday 2-5pm
{"day_of_week": 3, "start_time": time(16, 0), "end_time": time(18, 0)}, # Thursday 4-6pm
],
},
{
"role": "volunteer",
Expand All @@ -241,9 +268,15 @@ def seed_users(session: Session) -> None:
"date_of_diagnosis": date(2021, 3, 18), # Survivor
"has_blood_cancer": "yes",
"caring_for_someone": "no",
"timezone": "MST",
},
"treatments": [3, 6], # Chemotherapy, Radiation (matching Sarah's preferences)
"experiences": [1, 3, 4, 10], # Brain Fog, Feeling Overwhelmed, Fatigue, Anxiety/Depression
"volunteer_experience": "My cancer journey came with intense anxiety and depression alongside the physical symptoms. Through therapy and support groups, I learned to cope with the overwhelming emotions. I want to be that supportive voice for others facing the same battles.",
"availability_templates": [
{"day_of_week": 0, "start_time": time(10, 0), "end_time": time(12, 0)}, # Monday 10am-12pm
{"day_of_week": 4, "start_time": time(14, 30), "end_time": time(16, 30)}, # Friday 2:30-4:30pm
],
},
{
"role": "volunteer",
Expand All @@ -266,9 +299,16 @@ def seed_users(session: Session) -> None:
"date_of_diagnosis": date(2018, 9, 25), # Survivor
"has_blood_cancer": "yes",
"caring_for_someone": "no",
"timezone": "CST",
},
"treatments": [3, 6, 7], # Chemotherapy, Radiation, Maintenance Chemo
"experiences": [1, 4, 5], # Brain Fog, Feeling Overwhelmed, Fatigue (same as Sarah!)
"volunteer_experience": "Seven years ago, ALL changed everything. The fatigue was relentless, and brain fog made me feel like a stranger in my own mind. But I made it through, and now I want to offer hope and practical advice to anyone starting this journey.",
"availability_templates": [
{"day_of_week": 1, "start_time": time(15, 0), "end_time": time(17, 0)}, # Tuesday 3-5pm
{"day_of_week": 3, "start_time": time(10, 0), "end_time": time(12, 0)}, # Thursday 10am-12pm
{"day_of_week": 5, "start_time": time(14, 0), "end_time": time(16, 0)}, # Saturday 2-4pm
],
},
# Test Case 3: Participant who is a caregiver wanting caregiver volunteers
{
Expand Down Expand Up @@ -434,6 +474,22 @@ def seed_users(session: Session) -> None:
)
session.add(volunteer_data)

# Add availability templates for volunteers (if specified)
# Skip [email protected] so they can set their own availability
if (
user_info.get("availability_templates")
and user_info["user_data"]["email"] != "[email protected]"
):
for template_info in user_info["availability_templates"]:
availability_template = AvailabilityTemplate(
user_id=user.id,
day_of_week=template_info["day_of_week"],
start_time=template_info["start_time"],
end_time=template_info["end_time"],
is_active=True,
)
session.add(availability_template)

created_users.append((user, user_info["role"]))
print(f"Added {user_info['role']}: {user.first_name} {user.last_name}")

Expand Down
Loading