Context
Linked to need4deed-org/fe#577 — activity time tracking per active volunteer↔opportunity match.
What's needed
New entity: ActivityLog
Tied to the volunteer↔opportunity match (the join entity that holds the status). Fields:
| Field |
Type |
Notes |
| id |
number |
PK |
| date |
Date |
date of the volunteering session |
| hours |
decimal |
duration in hours, e.g. 2.5 |
| opportunityId |
number |
FK |
| volunteerId |
number |
FK |
| agentId |
number |
FK — scope to the agent (for permission checks) |
| createdAt |
timestamp |
|
| updatedAt |
timestamp |
|
The combination of opportunityId + volunteerId identifies the match. Add a unique constraint or index on (opportunityId, volunteerId) pair if not already present on the join entity.
Endpoints
| Method |
Path |
Auth |
Description |
| GET |
/opportunity/:opportunityId/volunteer/:volunteerId/activity-log |
COORDINATOR, NGO, ADMIN |
List all entries for this match |
| POST |
/opportunity/:opportunityId/volunteer/:volunteerId/activity-log |
COORDINATOR, NGO, ADMIN |
Create new entry |
| PATCH |
/activity-log/:id |
COORDINATOR, NGO, ADMIN |
Update date or hours |
| DELETE |
/activity-log/:id |
COORDINATOR, NGO, ADMIN |
Delete entry |
Response shape (GET list)
{
"data": [
{ "id": 1, "date": "2026-05-10", "hours": 3 },
{ "id": 2, "date": "2026-05-17", "hours": 2.5 }
],
"totalHours": 5.5,
"count": 2
}
Include `totalHours` (sum) in the list response — FE displays it as a footer.
POST / PATCH body
{ "date": "2026-05-10", "hours": 3 }
Permissions
- COORDINATOR and NGO roles: full CRUD, scoped to their agent
- ADMIN: full CRUD
- Volunteer/public: no access
SDK
Add `ApiActivityLogGet`, `ApiActivityLogPost`, `ApiActivityLogPatch` types to the SDK.
Future note
The total hours will eventually drive the Appreciation tier (t-shirt / bag / certificate). No logic needed now but the `totalHours` field in the response is the hook for it.
Tasks
Context
Linked to need4deed-org/fe#577 — activity time tracking per active volunteer↔opportunity match.
What's needed
New entity: ActivityLog
Tied to the volunteer↔opportunity match (the join entity that holds the status). Fields:
The combination of opportunityId + volunteerId identifies the match. Add a unique constraint or index on (opportunityId, volunteerId) pair if not already present on the join entity.
Endpoints
Response shape (GET list)
{ "data": [ { "id": 1, "date": "2026-05-10", "hours": 3 }, { "id": 2, "date": "2026-05-17", "hours": 2.5 } ], "totalHours": 5.5, "count": 2 }Include `totalHours` (sum) in the list response — FE displays it as a footer.
POST / PATCH body
{ "date": "2026-05-10", "hours": 3 }Permissions
SDK
Add `ApiActivityLogGet`, `ApiActivityLogPost`, `ApiActivityLogPatch` types to the SDK.
Future note
The total hours will eventually drive the Appreciation tier (t-shirt / bag / certificate). No logic needed now but the `totalHours` field in the response is the hook for it.
Tasks