Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
53 changes: 53 additions & 0 deletions backend/app/interfaces/volunteer_data_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""
Interface for volunteer data service operations.
Defines the contract for volunteer data CRUD operations.
"""

from abc import ABC, abstractmethod
from typing import List

from app.schemas.volunteer_data import (
VolunteerDataCreateRequest,
VolunteerDataResponse,
VolunteerDataUpdateRequest,
)


class IVolunteerDataService(ABC):
"""
Interface for volunteer data service operations
"""

@abstractmethod
async def create_volunteer_data(
self, volunteer_data: VolunteerDataCreateRequest
) -> VolunteerDataResponse:
"""Create new volunteer data entry"""
pass

@abstractmethod
async def get_volunteer_data_by_id(self, volunteer_data_id: str) -> VolunteerDataResponse:
"""Get volunteer data by ID"""
pass

@abstractmethod
async def get_volunteer_data_by_user_id(self, user_id: str) -> VolunteerDataResponse:
"""Get volunteer data by user ID"""
pass

@abstractmethod
async def get_all_volunteer_data(self) -> List[VolunteerDataResponse]:
"""Get all volunteer data entries"""
pass

@abstractmethod
async def update_volunteer_data_by_id(
self, volunteer_data_id: str, volunteer_data_update: VolunteerDataUpdateRequest
) -> VolunteerDataResponse:
"""Update volunteer data by ID"""
pass

@abstractmethod
async def delete_volunteer_data_by_id(self, volunteer_data_id: str) -> None:
"""Delete volunteer data by ID"""
pass
2 changes: 2 additions & 0 deletions backend/app/models/User.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,5 @@ class User(Base):
participant_matches = relationship("Match", back_populates="participant", foreign_keys=[Match.participant_id])

volunteer_matches = relationship("Match", back_populates="volunteer", foreign_keys=[Match.volunteer_id])

volunteer_data = relationship("VolunteerData", back_populates="user", uselist=False)
21 changes: 21 additions & 0 deletions backend/app/models/VolunteerData.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import uuid
from datetime import datetime

from sqlalchemy import Column, DateTime, ForeignKey, Text
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship

from .Base import Base


class VolunteerData(Base):
__tablename__ = "volunteer_data"

id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
user_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
experience = Column(Text, nullable=True)
references_json = Column(Text, nullable=True)
additional_comments = Column(Text, nullable=True)
submitted_at = Column(DateTime, default=datetime.utcnow, nullable=False)

user = relationship("User", back_populates="volunteer_data")
2 changes: 2 additions & 0 deletions backend/app/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from .Treatment import Treatment
from .User import FormStatus, User
from .UserData import UserData
from .VolunteerData import VolunteerData

# Used to avoid import errors for the models
__all__ = [
Expand All @@ -48,6 +49,7 @@
"TaskType",
"TaskPriority",
"TaskStatus",
"VolunteerData",
]

log = logging.getLogger(LOGGER_NAME("models"))
Expand Down
135 changes: 135 additions & 0 deletions backend/app/routes/volunteer_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
from fastapi import APIRouter, Depends, HTTPException

from app.middleware.auth import has_roles
from app.schemas.user import UserRole
from app.schemas.volunteer_data import (
VolunteerDataCreateRequest,
VolunteerDataListResponse,
VolunteerDataPublicSubmission,
VolunteerDataResponse,
VolunteerDataUpdateRequest,
)
from app.services.implementations.volunteer_data_service import VolunteerDataService
from app.utilities.service_utils import get_volunteer_data_service

router = APIRouter(
prefix="/volunteer-data",
tags=["volunteer-data"],
)


# Public endpoint - anyone can submit volunteer data
@router.post("/submit", response_model=VolunteerDataResponse)
async def submit_volunteer_data(
volunteer_data: VolunteerDataPublicSubmission,
volunteer_data_service: VolunteerDataService = Depends(get_volunteer_data_service),
):
"""Public endpoint for volunteers to submit their application data"""
try:
create_request = VolunteerDataCreateRequest(
user_id=None,
experience=volunteer_data.experience,
references_json=volunteer_data.references_json,
additional_comments=volunteer_data.additional_comments,
)
return await volunteer_data_service.create_volunteer_data(create_request)
except HTTPException as http_ex:
raise http_ex
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))


# Admin only - create volunteer data
@router.post("/", response_model=VolunteerDataResponse)
async def create_volunteer_data(
volunteer_data: VolunteerDataCreateRequest,
volunteer_data_service: VolunteerDataService = Depends(get_volunteer_data_service),
authorized: bool = has_roles([UserRole.ADMIN]),
):
try:
return await volunteer_data_service.create_volunteer_data(volunteer_data)
except HTTPException as http_ex:
raise http_ex
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))


# Admin only - get all volunteer data
@router.get("/", response_model=VolunteerDataListResponse)
async def get_all_volunteer_data(
volunteer_data_service: VolunteerDataService = Depends(get_volunteer_data_service),
authorized: bool = has_roles([UserRole.ADMIN]),
):
try:
volunteer_data_list = await volunteer_data_service.get_all_volunteer_data()
return VolunteerDataListResponse(
volunteer_data=volunteer_data_list, total=len(volunteer_data_list)
)
except HTTPException as http_ex:
raise http_ex
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))


# Admin only - get volunteer data by ID
@router.get("/{volunteer_data_id}", response_model=VolunteerDataResponse)
async def get_volunteer_data(
volunteer_data_id: str,
volunteer_data_service: VolunteerDataService = Depends(get_volunteer_data_service),
authorized: bool = has_roles([UserRole.ADMIN]),
):
try:
return await volunteer_data_service.get_volunteer_data_by_id(volunteer_data_id)
except HTTPException as http_ex:
raise http_ex
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))


# Admin only - get volunteer data by user ID
@router.get("/user/{user_id}", response_model=VolunteerDataResponse)
async def get_volunteer_data_by_user(
user_id: str,
volunteer_data_service: VolunteerDataService = Depends(get_volunteer_data_service),
authorized: bool = has_roles([UserRole.ADMIN]),
):
try:
return await volunteer_data_service.get_volunteer_data_by_user_id(user_id)
except HTTPException as http_ex:
raise http_ex
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))


# Admin only - update volunteer data
@router.put("/{volunteer_data_id}", response_model=VolunteerDataResponse)
async def update_volunteer_data(
volunteer_data_id: str,
volunteer_data_update: VolunteerDataUpdateRequest,
volunteer_data_service: VolunteerDataService = Depends(get_volunteer_data_service),
authorized: bool = has_roles([UserRole.ADMIN]),
):
try:
return await volunteer_data_service.update_volunteer_data_by_id(
volunteer_data_id, volunteer_data_update
)
except HTTPException as http_ex:
raise http_ex
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))


# Admin only - delete volunteer data
@router.delete("/{volunteer_data_id}")
async def delete_volunteer_data(
volunteer_data_id: str,
volunteer_data_service: VolunteerDataService = Depends(get_volunteer_data_service),
authorized: bool = has_roles([UserRole.ADMIN]),
):
try:
await volunteer_data_service.delete_volunteer_data_by_id(volunteer_data_id)
return {"message": "Volunteer data deleted successfully"}
except HTTPException as http_ex:
raise http_ex
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
73 changes: 73 additions & 0 deletions backend/app/schemas/volunteer_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""
Pydantic schemas for volunteer data validation and serialization.
Handles volunteer data CRUD and response models for the API.
"""

