Skip to content

Commit b40620a

Browse files
committed
Add UserData model and service integration
- Updated server.py to include user_data routes. - Modified models/__init__.py to export UserData model. - Enhanced User model to establish a relationship with UserData. - Introduced user_data_service for handling UserData operations. This commit integrates UserData functionality into the backend, allowing for better user data management.
1 parent 3000fc2 commit b40620a

File tree

10 files changed

+486
-2
lines changed

10 files changed

+486
-2
lines changed
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
from abc import ABC, abstractmethod
2+
from uuid import UUID
3+
4+
from app.schemas.user_data import UserDataCreateRequest, UserDataUpdateRequest
5+
6+
7+
class IUserDataService(ABC):
8+
"""
9+
UserDataService interface with user data management methods
10+
"""
11+
12+
@abstractmethod
13+
def get_user_data_by_id(self, user_data_id: UUID):
14+
"""
15+
Get user data by its ID
16+
17+
:param user_data_id: user data's id
18+
:type user_data_id: UUID
19+
:return: a UserDataResponse with user data information
20+
:rtype: UserDataResponse
21+
:raises Exception: if user data retrieval fails
22+
"""
23+
pass
24+
25+
@abstractmethod
26+
def get_user_data_by_user_id(self, user_id: UUID):
27+
"""
28+
Get user data associated with a user ID
29+
30+
:param user_id: user's id
31+
:type user_id: UUID
32+
:return: a UserDataResponse with user data information
33+
:rtype: UserDataResponse
34+
:raises Exception: if user data retrieval fails
35+
"""
36+
pass
37+
38+
@abstractmethod
39+
def create_user_data(self, user_data: UserDataCreateRequest):
40+
"""
41+
Create user data for a user
42+
43+
:param user_data: the user data to be created
44+
:type user_data: UserDataCreateRequest
45+
:return: the created user data
46+
:rtype: UserDataResponse
47+
:raises Exception: if user data creation fails
48+
"""
49+
pass
50+
51+
@abstractmethod
52+
def update_user_data_by_user_id(self, user_id: UUID, user_data: UserDataUpdateRequest):
53+
"""
54+
Update user data for a user
55+
56+
:param user_id: user's id
57+
:type user_id: UUID
58+
:param user_data: the user data to be updated
59+
:type user_data: UserDataUpdateRequest
60+
:return: the updated user data
61+
:rtype: UserDataResponse
62+
:raises Exception: if user data update fails
63+
"""
64+
pass
65+
66+
@abstractmethod
67+
def delete_user_data_by_id(self, user_data_id: UUID):
68+
"""
69+
Delete user data by its ID
70+
71+
:param user_data_id: user data's id
72+
:type user_data_id: UUID
73+
:raises Exception: if user data deletion fails
74+
"""
75+
pass
76+
77+
@abstractmethod
78+
def delete_user_data_by_user_id(self, user_id: UUID):
79+
"""
80+
Delete user data by user ID
81+
82+
:param user_id: user's id
83+
:type user_id: UUID
84+
:raises Exception: if user data deletion fails
85+
"""
86+
pass

backend/app/models/User.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ class User(Base):
1717
auth_id = Column(String, nullable=False)
1818

1919
role = relationship("Role")
20+
user_data = relationship("UserData", back_populates="user", uselist=False)

backend/app/models/UserData.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import uuid
2+
3+
from sqlalchemy import Column, ForeignKey, Integer, String, Date, Boolean, Text
4+
from sqlalchemy.dialects.postgresql import UUID
5+
from sqlalchemy.orm import relationship
6+
7+
from .Base import Base
8+
9+
10+
class UserData(Base):
11+
__tablename__ = "user_data"
12+
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
13+
user_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False, unique=True)
14+
date_of_birth = Column(Date, nullable=True)
15+
email = Column(String(120), nullable=True)
16+
phone = Column(String(20), nullable=True)
17+
postal_code = Column(String(10), nullable=True)
18+
province = Column(String(50), nullable=True)
19+
city = Column(String(100), nullable=True)
20+
language = Column(String(50), nullable=True)
21+
criminal_record_status = Column(Boolean, nullable=True)
22+
blood_cancer_status = Column(Boolean, nullable=True)
23+
caregiver_status = Column(Boolean, nullable=True)
24+
caregiver_type = Column(String(100), nullable=True)
25+
diagnostic = Column(String(200), nullable=True)
26+
date_of_diagnosis = Column(Date, nullable=True)
27+
gender_identity = Column(String(50), nullable=True)
28+
pronouns = Column(String(50), nullable=True)
29+
ethnicity = Column(String(100), nullable=True)
30+
marital_status = Column(String(50), nullable=True)
31+
children_status = Column(Boolean, nullable=True)
32+
treatment = Column(Text, nullable=True)
33+
experience = Column(Text, nullable=True)
34+
preferences = Column(Text, nullable=True)
35+
36+
# Relationship to User table
37+
user = relationship("User", back_populates="user_data")

