From d27a9f2e8e140445e6b7416c6a54e8ca0fc5d12d Mon Sep 17 00:00:00 2001 From: bkis Date: Tue, 22 Aug 2023 13:25:56 +0200 Subject: [PATCH 1/3] Fix: Nodes on level 0 are getting parent IDs Closes #56 --- Tekst-API/openapi.json | 39 ++++++++++++++++++++++++++-------- Tekst-API/tekst/models/text.py | 32 ++++++++++++++++------------ 2 files changed, 48 insertions(+), 23 deletions(-) diff --git a/Tekst-API/openapi.json b/Tekst-API/openapi.json index 1b1377c3..48abba09 100644 --- a/Tekst-API/openapi.json +++ b/Tekst-API/openapi.json @@ -3181,10 +3181,17 @@ "example": "5eb7cf5a86d9755df3a6c593" }, "parentId": { - "type": "string", + "anyOf": [ + { + "type": "string", + "example": "5eb7cf5a86d9755df3a6c593" + }, + { + "type": "null" + } + ], "title": "Parentid", - "description": "ID of parent node", - "example": "5eb7cf5a86d9755df3a6c593" + "description": "ID of parent node" }, "level": { "type": "integer", @@ -3246,10 +3253,17 @@ "example": "5eb7cf5a86d9755df3a6c593" }, "parentId": { - "type": "string", + "anyOf": [ + { + "type": "string", + "example": "5eb7cf5a86d9755df3a6c593" + }, + { + "type": "null" + } + ], "title": "Parentid", - "description": "ID of parent node", - "example": "5eb7cf5a86d9755df3a6c593" + "description": "ID of parent node" }, "level": { "type": "integer", @@ -3307,9 +3321,16 @@ "example": "5eb7cf5a86d9755df3a6c593" }, "parentId": { - "type": "string", - "title": "Parentid", - "example": "5eb7cf5a86d9755df3a6c593" + "anyOf": [ + { + "type": "string", + "example": "5eb7cf5a86d9755df3a6c593" + }, + { + "type": "null" + } + ], + "title": "Parentid" }, "level": { "type": "integer", diff --git a/Tekst-API/tekst/models/text.py b/Tekst-API/tekst/models/text.py index 14e8ff1b..39f11e39 100644 --- a/Tekst-API/tekst/models/text.py +++ b/Tekst-API/tekst/models/text.py @@ -125,20 +125,24 @@ class Settings(DocumentBase.Settings): class Node(ModelBase, ModelFactoryMixin): """A node in a text structure (e.g. chapter, paragraph, ...)""" - text_id: PydanticObjectId = Field( - ..., description="ID of the text this node belongs to" - ) - parent_id: PydanticObjectId = Field(None, description="ID of parent node") - level: Annotated[int, Field(ge=0, lt=32)] = Field( - ..., description="Index of structure level this node is on" - ) - position: Annotated[int, Field(ge=0)] = Field( - ..., description="Position among all text nodes on this level" - ) - label: Annotated[str, StringConstraints(min_length=1, max_length=256)] = Field( - ..., description="Label for identifying this text node in level context" - ) - meta: Metadata | None = Field(None, description="Arbitrary metadata") + text_id: Annotated[ + PydanticObjectId, Field(description="ID of the text this node belongs to") + ] + parent_id: Annotated[ + PydanticObjectId | None, Field(description="ID of parent node") + ] = None + level: Annotated[ + int, Field(ge=0, lt=32, description="Index of structure level this node is on") + ] + position: Annotated[ + int, Field(ge=0, description="Position among all text nodes on this level") + ] + label: Annotated[ + str, + StringConstraints(min_length=1, max_length=256), + Field(description="Label for identifying this text node in level context"), + ] + meta: Annotated[Metadata | None, Field(description="Arbitrary metadata")] = None class NodeDocument(Node, DocumentBase): From 07eb7cc2ffd615a28b5c5bdd3cff922d326d620c Mon Sep 17 00:00:00 2001 From: bkis Date: Tue, 22 Aug 2023 13:31:10 +0200 Subject: [PATCH 2/3] Remove LayerMinimalView --- Tekst-API/tekst/models/layer.py | 4 ---- Tekst-API/tekst/routers/units.py | 12 ++++++------ 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/Tekst-API/tekst/models/layer.py b/Tekst-API/tekst/models/layer.py index 76400896..a095de89 100644 --- a/Tekst-API/tekst/models/layer.py +++ b/Tekst-API/tekst/models/layer.py @@ -126,10 +126,6 @@ class Settings(DocumentBase.Settings): LayerBaseUpdate = LayerBase.get_update_model() -class LayerMinimalView(ReadBase): - layer_type: str - - class LayerNodeCoverage(ModelBase): label: str position: int diff --git a/Tekst-API/tekst/routers/units.py b/Tekst-API/tekst/routers/units.py index 6575afb7..6475bbcb 100644 --- a/Tekst-API/tekst/routers/units.py +++ b/Tekst-API/tekst/routers/units.py @@ -6,7 +6,7 @@ from tekst.auth import OptionalUserDep, UserDep from tekst.layer_types import layer_type_manager -from tekst.models.layer import LayerBaseDocument, LayerMinimalView +from tekst.models.layer import LayerBaseDocument from tekst.models.unit import UnitBase, UnitBaseDocument @@ -169,14 +169,14 @@ async def update_unit( @router.get("/", response_model=list[dict], status_code=status.HTTP_200_OK) async def find_units( user: OptionalUserDep, - layer_id: Annotated[ + layer_ids: Annotated[ list[PydanticObjectId], Query( alias="layerId", description="ID (or list of IDs) of layer(s) to return unit data for", ), ] = [], - node_id: Annotated[ + node_ids: Annotated[ list[PydanticObjectId], Query( alias="nodeId", @@ -196,15 +196,15 @@ async def find_units( await LayerBaseDocument.find( LayerBaseDocument.allowed_to_read(user), with_children=True ) - .project(LayerMinimalView) .to_list() ) readable_layer_ids = [layer.id for layer in readable_layers] + print(readable_layer_ids) units = ( await UnitBaseDocument.find( - In(UnitBaseDocument.layer_id, layer_id) if layer_id else {}, - In(UnitBaseDocument.node_id, node_id) if node_id else {}, + In(UnitBaseDocument.layer_id, layer_ids) if layer_ids else {}, + In(UnitBaseDocument.node_id, node_ids) if node_ids else {}, In(UnitBaseDocument.layer_id, readable_layer_ids), with_children=True, ) From bc68d9f9d3d831d2dba3972003603eb9afdddfee Mon Sep 17 00:00:00 2001 From: bkis Date: Tue, 22 Aug 2023 13:55:04 +0200 Subject: [PATCH 3/3] Fix: GET /units w/o query params returns all units Closes #55 --- Tekst-API/openapi.json | 2 +- Tekst-API/tekst/models/layer.py | 1 - Tekst-API/tekst/routers/layers.py | 18 ++++++++++++------ Tekst-API/tekst/routers/units.py | 26 +++++++++++++++----------- 4 files changed, 28 insertions(+), 19 deletions(-) diff --git a/Tekst-API/openapi.json b/Tekst-API/openapi.json index 48abba09..62ffe9da 100644 --- a/Tekst-API/openapi.json +++ b/Tekst-API/openapi.json @@ -1775,7 +1775,7 @@ "units" ], "summary": "Find units", - "description": "Returns a list of all data layer units matching the given criteria.\n\nAs the resulting list may contain units of different types, the\nreturned unit objects cannot be typed to their precise layer unit type.", + "description": "Returns a list of all data layer units matching the given criteria.\n\nRespects restricted layers and inactive texts.\nAs the resulting list may contain units of different types, the\nreturned unit objects cannot be typed to their precise layer unit type.", "operationId": "findUnits", "security": [ { diff --git a/Tekst-API/tekst/models/layer.py b/Tekst-API/tekst/models/layer.py index a095de89..8a9e9542 100644 --- a/Tekst-API/tekst/models/layer.py +++ b/Tekst-API/tekst/models/layer.py @@ -11,7 +11,6 @@ Metadata, ModelBase, ModelFactoryMixin, - ReadBase, ) from tekst.models.user import UserRead diff --git a/Tekst-API/tekst/routers/layers.py b/Tekst-API/tekst/routers/layers.py index c91ade0c..e4f38e0b 100644 --- a/Tekst-API/tekst/routers/layers.py +++ b/Tekst-API/tekst/routers/layers.py @@ -1,6 +1,7 @@ from typing import Annotated from beanie import PydanticObjectId +from beanie.operators import In from fastapi import APIRouter, HTTPException, Path, Query, status from tekst.auth import OptionalUserDep, UserDep @@ -16,11 +17,9 @@ async def get_layer( id: PydanticObjectId, user: OptionalUserDep ) -> layer_read_model: """A generic route for reading a layer definition from the database""" - layer_doc = ( - await layer_document_model.find(layer_document_model.id == id) - .find(layer_document_model.allowed_to_read(user)) - .first_or_none() - ) + layer_doc = await layer_document_model.find( + layer_document_model.id == id, layer_document_model.allowed_to_read(user) + ).first_or_none() if not layer_doc: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, @@ -167,9 +166,16 @@ async def find_layers( if layer_type: example["layer_type"] = layer_type + active_texts = await TextDocument.find( + TextDocument.is_active == True # noqa: E712 + ).to_list() + layer_docs = ( await LayerBaseDocument.find(example, with_children=True) - .find(LayerBaseDocument.allowed_to_read(user)) + .find( + LayerBaseDocument.allowed_to_read(user), + In(LayerBaseDocument.text_id, [text.id for text in active_texts]), + ) .limit(limit) .to_list() ) diff --git a/Tekst-API/tekst/routers/units.py b/Tekst-API/tekst/routers/units.py index 6475bbcb..e3cd4bcb 100644 --- a/Tekst-API/tekst/routers/units.py +++ b/Tekst-API/tekst/routers/units.py @@ -7,6 +7,7 @@ from tekst.auth import OptionalUserDep, UserDep from tekst.layer_types import layer_type_manager from tekst.models.layer import LayerBaseDocument +from tekst.models.text import TextDocument from tekst.models.unit import UnitBase, UnitBaseDocument @@ -55,9 +56,10 @@ async def create_unit(unit: unit_create_model, user: UserDep) -> unit_read_model status_code=status.HTTP_401_UNAUTHORIZED, detail="No write access for units belonging to this layer", ) - dupes_criteria = {"layerId": True, "nodeId": True} + # check for duplicates if await unit_document_model.find( - unit.model_dump(include=dupes_criteria) + UnitDocumentModel.layer_id == unit.layer_id, + UnitDocumentModel.node_id == unit.node_id, ).first_or_none(): raise HTTPException( status_code=status.HTTP_409_CONFLICT, @@ -188,24 +190,26 @@ async def find_units( """ Returns a list of all data layer units matching the given criteria. + Respects restricted layers and inactive texts. As the resulting list may contain units of different types, the returned unit objects cannot be typed to their precise layer unit type. """ - readable_layers = ( - await LayerBaseDocument.find( - LayerBaseDocument.allowed_to_read(user), with_children=True - ) - .to_list() - ) - readable_layer_ids = [layer.id for layer in readable_layers] - print(readable_layer_ids) + active_texts = await TextDocument.find( + TextDocument.is_active == True # noqa: E712 + ).to_list() + + readable_layers = await LayerBaseDocument.find( + LayerBaseDocument.allowed_to_read(user), + In(LayerBaseDocument.text_id, [text.id for text in active_texts]), + with_children=True, + ).to_list() units = ( await UnitBaseDocument.find( In(UnitBaseDocument.layer_id, layer_ids) if layer_ids else {}, In(UnitBaseDocument.node_id, node_ids) if node_ids else {}, - In(UnitBaseDocument.layer_id, readable_layer_ids), + In(UnitBaseDocument.layer_id, [layer.id for layer in readable_layers]), with_children=True, ) .limit(limit)