Add update (CRUD) for events and people; record the full-CRUD invariant
Events and people are now editable, not write-once: PATCH /events/{id} (type, structured date, place, notes) and PATCH /persons/{id} (vitals, privacy, and the primary name's given/surname). CLAUDE.md gains rule #8: every stored object must support full CRUD in API and UI — historical research is constant correction. Tests cover both updates.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Justin Paul <justin@jpaul.me>
This commit is contained in:
@@ -3,7 +3,7 @@ import uuid
|
||||
from fastapi import APIRouter, status
|
||||
|
||||
from app.api.deps import CurrentUser, SessionDep
|
||||
from app.schemas.event import EventCreate, EventRead
|
||||
from app.schemas.event import EventCreate, EventRead, EventUpdate
|
||||
from app.services import event_service, tree_service
|
||||
|
||||
router = APIRouter(prefix="/trees", tags=["events"])
|
||||
@@ -40,6 +40,25 @@ async def list_person_events(
|
||||
return [EventRead.model_validate(e) for e in events]
|
||||
|
||||
|
||||
@router.patch("/{tree_id}/events/{event_id}", response_model=EventRead)
|
||||
async def update_event(
|
||||
tree_id: uuid.UUID,
|
||||
event_id: uuid.UUID,
|
||||
data: EventUpdate,
|
||||
session: SessionDep,
|
||||
current: CurrentUser,
|
||||
) -> EventRead:
|
||||
tree = await tree_service.get_tree(session, viewer_id=current.id, tree_id=tree_id)
|
||||
event = await event_service.update_event(
|
||||
session,
|
||||
actor=current,
|
||||
tree=tree,
|
||||
event_id=event_id,
|
||||
changes=data.model_dump(exclude_unset=True),
|
||||
)
|
||||
return EventRead.model_validate(event)
|
||||
|
||||
|
||||
@router.delete("/{tree_id}/events/{event_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_event(
|
||||
tree_id: uuid.UUID, event_id: uuid.UUID, session: SessionDep, current: CurrentUser
|
||||
|
||||
@@ -3,7 +3,7 @@ import uuid
|
||||
from fastapi import APIRouter, status
|
||||
|
||||
from app.api.deps import CurrentUser, SessionDep
|
||||
from app.schemas.person import PersonCreate, PersonRead
|
||||
from app.schemas.person import PersonCreate, PersonRead, PersonUpdate
|
||||
from app.services import person_service, tree_service
|
||||
|
||||
# Persons are nested under their tree (the tenant boundary).
|
||||
@@ -56,6 +56,25 @@ async def list_persons(
|
||||
return [PersonRead.model_validate(p) for p in persons]
|
||||
|
||||
|
||||
@router.patch("/{tree_id}/persons/{person_id}", response_model=PersonRead)
|
||||
async def update_person(
|
||||
tree_id: uuid.UUID,
|
||||
person_id: uuid.UUID,
|
||||
data: PersonUpdate,
|
||||
session: SessionDep,
|
||||
current: CurrentUser,
|
||||
) -> PersonRead:
|
||||
tree = await tree_service.get_tree(session, viewer_id=current.id, tree_id=tree_id)
|
||||
person = await person_service.update_person(
|
||||
session,
|
||||
actor=current,
|
||||
tree=tree,
|
||||
person_id=person_id,
|
||||
changes=data.model_dump(exclude_unset=True),
|
||||
)
|
||||
return PersonRead.model_validate(person)
|
||||
|
||||
|
||||
@router.delete("/{tree_id}/persons/{person_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_person(
|
||||
tree_id: uuid.UUID, person_id: uuid.UUID, session: SessionDep, current: CurrentUser
|
||||
|
||||
Reference in New Issue
Block a user