Skip to content

Commit e602270

Browse files
committed
adding frontend + public endpoint to add volunteer data
1 parent 2c2ca76 commit e602270

File tree

9 files changed

+566
-4
lines changed

9 files changed

+566
-4
lines changed

backend/app/models/VolunteerData.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,10 @@ class VolunteerData(Base):
1212
__tablename__ = "volunteer_data"
1313

1414
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)
15+
user_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
1616
experience = Column(Text, nullable=True)
1717
references_json = Column(Text, nullable=True)
1818
additional_comments = Column(Text, nullable=True)
1919
submitted_at = Column(DateTime, default=datetime.utcnow, nullable=False)
2020

21-
user = relationship("User", back_populates="volunteer_data")
22-
__table_args__ = (UniqueConstraint('user_id', name='uq_volunteer_data_user_id'),)
21+
user = relationship("User", back_populates="volunteer_data")

backend/app/routes/volunteer_data.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from app.schemas.volunteer_data import (
66
VolunteerDataCreateRequest,
77
VolunteerDataListResponse,
8+
VolunteerDataPublicSubmission,
89
VolunteerDataResponse,
910
VolunteerDataUpdateRequest,
1011
)
@@ -17,6 +18,27 @@
1718
)
1819

1920

21+
# Public endpoint - anyone can submit volunteer data
22+
@router.post("/submit", response_model=VolunteerDataResponse)
23+
async def submit_volunteer_data(
24+
volunteer_data: VolunteerDataPublicSubmission,
25+
volunteer_data_service: VolunteerDataService = Depends(get_volunteer_data_service),
26+
):
27+
"""Public endpoint for volunteers to submit their application data"""
28+
try:
29+
create_request = VolunteerDataCreateRequest(
30+
user_id=None,
31+
experience=volunteer_data.experience,
32+
references_json=volunteer_data.references_json,
33+
additional_comments=volunteer_data.additional_comments,
34+
)
35+
return await volunteer_data_service.create_volunteer_data(create_request)
36+
except HTTPException as http_ex:
37+
raise http_ex
38+
except Exception as e:
39+
raise HTTPException(status_code=500, detail=str(e))
40+
41+
2042
# Admin only - create volunteer data
2143
@router.post("/", response_model=VolunteerDataResponse)
2244
async def create_volunteer_data(

backend/app/schemas/volunteer_data.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,15 @@ class VolunteerDataCreateRequest(VolunteerDataBase):
2525
Request schema for creating volunteer data
2626
"""
2727

28-
user_id: UUID = Field(..., description="User ID this volunteer data belongs to")
28+
user_id: Optional[UUID] = Field(None, description="User ID this volunteer data belongs to (optional for public submissions)")
29+
30+
31+
class VolunteerDataPublicSubmission(VolunteerDataBase):
32+
"""
33+
Request schema for public volunteer data submissions (no user_id required)
34+
"""
35+
36+
pass
2937

3038

3139
class VolunteerDataUpdateRequest(BaseModel):
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""make_user_id_nullable_in_volunteer_data
2+
3+
Revision ID: e71f29bbfe31
4+
Revises: 6cfd431cb2e9
5+
Create Date: 2025-06-22 01:09:44.851319
6+
7+
"""
8+
from typing import Sequence, Union
9+
10+
# revision identifiers, used by Alembic.
11+
revision: str = 'e71f29bbfe31'
12+
down_revision: Union[str, None] = '6cfd431cb2e9'
13+
branch_labels: Union[str, Sequence[str], None] = None
14+
depends_on: Union[str, Sequence[str], None] = None
15+
16+
17+
def upgrade() -> None:
18+
pass
19+
20+
21+
def downgrade() -> None:
22+
pass

frontend/package-lock.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
},
1111
"dependencies": {
1212
"@chakra-ui/react": "^3.13.0",
13+
"@heroicons/react": "^2.2.0",
1314
"next": "^14.2.24",
1415
"next-themes": "^0.4.6",
1516
"react": "^18",
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import type { NextApiRequest, NextApiResponse } from 'next';
2+
3+
const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:8000';
4+
5+
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
6+
const { method } = req;
7+
8+
try {
9+
let url = `${BACKEND_URL}/volunteer-data`;
10+
let fetchOptions: RequestInit = {
11+
method,
12+
headers: {
13+
'Content-Type': 'application/json',
14+
// Forward authorization header if present
15+
...(req.headers.authorization && {
16+
'Authorization': req.headers.authorization,
17+
}),
18+
},
19+
};
20+
21+
// Handle different HTTP methods
22+
switch (method) {
23+
case 'POST':
24+
// Create volunteer data
25+
fetchOptions.body = JSON.stringify(req.body);
26+
break;
27+
28+
case 'GET':
29+
// Get volunteer data - handle query parameters
30+
if (req.query.id) {
31+
url = `${BACKEND_URL}/volunteer-data/${req.query.id}`;
32+
} else if (req.query.user_id) {
33+
url = `${BACKEND_URL}/volunteer-data/user/${req.query.user_id}`;
34+
}
35+
// If no specific query, it will get all volunteer data
36+
break;
37+
38+
case 'PUT':
39+
// Update volunteer data
40+
if (req.query.id) {
41+
url = `${BACKEND_URL}/volunteer-data/${req.query.id}`;
42+
fetchOptions.body = JSON.stringify(req.body);
43+
} else {
44+
return res.status(400).json({ error: 'ID required for PUT request' });
45+
}
46+
break;
47+
48+
case 'DELETE':
49+
// Delete volunteer data
50+
if (req.query.id) {
51+
url = `${BACKEND_URL}/volunteer-data/${req.query.id}`;
52+
} else {
53+
return res.status(400).json({ error: 'ID required for DELETE request' });
54+
}
55+
break;
56+
57+
default:
58+
return res.status(405).json({ error: `Method ${method} not allowed` });
59+
}
60+
61+
// Make request to FastAPI backend
62+
const response = await fetch(url, fetchOptions);
63+
const data = await response.json();
64+
65+
// Forward the response status and data
66+
res.status(response.status).json(data);
67+
68+
} catch (error) {
69+
console.error('API proxy error:', error);
70+
res.status(500).json({
71+
error: 'Internal server error',
72+
details: error instanceof Error ? error.message : 'Unknown error'
73+
});
74+
}
75+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import type { NextApiRequest, NextApiResponse } from 'next';
2+
3+
const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:8000';
4+
5+
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
6+
if (req.method !== 'POST') {
7+
return res.status(405).json({ error: 'Method not allowed' });
8+
}
9+
10+
try {
11+
const response = await fetch(`${BACKEND_URL}/volunteer-data/submit`, {
12+
method: 'POST',
13+
headers: {
14+
'Content-Type': 'application/json',
15+
},
16+
body: JSON.stringify(req.body),
17+
});
18+
19+
const data = await response.json();
20+
res.status(response.status).json(data);
21+
22+
} catch (error) {
23+
console.error('API proxy error:', error);
24+
res.status(500).json({
25+
error: 'Internal server error',
26+
details: error instanceof Error ? error.message : 'Unknown error'
27+
});
28+
}
29+
}

0 commit comments

Comments
 (0)