backend/app/models/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@
1010
from .Base import Base
1111
from .Role import Role
1212
from .User import User
13+
from .UserData import UserData
1314

1415
# Used to avoid import errors for the models
15-
__all__ = ["Base", "User", "Role"]
16+
__all__ = ["Base", "User", "Role", "UserData"]
1617

1718
log = logging.getLogger(LOGGER_NAME("models"))
1819

backend/app/routes/user_data.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
from uuid import UUID
2+
3+
from fastapi import APIRouter, Depends, HTTPException
4+
5+
from app.middleware.auth import has_roles
6+
from app.schemas.user import UserRole
7+
from app.schemas.user_data import (
8+
UserDataCreateRequest,
9+
UserDataResponse,
10+
UserDataUpdateRequest,
11+
)
12+
from app.services.implementations.user_data_service import UserDataService
13+
from app.utilities.service_utils import get_user_data_service
14+
15+
router = APIRouter(
16+
prefix="/user-data",
17+
tags=["user-data"],
18+
)
19+
20+
21+
@router.post("/", response_model=UserDataResponse)
22+
async def create_user_data(
23+
user_data: UserDataCreateRequest,
24+
user_data_service: UserDataService = Depends(get_user_data_service),
25+
authorized: bool = has_roles([UserRole.ADMIN, UserRole.PARTICIPANT, UserRole.VOLUNTEER]),
26+
):
27+
"""Create user data for intake form"""
28+
try:
29+
return user_data_service.create_user_data(user_data)
30+
except HTTPException as http_ex:
31+
raise http_ex
32+
except Exception as e:
33+
raise HTTPException(status_code=500, detail=str(e))
34+
35+
36+
@router.get("/user/{user_id}", response_model=UserDataResponse)
37+
async def get_user_data_by_user_id(
38+
user_id: UUID,
39+
user_data_service: UserDataService = Depends(get_user_data_service),
40+
authorized: bool = has_roles([UserRole.ADMIN, UserRole.PARTICIPANT, UserRole.VOLUNTEER]),
41+
):
42+
"""Get user data by user ID"""
43+
try:
44+
return user_data_service.get_user_data_by_user_id(user_id)
45+
except HTTPException as http_ex:
46+
raise http_ex
47+
except Exception as e:
48+
raise HTTPException(status_code=500, detail=str(e))
49+
50+
51+
@router.get("/{user_data_id}", response_model=UserDataResponse)
52+
async def get_user_data_by_id(
53+
user_data_id: UUID,
54+
user_data_service: UserDataService = Depends(get_user_data_service),
55+
authorized: bool = has_roles([UserRole.ADMIN]),
56+
):
57+
"""Get user data by its ID"""
58+
try:
59+
return user_data_service.get_user_data_by_id(user_data_id)
60+
except HTTPException as http_ex:
61+
raise http_ex
62+
except Exception as e:
63+
raise HTTPException(status_code=500, detail=str(e))
64+
65+
66+
@router.put("/user/{user_id}", response_model=UserDataResponse)
67+
async def update_user_data_by_user_id(
68+
user_id: UUID,
69+
user_data: UserDataUpdateRequest,
70+
user_data_service: UserDataService = Depends(get_user_data_service),
71+
authorized: bool = has_roles([UserRole.ADMIN, UserRole.PARTICIPANT, UserRole.VOLUNTEER]),
72+
):
73+
"""Update user data by user ID"""
74+
try:
75+
return user_data_service.update_user_data_by_user_id(user_id, user_data)
76+
except HTTPException as http_ex:
77+
raise http_ex
78+
except Exception as e:
79+
raise HTTPException(status_code=500, detail=str(e))
80+
81+
82+
@router.delete("/{user_data_id}")
83+
async def delete_user_data_by_id(
84+
user_data_id: UUID,
85+
user_data_service: UserDataService = Depends(get_user_data_service),
86+
authorized: bool = has_roles([UserRole.ADMIN]),
87+
):
88+
"""Delete user data by its ID"""
89+
try:
90+
user_data_service.delete_user_data_by_id(user_data_id)
91+
return {"message": f"User data {user_data_id} deleted successfully"}
92+
except HTTPException as http_ex:
93+
raise http_ex
94+
except Exception as e:
95+
raise HTTPException(status_code=500, detail=str(e))
96+
97+
98+
@router.delete("/user/{user_id}")
99+
async def delete_user_data_by_user_id(
100+
user_id: UUID,
101+
user_data_service: UserDataService = Depends(get_user_data_service),
102+
authorized: bool = has_roles([UserRole.ADMIN]),
103+
):
104+
"""Delete user data by user ID"""
105+
try:
106+
user_data_service.delete_user_data_by_user_id(user_id)
107+
return {"message": f"User data for user {user_id} deleted successfully"}
108+
except HTTPException as http_ex:
109+
raise http_ex
110+
except Exception as e:
111+
raise HTTPException(status_code=500, detail=str(e))