from datetime import datetime
from typing import List, Optional
from uuid import UUID

from pydantic import BaseModel, ConfigDict, Field


class VolunteerDataBase(BaseModel):
"""
Base schema for volunteer data model with common attributes.
"""

experience: Optional[str] = Field(None, description="Volunteer experience description")
references_json: Optional[str] = Field(None, description="JSON string containing references")
additional_comments: Optional[str] = Field(None, description="Additional comments about volunteering")


class VolunteerDataCreateRequest(VolunteerDataBase):
"""
Request schema for creating volunteer data
"""

user_id: Optional[UUID] = Field(
None,
description="User ID this volunteer data belongs to (optional for public submissions)"
)


class VolunteerDataPublicSubmission(VolunteerDataBase):
"""
Request schema for public volunteer data submissions (no user_id required)
"""

pass


class VolunteerDataUpdateRequest(BaseModel):
"""
Request schema for updating volunteer data, all fields optional
"""

experience: Optional[str] = Field(None, description="Volunteer experience description")
references_json: Optional[str] = Field(None, description="JSON string containing references")
additional_comments: Optional[str] = Field(None, description="Additional comments about volunteering")


class VolunteerDataResponse(BaseModel):
"""
Response schema for volunteer data
"""

id: UUID
user_id: UUID
experience: Optional[str]
references_json: Optional[str]
additional_comments: Optional[str]
submitted_at: datetime

model_config = ConfigDict(from_attributes=True)


class VolunteerDataListResponse(BaseModel):
"""
Response schema for listing volunteer data
"""

volunteer_data: List[VolunteerDataResponse]
total: int
3 changes: 2 additions & 1 deletion backend/app/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from . import models
from .middleware.auth_middleware import AuthMiddleware
from .routes import auth, availability, intake, match, matching, ranking, send_email, suggested_times, task, test, user
from .routes import auth, availability, intake, match, matching, ranking, send_email, suggested_times, task, test, user, volunteer_data
from .utilities.constants import LOGGER_NAME
from .utilities.firebase_init import initialize_firebase
from .utilities.ses.ses_init import ensure_ses_templates
Expand Down Expand Up @@ -71,6 +71,7 @@ async def lifespan(_: FastAPI):
app.include_router(matching.router)
app.include_router(intake.router)
app.include_router(ranking.router)
app.include_router(volunteer_data.router)
app.include_router(send_email.router)
app.include_router(task.router)
app.include_router(test.router)
Expand Down
Loading
Loading