83 lines
3.1 KiB
Markdown
83 lines
3.1 KiB
Markdown
# TODO — Fix `GET /api/planned-days` 405 (planned-days router is incomplete)
|
||
|
||
**Status:** open · **Area:** backend · **Owner:** _unassigned_ · **Filed:** 2026-06-14
|
||
|
||
## Symptom
|
||
|
||
Loading the Dashboard and adding a day in the Calendar both log:
|
||
|
||
```
|
||
"GET /api/planned-days?child_id=3b9b6dad-... HTTP/1.1" 405 Method Not Allowed
|
||
```
|
||
|
||
Adding a day works (`POST /api/planned-days` → 201), but the follow-up refresh
|
||
(`GET /api/planned-days?child_id=…`) is rejected, so the calendar/dashboard never
|
||
show planned days.
|
||
|
||
## Root cause
|
||
|
||
`backend/app/routers/planned_days.py` defines `POST ""` but **no `GET ""`**. FastAPI
|
||
returns *405* (not 404) because the path exists for another verb. The list-by-child
|
||
endpoint was never finished — the `GET /range` handler (lines ~65–78) is a dead stub
|
||
(`pass` body, `= None` defaults on its `service`/`_current_user` deps) and `/range`
|
||
also collides with `GET /{planned_leave_day_id}`.
|
||
|
||
Same router has a second gap: `api.updatePlannedDay` issues `PUT /api/planned-days/{id}`
|
||
(used by Calendar edit, `frontend/src/pages/Calendar.tsx:99`), but there is **no PUT
|
||
handler** and no `PlanningService.update`. Editing an existing day will 405 once adding works.
|
||
|
||
## Fix
|
||
|
||
In `backend/app/services/planning_service.py`:
|
||
|
||
1. Add `list_by_child`:
|
||
```python
|
||
async def list_by_child(self, child_id: UUID) -> list[PlannedLeaveDay]:
|
||
return await self._repo.list(child_id=child_id)
|
||
```
|
||
(The SQLite `list()` already filters on `child_id` alone.)
|
||
2. Add `update(self, planned_leave_day_id: UUID, day_type: str | None, is_dubbeldag: bool | None) -> PlannedLeaveDay`
|
||
— load via `get_by_id` (raise `NotFoundError` if missing), apply provided fields, call `repo.update`.
|
||
|
||
In `backend/app/routers/planned_days.py`:
|
||
|
||
3. Add the list route **before** `GET /{planned_leave_day_id}`:
|
||
```python
|
||
@router.get("", response_model=list[PlannedLeaveDayResponse])
|
||
async def list_planned_days(
|
||
service: PlanningServiceDep,
|
||
_current_user: CurrentUserDep,
|
||
child_id: UUID = Query(...),
|
||
) -> list[PlannedLeaveDayResponse]:
|
||
return [_to_response(d) for d in await service.list_by_child(child_id)]
|
||
```
|
||
4. Delete the dead `GET /range` stub (lines ~65–78).
|
||
5. Add the `PUT /{planned_leave_day_id}` handler using the existing
|
||
`PlannedLeaveDayUpdate` model → `service.update(...)` → `_to_response`.
|
||
|
||
> Route ordering matters: declare `""`, `/workspace/...`, then `/{planned_leave_day_id}`
|
||
> last, so the UUID catch-all doesn't swallow the others.
|
||
|
||
## Verification
|
||
|
||
From `backend/`:
|
||
|
||
```bash
|
||
uv run ruff check . && uv run ruff format --check . && uv run pytest
|
||
```
|
||
|
||
Add tests:
|
||
- `GET /api/planned-days?child_id=…` → 200 with the seeded rows for that child.
|
||
- `PUT /api/planned-days/{id}` → 200, fields updated; unknown id → 404.
|
||
|
||
Manual: add a vacation day in the Calendar → `POST 201` then `GET 200` and the list
|
||
refreshes; edit an existing day → `PUT` succeeds.
|
||
|
||
After code changes: `graphify update .`
|
||
|
||
## Related
|
||
|
||
The 405 only blocks the *planned-days* list. The dashboard's empty **metrics /
|
||
parental-leave breakdown** is a separate snake_case↔camelCase issue —
|
||
see `DASHBOARD_EMPTY_DATA_TODO.md`.
|