backend/app/schemas/user_data.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"""
2+
Pydantic schemas for user data validation and serialization.
3+
Handles user data CRUD and response models for the API.
4+
"""
5+
6+
from datetime import date
7+
from typing import Optional
8+
from uuid import UUID
9+
10+
from pydantic import BaseModel, ConfigDict, EmailStr, Field
11+
12+
13+
class UserDataBase(BaseModel):
14+
"""
15+
Base schema for user data with common attributes shared across schemas.
16+
"""
17+
18+
date_of_birth: Optional[date] = None
19+
email: Optional[EmailStr] = None
20+
phone: Optional[str] = Field(None, max_length=20)
21+
postal_code: Optional[str] = Field(None, max_length=10)
22+
province: Optional[str] = Field(None, max_length=50)
23+
city: Optional[str] = Field(None, max_length=100)
24+
language: Optional[str] = Field(None, max_length=50)
25+
criminal_record_status: Optional[bool] = None
26+
blood_cancer_status: Optional[bool] = None
27+
caregiver_status: Optional[bool] = None
28+
caregiver_type: Optional[str] = Field(None, max_length=100)
29+
diagnostic: Optional[str] = Field(None, max_length=200)
30+
date_of_diagnosis: Optional[date] = None
31+
gender_identity: Optional[str] = Field(None, max_length=50)
32+
pronouns: Optional[str] = Field(None, max_length=50)
33+
ethnicity: Optional[str] = Field(None, max_length=100)
34+
marital_status: Optional[str] = Field(None, max_length=50)
35+
children_status: Optional[bool] = None
36+
treatment: Optional[str] = None
37+
experience: Optional[str] = None
38+
preferences: Optional[str] = None
39+
40+
41+
class UserDataCreateRequest(UserDataBase):
42+
"""
43+
Request schema for user data creation.
44+
"""
45+
46+
user_id: UUID
47+
48+
49+
class UserDataUpdateRequest(UserDataBase):
50+
"""
51+
Request schema for user data update.
52+
All fields are optional for partial updates.
53+
"""
54+
55+
pass
56+
57+
58+
class UserDataResponse(UserDataBase):
59+
"""
60+
Response schema for user data, maps directly from ORM UserData object.
61+
"""
62+
63+
id: UUID
64+
user_id: UUID
65+
66+
# from_attributes enables automatic mapping from SQLAlchemy model to Pydantic model
67+
model_config = ConfigDict(from_attributes=True)

backend/app/server.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from . import models
1010
from .middleware.auth_middleware import AuthMiddleware
11-
from .routes import auth, send_email, test, user
11+
from .routes import auth, send_email, test, user, user_data
1212
from .utilities.constants import LOGGER_NAME
1313
from .utilities.firebase_init import initialize_firebase
1414
from .utilities.ses.ses_init import ensure_ses_templates
@@ -61,6 +61,7 @@ async def lifespan(_: FastAPI):
6161
app.add_middleware(AuthMiddleware, public_paths=PUBLIC_PATHS)
6262
app.include_router(auth.router)
6363
app.include_router(user.router)
64+
app.include_router(user_data.router)
6465
app.include_router(send_email.router)
6566
app.include_router(test.router)
6667

0 commit comments

Comments
 (0)