Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cf5518c7ec | |||
| 26df03cfd7 |
@@ -3,7 +3,7 @@ import uuid
|
|||||||
from fastapi import APIRouter, status
|
from fastapi import APIRouter, status
|
||||||
|
|
||||||
from app.api.deps import CurrentUser, SessionDep
|
from app.api.deps import CurrentUser, SessionDep
|
||||||
from app.schemas.source import CitationCreate, CitationRead
|
from app.schemas.source import CitationCreate, CitationRead, CitationUpdate
|
||||||
from app.services import citation_service, tree_service
|
from app.services import citation_service, tree_service
|
||||||
|
|
||||||
router = APIRouter(prefix="/trees", tags=["citations"])
|
router = APIRouter(prefix="/trees", tags=["citations"])
|
||||||
@@ -31,6 +31,25 @@ async def list_citations(
|
|||||||
return [CitationRead.model_validate(c) for c in citations]
|
return [CitationRead.model_validate(c) for c in citations]
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch("/{tree_id}/citations/{citation_id}", response_model=CitationRead)
|
||||||
|
async def update_citation(
|
||||||
|
tree_id: uuid.UUID,
|
||||||
|
citation_id: uuid.UUID,
|
||||||
|
data: CitationUpdate,
|
||||||
|
session: SessionDep,
|
||||||
|
current: CurrentUser,
|
||||||
|
) -> CitationRead:
|
||||||
|
tree = await tree_service.get_tree(session, viewer_id=current.id, tree_id=tree_id)
|
||||||
|
citation = await citation_service.update_citation(
|
||||||
|
session,
|
||||||
|
actor=current,
|
||||||
|
tree=tree,
|
||||||
|
citation_id=citation_id,
|
||||||
|
changes=data.model_dump(exclude_unset=True),
|
||||||
|
)
|
||||||
|
return CitationRead.model_validate(citation)
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{tree_id}/citations/{citation_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/{tree_id}/citations/{citation_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_citation(
|
async def delete_citation(
|
||||||
tree_id: uuid.UUID, citation_id: uuid.UUID, session: SessionDep, current: CurrentUser
|
tree_id: uuid.UUID, citation_id: uuid.UUID, session: SessionDep, current: CurrentUser
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import uuid
|
|||||||
from fastapi import APIRouter, File, Form, Response, UploadFile, status
|
from fastapi import APIRouter, File, Form, Response, UploadFile, status
|
||||||
|
|
||||||
from app.api.deps import CurrentUser, ObjectStoreDep, SessionDep
|
from app.api.deps import CurrentUser, ObjectStoreDep, SessionDep
|
||||||
from app.schemas.media import MediaRead
|
from app.schemas.media import MediaRead, MediaUpdate
|
||||||
from app.services import media_service, tree_service
|
from app.services import media_service, tree_service
|
||||||
|
|
||||||
|
|
||||||
@@ -81,6 +81,26 @@ async def media_content(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch("/{tree_id}/media/{media_id}", response_model=MediaRead)
|
||||||
|
async def update_media(
|
||||||
|
tree_id: uuid.UUID,
|
||||||
|
media_id: uuid.UUID,
|
||||||
|
data: MediaUpdate,
|
||||||
|
session: SessionDep,
|
||||||
|
current: CurrentUser,
|
||||||
|
store: ObjectStoreDep,
|
||||||
|
) -> MediaRead:
|
||||||
|
tree = await tree_service.get_tree(session, viewer_id=current.id, tree_id=tree_id)
|
||||||
|
media = await media_service.update_media(
|
||||||
|
session,
|
||||||
|
actor=current,
|
||||||
|
tree=tree,
|
||||||
|
media_id=media_id,
|
||||||
|
changes=data.model_dump(exclude_unset=True),
|
||||||
|
)
|
||||||
|
return _read(media)
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{tree_id}/media/{media_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/{tree_id}/media/{media_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_media(
|
async def delete_media(
|
||||||
tree_id: uuid.UUID, media_id: uuid.UUID, session: SessionDep, current: CurrentUser
|
tree_id: uuid.UUID, media_id: uuid.UUID, session: SessionDep, current: CurrentUser
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import uuid
|
|||||||
from fastapi import APIRouter, status
|
from fastapi import APIRouter, status
|
||||||
|
|
||||||
from app.api.deps import CurrentUser, SessionDep
|
from app.api.deps import CurrentUser, SessionDep
|
||||||
from app.schemas.relationship import RelationshipCreate, RelationshipRead
|
from app.schemas.relationship import RelationshipCreate, RelationshipRead, RelationshipUpdate
|
||||||
from app.services import relationship_service, tree_service
|
from app.services import relationship_service, tree_service
|
||||||
|
|
||||||
router = APIRouter(prefix="/trees", tags=["relationships"])
|
router = APIRouter(prefix="/trees", tags=["relationships"])
|
||||||
@@ -47,6 +47,25 @@ async def list_person_relationships(
|
|||||||
return [RelationshipRead.model_validate(r) for r in rels]
|
return [RelationshipRead.model_validate(r) for r in rels]
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch("/{tree_id}/relationships/{relationship_id}", response_model=RelationshipRead)
|
||||||
|
async def update_relationship(
|
||||||
|
tree_id: uuid.UUID,
|
||||||
|
relationship_id: uuid.UUID,
|
||||||
|
data: RelationshipUpdate,
|
||||||
|
session: SessionDep,
|
||||||
|
current: CurrentUser,
|
||||||
|
) -> RelationshipRead:
|
||||||
|
tree = await tree_service.get_tree(session, viewer_id=current.id, tree_id=tree_id)
|
||||||
|
rel = await relationship_service.update_relationship(
|
||||||
|
session,
|
||||||
|
actor=current,
|
||||||
|
tree=tree,
|
||||||
|
relationship_id=relationship_id,
|
||||||
|
changes=data.model_dump(exclude_unset=True),
|
||||||
|
)
|
||||||
|
return RelationshipRead.model_validate(rel)
|
||||||
|
|
||||||
|
|
||||||
@router.delete(
|
@router.delete(
|
||||||
"/{tree_id}/relationships/{relationship_id}", status_code=status.HTTP_204_NO_CONTENT
|
"/{tree_id}/relationships/{relationship_id}", status_code=status.HTTP_204_NO_CONTENT
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import uuid
|
|||||||
from fastapi import APIRouter, status
|
from fastapi import APIRouter, status
|
||||||
|
|
||||||
from app.api.deps import CurrentUser, SessionDep
|
from app.api.deps import CurrentUser, SessionDep
|
||||||
from app.schemas.source import SourceCreate, SourceRead
|
from app.schemas.source import SourceCreate, SourceRead, SourceUpdate
|
||||||
from app.services import source_service, tree_service
|
from app.services import source_service, tree_service
|
||||||
|
|
||||||
router = APIRouter(prefix="/trees", tags=["sources"])
|
router = APIRouter(prefix="/trees", tags=["sources"])
|
||||||
@@ -40,6 +40,25 @@ async def get_source(
|
|||||||
return SourceRead.model_validate(source)
|
return SourceRead.model_validate(source)
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch("/{tree_id}/sources/{source_id}", response_model=SourceRead)
|
||||||
|
async def update_source(
|
||||||
|
tree_id: uuid.UUID,
|
||||||
|
source_id: uuid.UUID,
|
||||||
|
data: SourceUpdate,
|
||||||
|
session: SessionDep,
|
||||||
|
current: CurrentUser,
|
||||||
|
) -> SourceRead:
|
||||||
|
tree = await tree_service.get_tree(session, viewer_id=current.id, tree_id=tree_id)
|
||||||
|
source = await source_service.update_source(
|
||||||
|
session,
|
||||||
|
actor=current,
|
||||||
|
tree=tree,
|
||||||
|
source_id=source_id,
|
||||||
|
changes=data.model_dump(exclude_unset=True),
|
||||||
|
)
|
||||||
|
return SourceRead.model_validate(source)
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{tree_id}/sources/{source_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/{tree_id}/sources/{source_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_source(
|
async def delete_source(
|
||||||
tree_id: uuid.UUID, source_id: uuid.UUID, session: SessionDep, current: CurrentUser
|
tree_id: uuid.UUID, source_id: uuid.UUID, session: SessionDep, current: CurrentUser
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import uuid
|
|||||||
from fastapi import APIRouter, status
|
from fastapi import APIRouter, status
|
||||||
|
|
||||||
from app.api.deps import CurrentUser, SessionDep
|
from app.api.deps import CurrentUser, SessionDep
|
||||||
from app.schemas.tree import TreeCreate, TreeRead
|
from app.schemas.tree import TreeCreate, TreeRead, TreeUpdate
|
||||||
from app.services import tree_service
|
from app.services import tree_service
|
||||||
|
|
||||||
router = APIRouter(prefix="/trees", tags=["trees"])
|
router = APIRouter(prefix="/trees", tags=["trees"])
|
||||||
@@ -38,6 +38,16 @@ async def get_tree(tree_id: uuid.UUID, session: SessionDep, current: CurrentUser
|
|||||||
return TreeRead.model_validate(tree)
|
return TreeRead.model_validate(tree)
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch("/{tree_id}", response_model=TreeRead)
|
||||||
|
async def update_tree(
|
||||||
|
tree_id: uuid.UUID, data: TreeUpdate, session: SessionDep, current: CurrentUser
|
||||||
|
) -> TreeRead:
|
||||||
|
tree = await tree_service.update_tree(
|
||||||
|
session, actor=current, tree_id=tree_id, changes=data.model_dump(exclude_unset=True)
|
||||||
|
)
|
||||||
|
return TreeRead.model_validate(tree)
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{tree_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/{tree_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_tree(tree_id: uuid.UUID, session: SessionDep, current: CurrentUser) -> None:
|
async def delete_tree(tree_id: uuid.UUID, session: SessionDep, current: CurrentUser) -> None:
|
||||||
await tree_service.delete_tree(session, actor=current, tree_id=tree_id)
|
await tree_service.delete_tree(session, actor=current, tree_id=tree_id)
|
||||||
|
|||||||
@@ -4,6 +4,13 @@ from datetime import datetime
|
|||||||
from pydantic import BaseModel, ConfigDict
|
from pydantic import BaseModel, ConfigDict
|
||||||
|
|
||||||
|
|
||||||
|
class MediaUpdate(BaseModel):
|
||||||
|
title: str | None = None
|
||||||
|
person_id: uuid.UUID | None = None
|
||||||
|
event_id: uuid.UUID | None = None
|
||||||
|
source_id: uuid.UUID | None = None
|
||||||
|
|
||||||
|
|
||||||
class MediaRead(BaseModel):
|
class MediaRead(BaseModel):
|
||||||
model_config = ConfigDict(from_attributes=True)
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ class RelationshipCreate(BaseModel):
|
|||||||
notes: str | None = None
|
notes: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class RelationshipUpdate(BaseModel):
|
||||||
|
qualifier: ParentChildQualifier | None = None
|
||||||
|
notes: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class RelationshipRead(BaseModel):
|
class RelationshipRead(BaseModel):
|
||||||
model_config = ConfigDict(from_attributes=True)
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,23 @@ class SourceRead(BaseModel):
|
|||||||
created_at: datetime
|
created_at: datetime
|
||||||
|
|
||||||
|
|
||||||
|
class SourceUpdate(BaseModel):
|
||||||
|
title: str | None = None
|
||||||
|
author: str | None = None
|
||||||
|
source_type: str | None = None
|
||||||
|
repository: str | None = None
|
||||||
|
url: str | None = None
|
||||||
|
citation_text: str | None = None
|
||||||
|
publication_info: str | None = None
|
||||||
|
quality_note: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class CitationUpdate(BaseModel):
|
||||||
|
page: str | None = None
|
||||||
|
detail: str | None = None
|
||||||
|
confidence: CitationConfidence | None = None
|
||||||
|
|
||||||
|
|
||||||
class CitationCreate(BaseModel):
|
class CitationCreate(BaseModel):
|
||||||
source_id: uuid.UUID
|
source_id: uuid.UUID
|
||||||
# Exactly one target fact.
|
# Exactly one target fact.
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ class TreeCreate(BaseModel):
|
|||||||
visibility: TreeVisibility = TreeVisibility.private
|
visibility: TreeVisibility = TreeVisibility.private
|
||||||
|
|
||||||
|
|
||||||
|
class TreeUpdate(BaseModel):
|
||||||
|
name: str | None = None
|
||||||
|
description: str | None = None
|
||||||
|
visibility: TreeVisibility | None = None
|
||||||
|
|
||||||
|
|
||||||
class TreeRead(BaseModel):
|
class TreeRead(BaseModel):
|
||||||
model_config = ConfigDict(from_attributes=True)
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|
||||||
|
|||||||
@@ -113,6 +113,38 @@ async def list_citations(
|
|||||||
return list((await session.execute(stmt)).scalars().all())
|
return list((await session.execute(stmt)).scalars().all())
|
||||||
|
|
||||||
|
|
||||||
|
async def update_citation(
|
||||||
|
session: AsyncSession, *, actor: User, tree: Tree, citation_id: uuid.UUID, changes: dict
|
||||||
|
) -> Citation:
|
||||||
|
if not await privacy.can_edit_tree(session, user_id=actor.id, tree=tree):
|
||||||
|
raise Forbidden("not an editor of this tree")
|
||||||
|
citation = (
|
||||||
|
await session.execute(
|
||||||
|
select(Citation).where(
|
||||||
|
Citation.id == citation_id,
|
||||||
|
Citation.tree_id == tree.id,
|
||||||
|
Citation.deleted_at.is_(None),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).scalar_one_or_none()
|
||||||
|
if citation is None:
|
||||||
|
raise NotFound("citation not found")
|
||||||
|
for key in {"page", "detail", "confidence"} & changes.keys():
|
||||||
|
setattr(citation, key, changes[key])
|
||||||
|
record_audit(
|
||||||
|
session,
|
||||||
|
action="update",
|
||||||
|
entity_type="Citation",
|
||||||
|
entity_id=citation.id,
|
||||||
|
tree_id=tree.id,
|
||||||
|
actor_user_id=actor.id,
|
||||||
|
after=changes,
|
||||||
|
)
|
||||||
|
await session.commit()
|
||||||
|
await session.refresh(citation)
|
||||||
|
return citation
|
||||||
|
|
||||||
|
|
||||||
async def delete_citation(
|
async def delete_citation(
|
||||||
session: AsyncSession, *, actor: User, tree: Tree, citation_id: uuid.UUID
|
session: AsyncSession, *, actor: User, tree: Tree, citation_id: uuid.UUID
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
@@ -97,6 +97,36 @@ async def get_media(
|
|||||||
return media
|
return media
|
||||||
|
|
||||||
|
|
||||||
|
async def update_media(
|
||||||
|
session: AsyncSession, *, actor: User, tree: Tree, media_id: uuid.UUID, changes: dict
|
||||||
|
) -> Media:
|
||||||
|
if not await privacy.can_edit_tree(session, user_id=actor.id, tree=tree):
|
||||||
|
raise Forbidden("not an editor of this tree")
|
||||||
|
media = (
|
||||||
|
await session.execute(
|
||||||
|
select(Media).where(
|
||||||
|
Media.id == media_id, Media.tree_id == tree.id, Media.deleted_at.is_(None)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).scalar_one_or_none()
|
||||||
|
if media is None:
|
||||||
|
raise NotFound("media not found")
|
||||||
|
for key in {"title", "person_id", "event_id", "source_id"} & changes.keys():
|
||||||
|
setattr(media, key, changes[key])
|
||||||
|
record_audit(
|
||||||
|
session,
|
||||||
|
action="update",
|
||||||
|
entity_type="Media",
|
||||||
|
entity_id=media.id,
|
||||||
|
tree_id=tree.id,
|
||||||
|
actor_user_id=actor.id,
|
||||||
|
after=changes,
|
||||||
|
)
|
||||||
|
await session.commit()
|
||||||
|
await session.refresh(media)
|
||||||
|
return media
|
||||||
|
|
||||||
|
|
||||||
async def delete_media(
|
async def delete_media(
|
||||||
session: AsyncSession, *, actor: User, tree: Tree, media_id: uuid.UUID
|
session: AsyncSession, *, actor: User, tree: Tree, media_id: uuid.UUID
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
@@ -107,6 +107,44 @@ async def list_relationships_for_person(
|
|||||||
return list((await session.execute(stmt)).scalars().all())
|
return list((await session.execute(stmt)).scalars().all())
|
||||||
|
|
||||||
|
|
||||||
|
async def update_relationship(
|
||||||
|
session: AsyncSession, *, actor: User, tree: Tree, relationship_id: uuid.UUID, changes: dict
|
||||||
|
) -> Relationship:
|
||||||
|
if not await privacy.can_edit_tree(session, user_id=actor.id, tree=tree):
|
||||||
|
raise Forbidden("not an editor of this tree")
|
||||||
|
relationship = (
|
||||||
|
await session.execute(
|
||||||
|
select(Relationship).where(
|
||||||
|
Relationship.id == relationship_id,
|
||||||
|
Relationship.tree_id == tree.id,
|
||||||
|
Relationship.deleted_at.is_(None),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).scalar_one_or_none()
|
||||||
|
if relationship is None:
|
||||||
|
raise NotFound("relationship not found")
|
||||||
|
if (
|
||||||
|
"qualifier" in changes
|
||||||
|
and changes["qualifier"] is not None
|
||||||
|
and relationship.type is not RelationshipType.parent_child
|
||||||
|
):
|
||||||
|
raise Conflict("qualifier only applies to parent_child relationships")
|
||||||
|
for key in {"qualifier", "notes"} & changes.keys():
|
||||||
|
setattr(relationship, key, changes[key])
|
||||||
|
record_audit(
|
||||||
|
session,
|
||||||
|
action="update",
|
||||||
|
entity_type="Relationship",
|
||||||
|
entity_id=relationship.id,
|
||||||
|
tree_id=tree.id,
|
||||||
|
actor_user_id=actor.id,
|
||||||
|
after=changes,
|
||||||
|
)
|
||||||
|
await session.commit()
|
||||||
|
await session.refresh(relationship)
|
||||||
|
return relationship
|
||||||
|
|
||||||
|
|
||||||
async def delete_relationship(
|
async def delete_relationship(
|
||||||
session: AsyncSession, *, actor: User, tree: Tree, relationship_id: uuid.UUID
|
session: AsyncSession, *, actor: User, tree: Tree, relationship_id: uuid.UUID
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
@@ -86,6 +86,42 @@ async def get_source(
|
|||||||
return source
|
return source
|
||||||
|
|
||||||
|
|
||||||
|
_SOURCE_FIELDS = {
|
||||||
|
"title", "author", "source_type", "repository", "url", "citation_text",
|
||||||
|
"publication_info", "quality_note",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def update_source(
|
||||||
|
session: AsyncSession, *, actor: User, tree: Tree, source_id: uuid.UUID, changes: dict
|
||||||
|
) -> Source:
|
||||||
|
if not await privacy.can_edit_tree(session, user_id=actor.id, tree=tree):
|
||||||
|
raise Forbidden("not an editor of this tree")
|
||||||
|
source = (
|
||||||
|
await session.execute(
|
||||||
|
select(Source).where(
|
||||||
|
Source.id == source_id, Source.tree_id == tree.id, Source.deleted_at.is_(None)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).scalar_one_or_none()
|
||||||
|
if source is None:
|
||||||
|
raise NotFound("source not found")
|
||||||
|
for key in _SOURCE_FIELDS & changes.keys():
|
||||||
|
setattr(source, key, changes[key])
|
||||||
|
record_audit(
|
||||||
|
session,
|
||||||
|
action="update",
|
||||||
|
entity_type="Source",
|
||||||
|
entity_id=source.id,
|
||||||
|
tree_id=tree.id,
|
||||||
|
actor_user_id=actor.id,
|
||||||
|
after=changes,
|
||||||
|
)
|
||||||
|
await session.commit()
|
||||||
|
await session.refresh(source)
|
||||||
|
return source
|
||||||
|
|
||||||
|
|
||||||
async def delete_source(
|
async def delete_source(
|
||||||
session: AsyncSession, *, actor: User, tree: Tree, source_id: uuid.UUID
|
session: AsyncSession, *, actor: User, tree: Tree, source_id: uuid.UUID
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
@@ -62,6 +62,30 @@ async def get_tree(session: AsyncSession, *, viewer_id: uuid.UUID, tree_id: uuid
|
|||||||
return tree
|
return tree
|
||||||
|
|
||||||
|
|
||||||
|
async def update_tree(
|
||||||
|
session: AsyncSession, *, actor: User, tree_id: uuid.UUID, changes: dict
|
||||||
|
) -> Tree:
|
||||||
|
tree = await BaseRepository(session, Tree).get(tree_id)
|
||||||
|
if tree is None:
|
||||||
|
raise NotFound("tree not found")
|
||||||
|
if not await privacy.can_edit_tree(session, user_id=actor.id, tree=tree):
|
||||||
|
raise Forbidden("not an editor of this tree")
|
||||||
|
for key in {"name", "description", "visibility"} & changes.keys():
|
||||||
|
setattr(tree, key, changes[key])
|
||||||
|
record_audit(
|
||||||
|
session,
|
||||||
|
action="update",
|
||||||
|
entity_type="Tree",
|
||||||
|
entity_id=tree.id,
|
||||||
|
tree_id=tree.id,
|
||||||
|
actor_user_id=actor.id,
|
||||||
|
after=changes,
|
||||||
|
)
|
||||||
|
await session.commit()
|
||||||
|
await session.refresh(tree)
|
||||||
|
return tree
|
||||||
|
|
||||||
|
|
||||||
async def _owned_tree(session: AsyncSession, *, actor: User, tree_id: uuid.UUID) -> Tree:
|
async def _owned_tree(session: AsyncSession, *, actor: User, tree_id: uuid.UUID) -> Tree:
|
||||||
"""Load a tree (including soft-deleted) and require the actor be its owner."""
|
"""Load a tree (including soft-deleted) and require the actor be its owner."""
|
||||||
tree = await BaseRepository(session, Tree).get(tree_id, include_deleted=True)
|
tree = await BaseRepository(session, Tree).get(tree_id, include_deleted=True)
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
"""Update (the U in CRUD) for the remaining entities — rule #8."""
|
||||||
|
|
||||||
|
from tests.conftest import auth, register
|
||||||
|
|
||||||
|
|
||||||
|
async def _setup(client, email):
|
||||||
|
h = auth(await register(client, email))
|
||||||
|
tid = (await client.post("/api/v1/trees", json={"name": "T"}, headers=h)).json()["id"]
|
||||||
|
return h, tid
|
||||||
|
|
||||||
|
|
||||||
|
async def test_tree_update(client):
|
||||||
|
h, tid = await _setup(client, "u-tree@example.com")
|
||||||
|
r = await client.patch(
|
||||||
|
f"/api/v1/trees/{tid}", json={"name": "Renamed", "visibility": "unlisted"}, headers=h
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert r.json()["name"] == "Renamed" and r.json()["visibility"] == "unlisted"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_source_update(client):
|
||||||
|
h, tid = await _setup(client, "u-src@example.com")
|
||||||
|
sid = (
|
||||||
|
await client.post(f"/api/v1/trees/{tid}/sources", json={"title": "Old"}, headers=h)
|
||||||
|
).json()["id"]
|
||||||
|
r = await client.patch(
|
||||||
|
f"/api/v1/trees/{tid}/sources/{sid}",
|
||||||
|
json={"title": "New", "repository": "NARA"},
|
||||||
|
headers=h,
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert r.json()["title"] == "New" and r.json()["repository"] == "NARA"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_media_update(client):
|
||||||
|
h, tid = await _setup(client, "u-media@example.com")
|
||||||
|
mid = (
|
||||||
|
await client.post(
|
||||||
|
f"/api/v1/trees/{tid}/media",
|
||||||
|
files={"file": ("a.txt", b"x", "text/plain")},
|
||||||
|
data={"title": "old"},
|
||||||
|
headers=h,
|
||||||
|
)
|
||||||
|
).json()["id"]
|
||||||
|
r = await client.patch(f"/api/v1/trees/{tid}/media/{mid}", json={"title": "new"}, headers=h)
|
||||||
|
assert r.status_code == 200 and r.json()["title"] == "new"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_relationship_and_citation_update(client):
|
||||||
|
h, tid = await _setup(client, "u-rc@example.com")
|
||||||
|
|
||||||
|
async def mk(path, body):
|
||||||
|
return (await client.post(f"/api/v1/trees/{tid}/{path}", json=body, headers=h)).json()["id"]
|
||||||
|
|
||||||
|
p1 = await mk("persons", {"given": "A"})
|
||||||
|
p2 = await mk("persons", {"given": "B"})
|
||||||
|
rid = await mk(
|
||||||
|
"relationships",
|
||||||
|
{
|
||||||
|
"type": "parent_child",
|
||||||
|
"person_from_id": p1,
|
||||||
|
"person_to_id": p2,
|
||||||
|
"qualifier": "biological",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
r = await client.patch(
|
||||||
|
f"/api/v1/trees/{tid}/relationships/{rid}", json={"qualifier": "adoptive"}, headers=h
|
||||||
|
)
|
||||||
|
assert r.status_code == 200 and r.json()["qualifier"] == "adoptive"
|
||||||
|
|
||||||
|
src = await mk("sources", {"title": "S"})
|
||||||
|
cid = await mk("citations", {"source_id": src, "person_id": p1})
|
||||||
|
r2 = await client.patch(
|
||||||
|
f"/api/v1/trees/{tid}/citations/{cid}",
|
||||||
|
json={"page": "p.7", "confidence": "high"},
|
||||||
|
headers=h,
|
||||||
|
)
|
||||||
|
assert r2.status_code == 200
|
||||||
|
assert r2.json()["page"] == "p.7" and r2.json()["confidence"] == "high"
|
||||||
Reference in New Issue
Block a user