Skip to content

Commit 2c2ca76

Browse files
committed
add voluntter data table
1 parent 9b6e674 commit 2c2ca76

File tree

10 files changed

+457
-1
lines changed

10 files changed

+457
-1
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""
2+
Interface for volunteer data service operations.
3+
Defines the contract for volunteer data CRUD operations.
4+
"""
5+
6+
from abc import ABC, abstractmethod
7+
from typing import List
8+
9+
from app.schemas.volunteer_data import (
10+
VolunteerDataCreateRequest,
11+
VolunteerDataResponse,
12+
VolunteerDataUpdateRequest,
13+
)
14+
15+
16+
class IVolunteerDataService(ABC):
17+
"""
18+
Interface for volunteer data service operations
19+
"""
20+
21+
@abstractmethod
22+
async def create_volunteer_data(
23+
self, volunteer_data: VolunteerDataCreateRequest
24+
) -> VolunteerDataResponse:
25+
"""Create new volunteer data entry"""
26+
pass
27+
28+
@abstractmethod
29+
async def get_volunteer_data_by_id(self, volunteer_data_id: str) -> VolunteerDataResponse:
30+
"""Get volunteer data by ID"""
31+
pass
32+
33+
@abstractmethod
34+
async def get_volunteer_data_by_user_id(self, user_id: str) -> VolunteerDataResponse:
35+
"""Get volunteer data by user ID"""
36+
pass
37+
38+
@abstractmethod
39+
async def get_all_volunteer_data(self) -> List[VolunteerDataResponse]:
40+
"""Get all volunteer data entries"""
41+
pass
42+
43+
@abstractmethod
44+
async def update_volunteer_data_by_id(
45+
self, volunteer_data_id: str, volunteer_data_update: VolunteerDataUpdateRequest
46+
) -> VolunteerDataResponse:
47+
"""Update volunteer data by ID"""
48+
pass
49+
50+
@abstractmethod
51+
async def delete_volunteer_data_by_id(self, volunteer_data_id: str) -> None:
52+
"""Delete volunteer data by ID"""
53+
pass

backend/app/models/User.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,5 @@ class User(Base):
2626
participant_matches = relationship("Match", back_populates="participant", foreign_keys=[Match.participant_id])
2727

2828
volunteer_matches = relationship("Match", back_populates="volunteer", foreign_keys=[Match.volunteer_id])
29+
30+
volunteer_data = relationship("VolunteerData", back_populates="user", uselist=False)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import uuid
2+
from datetime import datetime
3+
4+
from sqlalchemy import Column, DateTime, ForeignKey, String, Text, UniqueConstraint
5+
from sqlalchemy.dialects.postgresql import UUID
6+
from sqlalchemy.orm import relationship
7+
8+
from .Base import Base
9+
10+
11+
class VolunteerData(Base):
12+
__tablename__ = "volunteer_data"
13+
14+
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
15+
user_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), unique=True, nullable=False)
16+
experience = Column(Text, nullable=True)
17+
references_json = Column(Text, nullable=True)
18+
additional_comments = Column(Text, nullable=True)
19+
submitted_at = Column(DateTime, default=datetime.utcnow, nullable=False)
20+
21+
user = relationship("User", back_populates="volunteer_data")
22+
__table_args__ = (UniqueConstraint('user_id', name='uq_volunteer_data_user_id'),)

backend/app/models/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from .SuggestedTime import suggested_times
1717
from .TimeBlock import TimeBlock
1818
from .User import User
19+
from .VolunteerData import VolunteerData
1920

2021
# Used to avoid import errors for the models
2122
__all__ = [
@@ -28,6 +29,7 @@
2829
"User",
2930
"available_times",
3031
"suggested_times",
32+
"VolunteerData",
3133
]
3234

3335
log = logging.getLogger(LOGGER_NAME("models"))
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
from fastapi import APIRouter, Depends, HTTPException
2+
3+
from app.middleware.auth import has_roles
4+
from app.schemas.user import UserRole
5+
from app.schemas.volunteer_data import (
6+
VolunteerDataCreateRequest,
7+
VolunteerDataListResponse,
8+
VolunteerDataResponse,
9+
VolunteerDataUpdateRequest,
10+
)
11+
from app.services.implementations.volunteer_data_service import VolunteerDataService
12+
from app.utilities.service_utils import get_volunteer_data_service
13+
14+
router = APIRouter(
15+
prefix="/volunteer-data",
16+
tags=["volunteer-data"],
17+
)
18+
19+
20+
# Admin only - create volunteer data
21+
@router.post("/", response_model=VolunteerDataResponse)
22+
async def create_volunteer_data(
23+
volunteer_data: VolunteerDataCreateRequest,
24+
volunteer_data_service: VolunteerDataService = Depends(get_volunteer_data_service),
25+
authorized: bool = has_roles([UserRole.ADMIN]),
26+
):
27+
try:
28+
return await volunteer_data_service.create_volunteer_data(volunteer_data)
29+
except HTTPException as http_ex:
30+
raise http_ex
31+
except Exception as e:
32+
raise HTTPException(status_code=500, detail=str(e))
33+
34+
35+
# Admin only - get all volunteer data
36+
@router.get("/", response_model=VolunteerDataListResponse)
37+
async def get_all_volunteer_data(
38+
volunteer_data_service: VolunteerDataService = Depends(get_volunteer_data_service),
39+
authorized: bool = has_roles([UserRole.ADMIN]),
40+
):
41+
try:
42+
volunteer_data_list = await volunteer_data_service.get_all_volunteer_data()
43+
return VolunteerDataListResponse(
44+
volunteer_data=volunteer_data_list, total=len(volunteer_data_list)
45+
)
46+
except HTTPException as http_ex:
47+
raise http_ex
48+
except Exception as e:
49+
raise HTTPException(status_code=500, detail=str(e))
50+
51+
52+
# Admin only - get volunteer data by ID
53+
@router.get("/{volunteer_data_id}", response_model=VolunteerDataResponse)
54+
async def get_volunteer_data(
55+
volunteer_data_id: str,
56+
volunteer_data_service: VolunteerDataService = Depends(get_volunteer_data_service),
57+
authorized: bool = has_roles([UserRole.ADMIN]),
58+
):
59+
try:
60+
return await volunteer_data_service.get_volunteer_data_by_id(volunteer_data_id)
61+
except HTTPException as http_ex:
62+
raise http_ex
63+
except Exception as e:
64+
raise HTTPException(status_code=500, detail=str(e))
65+
66+
67+
# Admin only - get volunteer data by user ID
68+
@router.get("/user/{user_id}", response_model=VolunteerDataResponse)
69+
async def get_volunteer_data_by_user(
70+
user_id: str,
71+
volunteer_data_service: VolunteerDataService = Depends(get_volunteer_data_service),
72+
authorized: bool = has_roles([UserRole.ADMIN]),
73+
):
74+
try:
75+
return await volunteer_data_service.get_volunteer_data_by_user_id(user_id)
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+
# Admin only - update volunteer data
83+
@router.put("/{volunteer_data_id}", response_model=VolunteerDataResponse)
84+
async def update_volunteer_data(
85+
volunteer_data_id: str,
86+
volunteer_data_update: VolunteerDataUpdateRequest,
87+
volunteer_data_service: VolunteerDataService = Depends(get_volunteer_data_service),
88+
authorized: bool = has_roles([UserRole.ADMIN]),
89+
):
90+
try:
91+
return await volunteer_data_service.update_volunteer_data_by_id(
92+
volunteer_data_id, volunteer_data_update
93+
)
94+
except HTTPException as http_ex:
95+
raise http_ex
96+
except Exception as e:
97+
raise HTTPException(status_code=500, detail=str(e))
98+
99+
100+
# Admin only - delete volunteer data
101+
@router.delete("/{volunteer_data_id}")
102+
async def delete_volunteer_data(
103+
volunteer_data_id: str,
104+
volunteer_data_service: VolunteerDataService = Depends(get_volunteer_data_service),
105+
authorized: bool = has_roles([UserRole.ADMIN]),
106+
):
107+
try:
108+
await volunteer_data_service.delete_volunteer_data_by_id(volunteer_data_id)
109+
return {"message": "Volunteer data deleted successfully"}
110+
except HTTPException as http_ex:
111+
raise http_ex
112+
except Exception as e:
113+
raise HTTPException(status_code=500, detail=str(e))
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
"""
2+
Pydantic schemas for volunteer data validation and serialization.
3+
Handles volunteer data CRUD and response models for the API.
4+
"""
5+
6+
from datetime import datetime
7+
from typing import List, Optional
8+
from uuid import UUID
9+
10+
from pydantic import BaseModel, ConfigDict, Field
11+
12+
13+
class VolunteerDataBase(BaseModel):
14+
"""
15+
Base schema for volunteer data model with common attributes.
16+
"""
17+
18+
experience: Optional[str] = Field(None, description="Volunteer experience description")
19+
references_json: Optional[str] = Field(None, description="JSON string containing references")
20+
additional_comments: Optional[str] = Field(None, description="Additional comments about volunteering")
21+
22+
23+
class VolunteerDataCreateRequest(VolunteerDataBase):
24+
"""
25+
Request schema for creating volunteer data
26+
"""
27+
28+
user_id: UUID = Field(..., description="User ID this volunteer data belongs to")
29+
30+
31+
class VolunteerDataUpdateRequest(BaseModel):
32+
"""
33+
Request schema for updating volunteer data, all fields optional
34+
"""
35+
36+
experience: Optional[str] = Field(None, description="Volunteer experience description")
37+
references_json: Optional[str] = Field(None, description="JSON string containing references")
38+
additional_comments: Optional[str] = Field(None, description="Additional comments about volunteering")
39+
40+
41+
class VolunteerDataResponse(BaseModel):
42+
"""
43+
Response schema for volunteer data
44+
"""
45+
46+
id: UUID
47+
user_id: UUID
48+
experience: Optional[str]
49+
references_json: Optional[str]
50+
additional_comments: Optional[str]
51+
submitted_at: datetime
52+
53+
model_config = ConfigDict(from_attributes=True)
54+
55+
56+
class VolunteerDataListResponse(BaseModel):
57+
"""
58+
Response schema for listing volunteer data
59+
"""
60+
61+
volunteer_data: List[VolunteerDataResponse]
62+
total: int

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, availability, match, send_email, suggested_times, test, user
11+
from .routes import auth, availability, match, send_email, suggested_times, test, user, volunteer_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
@@ -64,6 +64,7 @@ async def lifespan(_: FastAPI):
6464
app.include_router(availability.router)
6565
app.include_router(suggested_times.router)
6666
app.include_router(match.router)
67+
app.include_router(volunteer_data.router)
6768
app.include_router(send_email.router)
6869
app.include_router(test.router)
6970

0 commit comments

Comments
 (0)