diff --git a/Tekst-API/openapi.json b/Tekst-API/openapi.json index c801f9c7..5559c426 100644 --- a/Tekst-API/openapi.json +++ b/Tekst-API/openapi.json @@ -116,7 +116,7 @@ "browse" ], "summary": "Get unit siblings", - "description": "Returns a list of all data layer units belonging to the data layer\nwith the given ID, associated to nodes that are children of the parent node\nwith the given ID.\n\nAs the resulting list may contain units of arbitrary type, the\nreturned unit objects cannot be typed to their precise layer unit type.\nAlso, the returned unit objects have an additional property containing their\nrespective node's label, level and position.", + "description": "Returns a list of all resource units belonging to the resource\nwith the given ID, associated to nodes that are children of the parent node\nwith the given ID.\n\nAs the resulting list may contain units of arbitrary type, the\nreturned unit objects cannot be typed to their precise resource unit type.\nAlso, the returned unit objects have an additional property containing their\nrespective node's label, level and position.", "operationId": "getUnitSiblings", "security": [ { @@ -128,16 +128,16 @@ ], "parameters": [ { - "name": "layerId", + "name": "resourceId", "in": "query", "required": true, "schema": { "type": "string", "example": "5eb7cf5a86d9755df3a6c593", - "description": "ID of layer the requested units belong to", - "title": "Layerid" + "description": "ID of resource the requested units belong to", + "title": "Resourceid" }, - "description": "ID of layer the requested units belong to" + "description": "ID of resource the requested units belong to" }, { "name": "parentNodeId", @@ -176,7 +176,7 @@ } ], "discriminator": { - "propertyName": "layerType", + "propertyName": "resourceType", "mapping": { "debug": "#/components/schemas/DebugUnitRead", "plaintext": "#/components/schemas/PlaintextUnitRead" @@ -381,13 +381,13 @@ } } }, - "/browse/layers/{id}/coverage": { + "/browse/resources/{id}/coverage": { "get": { "tags": [ "browse" ], - "summary": "Get layer coverage data", - "operationId": "getLayerCoverageData", + "summary": "Get resource coverage data", + "operationId": "getResourceCoverageData", "security": [ { "APIKeyCookie": [] @@ -416,9 +416,9 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/LayerNodeCoverage" + "$ref": "#/components/schemas/ResourceNodeCoverage" }, - "title": "Response Get Layer Coverage Data Browse Layers Id Coverage Get" + "title": "Response Get Resource Coverage Data Browse Resources Id Coverage Get" } } } @@ -439,13 +439,14 @@ } } }, - "/layers": { + "/nodes": { "post": { "tags": [ - "layers" + "nodes" ], - "summary": "Create layer", - "operationId": "createLayer", + "summary": "Create node", + "description": "Creates a new node. The position will be automatically set to the last position\nof the node's parent (or the first parent before that has children).", + "operationId": "createNode", "security": [ { "APIKeyCookie": [] @@ -459,48 +460,18 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/DebugLayerCreate" - }, - { - "$ref": "#/components/schemas/PlaintextLayerCreate" - } - ], - "discriminator": { - "propertyName": "layerType", - "mapping": { - "debug": "#/components/schemas/DebugLayerCreate", - "plaintext": "#/components/schemas/PlaintextLayerCreate" - } - }, - "title": "Layer" + "$ref": "#/components/schemas/NodeCreate" } } } }, "responses": { "201": { - "description": "Created", + "description": "Successful Response", "content": { "application/json": { "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/DebugLayerRead" - }, - { - "$ref": "#/components/schemas/PlaintextLayerRead" - } - ], - "discriminator": { - "propertyName": "layerType", - "mapping": { - "debug": "#/components/schemas/DebugLayerRead", - "plaintext": "#/components/schemas/PlaintextLayerRead" - } - }, - "title": "Response Create Layer Layers Post" + "$ref": "#/components/schemas/NodeRead" } } } @@ -522,19 +493,10 @@ }, "get": { "tags": [ - "layers" - ], - "summary": "Find layers", - "description": "Returns a list of all data layers matching the given criteria.\n\nAs the resulting list of data layers may contain layers of different types, the\nreturned layer objects cannot be typed to their precise layer type.", - "operationId": "findLayers", - "security": [ - { - "APIKeyCookie": [] - }, - { - "OAuth2PasswordBearer": [] - } + "nodes" ], + "summary": "Find nodes", + "operationId": "findNodes", "parameters": [ { "name": "textId", @@ -556,12 +518,22 @@ } }, { - "name": "layerType", + "name": "position", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "title": "Position" + } + }, + { + "name": "parentId", "in": "query", "required": false, "schema": { "type": "string", - "title": "Layertype" + "example": "5eb7cf5a86d9755df3a6c593", + "title": "Parentid" } }, { @@ -570,7 +542,7 @@ "required": false, "schema": { "type": "integer", - "default": 4096, + "default": 1000, "title": "Limit" } } @@ -583,23 +555,9 @@ "schema": { "type": "array", "items": { - "oneOf": [ - { - "$ref": "#/components/schemas/DebugLayerRead" - }, - { - "$ref": "#/components/schemas/PlaintextLayerRead" - } - ], - "discriminator": { - "propertyName": "layerType", - "mapping": { - "debug": "#/components/schemas/DebugLayerRead", - "plaintext": "#/components/schemas/PlaintextLayerRead" - } - } + "$ref": "#/components/schemas/NodeRead" }, - "title": "Response Find Layers Layers Get" + "title": "Response Find Nodes Nodes Get" } } } @@ -620,13 +578,13 @@ } } }, - "/layers/{id}": { - "patch": { + "/nodes/children": { + "get": { "tags": [ - "layers" + "nodes" ], - "summary": "Update layer", - "operationId": "updateLayer", + "summary": "Get children", + "operationId": "getChildren", "security": [ { "APIKeyCookie": [] @@ -637,63 +595,61 @@ ], "parameters": [ { - "name": "id", - "in": "path", - "required": true, + "name": "parentId", + "in": "query", + "required": false, "schema": { - "type": "string", - "example": "5eb7cf5a86d9755df3a6c593", - "title": "Id" + "anyOf": [ + { + "type": "string", + "example": "5eb7cf5a86d9755df3a6c593" + }, + { + "type": "null" + } + ], + "title": "Parentid" } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/DebugLayerUpdate" - }, - { - "$ref": "#/components/schemas/PlaintextLayerUpdate" - } - ], - "discriminator": { - "propertyName": "layerType", - "mapping": { - "debug": "#/components/schemas/DebugLayerUpdate", - "plaintext": "#/components/schemas/PlaintextLayerUpdate" - } + }, + { + "name": "textId", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string", + "example": "5eb7cf5a86d9755df3a6c593" }, - "title": "Updates" - } + { + "type": "null" + } + ], + "title": "Textid" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 9999, + "title": "Limit" } } - }, + ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/DebugLayerRead" - }, - { - "$ref": "#/components/schemas/PlaintextLayerRead" - } - ], - "discriminator": { - "propertyName": "layerType", - "mapping": { - "debug": "#/components/schemas/DebugLayerRead", - "plaintext": "#/components/schemas/PlaintextLayerRead" - } + "type": "array", + "items": { + "$ref": "#/components/schemas/NodeRead" }, - "title": "Response Update Layer Layers Id Patch" + "title": "Response Get Children Nodes Children Get" } } } @@ -712,21 +668,15 @@ } } } - }, + } + }, + "/nodes/{id}": { "get": { "tags": [ - "layers" - ], - "summary": "Get layer", - "operationId": "getLayer", - "security": [ - { - "APIKeyCookie": [] - }, - { - "OAuth2PasswordBearer": [] - } + "nodes" ], + "summary": "Get node", + "operationId": "getNode", "parameters": [ { "name": "id", @@ -745,22 +695,7 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/DebugLayerRead" - }, - { - "$ref": "#/components/schemas/PlaintextLayerRead" - } - ], - "discriminator": { - "propertyName": "layerType", - "mapping": { - "debug": "#/components/schemas/DebugLayerRead", - "plaintext": "#/components/schemas/PlaintextLayerRead" - } - }, - "title": "Response Get Layer Layers Id Get" + "$ref": "#/components/schemas/NodeRead" } } } @@ -780,12 +715,12 @@ } } }, - "delete": { + "patch": { "tags": [ - "layers" + "nodes" ], - "summary": "Delete layer", - "operationId": "deleteLayer", + "summary": "Update node", + "operationId": "updateNode", "security": [ { "APIKeyCookie": [] @@ -806,9 +741,26 @@ } } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NodeUpdate" + } + } + } + }, "responses": { - "204": { - "description": "Successful Response" + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NodeRead" + } + } + } }, "404": { "description": "Not found" @@ -824,15 +776,14 @@ } } } - } - }, - "/layers/{id}/propose": { - "post": { + }, + "delete": { "tags": [ - "layers" + "nodes" ], - "summary": "Propose layer", - "operationId": "proposeLayer", + "summary": "Delete node", + "description": "Deletes the specified node. Also deletes any associated units, child nodes and units associated with child nodes.", + "operationId": "deleteNode", "security": [ { "APIKeyCookie": [] @@ -859,22 +810,7 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/DebugLayerRead" - }, - { - "$ref": "#/components/schemas/PlaintextLayerRead" - } - ], - "discriminator": { - "propertyName": "layerType", - "mapping": { - "debug": "#/components/schemas/DebugLayerRead", - "plaintext": "#/components/schemas/PlaintextLayerRead" - } - }, - "title": "Response Propose Layer Layers Id Propose Post" + "$ref": "#/components/schemas/DeleteNodeResult" } } } @@ -895,13 +831,14 @@ } } }, - "/layers/{id}/unpropose": { + "/nodes/{id}/move": { "post": { "tags": [ - "layers" + "nodes" ], - "summary": "Unpropose layer", - "operationId": "unproposeLayer", + "summary": "Move node", + "description": "Moves the specified node to a new position on its structure level.", + "operationId": "moveNode", "security": [ { "APIKeyCookie": [] @@ -922,28 +859,23 @@ } } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MoveNodeRequestBody" + } + } + } + }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/DebugLayerRead" - }, - { - "$ref": "#/components/schemas/PlaintextLayerRead" - } - ], - "discriminator": { - "propertyName": "layerType", - "mapping": { - "debug": "#/components/schemas/DebugLayerRead", - "plaintext": "#/components/schemas/PlaintextLayerRead" - } - }, - "title": "Response Unpropose Layer Layers Id Unpropose Post" + "$ref": "#/components/schemas/NodeRead" } } } @@ -964,30 +896,63 @@ } } }, - "/layers/{id}/publish": { - "post": { + "/platform": { + "get": { "tags": [ - "layers" + "platform" ], - "summary": "Publish layer", - "operationId": "publishLayer", - "security": [ - { - "APIKeyCookie": [] - }, - { + "summary": "Get platform data", + "description": "Returns data the client needs to initialize", + "operationId": "getPlatformData", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlatformData" + } + } + } + }, + "404": { + "description": "Not found" + } + }, + "security": [ + { + "APIKeyCookie": [] + }, + { "OAuth2PasswordBearer": [] } + ] + } + }, + "/platform/users/{usernameOrId}": { + "get": { + "tags": [ + "platform" ], + "summary": "Get public user info", + "description": "Returns public information on the user with the specified username or ID", + "operationId": "getPublicUserInfo", "parameters": [ { - "name": "id", + "name": "usernameOrId", "in": "path", "required": true, "schema": { - "type": "string", - "example": "5eb7cf5a86d9755df3a6c593", - "title": "Id" + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "example": "5eb7cf5a86d9755df3a6c593" + } + ], + "title": "Usernameorid" } } ], @@ -997,22 +962,7 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/DebugLayerRead" - }, - { - "$ref": "#/components/schemas/PlaintextLayerRead" - } - ], - "discriminator": { - "propertyName": "layerType", - "mapping": { - "debug": "#/components/schemas/DebugLayerRead", - "plaintext": "#/components/schemas/PlaintextLayerRead" - } - }, - "title": "Response Publish Layer Layers Id Publish Post" + "$ref": "#/components/schemas/UserReadPublic" } } } @@ -1033,13 +983,14 @@ } } }, - "/layers/{id}/unpublish": { - "post": { + "/platform/users": { + "get": { "tags": [ - "layers" + "platform" ], - "summary": "Unpublish layer", - "operationId": "unpublishLayer", + "summary": "Find public users", + "description": "Returns a list of public users matching the given query.\n\nOnly returns active user accounts. The query is considered to match a full token\n(e.g. first name, last name, username, a word in the affiliation field).", + "operationId": "findPublicUsers", "security": [ { "APIKeyCookie": [] @@ -1050,13 +1001,19 @@ ], "parameters": [ { - "name": "id", - "in": "path", + "name": "q", + "in": "query", "required": true, "schema": { - "type": "string", - "example": "5eb7cf5a86d9755df3a6c593", - "title": "Id" + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Q" } } ], @@ -1066,22 +1023,11 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/DebugLayerRead" - }, - { - "$ref": "#/components/schemas/PlaintextLayerRead" - } - ], - "discriminator": { - "propertyName": "layerType", - "mapping": { - "debug": "#/components/schemas/DebugLayerRead", - "plaintext": "#/components/schemas/PlaintextLayerRead" - } + "type": "array", + "items": { + "$ref": "#/components/schemas/UserReadPublic" }, - "title": "Response Unpublish Layer Layers Id Unpublish Post" + "title": "Response Find Public Users Platform Users Get" } } } @@ -1102,39 +1048,30 @@ } } }, - "/nodes": { - "post": { + "/platform/settings": { + "patch": { "tags": [ - "nodes" - ], - "summary": "Create node", - "description": "Creates a new node. The position will be automatically set to the last position\nof the node's parent (or the first parent before that has children).", - "operationId": "createNode", - "security": [ - { - "APIKeyCookie": [] - }, - { - "OAuth2PasswordBearer": [] - } + "platform" ], + "summary": "Update platform settings", + "operationId": "updatePlatformSettings", "requestBody": { - "required": true, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/NodeCreate" + "$ref": "#/components/schemas/PlatformSettingsUpdate" } } - } + }, + "required": true }, "responses": { - "201": { + "200": { "description": "Successful Response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/NodeRead" + "$ref": "#/components/schemas/PlatformSettingsRead" } } } @@ -1152,61 +1089,33 @@ } } } - } - }, + }, + "security": [ + { + "APIKeyCookie": [] + }, + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/platform/segments/{id}": { "get": { "tags": [ - "nodes" + "platform" ], - "summary": "Find nodes", - "operationId": "findNodes", + "summary": "Get segment", + "operationId": "getSegment", "parameters": [ { - "name": "textId", - "in": "query", + "name": "id", + "in": "path", "required": true, "schema": { "type": "string", "example": "5eb7cf5a86d9755df3a6c593", - "title": "Textid" - } - }, - { - "name": "level", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "title": "Level" - } - }, - { - "name": "position", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "title": "Position" - } - }, - { - "name": "parentId", - "in": "query", - "required": false, - "schema": { - "type": "string", - "example": "5eb7cf5a86d9755df3a6c593", - "title": "Parentid" - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "default": 1000, - "title": "Limit" + "title": "Id" } } ], @@ -1216,11 +1125,7 @@ "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/NodeRead" - }, - "title": "Response Find Nodes Nodes Get" + "$ref": "#/components/schemas/ClientSegmentRead" } } } @@ -1239,15 +1144,13 @@ } } } - } - }, - "/nodes/children": { - "get": { + }, + "patch": { "tags": [ - "nodes" + "platform" ], - "summary": "Get children", - "operationId": "getChildren", + "summary": "Update segment", + "operationId": "updateSegment", "security": [ { "APIKeyCookie": [] @@ -1258,61 +1161,33 @@ ], "parameters": [ { - "name": "parentId", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string", - "example": "5eb7cf5a86d9755df3a6c593" - }, - { - "type": "null" - } - ], - "title": "Parentid" - } - }, - { - "name": "textId", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string", - "example": "5eb7cf5a86d9755df3a6c593" - }, - { - "type": "null" - } - ], - "title": "Textid" - } - }, - { - "name": "limit", - "in": "query", - "required": false, + "name": "id", + "in": "path", + "required": true, "schema": { - "type": "integer", - "default": 9999, - "title": "Limit" + "type": "string", + "example": "5eb7cf5a86d9755df3a6c593", + "title": "Id" } } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClientSegmentUpdate" + } + } + } + }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/NodeRead" - }, - "title": "Response Get Children Nodes Children Get" + "$ref": "#/components/schemas/ClientSegmentRead" } } } @@ -1331,15 +1206,21 @@ } } } - } - }, - "/nodes/{id}": { - "get": { + }, + "delete": { "tags": [ - "nodes" + "platform" + ], + "summary": "Delete segment", + "operationId": "deleteSegment", + "security": [ + { + "APIKeyCookie": [] + }, + { + "OAuth2PasswordBearer": [] + } ], - "summary": "Get node", - "operationId": "getNode", "parameters": [ { "name": "id", @@ -1353,12 +1234,49 @@ } ], "responses": { - "200": { + "204": { + "description": "Successful Response" + }, + "404": { + "description": "Not found" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/platform/segments": { + "post": { + "tags": [ + "platform" + ], + "summary": "Create segment", + "operationId": "createSegment", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClientSegmentCreate" + } + } + }, + "required": true + }, + "responses": { + "201": { "description": "Successful Response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/NodeRead" + "$ref": "#/components/schemas/ClientSegmentRead" } } } @@ -1376,14 +1294,7 @@ } } } - } - }, - "patch": { - "tags": [ - "nodes" - ], - "summary": "Update node", - "operationId": "updateNode", + }, "security": [ { "APIKeyCookie": [] @@ -1391,17 +1302,22 @@ { "OAuth2PasswordBearer": [] } + ] + } + }, + "/resources": { + "post": { + "tags": [ + "resources" ], - "parameters": [ + "summary": "Create resource", + "operationId": "createResource", + "security": [ { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "example": "5eb7cf5a86d9755df3a6c593", - "title": "Id" - } + "APIKeyCookie": [] + }, + { + "OAuth2PasswordBearer": [] } ], "requestBody": { @@ -1409,18 +1325,48 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/NodeUpdate" + "oneOf": [ + { + "$ref": "#/components/schemas/DebugResourceCreate" + }, + { + "$ref": "#/components/schemas/PlaintextResourceCreate" + } + ], + "discriminator": { + "propertyName": "resourceType", + "mapping": { + "debug": "#/components/schemas/DebugResourceCreate", + "plaintext": "#/components/schemas/PlaintextResourceCreate" + } + }, + "title": "Resource" } } } }, "responses": { - "200": { - "description": "Successful Response", + "201": { + "description": "Created", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/NodeRead" + "oneOf": [ + { + "$ref": "#/components/schemas/DebugResourceRead" + }, + { + "$ref": "#/components/schemas/PlaintextResourceRead" + } + ], + "discriminator": { + "propertyName": "resourceType", + "mapping": { + "debug": "#/components/schemas/DebugResourceRead", + "plaintext": "#/components/schemas/PlaintextResourceRead" + } + }, + "title": "Response Create Resource Resources Post" } } } @@ -1440,13 +1386,13 @@ } } }, - "delete": { + "get": { "tags": [ - "nodes" + "resources" ], - "summary": "Delete node", - "description": "Deletes the specified node. Also deletes any associated units, child nodes and units associated with child nodes.", - "operationId": "deleteNode", + "summary": "Find resources", + "description": "Returns a list of all resources matching the given criteria.\n\nAs the resulting list of resources may contain resources of different types, the\nreturned resource objects cannot be typed to their precise resource type.", + "operationId": "findResources", "security": [ { "APIKeyCookie": [] @@ -1457,13 +1403,41 @@ ], "parameters": [ { - "name": "id", - "in": "path", + "name": "textId", + "in": "query", "required": true, "schema": { "type": "string", "example": "5eb7cf5a86d9755df3a6c593", - "title": "Id" + "title": "Textid" + } + }, + { + "name": "level", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "title": "Level" + } + }, + { + "name": "resourceType", + "in": "query", + "required": false, + "schema": { + "type": "string", + "title": "Resourcetype" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 4096, + "title": "Limit" } } ], @@ -1473,7 +1447,25 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DeleteNodeResult" + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/DebugResourceRead" + }, + { + "$ref": "#/components/schemas/PlaintextResourceRead" + } + ], + "discriminator": { + "propertyName": "resourceType", + "mapping": { + "debug": "#/components/schemas/DebugResourceRead", + "plaintext": "#/components/schemas/PlaintextResourceRead" + } + } + }, + "title": "Response Find Resources Resources Get" } } } @@ -1494,14 +1486,13 @@ } } }, - "/nodes/{id}/move": { - "post": { + "/resources/{id}": { + "patch": { "tags": [ - "nodes" + "resources" ], - "summary": "Move node", - "description": "Moves the specified node to a new position on its structure level.", - "operationId": "moveNode", + "summary": "Update resource", + "operationId": "updateResource", "security": [ { "APIKeyCookie": [] @@ -1527,7 +1518,22 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/MoveNodeRequestBody" + "oneOf": [ + { + "$ref": "#/components/schemas/DebugResourceUpdate" + }, + { + "$ref": "#/components/schemas/PlaintextResourceUpdate" + } + ], + "discriminator": { + "propertyName": "resourceType", + "mapping": { + "debug": "#/components/schemas/DebugResourceUpdate", + "plaintext": "#/components/schemas/PlaintextResourceUpdate" + } + }, + "title": "Updates" } } } @@ -1538,7 +1544,22 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/NodeRead" + "oneOf": [ + { + "$ref": "#/components/schemas/DebugResourceRead" + }, + { + "$ref": "#/components/schemas/PlaintextResourceRead" + } + ], + "discriminator": { + "propertyName": "resourceType", + "mapping": { + "debug": "#/components/schemas/DebugResourceRead", + "plaintext": "#/components/schemas/PlaintextResourceRead" + } + }, + "title": "Response Update Resource Resources Id Patch" } } } @@ -1557,31 +1578,13 @@ } } } - } - }, - "/platform": { + }, "get": { "tags": [ - "platform" + "resources" ], - "summary": "Get platform data", - "description": "Returns data the client needs to initialize", - "operationId": "getPlatformData", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PlatformData" - } - } - } - }, - "404": { - "description": "Not found" - } - }, + "summary": "Get resource", + "operationId": "getResource", "security": [ { "APIKeyCookie": [] @@ -1589,33 +1592,16 @@ { "OAuth2PasswordBearer": [] } - ] - } - }, - "/platform/users/{usernameOrId}": { - "get": { - "tags": [ - "platform" ], - "summary": "Get public user info", - "description": "Returns public information on the user with the specified username or ID", - "operationId": "getPublicUserInfo", "parameters": [ { - "name": "usernameOrId", + "name": "id", "in": "path", "required": true, "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "example": "5eb7cf5a86d9755df3a6c593" - } - ], - "title": "Usernameorid" + "type": "string", + "example": "5eb7cf5a86d9755df3a6c593", + "title": "Id" } } ], @@ -1625,7 +1611,22 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UserReadPublic" + "oneOf": [ + { + "$ref": "#/components/schemas/DebugResourceRead" + }, + { + "$ref": "#/components/schemas/PlaintextResourceRead" + } + ], + "discriminator": { + "propertyName": "resourceType", + "mapping": { + "debug": "#/components/schemas/DebugResourceRead", + "plaintext": "#/components/schemas/PlaintextResourceRead" + } + }, + "title": "Response Get Resource Resources Id Get" } } } @@ -1644,16 +1645,13 @@ } } } - } - }, - "/platform/users": { - "get": { + }, + "delete": { "tags": [ - "platform" + "resources" ], - "summary": "Find public users", - "description": "Returns a list of public users matching the given query.\n\nOnly returns active user accounts. The query is considered to match a full token\n(e.g. first name, last name, username, a word in the affiliation field).", - "operationId": "findPublicUsers", + "summary": "Delete resource", + "operationId": "deleteResource", "security": [ { "APIKeyCookie": [] @@ -1664,36 +1662,19 @@ ], "parameters": [ { - "name": "q", - "in": "query", + "name": "id", + "in": "path", "required": true, "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Q" + "type": "string", + "example": "5eb7cf5a86d9755df3a6c593", + "title": "Id" } } ], "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/UserReadPublic" - }, - "title": "Response Find Public Users Platform Users Get" - } - } - } + "204": { + "description": "Successful Response" }, "404": { "description": "Not found" @@ -1711,48 +1692,13 @@ } } }, - "/platform/settings": { - "patch": { + "/resources/{id}/propose": { + "post": { "tags": [ - "platform" + "resources" ], - "summary": "Update platform settings", - "operationId": "updatePlatformSettings", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PlatformSettingsUpdate" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PlatformSettingsRead" - } - } - } - }, - "404": { - "description": "Not found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, + "summary": "Propose resource", + "operationId": "proposeResource", "security": [ { "APIKeyCookie": [] @@ -1760,16 +1706,7 @@ { "OAuth2PasswordBearer": [] } - ] - } - }, - "/platform/segments/{id}": { - "get": { - "tags": [ - "platform" ], - "summary": "Get segment", - "operationId": "getSegment", "parameters": [ { "name": "id", @@ -1788,7 +1725,22 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ClientSegmentRead" + "oneOf": [ + { + "$ref": "#/components/schemas/DebugResourceRead" + }, + { + "$ref": "#/components/schemas/PlaintextResourceRead" + } + ], + "discriminator": { + "propertyName": "resourceType", + "mapping": { + "debug": "#/components/schemas/DebugResourceRead", + "plaintext": "#/components/schemas/PlaintextResourceRead" + } + }, + "title": "Response Propose Resource Resources Id Propose Post" } } } @@ -1807,13 +1759,15 @@ } } } - }, - "patch": { + } + }, + "/resources/{id}/unpropose": { + "post": { "tags": [ - "platform" + "resources" ], - "summary": "Update segment", - "operationId": "updateSegment", + "summary": "Unpropose resource", + "operationId": "unproposeResource", "security": [ { "APIKeyCookie": [] @@ -1834,23 +1788,28 @@ } } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ClientSegmentUpdate" - } - } - } - }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ClientSegmentRead" + "oneOf": [ + { + "$ref": "#/components/schemas/DebugResourceRead" + }, + { + "$ref": "#/components/schemas/PlaintextResourceRead" + } + ], + "discriminator": { + "propertyName": "resourceType", + "mapping": { + "debug": "#/components/schemas/DebugResourceRead", + "plaintext": "#/components/schemas/PlaintextResourceRead" + } + }, + "title": "Response Unpropose Resource Resources Id Unpropose Post" } } } @@ -1869,13 +1828,15 @@ } } } - }, - "delete": { + } + }, + "/resources/{id}/publish": { + "post": { "tags": [ - "platform" + "resources" ], - "summary": "Delete segment", - "operationId": "deleteSegment", + "summary": "Publish resource", + "operationId": "publishResource", "security": [ { "APIKeyCookie": [] @@ -1897,8 +1858,30 @@ } ], "responses": { - "204": { - "description": "Successful Response" + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/DebugResourceRead" + }, + { + "$ref": "#/components/schemas/PlaintextResourceRead" + } + ], + "discriminator": { + "propertyName": "resourceType", + "mapping": { + "debug": "#/components/schemas/DebugResourceRead", + "plaintext": "#/components/schemas/PlaintextResourceRead" + } + }, + "title": "Response Publish Resource Resources Id Publish Post" + } + } + } }, "404": { "description": "Not found" @@ -1916,30 +1899,55 @@ } } }, - "/platform/segments": { + "/resources/{id}/unpublish": { "post": { "tags": [ - "platform" + "resources" ], - "summary": "Create segment", - "operationId": "createSegment", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ClientSegmentCreate" - } + "summary": "Unpublish resource", + "operationId": "unpublishResource", + "security": [ + { + "APIKeyCookie": [] + }, + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "example": "5eb7cf5a86d9755df3a6c593", + "title": "Id" } - }, - "required": true - }, + } + ], "responses": { - "201": { + "200": { "description": "Successful Response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ClientSegmentRead" + "oneOf": [ + { + "$ref": "#/components/schemas/DebugResourceRead" + }, + { + "$ref": "#/components/schemas/PlaintextResourceRead" + } + ], + "discriminator": { + "propertyName": "resourceType", + "mapping": { + "debug": "#/components/schemas/DebugResourceRead", + "plaintext": "#/components/schemas/PlaintextResourceRead" + } + }, + "title": "Response Unpublish Resource Resources Id Unpublish Post" } } } @@ -1957,15 +1965,7 @@ } } } - }, - "security": [ - { - "APIKeyCookie": [] - }, - { - "OAuth2PasswordBearer": [] - } - ] + } } }, "/texts": { @@ -2522,7 +2522,7 @@ } ], "discriminator": { - "propertyName": "layerType", + "propertyName": "resourceType", "mapping": { "debug": "#/components/schemas/DebugUnitCreate", "plaintext": "#/components/schemas/PlaintextUnitCreate" @@ -2548,7 +2548,7 @@ } ], "discriminator": { - "propertyName": "layerType", + "propertyName": "resourceType", "mapping": { "debug": "#/components/schemas/DebugUnitRead", "plaintext": "#/components/schemas/PlaintextUnitRead" @@ -2579,7 +2579,7 @@ "units" ], "summary": "Find units", - "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.", + "description": "Returns a list of all resource units matching the given criteria.\n\nRespects restricted resources and inactive texts.\nAs the resulting list may contain units of different types, the\nreturned unit objects cannot be typed to their precise resource unit type.", "operationId": "findUnits", "security": [ { @@ -2591,7 +2591,7 @@ ], "parameters": [ { - "name": "layerId", + "name": "resourceId", "in": "query", "required": false, "schema": { @@ -2600,11 +2600,11 @@ "type": "string", "example": "5eb7cf5a86d9755df3a6c593" }, - "description": "ID (or list of IDs) of layer(s) to return unit data for", + "description": "ID (or list of IDs) of resource(s) to return unit data for", "default": [], - "title": "Layerid" + "title": "Resourceid" }, - "description": "ID (or list of IDs) of layer(s) to return unit data for" + "description": "ID (or list of IDs) of resource(s) to return unit data for" }, { "name": "nodeId", @@ -2652,7 +2652,7 @@ } ], "discriminator": { - "propertyName": "layerType", + "propertyName": "resourceType", "mapping": { "debug": "#/components/schemas/DebugUnitRead", "plaintext": "#/components/schemas/PlaintextUnitRead" @@ -2723,7 +2723,7 @@ } ], "discriminator": { - "propertyName": "layerType", + "propertyName": "resourceType", "mapping": { "debug": "#/components/schemas/DebugUnitRead", "plaintext": "#/components/schemas/PlaintextUnitRead" @@ -2789,7 +2789,7 @@ } ], "discriminator": { - "propertyName": "layerType", + "propertyName": "resourceType", "mapping": { "debug": "#/components/schemas/DebugUnitUpdate", "plaintext": "#/components/schemas/PlaintextUnitUpdate" @@ -2815,7 +2815,7 @@ } ], "discriminator": { - "propertyName": "layerType", + "propertyName": "resourceType", "mapping": { "debug": "#/components/schemas/DebugUnitRead", "plaintext": "#/components/schemas/PlaintextUnitRead" @@ -4090,51 +4090,51 @@ "type": "object", "title": "ClientSegmentUpdate" }, - "DebugLayerConfig": { + "DebugResourceConfig": { "properties": { "showOnParentLevel": { "type": "boolean", "title": "Showonparentlevel", - "description": "Show combined contents of this layer on the parent level", + "description": "Show combined contents of this resource on the parent level", "default": false } }, "type": "object", - "title": "DebugLayerConfig" + "title": "DebugResourceConfig" }, - "DebugLayerCreate": { + "DebugResourceCreate": { "properties": { "title": { "type": "string", "maxLength": 64, "minLength": 1, "title": "Title", - "description": "Title of this layer" + "description": "Title of this resource" }, "description": { "items": { - "$ref": "#/components/schemas/LayerDescriptionTranslation" + "$ref": "#/components/schemas/ResourceDescriptionTranslation" }, "type": "array", "maxItems": 3, "title": "Description", - "description": "Short, concise description of this data layer", + "description": "Short, concise description of this resource", "default": [] }, "textId": { "type": "string", "title": "Textid", - "description": "ID of the text this layer belongs to", + "description": "ID of the text this resource belongs to", "example": "5eb7cf5a86d9755df3a6c593" }, "level": { "type": "integer", "title": "Level", - "description": "Text level this layer belongs to" + "description": "Text level this resource belongs to" }, - "layerType": { + "resourceType": { "const": "debug", - "title": "Layertype" + "title": "Resourcetype" }, "ownerId": { "anyOf": [ @@ -4147,7 +4147,7 @@ } ], "title": "Ownerid", - "description": "User owning this layer" + "description": "User owning this resource" }, "category": { "anyOf": [ @@ -4160,7 +4160,7 @@ } ], "title": "Category", - "description": "Data layer category key" + "description": "Resource category key" }, "sharedRead": { "items": { @@ -4170,7 +4170,7 @@ "type": "array", "maxItems": 64, "title": "Sharedread", - "description": "Users with shared read access to this layer", + "description": "Users with shared read access to this resource", "default": [] }, "sharedWrite": { @@ -4181,19 +4181,19 @@ "type": "array", "maxItems": 64, "title": "Sharedwrite", - "description": "Users with shared write access to this layer", + "description": "Users with shared write access to this resource", "default": [] }, "public": { "type": "boolean", "title": "Public", - "description": "Publication status of this layer", + "description": "Publication status of this resource", "default": false }, "proposed": { "type": "boolean", "title": "Proposed", - "description": "Whether this layer has been proposed for publication", + "description": "Whether this resource has been proposed for publication", "default": false }, "citation": { @@ -4207,7 +4207,7 @@ } ], "title": "Citation", - "description": "Citation details for this layer" + "description": "Citation details for this resource" }, "meta": { "items": { @@ -4222,18 +4222,18 @@ }, "comment": { "items": { - "$ref": "#/components/schemas/LayerCommentTranslation" + "$ref": "#/components/schemas/ResourceCommentTranslation" }, "type": "array", "maxItems": 3, "title": "Comment", - "description": "Plaintext, potentially multiline comment on this layer", + "description": "Plaintext, potentially multiline comment on this resource", "default": [] }, "config": { "allOf": [ { - "$ref": "#/components/schemas/DebugLayerConfig" + "$ref": "#/components/schemas/DebugResourceConfig" } ], "default": { @@ -4246,11 +4246,11 @@ "title", "textId", "level", - "layerType" + "resourceType" ], - "title": "DebugLayerCreate" + "title": "DebugResourceCreate" }, - "DebugLayerRead": { + "DebugResourceRead": { "properties": { "id": { "type": "string", @@ -4267,7 +4267,7 @@ } ], "title": "Writable", - "description": "Whether this layer is writable for the requesting user" + "description": "Whether this resource is writable for the requesting user" }, "owner": { "anyOf": [ @@ -4278,7 +4278,7 @@ "type": "null" } ], - "description": "Public user data for user owning this layer" + "description": "Public user data for user owning this resource" }, "sharedReadUsers": { "anyOf": [ @@ -4293,7 +4293,7 @@ } ], "title": "Sharedreadusers", - "description": "Public user data for users allowed to read this layer" + "description": "Public user data for users allowed to read this resource" }, "sharedWriteUsers": { "anyOf": [ @@ -4308,39 +4308,39 @@ } ], "title": "Sharedwriteusers", - "description": "Public user data for users allowed to write this layer" + "description": "Public user data for users allowed to write this resource" }, "title": { "type": "string", "maxLength": 64, "minLength": 1, "title": "Title", - "description": "Title of this layer" + "description": "Title of this resource" }, "description": { "items": { - "$ref": "#/components/schemas/LayerDescriptionTranslation" + "$ref": "#/components/schemas/ResourceDescriptionTranslation" }, "type": "array", "maxItems": 3, "title": "Description", - "description": "Short, concise description of this data layer", + "description": "Short, concise description of this resource", "default": [] }, "textId": { "type": "string", "title": "Textid", - "description": "ID of the text this layer belongs to", + "description": "ID of the text this resource belongs to", "example": "5eb7cf5a86d9755df3a6c593" }, "level": { "type": "integer", "title": "Level", - "description": "Text level this layer belongs to" + "description": "Text level this resource belongs to" }, - "layerType": { + "resourceType": { "const": "debug", - "title": "Layertype" + "title": "Resourcetype" }, "ownerId": { "anyOf": [ @@ -4353,7 +4353,7 @@ } ], "title": "Ownerid", - "description": "User owning this layer" + "description": "User owning this resource" }, "category": { "anyOf": [ @@ -4366,7 +4366,7 @@ } ], "title": "Category", - "description": "Data layer category key" + "description": "Resource category key" }, "sharedRead": { "items": { @@ -4376,7 +4376,7 @@ "type": "array", "maxItems": 64, "title": "Sharedread", - "description": "Users with shared read access to this layer", + "description": "Users with shared read access to this resource", "default": [] }, "sharedWrite": { @@ -4387,19 +4387,19 @@ "type": "array", "maxItems": 64, "title": "Sharedwrite", - "description": "Users with shared write access to this layer", + "description": "Users with shared write access to this resource", "default": [] }, "public": { "type": "boolean", "title": "Public", - "description": "Publication status of this layer", + "description": "Publication status of this resource", "default": false }, "proposed": { "type": "boolean", "title": "Proposed", - "description": "Whether this layer has been proposed for publication", + "description": "Whether this resource has been proposed for publication", "default": false }, "citation": { @@ -4413,7 +4413,7 @@ } ], "title": "Citation", - "description": "Citation details for this layer" + "description": "Citation details for this resource" }, "meta": { "items": { @@ -4428,18 +4428,18 @@ }, "comment": { "items": { - "$ref": "#/components/schemas/LayerCommentTranslation" + "$ref": "#/components/schemas/ResourceCommentTranslation" }, "type": "array", "maxItems": 3, "title": "Comment", - "description": "Plaintext, potentially multiline comment on this layer", + "description": "Plaintext, potentially multiline comment on this resource", "default": [] }, "config": { "allOf": [ { - "$ref": "#/components/schemas/DebugLayerConfig" + "$ref": "#/components/schemas/DebugResourceConfig" } ], "default": { @@ -4454,11 +4454,11 @@ "title", "textId", "level", - "layerType" + "resourceType" ], - "title": "DebugLayerRead" + "title": "DebugResourceRead" }, - "DebugLayerUpdate": { + "DebugResourceUpdate": { "properties": { "title": { "anyOf": [ @@ -4473,12 +4473,12 @@ }, "description": { "items": { - "$ref": "#/components/schemas/LayerDescriptionTranslation" + "$ref": "#/components/schemas/ResourceDescriptionTranslation" }, "type": "array", "maxItems": 3, "title": "Description", - "description": "Short, concise description of this data layer", + "description": "Short, concise description of this resource", "default": [] }, "textId": { @@ -4504,9 +4504,9 @@ ], "title": "Level" }, - "layerType": { + "resourceType": { "const": "debug", - "title": "Layertype" + "title": "Resourcetype" }, "ownerId": { "anyOf": [ @@ -4519,7 +4519,7 @@ } ], "title": "Ownerid", - "description": "User owning this layer" + "description": "User owning this resource" }, "category": { "anyOf": [ @@ -4532,7 +4532,7 @@ } ], "title": "Category", - "description": "Data layer category key" + "description": "Resource category key" }, "sharedRead": { "items": { @@ -4542,7 +4542,7 @@ "type": "array", "maxItems": 64, "title": "Sharedread", - "description": "Users with shared read access to this layer", + "description": "Users with shared read access to this resource", "default": [] }, "sharedWrite": { @@ -4553,19 +4553,19 @@ "type": "array", "maxItems": 64, "title": "Sharedwrite", - "description": "Users with shared write access to this layer", + "description": "Users with shared write access to this resource", "default": [] }, "public": { "type": "boolean", "title": "Public", - "description": "Publication status of this layer", + "description": "Publication status of this resource", "default": false }, "proposed": { "type": "boolean", "title": "Proposed", - "description": "Whether this layer has been proposed for publication", + "description": "Whether this resource has been proposed for publication", "default": false }, "citation": { @@ -4579,7 +4579,7 @@ } ], "title": "Citation", - "description": "Citation details for this layer" + "description": "Citation details for this resource" }, "meta": { "items": { @@ -4594,18 +4594,18 @@ }, "comment": { "items": { - "$ref": "#/components/schemas/LayerCommentTranslation" + "$ref": "#/components/schemas/ResourceCommentTranslation" }, "type": "array", "maxItems": 3, "title": "Comment", - "description": "Plaintext, potentially multiline comment on this layer", + "description": "Plaintext, potentially multiline comment on this resource", "default": [] }, "config": { "allOf": [ { - "$ref": "#/components/schemas/DebugLayerConfig" + "$ref": "#/components/schemas/DebugResourceConfig" } ], "default": { @@ -4615,21 +4615,21 @@ }, "type": "object", "required": [ - "layerType" + "resourceType" ], - "title": "DebugLayerUpdate" + "title": "DebugResourceUpdate" }, "DebugUnitCreate": { "properties": { - "layerId": { + "resourceId": { "type": "string", - "title": "Layerid", - "description": "Data layer ID", + "title": "Resourceid", + "description": "Resource ID", "example": "5eb7cf5a86d9755df3a6c593" }, - "layerType": { + "resourceType": { "const": "debug", - "title": "Layertype" + "title": "Resourcetype" }, "nodeId": { "type": "string", @@ -4665,8 +4665,8 @@ }, "type": "object", "required": [ - "layerId", - "layerType", + "resourceId", + "resourceType", "nodeId" ], "title": "DebugUnitCreate" @@ -4678,15 +4678,15 @@ "title": "Id", "example": "5eb7cf5a86d9755df3a6c593" }, - "layerId": { + "resourceId": { "type": "string", - "title": "Layerid", - "description": "Data layer ID", + "title": "Resourceid", + "description": "Resource ID", "example": "5eb7cf5a86d9755df3a6c593" }, - "layerType": { + "resourceType": { "const": "debug", - "title": "Layertype" + "title": "Resourcetype" }, "nodeId": { "type": "string", @@ -4724,15 +4724,15 @@ "type": "object", "required": [ "id", - "layerId", - "layerType", + "resourceId", + "resourceType", "nodeId" ], "title": "DebugUnitRead" }, "DebugUnitUpdate": { "properties": { - "layerId": { + "resourceId": { "anyOf": [ { "type": "string", @@ -4742,11 +4742,11 @@ "type": "null" } ], - "title": "Layerid" + "title": "Resourceid" }, - "layerType": { + "resourceType": { "const": "debug", - "title": "Layertype" + "title": "Resourcetype" }, "nodeId": { "anyOf": [ @@ -4788,7 +4788,7 @@ }, "type": "object", "required": [ - "layerType" + "resourceType" ], "title": "DebugUnitUpdate" }, @@ -4942,134 +4942,6 @@ "type": "object", "title": "HTTPValidationError" }, - "LayerCategory-Input": { - "properties": { - "key": { - "type": "string", - "maxLength": 16, - "minLength": 1, - "title": "Key" - }, - "translations": { - "items": { - "$ref": "#/components/schemas/LayerCategoryTranslation" - }, - "type": "array", - "maxItems": 3, - "title": "Translations" - } - }, - "type": "object", - "required": [ - "key", - "translations" - ], - "title": "LayerCategory" - }, - "LayerCategory-Output": { - "properties": { - "key": { - "type": "string", - "maxLength": 16, - "minLength": 1, - "title": "Key" - }, - "translations": { - "items": { - "$ref": "#/components/schemas/LayerCategoryTranslation" - }, - "type": "array", - "maxItems": 3, - "title": "Translations" - } - }, - "type": "object", - "required": [ - "key", - "translations" - ], - "title": "LayerCategory" - }, - "LayerCategoryTranslation": { - "properties": { - "locale": { - "$ref": "#/components/schemas/Locale" - }, - "translation": { - "type": "string", - "maxLength": 32, - "minLength": 1, - "title": "Translation" - } - }, - "type": "object", - "required": [ - "locale", - "translation" - ], - "title": "LayerCategoryTranslation" - }, - "LayerCommentTranslation": { - "properties": { - "locale": { - "$ref": "#/components/schemas/Locale" - }, - "translation": { - "type": "string", - "maxLength": 2000, - "minLength": 1, - "title": "Translation" - } - }, - "type": "object", - "required": [ - "locale", - "translation" - ], - "title": "LayerCommentTranslation" - }, - "LayerDescriptionTranslation": { - "properties": { - "locale": { - "$ref": "#/components/schemas/Locale" - }, - "translation": { - "type": "string", - "maxLength": 512, - "minLength": 1, - "title": "Translation" - } - }, - "type": "object", - "required": [ - "locale", - "translation" - ], - "title": "LayerDescriptionTranslation" - }, - "LayerNodeCoverage": { - "properties": { - "label": { - "type": "string", - "title": "Label" - }, - "position": { - "type": "integer", - "title": "Position" - }, - "covered": { - "type": "boolean", - "title": "Covered" - } - }, - "type": "object", - "required": [ - "label", - "position", - "covered" - ], - "title": "LayerNodeCoverage" - }, "Locale": { "type": "string", "enum": [ @@ -5304,12 +5176,12 @@ "type": "object", "title": "NodeUpdate" }, - "PlaintextLayerConfig": { + "PlaintextResourceConfig": { "properties": { "showOnParentLevel": { "type": "boolean", "title": "Showonparentlevel", - "description": "Show combined contents of this layer on the parent level", + "description": "Show combined contents of this resource on the parent level", "default": false }, "deeplLinks": { @@ -5329,41 +5201,41 @@ } }, "type": "object", - "title": "PlaintextLayerConfig" + "title": "PlaintextResourceConfig" }, - "PlaintextLayerCreate": { + "PlaintextResourceCreate": { "properties": { "title": { "type": "string", "maxLength": 64, "minLength": 1, "title": "Title", - "description": "Title of this layer" + "description": "Title of this resource" }, "description": { "items": { - "$ref": "#/components/schemas/LayerDescriptionTranslation" + "$ref": "#/components/schemas/ResourceDescriptionTranslation" }, "type": "array", "maxItems": 3, "title": "Description", - "description": "Short, concise description of this data layer", + "description": "Short, concise description of this resource", "default": [] }, "textId": { "type": "string", "title": "Textid", - "description": "ID of the text this layer belongs to", + "description": "ID of the text this resource belongs to", "example": "5eb7cf5a86d9755df3a6c593" }, "level": { "type": "integer", "title": "Level", - "description": "Text level this layer belongs to" + "description": "Text level this resource belongs to" }, - "layerType": { + "resourceType": { "const": "plaintext", - "title": "Layertype" + "title": "Resourcetype" }, "ownerId": { "anyOf": [ @@ -5376,7 +5248,7 @@ } ], "title": "Ownerid", - "description": "User owning this layer" + "description": "User owning this resource" }, "category": { "anyOf": [ @@ -5389,7 +5261,7 @@ } ], "title": "Category", - "description": "Data layer category key" + "description": "Resource category key" }, "sharedRead": { "items": { @@ -5399,7 +5271,7 @@ "type": "array", "maxItems": 64, "title": "Sharedread", - "description": "Users with shared read access to this layer", + "description": "Users with shared read access to this resource", "default": [] }, "sharedWrite": { @@ -5410,19 +5282,19 @@ "type": "array", "maxItems": 64, "title": "Sharedwrite", - "description": "Users with shared write access to this layer", + "description": "Users with shared write access to this resource", "default": [] }, "public": { "type": "boolean", "title": "Public", - "description": "Publication status of this layer", + "description": "Publication status of this resource", "default": false }, "proposed": { "type": "boolean", "title": "Proposed", - "description": "Whether this layer has been proposed for publication", + "description": "Whether this resource has been proposed for publication", "default": false }, "citation": { @@ -5436,7 +5308,7 @@ } ], "title": "Citation", - "description": "Citation details for this layer" + "description": "Citation details for this resource" }, "meta": { "items": { @@ -5451,18 +5323,18 @@ }, "comment": { "items": { - "$ref": "#/components/schemas/LayerCommentTranslation" + "$ref": "#/components/schemas/ResourceCommentTranslation" }, "type": "array", "maxItems": 3, "title": "Comment", - "description": "Plaintext, potentially multiline comment on this layer", + "description": "Plaintext, potentially multiline comment on this resource", "default": [] }, "config": { "allOf": [ { - "$ref": "#/components/schemas/PlaintextLayerConfig" + "$ref": "#/components/schemas/PlaintextResourceConfig" } ], "default": { @@ -5483,11 +5355,11 @@ "title", "textId", "level", - "layerType" + "resourceType" ], - "title": "PlaintextLayerCreate" + "title": "PlaintextResourceCreate" }, - "PlaintextLayerRead": { + "PlaintextResourceRead": { "properties": { "id": { "type": "string", @@ -5504,7 +5376,7 @@ } ], "title": "Writable", - "description": "Whether this layer is writable for the requesting user" + "description": "Whether this resource is writable for the requesting user" }, "owner": { "anyOf": [ @@ -5515,7 +5387,7 @@ "type": "null" } ], - "description": "Public user data for user owning this layer" + "description": "Public user data for user owning this resource" }, "sharedReadUsers": { "anyOf": [ @@ -5530,7 +5402,7 @@ } ], "title": "Sharedreadusers", - "description": "Public user data for users allowed to read this layer" + "description": "Public user data for users allowed to read this resource" }, "sharedWriteUsers": { "anyOf": [ @@ -5545,39 +5417,39 @@ } ], "title": "Sharedwriteusers", - "description": "Public user data for users allowed to write this layer" + "description": "Public user data for users allowed to write this resource" }, "title": { "type": "string", "maxLength": 64, "minLength": 1, "title": "Title", - "description": "Title of this layer" + "description": "Title of this resource" }, "description": { "items": { - "$ref": "#/components/schemas/LayerDescriptionTranslation" + "$ref": "#/components/schemas/ResourceDescriptionTranslation" }, "type": "array", "maxItems": 3, "title": "Description", - "description": "Short, concise description of this data layer", + "description": "Short, concise description of this resource", "default": [] }, "textId": { "type": "string", "title": "Textid", - "description": "ID of the text this layer belongs to", + "description": "ID of the text this resource belongs to", "example": "5eb7cf5a86d9755df3a6c593" }, "level": { "type": "integer", "title": "Level", - "description": "Text level this layer belongs to" + "description": "Text level this resource belongs to" }, - "layerType": { + "resourceType": { "const": "plaintext", - "title": "Layertype" + "title": "Resourcetype" }, "ownerId": { "anyOf": [ @@ -5590,7 +5462,7 @@ } ], "title": "Ownerid", - "description": "User owning this layer" + "description": "User owning this resource" }, "category": { "anyOf": [ @@ -5603,7 +5475,7 @@ } ], "title": "Category", - "description": "Data layer category key" + "description": "Resource category key" }, "sharedRead": { "items": { @@ -5613,7 +5485,7 @@ "type": "array", "maxItems": 64, "title": "Sharedread", - "description": "Users with shared read access to this layer", + "description": "Users with shared read access to this resource", "default": [] }, "sharedWrite": { @@ -5624,19 +5496,19 @@ "type": "array", "maxItems": 64, "title": "Sharedwrite", - "description": "Users with shared write access to this layer", + "description": "Users with shared write access to this resource", "default": [] }, "public": { "type": "boolean", "title": "Public", - "description": "Publication status of this layer", + "description": "Publication status of this resource", "default": false }, "proposed": { "type": "boolean", "title": "Proposed", - "description": "Whether this layer has been proposed for publication", + "description": "Whether this resource has been proposed for publication", "default": false }, "citation": { @@ -5650,7 +5522,7 @@ } ], "title": "Citation", - "description": "Citation details for this layer" + "description": "Citation details for this resource" }, "meta": { "items": { @@ -5665,18 +5537,18 @@ }, "comment": { "items": { - "$ref": "#/components/schemas/LayerCommentTranslation" + "$ref": "#/components/schemas/ResourceCommentTranslation" }, "type": "array", "maxItems": 3, "title": "Comment", - "description": "Plaintext, potentially multiline comment on this layer", + "description": "Plaintext, potentially multiline comment on this resource", "default": [] }, "config": { "allOf": [ { - "$ref": "#/components/schemas/PlaintextLayerConfig" + "$ref": "#/components/schemas/PlaintextResourceConfig" } ], "default": { @@ -5699,11 +5571,11 @@ "title", "textId", "level", - "layerType" + "resourceType" ], - "title": "PlaintextLayerRead" + "title": "PlaintextResourceRead" }, - "PlaintextLayerUpdate": { + "PlaintextResourceUpdate": { "properties": { "title": { "anyOf": [ @@ -5718,12 +5590,12 @@ }, "description": { "items": { - "$ref": "#/components/schemas/LayerDescriptionTranslation" + "$ref": "#/components/schemas/ResourceDescriptionTranslation" }, "type": "array", "maxItems": 3, "title": "Description", - "description": "Short, concise description of this data layer", + "description": "Short, concise description of this resource", "default": [] }, "textId": { @@ -5749,9 +5621,9 @@ ], "title": "Level" }, - "layerType": { + "resourceType": { "const": "plaintext", - "title": "Layertype" + "title": "Resourcetype" }, "ownerId": { "anyOf": [ @@ -5764,7 +5636,7 @@ } ], "title": "Ownerid", - "description": "User owning this layer" + "description": "User owning this resource" }, "category": { "anyOf": [ @@ -5777,7 +5649,7 @@ } ], "title": "Category", - "description": "Data layer category key" + "description": "Resource category key" }, "sharedRead": { "items": { @@ -5787,7 +5659,7 @@ "type": "array", "maxItems": 64, "title": "Sharedread", - "description": "Users with shared read access to this layer", + "description": "Users with shared read access to this resource", "default": [] }, "sharedWrite": { @@ -5798,19 +5670,19 @@ "type": "array", "maxItems": 64, "title": "Sharedwrite", - "description": "Users with shared write access to this layer", + "description": "Users with shared write access to this resource", "default": [] }, "public": { "type": "boolean", "title": "Public", - "description": "Publication status of this layer", + "description": "Publication status of this resource", "default": false }, "proposed": { "type": "boolean", "title": "Proposed", - "description": "Whether this layer has been proposed for publication", + "description": "Whether this resource has been proposed for publication", "default": false }, "citation": { @@ -5824,7 +5696,7 @@ } ], "title": "Citation", - "description": "Citation details for this layer" + "description": "Citation details for this resource" }, "meta": { "items": { @@ -5839,18 +5711,18 @@ }, "comment": { "items": { - "$ref": "#/components/schemas/LayerCommentTranslation" + "$ref": "#/components/schemas/ResourceCommentTranslation" }, "type": "array", "maxItems": 3, "title": "Comment", - "description": "Plaintext, potentially multiline comment on this layer", + "description": "Plaintext, potentially multiline comment on this resource", "default": [] }, "config": { "allOf": [ { - "$ref": "#/components/schemas/PlaintextLayerConfig" + "$ref": "#/components/schemas/PlaintextResourceConfig" } ], "default": { @@ -5868,21 +5740,21 @@ }, "type": "object", "required": [ - "layerType" + "resourceType" ], - "title": "PlaintextLayerUpdate" + "title": "PlaintextResourceUpdate" }, "PlaintextUnitCreate": { "properties": { - "layerId": { + "resourceId": { "type": "string", - "title": "Layerid", - "description": "Data layer ID", + "title": "Resourceid", + "description": "Resource ID", "example": "5eb7cf5a86d9755df3a6c593" }, - "layerType": { + "resourceType": { "const": "plaintext", - "title": "Layertype" + "title": "Resourcetype" }, "nodeId": { "type": "string", @@ -5918,8 +5790,8 @@ }, "type": "object", "required": [ - "layerId", - "layerType", + "resourceId", + "resourceType", "nodeId" ], "title": "PlaintextUnitCreate" @@ -5931,15 +5803,15 @@ "title": "Id", "example": "5eb7cf5a86d9755df3a6c593" }, - "layerId": { + "resourceId": { "type": "string", - "title": "Layerid", - "description": "Data layer ID", + "title": "Resourceid", + "description": "Resource ID", "example": "5eb7cf5a86d9755df3a6c593" }, - "layerType": { + "resourceType": { "const": "plaintext", - "title": "Layertype" + "title": "Resourcetype" }, "nodeId": { "type": "string", @@ -5977,15 +5849,15 @@ "type": "object", "required": [ "id", - "layerId", - "layerType", + "resourceId", + "resourceType", "nodeId" ], "title": "PlaintextUnitRead" }, "PlaintextUnitUpdate": { "properties": { - "layerId": { + "resourceId": { "anyOf": [ { "type": "string", @@ -5995,11 +5867,11 @@ "type": "null" } ], - "title": "Layerid" + "title": "Resourceid" }, - "layerType": { + "resourceType": { "const": "plaintext", - "title": "Layertype" + "title": "Resourcetype" }, "nodeId": { "anyOf": [ @@ -6041,7 +5913,7 @@ }, "type": "object", "required": [ - "layerType" + "resourceType" ], "title": "PlaintextUnitUpdate" }, @@ -6283,19 +6155,19 @@ "description": "Custom label for main navigation info entry", "default": [] }, - "layerCategories": { + "resourceCategories": { "items": { - "$ref": "#/components/schemas/LayerCategory-Output" + "$ref": "#/components/schemas/ResourceCategory-Output" }, "type": "array", - "title": "Layercategories", - "description": "Layer categories to categorize layers in", + "title": "Resourcecategories", + "description": "Resource categories to categorize resources in", "default": [] }, - "showLayerCategoryHeadings": { + "showResourceCategoryHeadings": { "type": "boolean", - "title": "Showlayercategoryheadings", - "description": "Show layer category headings in browse view", + "title": "Showresourcecategoryheadings", + "description": "Show resource category headings in browse view", "default": true }, "alwaysShowTextInfo": { @@ -6431,19 +6303,19 @@ "description": "Custom label for main navigation info entry", "default": [] }, - "layerCategories": { + "resourceCategories": { "items": { - "$ref": "#/components/schemas/LayerCategory-Input" + "$ref": "#/components/schemas/ResourceCategory-Input" }, "type": "array", - "title": "Layercategories", - "description": "Layer categories to categorize layers in", + "title": "Resourcecategories", + "description": "Resource categories to categorize resources in", "default": [] }, - "showLayerCategoryHeadings": { + "showResourceCategoryHeadings": { "type": "boolean", - "title": "Showlayercategoryheadings", - "description": "Show layer category headings in browse view", + "title": "Showresourcecategoryheadings", + "description": "Show resource category headings in browse view", "default": true }, "alwaysShowTextInfo": { @@ -6490,6 +6362,134 @@ "title": "PlatformStats", "description": "Platform statistics data" }, + "ResourceCategory-Input": { + "properties": { + "key": { + "type": "string", + "maxLength": 16, + "minLength": 1, + "title": "Key" + }, + "translations": { + "items": { + "$ref": "#/components/schemas/ResourceCategoryTranslation" + }, + "type": "array", + "maxItems": 3, + "title": "Translations" + } + }, + "type": "object", + "required": [ + "key", + "translations" + ], + "title": "ResourceCategory" + }, + "ResourceCategory-Output": { + "properties": { + "key": { + "type": "string", + "maxLength": 16, + "minLength": 1, + "title": "Key" + }, + "translations": { + "items": { + "$ref": "#/components/schemas/ResourceCategoryTranslation" + }, + "type": "array", + "maxItems": 3, + "title": "Translations" + } + }, + "type": "object", + "required": [ + "key", + "translations" + ], + "title": "ResourceCategory" + }, + "ResourceCategoryTranslation": { + "properties": { + "locale": { + "$ref": "#/components/schemas/Locale" + }, + "translation": { + "type": "string", + "maxLength": 32, + "minLength": 1, + "title": "Translation" + } + }, + "type": "object", + "required": [ + "locale", + "translation" + ], + "title": "ResourceCategoryTranslation" + }, + "ResourceCommentTranslation": { + "properties": { + "locale": { + "$ref": "#/components/schemas/Locale" + }, + "translation": { + "type": "string", + "maxLength": 2000, + "minLength": 1, + "title": "Translation" + } + }, + "type": "object", + "required": [ + "locale", + "translation" + ], + "title": "ResourceCommentTranslation" + }, + "ResourceDescriptionTranslation": { + "properties": { + "locale": { + "$ref": "#/components/schemas/Locale" + }, + "translation": { + "type": "string", + "maxLength": 512, + "minLength": 1, + "title": "Translation" + } + }, + "type": "object", + "required": [ + "locale", + "translation" + ], + "title": "ResourceDescriptionTranslation" + }, + "ResourceNodeCoverage": { + "properties": { + "label": { + "type": "string", + "title": "Label" + }, + "position": { + "type": "integer", + "title": "Position" + }, + "covered": { + "type": "boolean", + "title": "Covered" + } + }, + "type": "object", + "required": [ + "label", + "position", + "covered" + ], + "title": "ResourceNodeCoverage" + }, "TextCreate": { "properties": { "title": { @@ -6695,24 +6695,24 @@ "type": "integer", "title": "Nodescount" }, - "layersCount": { + "resourcesCount": { "type": "integer", - "title": "Layerscount" + "title": "Resourcescount" }, - "layerTypes": { + "resourceTypes": { "additionalProperties": { "type": "integer" }, "type": "object", - "title": "Layertypes" + "title": "Resourcetypes" } }, "type": "object", "required": [ "id", "nodesCount", - "layersCount", - "layerTypes" + "resourcesCount", + "resourceTypes" ], "title": "TextStats", "description": "Text statistics data" diff --git a/Tekst-API/tekst/app.py b/Tekst-API/tekst/app.py index 39abb88f..7c81eb97 100644 --- a/Tekst-API/tekst/app.py +++ b/Tekst-API/tekst/app.py @@ -10,9 +10,9 @@ from tekst.config import TekstConfig, get_config from tekst.db import init_odm from tekst.dependencies import get_db, get_db_client -from tekst.layer_types import init_layer_types_mgr from tekst.logging import log, setup_logging from tekst.openapi import customize_openapi +from tekst.resource_types import init_resource_types_mgr from tekst.routers import setup_routes from tekst.settings import get_settings @@ -27,7 +27,7 @@ async def startup_routine(app: FastAPI) -> None: # blank line for visual separation of app runs in dev mode print(file=sys.stderr) - init_layer_types_mgr() + init_resource_types_mgr() setup_routes(app) # this is ugly, but unfortunately we don't have access to FastAPI's diff --git a/Tekst-API/tekst/auth.py b/Tekst-API/tekst/auth.py index b63eef86..39b5d7e4 100644 --- a/Tekst-API/tekst/auth.py +++ b/Tekst-API/tekst/auth.py @@ -46,7 +46,7 @@ from tekst.config import TekstConfig, get_config from tekst.email import TemplateIdentifier, send_email from tekst.logging import log -from tekst.models.layer import LayerBaseDocument +from tekst.models.resource import ResourceBaseDocument from tekst.models.unit import UnitBaseDocument from tekst.models.user import UserCreate, UserDocument, UserRead, UserUpdate @@ -227,33 +227,33 @@ async def on_after_reset_password( async def on_before_delete( self, user: UserDocument, request: Request | None = None ): - # find owned data layers - layers_docs = await LayerBaseDocument.find( - LayerBaseDocument.owner_id == user.id, with_children=True + # find owned resources + resources_docs = await ResourceBaseDocument.find( + ResourceBaseDocument.owner_id == user.id, with_children=True ).to_list() - owned_layers_ids = [layer.id for layer in layers_docs] - # delete units of owned data layers + owned_resources_ids = [resource.id for resource in resources_docs] + # delete units of owned resources await UnitBaseDocument.find( - In(UnitBaseDocument.layer_id, owned_layers_ids), + In(UnitBaseDocument.resource_id, owned_resources_ids), with_children=True, ).delete() - # delete owned data layers - await LayerBaseDocument.find_one( - In(LayerBaseDocument.id, owned_layers_ids), + # delete owned resources + await ResourceBaseDocument.find_one( + In(ResourceBaseDocument.id, owned_resources_ids), with_children=True, ).delete() - # remove user ID from layer shares - await LayerBaseDocument.find( - LayerBaseDocument.shared_read == str(user.id), + # remove user ID from resource shares + await ResourceBaseDocument.find( + ResourceBaseDocument.shared_read == str(user.id), with_children=True, ).update( - Pull(LayerBaseDocument.shared_read == str(user.id)), + Pull(ResourceBaseDocument.shared_read == str(user.id)), ) - await LayerBaseDocument.find( - LayerBaseDocument.shared_write == str(user.id), + await ResourceBaseDocument.find( + ResourceBaseDocument.shared_write == str(user.id), with_children=True, ).update( - Pull(LayerBaseDocument.shared_write == str(user.id)), + Pull(ResourceBaseDocument.shared_write == str(user.id)), ) async def on_after_delete(self, user: UserDocument, request: Request | None = None): diff --git a/Tekst-API/tekst/db.py b/Tekst-API/tekst/db.py index 21c353ab..8b4f8b86 100644 --- a/Tekst-API/tekst/db.py +++ b/Tekst-API/tekst/db.py @@ -4,15 +4,15 @@ from tekst.auth import AccessToken from tekst.config import TekstConfig, get_config -from tekst.layer_types import layer_types_mgr from tekst.logging import log -from tekst.models.layer import LayerBaseDocument from tekst.models.node import NodeDocument +from tekst.models.resource import ResourceBaseDocument from tekst.models.segment import ClientSegmentDocument from tekst.models.settings import PlatformSettingsDocument from tekst.models.text import TextDocument from tekst.models.unit import UnitBaseDocument from tekst.models.user import UserDocument +from tekst.resource_types import resource_types_mgr _cfg: TekstConfig = get_config() @@ -44,16 +44,16 @@ async def init_odm(db: Database) -> None: models = [ TextDocument, NodeDocument, - LayerBaseDocument, + ResourceBaseDocument, UnitBaseDocument, PlatformSettingsDocument, ClientSegmentDocument, UserDocument, AccessToken, ] - # add layer type models - for lt_class in layer_types_mgr.get_all().values(): - models.append(lt_class.layer_model().document_model()) + # add resource type models + for lt_class in resource_types_mgr.get_all().values(): + models.append(lt_class.resource_model().document_model()) models.append(lt_class.unit_model().document_model()) # init beanie ODM await init_beanie(database=db, allow_index_dropping=True, document_models=models) diff --git a/Tekst-API/tekst/layer_types/__init__.py b/Tekst-API/tekst/layer_types/__init__.py deleted file mode 100644 index 50f859bf..00000000 --- a/Tekst-API/tekst/layer_types/__init__.py +++ /dev/null @@ -1,207 +0,0 @@ -import importlib -import inspect -import pkgutil - -from abc import ABC, abstractmethod -from typing import Annotated, Union - -from fastapi import Body -from humps import decamelize - -from tekst.logging import log -from tekst.models.common import ReadBase -from tekst.models.layer import ( - LayerBase, - LayerBaseDocument, - LayerBaseUpdate, - LayerReadExtras, -) -from tekst.models.unit import UnitBase, UnitBaseDocument, UnitBaseUpdate - - -class LayerTypeABC(ABC): - """Abstract base class for defining a data layer type""" - - @classmethod - @abstractmethod - def get_description(cls) -> str: - """A short, one-line description of this layer type""" - ... - - @classmethod - def get_name(cls) -> str: - """Returns the name of this layer type""" - return cls.__name__ - - @classmethod - def get_key(cls) -> str: - """Returns the key identifying this layer type""" - return decamelize(cls.__name__) - - @classmethod - @abstractmethod - def layer_model(cls) -> type[LayerBase]: - """Returns the layer base model for this type of data layer""" - ... - - @classmethod - @abstractmethod - def unit_model(cls) -> type[UnitBase]: - """Returns the unit base model for units of this type of data layer""" - ... - - @classmethod - def prepare_import_template(cls) -> dict: - """Returns the base template for import data for this data layer type""" - create_model = cls.unit_model().create_model() - schema = create_model.schema() - template_fields = create_model.get_template_fields() - required = schema.get("required", []) - include_layer_props = ("description", "type", "additionalProperties") - template = { - "_title": "Title of this data layer", # will be overridden - "_level": -1, # will be overridden - "_description": cls.get_description(), - "_unitSchema": {}, # will be populated in the next step - "units": [], # will be populated on template request - } - # generate unit schema for the template - for prop, val in schema.get("properties", {}).items(): - if prop in template_fields: - unit_schema = {k: v for k, v in val.items() if k in include_layer_props} - unit_schema["required"] = prop in required - template["_unitSchema"][prop] = unit_schema - return template - - -class LayerTypeManager: - __layer_types: dict[str, LayerTypeABC] = dict() - - def register(self, layer_type_class: type[LayerTypeABC], layer_type_name: str): - # create layer/unit document models - layer_type_class.layer_model().document_model(LayerBaseDocument) - layer_type_class.layer_model().update_model(LayerBaseUpdate) - layer_type_class.unit_model().document_model(UnitBaseDocument) - layer_type_class.unit_model().update_model(UnitBaseUpdate) - # register instance - self.__layer_types[layer_type_name.lower()] = layer_type_class() - - def get(self, layer_type_name: str) -> LayerTypeABC: - return self.__layer_types.get(layer_type_name.lower()) - - def get_all(self) -> dict[str, LayerTypeABC]: - return self.__layer_types - - def list_names(self) -> list[str]: - return list(self.__layer_types.keys()) - - -def init_layer_types_mgr() -> None: - global layer_types_mgr - if layer_types_mgr is not None: - return layer_types_mgr - log.info("Initializing layer types...") - # init manager - manager = LayerTypeManager() - # get internal layer type module names - lt_modules = [mod.name.lower() for mod in pkgutil.iter_modules(__path__)] - for lt_module in lt_modules: - module = importlib.import_module(f"{__name__}.{lt_module.lower()}") - layer_types_from_module = inspect.getmembers( - module, lambda o: inspect.isclass(o) and issubclass(o, LayerTypeABC) - ) - for layer_type_impl in layer_types_from_module: - # exclude LayerTypeABC class (which is weirdly picked up here) - if layer_type_impl[1] is not LayerTypeABC: - layer_type_class = layer_type_impl[1] - # initialize layer type CRUD models (don't init document models here!) - layer_type_class.layer_model().create_model() - layer_type_class.layer_model().read_model((LayerReadExtras, ReadBase)) - layer_type_class.layer_model().update_model() - # register layer type instance with layer type manager - log.info(f"Registering layer type: {layer_type_class.get_name()}") - manager.register(layer_type_class, layer_type_class.get_key()) - layer_types_mgr = manager - - -# global variable to hold layer type manager instance -layer_types_mgr: LayerTypeManager = None -init_layer_types_mgr() - - -# ### create union type aliases for models of any layer type model - -# CREATE -AnyLayerCreate = Union[ # noqa: UP007 - tuple( - [lt.layer_model().create_model() for lt in layer_types_mgr.get_all().values()] - ) -] -AnyLayerCreateBody = Annotated[ - AnyLayerCreate, - Body(discriminator="layer_type"), -] - -# READ -AnyLayerRead = Union[ # noqa: UP007 - tuple([lt.layer_model().read_model() for lt in layer_types_mgr.get_all().values()]) -] -AnyLayerReadBody = Annotated[ - AnyLayerRead, - Body(discriminator="layer_type"), -] - -# UPDATE -AnyLayerUpdate = Union[ # noqa: UP007 - tuple( - [lt.layer_model().update_model() for lt in layer_types_mgr.get_all().values()] - ) -] -AnyLayerUpdateBody = Annotated[ - AnyLayerUpdate, - Body(discriminator="layer_type"), -] - -# DOCUMENT -AnyLayerDocument = Union[ # noqa: UP007 - tuple( - [lt.layer_model().document_model() for lt in layer_types_mgr.get_all().values()] - ) -] - - -# ### create union type aliases for models of any unit type model - -# CREATE -AnyUnitCreate = Union[ # noqa: UP007 - tuple([lt.unit_model().create_model() for lt in layer_types_mgr.get_all().values()]) -] -AnyUnitCreateBody = Annotated[ - AnyUnitCreate, - Body(discriminator="layer_type"), -] - -# READ -AnyUnitRead = Union[ # noqa: UP007 - tuple([lt.unit_model().read_model() for lt in layer_types_mgr.get_all().values()]) -] -AnyUnitReadBody = Annotated[ - AnyUnitRead, - Body(discriminator="layer_type"), -] - -# UPDATE -AnyUnitUpdate = Union[ # noqa: UP007 - tuple([lt.unit_model().update_model() for lt in layer_types_mgr.get_all().values()]) -] -AnyUnitUpdateBody = Annotated[ - AnyUnitUpdate, - Body(discriminator="layer_type"), -] - -# DOCUMENT -AnyUnitDocument = Union[ # noqa: UP007 - tuple( - [lt.unit_model().document_model() for lt in layer_types_mgr.get_all().values()] - ) -] diff --git a/Tekst-API/tekst/layer_types/debug.py b/Tekst-API/tekst/layer_types/debug.py deleted file mode 100644 index 96110e7f..00000000 --- a/Tekst-API/tekst/layer_types/debug.py +++ /dev/null @@ -1,45 +0,0 @@ -from typing import Literal - -from pydantic import Field - -from tekst.layer_types import LayerTypeABC -from tekst.models.common import LayerConfigBase -from tekst.models.layer import LayerBase -from tekst.models.unit import UnitBase - - -class Debug(LayerTypeABC): - """A simple plaintext layer type""" - - @classmethod - def get_description(cls) -> str: - return "Just a temporary debug layer" - - @classmethod - def layer_model(cls) -> type["DebugLayer"]: - return DebugLayer - - @classmethod - def unit_model(cls) -> type["DebugUnit"]: - return DebugUnit - - -class DebugLayerConfig(LayerConfigBase): - pass - - -class DebugLayer(LayerBase): - layer_type: Literal["debug"] # snake_cased layer type classname - config: DebugLayerConfig = DebugLayerConfig() - - -class DebugUnit(UnitBase): - """A unit of a plaintext data layer""" - - layer_type: Literal["debug"] # snake_cased layer type classname - text: str | None = Field( - None, - description="Text content of the debug unit", - ) - - _template_fields = ("text",) diff --git a/Tekst-API/tekst/layer_types/plaintext.py b/Tekst-API/tekst/layer_types/plaintext.py deleted file mode 100644 index de9f9bcd..00000000 --- a/Tekst-API/tekst/layer_types/plaintext.py +++ /dev/null @@ -1,46 +0,0 @@ -from typing import Literal - -from pydantic import Field - -from tekst.layer_types import LayerTypeABC -from tekst.models.common import LayerConfigBase -from tekst.models.layer import LayerBase -from tekst.models.layer_configs import DeepLLinksConfig -from tekst.models.unit import UnitBase - - -class Plaintext(LayerTypeABC): - """A simple plaintext layer type""" - - @classmethod - def get_description(cls) -> str: - return "A simple plaintext data layer" - - @classmethod - def layer_model(cls) -> type["PlaintextLayer"]: - return PlaintextLayer - - @classmethod - def unit_model(cls) -> type["PlaintextUnit"]: - return PlaintextUnit - - -class PlaintextLayerConfig(LayerConfigBase): - deepl_links: DeepLLinksConfig = DeepLLinksConfig() - - -class PlaintextLayer(LayerBase): - layer_type: Literal["plaintext"] # snake_cased layer type classname - config: PlaintextLayerConfig = PlaintextLayerConfig() - - -class PlaintextUnit(UnitBase): - """A unit of a plaintext data layer""" - - layer_type: Literal["plaintext"] # snake_cased layer type classname - text: str | None = Field( - None, - description="Text content of the plaintext unit", - ) - - _template_fields = ("text",) diff --git a/Tekst-API/tekst/models/common.py b/Tekst-API/tekst/models/common.py index 13815bae..bca0b2ab 100644 --- a/Tekst-API/tekst/models/common.py +++ b/Tekst-API/tekst/models/common.py @@ -160,8 +160,10 @@ def update_model(cls, bases: type | tuple[type] = ()) -> type[ModelBase]: return cls._update_model -class LayerConfigBase(ModelBase): +class ResourceConfigBase(ModelBase): show_on_parent_level: Annotated[ bool, - Field(description="Show combined contents of this layer on the parent level"), + Field( + description="Show combined contents of this resource on the parent level" + ), ] = False diff --git a/Tekst-API/tekst/models/platform.py b/Tekst-API/tekst/models/platform.py index ed273cac..f0758458 100644 --- a/Tekst-API/tekst/models/platform.py +++ b/Tekst-API/tekst/models/platform.py @@ -40,8 +40,8 @@ class TextStats(ModelBase): id: PydanticObjectId nodes_count: int - layers_count: int - layer_types: dict[str, int] + resources_count: int + resource_types: dict[str, int] class PlatformStats(ModelBase): diff --git a/Tekst-API/tekst/models/layer.py b/Tekst-API/tekst/models/resource.py similarity index 62% rename from Tekst-API/tekst/models/layer.py rename to Tekst-API/tekst/models/resource.py index ec15d1f8..4f5bc15f 100644 --- a/Tekst-API/tekst/models/layer.py +++ b/Tekst-API/tekst/models/resource.py @@ -14,10 +14,10 @@ from tekst.models.common import ( DocumentBase, - LayerConfigBase, Metadata, ModelBase, ModelFactoryMixin, + ResourceConfigBase, TranslationBase, Translations, ) @@ -25,73 +25,77 @@ from tekst.models.user import UserRead, UserReadPublic -class LayerDescriptionTranslation(TranslationBase): +class ResourceDescriptionTranslation(TranslationBase): translation: Annotated[ str, StringConstraints(strip_whitespace=True, min_length=1, max_length=512) ] -class LayerCommentTranslation(TranslationBase): +class ResourceCommentTranslation(TranslationBase): translation: Annotated[ str, StringConstraints(strip_whitespace=True, min_length=1, max_length=2000) ] -class LayerBase(ModelBase, ModelFactoryMixin): - """A data layer describing a set of data on a text""" +class ResourceBase(ModelBase, ModelFactoryMixin): + """A resource describing a set of data on a text""" title: Annotated[ - str, Field(min_length=1, max_length=64, description="Title of this layer") + str, Field(min_length=1, max_length=64, description="Title of this resource") ] description: Annotated[ - Translations[LayerDescriptionTranslation], + Translations[ResourceDescriptionTranslation], Field( - description="Short, concise description of this data layer", + description="Short, concise description of this resource", ), ] = [] text_id: Annotated[ PydanticObjectId, - Field(description="ID of the text this layer belongs to"), + Field(description="ID of the text this resource belongs to"), ] - level: Annotated[int, Field(description="Text level this layer belongs to")] - layer_type: Annotated[ - str, Field(description="A string identifying one of the available layer types") + level: Annotated[int, Field(description="Text level this resource belongs to")] + resource_type: Annotated[ + str, + Field(description="A string identifying one of the available resource types"), ] owner_id: Annotated[ - PydanticObjectId | None, Field(description="User owning this layer") + PydanticObjectId | None, Field(description="User owning this resource") ] = None category: Annotated[ str | None, - Field(description="Data layer category key", max_length=16), + Field(description="Resource category key", max_length=16), ] = None shared_read: Annotated[ list[PydanticObjectId], - Field(description="Users with shared read access to this layer", max_length=64), + Field( + description="Users with shared read access to this resource", max_length=64 + ), ] = [] shared_write: Annotated[ list[PydanticObjectId], Field( - description="Users with shared write access to this layer", max_length=64 + description="Users with shared write access to this resource", max_length=64 ), ] = [] public: Annotated[ - bool, Field(description="Publication status of this layer") + bool, Field(description="Publication status of this resource") ] = False proposed: Annotated[ - bool, Field(description="Whether this layer has been proposed for publication") + bool, + Field(description="Whether this resource has been proposed for publication"), ] = False citation: Annotated[ str | None, - Field(description="Citation details for this layer", max_length=1000), + Field(description="Citation details for this resource", max_length=1000), ] = None meta: Metadata = [] comment: Annotated[ - Translations[LayerCommentTranslation], + Translations[ResourceCommentTranslation], Field( - description="Plaintext, potentially multiline comment on this layer", + description="Plaintext, potentially multiline comment on this resource", ), ] = [] - config: LayerConfigBase = LayerConfigBase() + config: ResourceConfigBase = ResourceConfigBase() @field_validator("description", mode="after") @classmethod @@ -102,16 +106,16 @@ def handle_whitespaces_in_description(cls, v): ).strip() return v - @field_validator("layer_type") + @field_validator("resource_type") @classmethod - def validate_layer_type_name(cls, v): - from tekst.layer_types import layer_types_mgr + def validate_resource_type_name(cls, v): + from tekst.resource_types import resource_types_mgr - layer_type_names = layer_types_mgr.list_names() - if v.lower() not in layer_type_names: + resource_type_names = resource_types_mgr.list_names() + if v.lower() not in resource_type_names: raise ValueError( - f"Given layer type ({v}) is not a valid " - f"layer type name (one of {layer_type_names})." + f"Given resource type ({v}) is not a valid " + f"resource type name (one of {resource_type_names})." ) return v.lower() @@ -129,7 +133,7 @@ def model_postprocess(self): if self.public: # cannot be both public and proposed, public has priority self.proposed = False - # published layers do not have an owner nor shares, only admins can edit + # published resources do not have an owner nor shares, only admins can edit self.owner_id = None self.shared_read = [] self.shared_write = [] @@ -159,11 +163,11 @@ def restricted_fields(self, user: UserRead | None = None) -> set[str] | None: # as those have to be used as bases for inheriting model's document/update models -class LayerBaseDocument(LayerBase, DocumentBase): +class ResourceBaseDocument(ResourceBase, DocumentBase): class Settings(DocumentBase.Settings): - name = "layers" + name = "resources" is_root = True - indexes = ["text_id", "level", "layer_type", "owner_id"] + indexes = ["text_id", "level", "resource_type", "owner_id"] @classmethod async def allowed_to_read(cls, user: UserRead | None) -> dict: @@ -181,15 +185,15 @@ async def allowed_to_read(cls, user: UserRead | None) -> dict: return And( Or( - In(LayerBaseDocument.text_id, active_texts_ids), - LayerBaseDocument.owner_id == user.id, + In(ResourceBaseDocument.text_id, active_texts_ids), + ResourceBaseDocument.owner_id == user.id, ), Or( - LayerBaseDocument.public == True, # noqa: E712 - LayerBaseDocument.proposed == True, # noqa: E712 - LayerBaseDocument.owner_id == user.id, - LayerBaseDocument.shared_read == str(user.id), - LayerBaseDocument.shared_write == str(user.id), + ResourceBaseDocument.public == True, # noqa: E712 + ResourceBaseDocument.proposed == True, # noqa: E712 + ResourceBaseDocument.owner_id == user.id, + ResourceBaseDocument.shared_read == str(user.id), + ResourceBaseDocument.shared_write == str(user.id), ), ) @@ -199,43 +203,43 @@ def allowed_to_write(cls, user: UserRead | None) -> dict: return {} uid = user.id if user else "no_id" return Or( - LayerBaseDocument.owner_id == uid, - LayerBaseDocument.shared_write == str(uid), + ResourceBaseDocument.owner_id == uid, + ResourceBaseDocument.shared_write == str(uid), ) -class LayerReadExtras(ModelBase): +class ResourceReadExtras(ModelBase): writable: Annotated[ bool | None, - Field(description="Whether this layer is writable for the requesting user"), + Field(description="Whether this resource is writable for the requesting user"), ] = None owner: Annotated[ UserReadPublic | None, Field( - description="Public user data for user owning this layer", + description="Public user data for user owning this resource", ), ] = None shared_read_users: Annotated[ list[UserReadPublic] | None, Field( - description="Public user data for users allowed to read this layer", + description="Public user data for users allowed to read this resource", ), ] = None shared_write_users: Annotated[ list[UserReadPublic] | None, Field( - description="Public user data for users allowed to write this layer", + description="Public user data for users allowed to write this resource", ), ] = None -LayerBaseRead = create_model( - "LayerBaseRead", __base__=(LayerBase.read_model(), LayerReadExtras) +ResourceBaseRead = create_model( + "ResourceBaseRead", __base__=(ResourceBase.read_model(), ResourceReadExtras) ) -LayerBaseUpdate = LayerBase.update_model() +ResourceBaseUpdate = ResourceBase.update_model() -class LayerNodeCoverage(ModelBase): +class ResourceNodeCoverage(ModelBase): label: str position: int covered: bool diff --git a/Tekst-API/tekst/models/layer_configs.py b/Tekst-API/tekst/models/resource_configs.py similarity index 100% rename from Tekst-API/tekst/models/layer_configs.py rename to Tekst-API/tekst/models/resource_configs.py diff --git a/Tekst-API/tekst/models/settings.py b/Tekst-API/tekst/models/settings.py index 9c5e970e..f0270f2e 100644 --- a/Tekst-API/tekst/models/settings.py +++ b/Tekst-API/tekst/models/settings.py @@ -30,17 +30,17 @@ class PlatformNavInfoEntryTranslation(TranslationBase): ] -class LayerCategoryTranslation(TranslationBase): +class ResourceCategoryTranslation(TranslationBase): translation: Annotated[ str, StringConstraints(strip_whitespace=True, min_length=1, max_length=32) ] -class LayerCategory(TypedDict): +class ResourceCategory(TypedDict): key: Annotated[ str, StringConstraints(strip_whitespace=True, min_length=1, max_length=16) ] - translations: Translations[LayerCategoryTranslation] + translations: Translations[ResourceCategoryTranslation] class PlatformSettings(ModelBase, ModelFactoryMixin): @@ -86,12 +86,12 @@ class PlatformSettings(ModelBase, ModelFactoryMixin): Translations[PlatformNavInfoEntryTranslation], Field(description="Custom label for main navigation info entry"), ] = [] - layer_categories: Annotated[ - list[LayerCategory], - Field(description="Layer categories to categorize layers in"), + resource_categories: Annotated[ + list[ResourceCategory], + Field(description="Resource categories to categorize resources in"), ] = [] - show_layer_category_headings: Annotated[ - bool, Field(description="Show layer category headings in browse view") + show_resource_category_headings: Annotated[ + bool, Field(description="Show resource category headings in browse view") ] = True always_show_text_info: Annotated[ bool, diff --git a/Tekst-API/tekst/models/unit.py b/Tekst-API/tekst/models/unit.py index 23275f65..3c752ca5 100644 --- a/Tekst-API/tekst/models/unit.py +++ b/Tekst-API/tekst/models/unit.py @@ -11,11 +11,12 @@ class UnitBase(ModelBase, ModelFactoryMixin): - """A base model for types of data units belonging to a certain data layer""" + """A base model for types of data units belonging to a certain resource""" - layer_id: PydanticObjectId = Field(..., description="Data layer ID") - layer_type: Annotated[ - str, Field(description="A string identifying one of the available layer types") + resource_id: PydanticObjectId = Field(..., description="Resource ID") + resource_type: Annotated[ + str, + Field(description="A string identifying one of the available resource types"), ] node_id: PydanticObjectId = Field(..., description="Parent text node ID") comment: Annotated[ @@ -28,16 +29,16 @@ class UnitBase(ModelBase, ModelFactoryMixin): __template_fields: tuple[str] = ("comment",) - @field_validator("layer_type") + @field_validator("resource_type") @classmethod - def validate_layer_type_name(cls, v): - from tekst.layer_types import layer_types_mgr + def validate_resource_type_name(cls, v): + from tekst.resource_types import resource_types_mgr - layer_type_names = layer_types_mgr.list_names() - if v.lower() not in layer_type_names: + resource_type_names = resource_types_mgr.list_names() + if v.lower() not in resource_type_names: raise ValueError( - f"Given layer type ({v}) is not a valid " - f"layer type name (one of {layer_type_names})." + f"Given resource type ({v}) is not a valid " + f"resource type name (one of {resource_type_names})." ) return v.lower() @@ -54,7 +55,7 @@ class UnitBaseDocument(UnitBase, DocumentBase): class Settings(DocumentBase.Settings): name = "units" is_root = True - indexes = ["layer_id", "node_id"] + indexes = ["resource_id", "node_id"] UnitBaseUpdate = UnitBase.update_model() diff --git a/Tekst-API/tekst/resource_types/__init__.py b/Tekst-API/tekst/resource_types/__init__.py new file mode 100644 index 00000000..961ba5de --- /dev/null +++ b/Tekst-API/tekst/resource_types/__init__.py @@ -0,0 +1,236 @@ +import importlib +import inspect +import pkgutil + +from abc import ABC, abstractmethod +from typing import Annotated, Union + +from fastapi import Body +from humps import decamelize + +from tekst.logging import log +from tekst.models.common import ReadBase +from tekst.models.resource import ( + ResourceBase, + ResourceBaseDocument, + ResourceBaseUpdate, + ResourceReadExtras, +) +from tekst.models.unit import UnitBase, UnitBaseDocument, UnitBaseUpdate + + +class ResourceTypeABC(ABC): + """Abstract base class for defining a resource type""" + + @classmethod + @abstractmethod + def get_description(cls) -> str: + """A short, one-line description of this resource type""" + ... + + @classmethod + def get_name(cls) -> str: + """Returns the name of this resource type""" + return cls.__name__ + + @classmethod + def get_key(cls) -> str: + """Returns the key identifying this resource type""" + return decamelize(cls.__name__) + + @classmethod + @abstractmethod + def resource_model(cls) -> type[ResourceBase]: + """Returns the resource base model for this type of resource""" + ... + + @classmethod + @abstractmethod + def unit_model(cls) -> type[UnitBase]: + """Returns the unit base model for units of this type of resource""" + ... + + @classmethod + def prepare_import_template(cls) -> dict: + """Returns the base template for import data for this resource type""" + create_model = cls.unit_model().create_model() + schema = create_model.schema() + template_fields = create_model.get_template_fields() + required = schema.get("required", []) + include_resource_props = ("description", "type", "additionalProperties") + template = { + "_title": "Title of this resource", # will be overridden + "_level": -1, # will be overridden + "_description": cls.get_description(), + "_unitSchema": {}, # will be populated in the next step + "units": [], # will be populated on template request + } + # generate unit schema for the template + for prop, val in schema.get("properties", {}).items(): + if prop in template_fields: + unit_schema = { + k: v for k, v in val.items() if k in include_resource_props + } + unit_schema["required"] = prop in required + template["_unitSchema"][prop] = unit_schema + return template + + +class ResourceTypeManager: + __resource_types: dict[str, ResourceTypeABC] = dict() + + def register( + self, resource_type_class: type[ResourceTypeABC], resource_type_name: str + ): + # create resource/unit document models + resource_type_class.resource_model().document_model(ResourceBaseDocument) + resource_type_class.resource_model().update_model(ResourceBaseUpdate) + resource_type_class.unit_model().document_model(UnitBaseDocument) + resource_type_class.unit_model().update_model(UnitBaseUpdate) + # register instance + self.__resource_types[resource_type_name.lower()] = resource_type_class() + + def get(self, resource_type_name: str) -> ResourceTypeABC: + return self.__resource_types.get(resource_type_name.lower()) + + def get_all(self) -> dict[str, ResourceTypeABC]: + return self.__resource_types + + def list_names(self) -> list[str]: + return list(self.__resource_types.keys()) + + +def init_resource_types_mgr() -> None: + global resource_types_mgr + if resource_types_mgr is not None: + return resource_types_mgr + log.info("Initializing resource types...") + # init manager + manager = ResourceTypeManager() + # get internal resource type module names + lt_modules = [mod.name.lower() for mod in pkgutil.iter_modules(__path__)] + for lt_module in lt_modules: + module = importlib.import_module(f"{__name__}.{lt_module.lower()}") + resource_types_from_module = inspect.getmembers( + module, lambda o: inspect.isclass(o) and issubclass(o, ResourceTypeABC) + ) + for resource_type_impl in resource_types_from_module: + # exclude ResourceTypeABC class (which is weirdly picked up here) + if resource_type_impl[1] is not ResourceTypeABC: + resource_type_class = resource_type_impl[1] + # init resource type CRUD models (don't init document models here!) + resource_type_class.resource_model().create_model() + resource_type_class.resource_model().read_model( + (ResourceReadExtras, ReadBase) + ) + resource_type_class.resource_model().update_model() + # register resource type instance with resource type manager + log.info(f"Registering resource type: {resource_type_class.get_name()}") + manager.register(resource_type_class, resource_type_class.get_key()) + resource_types_mgr = manager + + +# global variable to hold resource type manager instance +resource_types_mgr: ResourceTypeManager = None +init_resource_types_mgr() + + +# ### create union type aliases for models of any resource type model + +# CREATE +AnyResourceCreate = Union[ # noqa: UP007 + tuple( + [ + lt.resource_model().create_model() + for lt in resource_types_mgr.get_all().values() + ] + ) +] +AnyResourceCreateBody = Annotated[ + AnyResourceCreate, + Body(discriminator="resource_type"), +] + +# READ +AnyResourceRead = Union[ # noqa: UP007 + tuple( + [ + lt.resource_model().read_model() + for lt in resource_types_mgr.get_all().values() + ] + ) +] +AnyResourceReadBody = Annotated[ + AnyResourceRead, + Body(discriminator="resource_type"), +] + +# UPDATE +AnyResourceUpdate = Union[ # noqa: UP007 + tuple( + [ + lt.resource_model().update_model() + for lt in resource_types_mgr.get_all().values() + ] + ) +] +AnyResourceUpdateBody = Annotated[ + AnyResourceUpdate, + Body(discriminator="resource_type"), +] + +# DOCUMENT +AnyResourceDocument = Union[ # noqa: UP007 + tuple( + [ + lt.resource_model().document_model() + for lt in resource_types_mgr.get_all().values() + ] + ) +] + + +# ### create union type aliases for models of any unit type model + +# CREATE +AnyUnitCreate = Union[ # noqa: UP007 + tuple( + [lt.unit_model().create_model() for lt in resource_types_mgr.get_all().values()] + ) +] +AnyUnitCreateBody = Annotated[ + AnyUnitCreate, + Body(discriminator="resource_type"), +] + +# READ +AnyUnitRead = Union[ # noqa: UP007 + tuple( + [lt.unit_model().read_model() for lt in resource_types_mgr.get_all().values()] + ) +] +AnyUnitReadBody = Annotated[ + AnyUnitRead, + Body(discriminator="resource_type"), +] + +# UPDATE +AnyUnitUpdate = Union[ # noqa: UP007 + tuple( + [lt.unit_model().update_model() for lt in resource_types_mgr.get_all().values()] + ) +] +AnyUnitUpdateBody = Annotated[ + AnyUnitUpdate, + Body(discriminator="resource_type"), +] + +# DOCUMENT +AnyUnitDocument = Union[ # noqa: UP007 + tuple( + [ + lt.unit_model().document_model() + for lt in resource_types_mgr.get_all().values() + ] + ) +] diff --git a/Tekst-API/tekst/resource_types/debug.py b/Tekst-API/tekst/resource_types/debug.py new file mode 100644 index 00000000..277dbc07 --- /dev/null +++ b/Tekst-API/tekst/resource_types/debug.py @@ -0,0 +1,45 @@ +from typing import Literal + +from pydantic import Field + +from tekst.models.common import ResourceConfigBase +from tekst.models.resource import ResourceBase +from tekst.models.unit import UnitBase +from tekst.resource_types import ResourceTypeABC + + +class Debug(ResourceTypeABC): + """A simple plaintext resource type""" + + @classmethod + def get_description(cls) -> str: + return "Just a temporary debug resource" + + @classmethod + def resource_model(cls) -> type["DebugResource"]: + return DebugResource + + @classmethod + def unit_model(cls) -> type["DebugUnit"]: + return DebugUnit + + +class DebugResourceConfig(ResourceConfigBase): + pass + + +class DebugResource(ResourceBase): + resource_type: Literal["debug"] # snake_cased resource type classname + config: DebugResourceConfig = DebugResourceConfig() + + +class DebugUnit(UnitBase): + """A unit of a plaintext resource""" + + resource_type: Literal["debug"] # snake_cased resource type classname + text: str | None = Field( + None, + description="Text content of the debug unit", + ) + + _template_fields = ("text",) diff --git a/Tekst-API/tekst/resource_types/plaintext.py b/Tekst-API/tekst/resource_types/plaintext.py new file mode 100644 index 00000000..2ca64611 --- /dev/null +++ b/Tekst-API/tekst/resource_types/plaintext.py @@ -0,0 +1,46 @@ +from typing import Literal + +from pydantic import Field + +from tekst.models.common import ResourceConfigBase +from tekst.models.resource import ResourceBase +from tekst.models.resource_configs import DeepLLinksConfig +from tekst.models.unit import UnitBase +from tekst.resource_types import ResourceTypeABC + + +class Plaintext(ResourceTypeABC): + """A simple plaintext resource type""" + + @classmethod + def get_description(cls) -> str: + return "A simple plaintext resource" + + @classmethod + def resource_model(cls) -> type["PlaintextResource"]: + return PlaintextResource + + @classmethod + def unit_model(cls) -> type["PlaintextUnit"]: + return PlaintextUnit + + +class PlaintextResourceConfig(ResourceConfigBase): + deepl_links: DeepLLinksConfig = DeepLLinksConfig() + + +class PlaintextResource(ResourceBase): + resource_type: Literal["plaintext"] # snake_cased resource type classname + config: PlaintextResourceConfig = PlaintextResourceConfig() + + +class PlaintextUnit(UnitBase): + """A unit of a plaintext resource""" + + resource_type: Literal["plaintext"] # snake_cased resource type classname + text: str | None = Field( + None, + description="Text content of the plaintext unit", + ) + + _template_fields = ("text",) diff --git a/Tekst-API/tekst/routers/admin.py b/Tekst-API/tekst/routers/admin.py index c9f389ce..8dc2fa09 100644 --- a/Tekst-API/tekst/routers/admin.py +++ b/Tekst-API/tekst/routers/admin.py @@ -2,12 +2,12 @@ from tekst.auth import SuperuserDep from tekst.email import send_test_email -from tekst.layer_types import layer_types_mgr -from tekst.models.layer import LayerBaseDocument from tekst.models.node import NodeDocument from tekst.models.platform import PlatformStats, TextStats +from tekst.models.resource import ResourceBaseDocument from tekst.models.text import TextDocument from tekst.models.user import UserDocument, UserRead +from tekst.resource_types import resource_types_mgr router = APIRouter( @@ -22,29 +22,29 @@ @router.get("/stats", response_model=PlatformStats, status_code=status.HTTP_200_OK) async def get_stats(su: SuperuserDep) -> PlatformStats: - layer_type_names = layer_types_mgr.list_names() + resource_type_names = resource_types_mgr.list_names() texts = await TextDocument.find_all().to_list() text_stats = [] for text in texts: nodes_count = await NodeDocument.find(NodeDocument.text_id == text.id).count() - layer_types = { + resource_types = { lt_name: ( - await LayerBaseDocument.find( - LayerBaseDocument.text_id == text.id, - LayerBaseDocument.layer_type == lt_name, + await ResourceBaseDocument.find( + ResourceBaseDocument.text_id == text.id, + ResourceBaseDocument.resource_type == lt_name, with_children=True, ).count() ) - for lt_name in layer_type_names + for lt_name in resource_type_names } - layers_count = sum(layer_types.values()) + resources_count = sum(resource_types.values()) text_stats.append( TextStats( id=text.id, nodes_count=nodes_count, - layers_count=layers_count, - layer_types=layer_types, + resources_count=resources_count, + resource_types=resource_types, ) ) diff --git a/Tekst-API/tekst/routers/browse.py b/Tekst-API/tekst/routers/browse.py index 0dd1b9fa..fac13339 100644 --- a/Tekst-API/tekst/routers/browse.py +++ b/Tekst-API/tekst/routers/browse.py @@ -5,13 +5,13 @@ from fastapi import APIRouter, HTTPException, Path, Query, status from tekst.auth import OptionalUserDep -from tekst.layer_types import AnyUnitRead, AnyUnitReadBody, layer_types_mgr -from tekst.models.layer import LayerBaseDocument, LayerNodeCoverage from tekst.models.node import ( NodeDocument, NodeRead, ) +from tekst.models.resource import ResourceBaseDocument, ResourceNodeCoverage from tekst.models.unit import UnitBaseDocument +from tekst.resource_types import AnyUnitRead, AnyUnitReadBody, resource_types_mgr # initialize unit router @@ -29,9 +29,12 @@ ) async def get_unit_siblings( user: OptionalUserDep, - layer_id: Annotated[ + resource_id: Annotated[ PydanticObjectId, - Query(description="ID of layer the requested units belong to", alias="layerId"), + Query( + description="ID of resource the requested units belong to", + alias="resourceId", + ), ], parent_node_id: Annotated[ PydanticObjectId | None, @@ -42,42 +45,42 @@ async def get_unit_siblings( ] = None, ) -> list[AnyUnitRead]: """ - Returns a list of all data layer units belonging to the data layer + Returns a list of all resource units belonging to the resource with the given ID, associated to nodes that are children of the parent node with the given ID. As the resulting list may contain units of arbitrary type, the - returned unit objects cannot be typed to their precise layer unit type. + returned unit objects cannot be typed to their precise resource unit type. Also, the returned unit objects have an additional property containing their respective node's label, level and position. """ - layer = await LayerBaseDocument.find_one( - LayerBaseDocument.id == layer_id, - await LayerBaseDocument.allowed_to_read(user), + resource = await ResourceBaseDocument.find_one( + ResourceBaseDocument.id == resource_id, + await ResourceBaseDocument.allowed_to_read(user), with_children=True, ) - if not layer: + if not resource: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, - detail=f"Layer with ID {layer_id} could not be found.", + detail=f"Resource with ID {resource_id} could not be found.", ) nodes = await NodeDocument.find( - NodeDocument.text_id == layer.text_id, - NodeDocument.level == layer.level, + NodeDocument.text_id == resource.text_id, + NodeDocument.level == resource.level, NodeDocument.parent_id == parent_node_id, ).to_list() unit_docs = await UnitBaseDocument.find( - UnitBaseDocument.layer_id == layer_id, + UnitBaseDocument.resource_id == resource_id, In(UnitBaseDocument.node_id, [node.id for node in nodes]), with_children=True, ).to_list() return [ - layer_types_mgr.get(unit_doc.layer_type) + resource_types_mgr.get(unit_doc.resource_type) .unit_model() .read_model()(**unit_doc.model_dump()) for unit_doc in unit_docs @@ -186,23 +189,23 @@ async def get_path_options_by_root_id( return options -@router.get("/layers/{id}/coverage", status_code=status.HTTP_200_OK) -async def get_layer_coverage_data( - layer_id: Annotated[PydanticObjectId, Path(alias="id")], user: OptionalUserDep -) -> list[LayerNodeCoverage]: - layer_doc = await LayerBaseDocument.find_one( - LayerBaseDocument.id == layer_id, - await LayerBaseDocument.allowed_to_read(user), +@router.get("/resources/{id}/coverage", status_code=status.HTTP_200_OK) +async def get_resource_coverage_data( + resource_id: Annotated[PydanticObjectId, Path(alias="id")], user: OptionalUserDep +) -> list[ResourceNodeCoverage]: + resource_doc = await ResourceBaseDocument.find_one( + ResourceBaseDocument.id == resource_id, + await ResourceBaseDocument.allowed_to_read(user), with_children=True, ) - if not layer_doc: + if not resource_doc: raise HTTPException( - status.HTTP_404_NOT_FOUND, detail=f"No layer with ID {layer_id}" + status.HTTP_404_NOT_FOUND, detail=f"No resource with ID {resource_id}" ) return ( await NodeDocument.find( - NodeDocument.text_id == layer_doc.text_id, - NodeDocument.level == layer_doc.level, + NodeDocument.text_id == resource_doc.text_id, + NodeDocument.level == resource_doc.level, ) .sort(+NodeDocument.position) .aggregate( @@ -212,14 +215,14 @@ async def get_layer_coverage_data( "from": "units", "localField": "_id", "foreignField": "node_id", - "let": {"node_id": "$_id", "layer_id": layer_id}, + "let": {"node_id": "$_id", "resource_id": resource_id}, "pipeline": [ { "$match": { "$expr": { "$and": [ {"$eq": ["$node_id", "$$node_id"]}, - {"$gte": ["$layer_id", "$$layer_id"]}, + {"$gte": ["$resource_id", "$$resource_id"]}, ] } } @@ -237,7 +240,7 @@ async def get_layer_coverage_data( } }, ], - projection_model=LayerNodeCoverage, + projection_model=ResourceNodeCoverage, ) .to_list() ) diff --git a/Tekst-API/tekst/routers/layers.py b/Tekst-API/tekst/routers/layers.py deleted file mode 100644 index 55883148..00000000 --- a/Tekst-API/tekst/routers/layers.py +++ /dev/null @@ -1,414 +0,0 @@ -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, SuperuserDep, UserDep -from tekst.layer_types import ( - AnyLayerCreateBody, - AnyLayerRead, - AnyLayerReadBody, - AnyLayerUpdateBody, - layer_types_mgr, -) -from tekst.models.layer import LayerBaseDocument -from tekst.models.text import TextDocument -from tekst.models.unit import UnitBaseDocument -from tekst.models.user import UserDocument, UserRead, UserReadPublic - - -async def preprocess_layer_read( - layer_doc: LayerBaseDocument, - for_user: UserRead | None = None, -) -> AnyLayerRead: - # convert layer document to layer type's read model instance - layer = ( - layer_types_mgr.get(layer_doc.layer_type) - .layer_model() - .read_model()( - **layer_doc.model_dump(exclude=layer_doc.restricted_fields(for_user)) - ) - ) - # include writable flag (if applicable) - layer.writable = bool( - for_user - and ( - for_user.is_superuser - or for_user.id == layer.owner_id - or for_user.id in layer.shared_write - ) - ) - # include owner user data in each layer model (if an owner id is set) - if layer.owner_id: - layer.owner = UserReadPublic.model_from(await UserDocument.get(layer.owner_id)) - # include shared-with user data in each layer model (if any) - if for_user and (for_user.is_superuser or for_user.id == layer.owner_id): - if layer.shared_read: - layer.shared_read_users = await UserDocument.find( - In(UserDocument.id, layer.shared_read) - ).to_list() - if layer.shared_write: - layer.shared_write_users = await UserDocument.find( - In(UserDocument.id, layer.shared_write) - ).to_list() - return layer - - -router = APIRouter( - prefix="/layers", - tags=["layers"], - responses={ - status.HTTP_404_NOT_FOUND: {"description": "Not found"}, - }, -) - - -@router.post( - "", - response_model=AnyLayerReadBody, - status_code=status.HTTP_201_CREATED, - responses={status.HTTP_201_CREATED: {"description": "Created"}}, -) -async def create_layer(layer: AnyLayerCreateBody, user: UserDep) -> AnyLayerRead: - text = await TextDocument.get(layer.text_id) - if not text: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"Layer refers to non-existent text '{layer.text_id}'", - ) - if layer.level > len(text.levels) - 1: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"Text '{text.title}' only has {len(text.levels)} levels", - ) - # force some values on creation - layer.owner_id = user.id - layer.proposed = False - layer.public = False - # find document model for this layer type, instantiate, create - layer_doc = ( - await layer_types_mgr.get(layer.layer_type) - .layer_model() - .document_model() - .model_from(layer) - .create() - ) - return await preprocess_layer_read(layer_doc, user) - - -@router.patch("/{id}", response_model=AnyLayerReadBody, status_code=status.HTTP_200_OK) -async def update_layer( - layer_id: Annotated[PydanticObjectId, Path(alias="id")], - updates: AnyLayerUpdateBody, - user: UserDep, -) -> AnyLayerRead: - layer_doc = ( - await layer_types_mgr.get(updates.layer_type) - .layer_model() - .document_model() - .find_one( - LayerBaseDocument.id == layer_id, - LayerBaseDocument.allowed_to_write(user), - with_children=True, - ) - ) - if not layer_doc: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"Layer {layer_id} doesn't exist or requires extra permissions", - ) - # conditionally force certain updates - if layer_doc.public: - updates.shared_read = [] - updates.shared_write = [] - # only allow shares modification for owner or superuser - if not user.is_superuser and layer_doc.owner_id != user.id: - updates.shared_read = layer_doc.shared_read - updates.shared_write = layer_doc.shared_write - # update document with reduced updates - await layer_doc.apply( - updates.model_dump( - exclude_unset=True, - # force-keep non-updatable fields - exclude={ - "public", - "proposed", - "text_id", - "owner_id", - "level", - "layer_type", - }, - ) - ) - return await preprocess_layer_read(layer_doc, user) - - -@router.get("", response_model=list[AnyLayerReadBody], status_code=status.HTTP_200_OK) -async def find_layers( - user: OptionalUserDep, - text_id: Annotated[PydanticObjectId, Query(alias="textId")], - level: int = None, - layer_type: Annotated[str, Query(alias="layerType")] = None, - limit: int = 4096, -) -> list[AnyLayerRead]: - """ - Returns a list of all data layers matching the given criteria. - - As the resulting list of data layers may contain layers of different types, the - returned layer objects cannot be typed to their precise layer type. - """ - example = {"text_id": text_id} - - # add to example - if level is not None: - example["level"] = level - if layer_type: - example["layer_type"] = layer_type - - # query for layers the user is allowed to read and that belong to active texts - layer_docs = ( - await LayerBaseDocument.find( - example, await LayerBaseDocument.allowed_to_read(user), with_children=True - ) - .limit(limit) - .to_list() - ) - # return processed results - return [await preprocess_layer_read(layer_doc, user) for layer_doc in layer_docs] - - -# -# TODO: rebuild template endpoint using beanie logic -# - -# @router.get("/template", status_code=status.HTTP_200_OK) -# async def get_layer_template(layer_id: str, db_io: DbIO = Depends(get_db_io)) -> dict: -# layer_data = await db_io.find_one("layers", layer_id) - -# if not layer_data: -# raise HTTPException( -# status.HTTP_400_BAD_REQUEST, -# detail=f"Layer with ID {layer_id} doesn't exist", -# ) -# -# layer_types_mgr = layer_types_mgr - -# # decode layer data: Usually, this is handled automatically by our models, but -# # in this case we're returning a raw dict/JSON, so we have to manually make sure -# # that a) the ID field is called "id" and b) the DocumentId is encoded as str. -# layer_read_model = layer_types_mgr \ -# .get(layer_data["layerType"]).get_layer_read_model() -# layer_data = layer_read_model(**layer_data).model_dump() - -# # import unit type for the requested layer -# template = layer_types_mgr \ -# .get(layer_data["layerType"]).prepare_import_template() -# # apply data from layer instance -# template["layerId"] = str(layer_data["id"]) -# template["_level"] = layer_data["level"] -# template["_title"] = layer_data["title"] -# template["_description"] = layer_data.get("description", None) - -# # generate unit template -# node_template = {key: None for key in template["_unitSchema"].keys()} - -# # get IDs of all nodes on this structure level as a base for unit templates -# nodes = await db_io.find( -# "nodes", -# example={"textSlug": layer_data["textSlug"], "level": layer_data["level"]}, -# projection={"_id", "label"}, -# limit=0, -# ) - -# # fill in unit templates with IDs -# template["units"] = [ -# model_dump(nodeId=str(node["_id"]), **node_template) for node in nodes -# ] - -# # create temporary file and stream it as a file response -# tempfile = NamedTemporaryFile(mode="w") -# tempfile.write(json.dumps(template, indent=2)) -# tempfile.flush() - -# # prepare headers -# filename = ( -# f"{layer_data['textSlug']}_layer_{safe_name(template['layerId'])}" -# "_template.json" -# ) -# headers = {"Content-Disposition": f'attachment; filename="{filename}"'} - -# log.debug(f"Serving layer template as temporary file {tempfile.name}") -# return FileResponse( -# tempfile.name, -# headers=headers, -# media_type="application/json", -# background=BackgroundTask(tempfile.close), -# ) - - -@router.get("/{id}", status_code=status.HTTP_200_OK, response_model=AnyLayerReadBody) -async def get_layer( - user: OptionalUserDep, - layer_id: Annotated[PydanticObjectId, Path(alias="id")], -) -> AnyLayerRead: - layer_doc = await LayerBaseDocument.find_one( - LayerBaseDocument.id == layer_id, - await LayerBaseDocument.allowed_to_read(user), - with_children=True, - ) - if not layer_doc: - raise HTTPException( - status.HTTP_404_NOT_FOUND, detail=f"No layer with ID {layer_id}" - ) - return await preprocess_layer_read(layer_doc, user) - - -@router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_layer( - user: UserDep, layer_id: Annotated[PydanticObjectId, Path(alias="id")] -) -> None: - layer_doc = await LayerBaseDocument.get(layer_id, with_children=True) - if not layer_doc: - raise HTTPException( - status.HTTP_404_NOT_FOUND, detail=f"No layer with ID {layer_id}" - ) - if not user.is_superuser and user.id != layer_doc.owner_id: - raise HTTPException(status.HTTP_401_UNAUTHORIZED) - if layer_doc.public: - raise HTTPException( - status.HTTP_400_BAD_REQUEST, - detail="Cannot delete a published layer", - ) - if layer_doc.proposed: - raise HTTPException( - status.HTTP_400_BAD_REQUEST, - detail="Cannot delete a proposed layer", - ) - # all fine - # delete units - await UnitBaseDocument.find( - UnitBaseDocument.layer_id == layer_id, - with_children=True, - ).delete() - # delete layer - await LayerBaseDocument.find_one( - LayerBaseDocument.id == layer_id, - with_children=True, - ).delete() - - -@router.post( - "/{id}/propose", response_model=AnyLayerReadBody, status_code=status.HTTP_200_OK -) -async def propose_layer( - user: UserDep, layer_id: Annotated[PydanticObjectId, Path(alias="id")] -) -> AnyLayerRead: - layer_doc = await LayerBaseDocument.get(layer_id, with_children=True) - if not layer_doc: - raise HTTPException( - status.HTTP_404_NOT_FOUND, detail=f"No layer with ID {layer_id}" - ) - if not user.is_superuser and user.id != layer_doc.owner_id: - raise HTTPException(status.HTTP_401_UNAUTHORIZED) - if layer_doc.public: - raise HTTPException( - status.HTTP_400_BAD_REQUEST, - detail=f"Layer with ID {layer_id} is already public", - ) - if layer_doc.proposed: - raise HTTPException( - status.HTTP_400_BAD_REQUEST, - detail=f"Layer with ID {layer_id} is already proposed for publication", - ) - # all fine, propose layer - await layer_doc.set({LayerBaseDocument.proposed: True}) - return await preprocess_layer_read(layer_doc, user) - - -@router.post( - "/{id}/unpropose", response_model=AnyLayerReadBody, status_code=status.HTTP_200_OK -) -async def unpropose_layer( - user: UserDep, layer_id: Annotated[PydanticObjectId, Path(alias="id")] -) -> AnyLayerRead: - layer_doc = await LayerBaseDocument.get(layer_id, with_children=True) - if not layer_doc: - raise HTTPException( - status.HTTP_404_NOT_FOUND, detail=f"No layer with ID {layer_id}" - ) - if not user.is_superuser and user.id != layer_doc.owner_id: - raise HTTPException(status.HTTP_401_UNAUTHORIZED) - if not layer_doc.proposed: - raise HTTPException( - status.HTTP_400_BAD_REQUEST, - detail=f"Layer with ID {layer_id} is not proposed for publication", - ) - # all fine, unpropose layer - await layer_doc.set( - { - LayerBaseDocument.proposed: False, - LayerBaseDocument.public: False, - } - ) - return await preprocess_layer_read(layer_doc, user) - - -@router.post( - "/{id}/publish", response_model=AnyLayerReadBody, status_code=status.HTTP_200_OK -) -async def publish_layer( - user: SuperuserDep, layer_id: Annotated[PydanticObjectId, Path(alias="id")] -) -> AnyLayerRead: - layer_doc = await LayerBaseDocument.get(layer_id, with_children=True) - if not layer_doc: - raise HTTPException( - status.HTTP_404_NOT_FOUND, detail=f"No layer with ID {layer_id}" - ) - if layer_doc.public: - raise HTTPException( - status.HTTP_400_BAD_REQUEST, - detail=f"Layer with ID {layer_id} is already public", - ) - if not layer_doc.proposed: - raise HTTPException( - status.HTTP_400_BAD_REQUEST, - detail=f"Layer with ID {layer_id} is not proposed for publication", - ) - # all fine, publish layer - await layer_doc.set( - { - LayerBaseDocument.public: True, - LayerBaseDocument.proposed: False, - LayerBaseDocument.owner_id: None, - LayerBaseDocument.shared_read: [], - LayerBaseDocument.shared_write: [], - } - ) - return await preprocess_layer_read(layer_doc, user) - - -@router.post( - "/{id}/unpublish", response_model=AnyLayerReadBody, status_code=status.HTTP_200_OK -) -async def unpublish_layer( - user: SuperuserDep, layer_id: Annotated[PydanticObjectId, Path(alias="id")] -) -> AnyLayerRead: - layer_doc = await LayerBaseDocument.get(layer_id, with_children=True) - if not layer_doc: - raise HTTPException( - status.HTTP_404_NOT_FOUND, detail=f"No layer with ID {layer_id}" - ) - if not layer_doc.public: - raise HTTPException( - status.HTTP_400_BAD_REQUEST, - detail=f"Layer with ID {layer_id} is not public", - ) - # all fine, unpublish layer - await layer_doc.set( - { - LayerBaseDocument.public: False, - LayerBaseDocument.proposed: False, - } - ) - return await preprocess_layer_read(layer_doc, user) diff --git a/Tekst-API/tekst/routers/resources.py b/Tekst-API/tekst/routers/resources.py new file mode 100644 index 00000000..9407b488 --- /dev/null +++ b/Tekst-API/tekst/routers/resources.py @@ -0,0 +1,437 @@ +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, SuperuserDep, UserDep +from tekst.models.resource import ResourceBaseDocument +from tekst.models.text import TextDocument +from tekst.models.unit import UnitBaseDocument +from tekst.models.user import UserDocument, UserRead, UserReadPublic +from tekst.resource_types import ( + AnyResourceCreateBody, + AnyResourceRead, + AnyResourceReadBody, + AnyResourceUpdateBody, + resource_types_mgr, +) + + +async def preprocess_resource_read( + resource_doc: ResourceBaseDocument, + for_user: UserRead | None = None, +) -> AnyResourceRead: + # convert resource document to resource type's read model instance + resource = ( + resource_types_mgr.get(resource_doc.resource_type) + .resource_model() + .read_model()( + **resource_doc.model_dump(exclude=resource_doc.restricted_fields(for_user)) + ) + ) + # include writable flag (if applicable) + resource.writable = bool( + for_user + and ( + for_user.is_superuser + or for_user.id == resource.owner_id + or for_user.id in resource.shared_write + ) + ) + # include owner user data in each resource model (if an owner id is set) + if resource.owner_id: + resource.owner = UserReadPublic.model_from( + await UserDocument.get(resource.owner_id) + ) + # include shared-with user data in each resource model (if any) + if for_user and (for_user.is_superuser or for_user.id == resource.owner_id): + if resource.shared_read: + resource.shared_read_users = await UserDocument.find( + In(UserDocument.id, resource.shared_read) + ).to_list() + if resource.shared_write: + resource.shared_write_users = await UserDocument.find( + In(UserDocument.id, resource.shared_write) + ).to_list() + return resource + + +router = APIRouter( + prefix="/resources", + tags=["resources"], + responses={ + status.HTTP_404_NOT_FOUND: {"description": "Not found"}, + }, +) + + +@router.post( + "", + response_model=AnyResourceReadBody, + status_code=status.HTTP_201_CREATED, + responses={status.HTTP_201_CREATED: {"description": "Created"}}, +) +async def create_resource( + resource: AnyResourceCreateBody, user: UserDep +) -> AnyResourceRead: + text = await TextDocument.get(resource.text_id) + if not text: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Resource refers to non-existent text '{resource.text_id}'", + ) + if resource.level > len(text.levels) - 1: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Text '{text.title}' only has {len(text.levels)} levels", + ) + # force some values on creation + resource.owner_id = user.id + resource.proposed = False + resource.public = False + # find document model for this resource type, instantiate, create + resource_doc = ( + await resource_types_mgr.get(resource.resource_type) + .resource_model() + .document_model() + .model_from(resource) + .create() + ) + return await preprocess_resource_read(resource_doc, user) + + +@router.patch( + "/{id}", response_model=AnyResourceReadBody, status_code=status.HTTP_200_OK +) +async def update_resource( + resource_id: Annotated[PydanticObjectId, Path(alias="id")], + updates: AnyResourceUpdateBody, + user: UserDep, +) -> AnyResourceRead: + resource_doc = ( + await resource_types_mgr.get(updates.resource_type) + .resource_model() + .document_model() + .find_one( + ResourceBaseDocument.id == resource_id, + ResourceBaseDocument.allowed_to_write(user), + with_children=True, + ) + ) + if not resource_doc: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Resource {resource_id} doesn't exist / requires extra permissions", + ) + # conditionally force certain updates + if resource_doc.public: + updates.shared_read = [] + updates.shared_write = [] + # only allow shares modification for owner or superuser + if not user.is_superuser and resource_doc.owner_id != user.id: + updates.shared_read = resource_doc.shared_read + updates.shared_write = resource_doc.shared_write + # update document with reduced updates + await resource_doc.apply( + updates.model_dump( + exclude_unset=True, + # force-keep non-updatable fields + exclude={ + "public", + "proposed", + "text_id", + "owner_id", + "level", + "resource_type", + }, + ) + ) + return await preprocess_resource_read(resource_doc, user) + + +@router.get( + "", response_model=list[AnyResourceReadBody], status_code=status.HTTP_200_OK +) +async def find_resources( + user: OptionalUserDep, + text_id: Annotated[PydanticObjectId, Query(alias="textId")], + level: int = None, + resource_type: Annotated[str, Query(alias="resourceType")] = None, + limit: int = 4096, +) -> list[AnyResourceRead]: + """ + Returns a list of all resources matching the given criteria. + + As the resulting list of resources may contain resources of different types, the + returned resource objects cannot be typed to their precise resource type. + """ + example = {"text_id": text_id} + + # add to example + if level is not None: + example["level"] = level + if resource_type: + example["resource_type"] = resource_type + + # query for resources the user is allowed to read and that belong to active texts + resource_docs = ( + await ResourceBaseDocument.find( + example, + await ResourceBaseDocument.allowed_to_read(user), + with_children=True, + ) + .limit(limit) + .to_list() + ) + # return processed results + return [ + await preprocess_resource_read(resource_doc, user) + for resource_doc in resource_docs + ] + + +# +# TODO: rebuild template endpoint using beanie logic +# + +# @router.get("/template", status_code=status.HTTP_200_OK) +# async def get_resource_template( +# resource_id: str, +# db_io: DbIO = Depends(get_db_io) +# ) -> dict: +# resource_data = await db_io.find_one("resources", resource_id) + +# if not resource_data: +# raise HTTPException( +# status.HTTP_400_BAD_REQUEST, +# detail=f"Resource with ID {resource_id} doesn't exist", +# ) +# +# resource_types_mgr = resource_types_mgr + +# # decode resource data: Usually, this is handled automatically by our models, but +# # in this case we're returning a raw dict/JSON, so we have to manually make sure +# # that a) the ID field is called "id" and b) the DocumentId is encoded as str. +# resource_read_model = resource_types_mgr \ +# .get(resource_data["resourceType"]).get_resource_read_model() +# resource_data = resource_read_model(**resource_data).model_dump() + +# # import unit type for the requested resource +# template = resource_types_mgr \ +# .get(resource_data["resourceType"]).prepare_import_template() +# # apply data from resource instance +# template["resourceId"] = str(resource_data["id"]) +# template["_level"] = resource_data["level"] +# template["_title"] = resource_data["title"] +# template["_description"] = resource_data.get("description", None) + +# # generate unit template +# node_template = {key: None for key in template["_unitSchema"].keys()} + +# # get IDs of all nodes on this structure level as a base for unit templates +# nodes = await db_io.find( +# "nodes", +# example={ +# "textSlug": resource_data["textSlug"], +# "level": resource_data["level"] +# }, +# projection={"_id", "label"}, +# limit=0, +# ) + +# # fill in unit templates with IDs +# template["units"] = [ +# model_dump(nodeId=str(node["_id"]), **node_template) for node in nodes +# ] + +# # create temporary file and stream it as a file response +# tempfile = NamedTemporaryFile(mode="w") +# tempfile.write(json.dumps(template, indent=2)) +# tempfile.flush() + +# # prepare headers +# filename = ( +# f"{resource_data['textSlug']}_resource_{safe_name(template['resourceId'])}" +# "_template.json" +# ) +# headers = {"Content-Disposition": f'attachment; filename="{filename}"'} + +# log.debug(f"Serving resource template as temporary file {tempfile.name}") +# return FileResponse( +# tempfile.name, +# headers=headers, +# media_type="application/json", +# background=BackgroundTask(tempfile.close), +# ) + + +@router.get("/{id}", status_code=status.HTTP_200_OK, response_model=AnyResourceReadBody) +async def get_resource( + user: OptionalUserDep, + resource_id: Annotated[PydanticObjectId, Path(alias="id")], +) -> AnyResourceRead: + resource_doc = await ResourceBaseDocument.find_one( + ResourceBaseDocument.id == resource_id, + await ResourceBaseDocument.allowed_to_read(user), + with_children=True, + ) + if not resource_doc: + raise HTTPException( + status.HTTP_404_NOT_FOUND, detail=f"No resource with ID {resource_id}" + ) + return await preprocess_resource_read(resource_doc, user) + + +@router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_resource( + user: UserDep, resource_id: Annotated[PydanticObjectId, Path(alias="id")] +) -> None: + resource_doc = await ResourceBaseDocument.get(resource_id, with_children=True) + if not resource_doc: + raise HTTPException( + status.HTTP_404_NOT_FOUND, detail=f"No resource with ID {resource_id}" + ) + if not user.is_superuser and user.id != resource_doc.owner_id: + raise HTTPException(status.HTTP_401_UNAUTHORIZED) + if resource_doc.public: + raise HTTPException( + status.HTTP_400_BAD_REQUEST, + detail="Cannot delete a published resource", + ) + if resource_doc.proposed: + raise HTTPException( + status.HTTP_400_BAD_REQUEST, + detail="Cannot delete a proposed resource", + ) + # all fine + # delete units + await UnitBaseDocument.find( + UnitBaseDocument.resource_id == resource_id, + with_children=True, + ).delete() + # delete resource + await ResourceBaseDocument.find_one( + ResourceBaseDocument.id == resource_id, + with_children=True, + ).delete() + + +@router.post( + "/{id}/propose", response_model=AnyResourceReadBody, status_code=status.HTTP_200_OK +) +async def propose_resource( + user: UserDep, resource_id: Annotated[PydanticObjectId, Path(alias="id")] +) -> AnyResourceRead: + resource_doc = await ResourceBaseDocument.get(resource_id, with_children=True) + if not resource_doc: + raise HTTPException( + status.HTTP_404_NOT_FOUND, detail=f"No resource with ID {resource_id}" + ) + if not user.is_superuser and user.id != resource_doc.owner_id: + raise HTTPException(status.HTTP_401_UNAUTHORIZED) + if resource_doc.public: + raise HTTPException( + status.HTTP_400_BAD_REQUEST, + detail=f"Resource with ID {resource_id} already public", + ) + if resource_doc.proposed: + raise HTTPException( + status.HTTP_400_BAD_REQUEST, + detail=f"Resource with ID {resource_id} already proposed for publication", + ) + # all fine, propose resource + await resource_doc.set({ResourceBaseDocument.proposed: True}) + return await preprocess_resource_read(resource_doc, user) + + +@router.post( + "/{id}/unpropose", + response_model=AnyResourceReadBody, + status_code=status.HTTP_200_OK, +) +async def unpropose_resource( + user: UserDep, resource_id: Annotated[PydanticObjectId, Path(alias="id")] +) -> AnyResourceRead: + resource_doc = await ResourceBaseDocument.get(resource_id, with_children=True) + if not resource_doc: + raise HTTPException( + status.HTTP_404_NOT_FOUND, detail=f"No resource with ID {resource_id}" + ) + if not user.is_superuser and user.id != resource_doc.owner_id: + raise HTTPException(status.HTTP_401_UNAUTHORIZED) + if not resource_doc.proposed: + raise HTTPException( + status.HTTP_400_BAD_REQUEST, + detail=f"Resource with ID {resource_id} is not proposed for publication", + ) + # all fine, unpropose resource + await resource_doc.set( + { + ResourceBaseDocument.proposed: False, + ResourceBaseDocument.public: False, + } + ) + return await preprocess_resource_read(resource_doc, user) + + +@router.post( + "/{id}/publish", response_model=AnyResourceReadBody, status_code=status.HTTP_200_OK +) +async def publish_resource( + user: SuperuserDep, resource_id: Annotated[PydanticObjectId, Path(alias="id")] +) -> AnyResourceRead: + resource_doc = await ResourceBaseDocument.get(resource_id, with_children=True) + if not resource_doc: + raise HTTPException( + status.HTTP_404_NOT_FOUND, detail=f"No resource with ID {resource_id}" + ) + if resource_doc.public: + raise HTTPException( + status.HTTP_400_BAD_REQUEST, + detail=f"Resource with ID {resource_id} is already public", + ) + if not resource_doc.proposed: + raise HTTPException( + status.HTTP_400_BAD_REQUEST, + detail=f"Resource with ID {resource_id} is not proposed for publication", + ) + # all fine, publish resource + await resource_doc.set( + { + ResourceBaseDocument.public: True, + ResourceBaseDocument.proposed: False, + ResourceBaseDocument.owner_id: None, + ResourceBaseDocument.shared_read: [], + ResourceBaseDocument.shared_write: [], + } + ) + return await preprocess_resource_read(resource_doc, user) + + +@router.post( + "/{id}/unpublish", + response_model=AnyResourceReadBody, + status_code=status.HTTP_200_OK, +) +async def unpublish_resource( + user: SuperuserDep, resource_id: Annotated[PydanticObjectId, Path(alias="id")] +) -> AnyResourceRead: + resource_doc = await ResourceBaseDocument.get(resource_id, with_children=True) + if not resource_doc: + raise HTTPException( + status.HTTP_404_NOT_FOUND, detail=f"No resource with ID {resource_id}" + ) + if not resource_doc.public: + raise HTTPException( + status.HTTP_400_BAD_REQUEST, + detail=f"Resource with ID {resource_id} is not public", + ) + # all fine, unpublish resource + await resource_doc.set( + { + ResourceBaseDocument.public: False, + ResourceBaseDocument.proposed: False, + } + ) + return await preprocess_resource_read(resource_doc, user) diff --git a/Tekst-API/tekst/routers/texts.py b/Tekst-API/tekst/routers/texts.py index 4b870c39..23341c7a 100644 --- a/Tekst-API/tekst/routers/texts.py +++ b/Tekst-API/tekst/routers/texts.py @@ -20,8 +20,8 @@ from tekst.dependencies import get_temp_dir from tekst.models.common import Translations from tekst.models.exchange import NodeDefinition, TextStructureDefinition -from tekst.models.layer import LayerBaseDocument from tekst.models.node import NodeDocument +from tekst.models.resource import ResourceBaseDocument from tekst.models.text import ( TextCreate, TextDocument, @@ -263,12 +263,12 @@ async def insert_level( text_doc.default_level += 1 await text_doc.save() - # update all existing layers with level >= index - await LayerBaseDocument.find( - LayerBaseDocument.text_id == text_id, - LayerBaseDocument.level >= index, + # update all existing resources with level >= index + await ResourceBaseDocument.find( + ResourceBaseDocument.text_id == text_id, + ResourceBaseDocument.level >= index, with_children=True, - ).inc({LayerBaseDocument.level: 1}) + ).inc({ResourceBaseDocument.level: 1}) # update all existing nodes with level >= index await NodeDocument.find( @@ -387,19 +387,19 @@ async def delete_level( target_child.parent_id = target_level_node.parent_id await target_child.save() - # delete all existing layers with level == index - await LayerBaseDocument.find( - LayerBaseDocument.text_id == text_id, - LayerBaseDocument.level == index, + # delete all existing resources with level == index + await ResourceBaseDocument.find( + ResourceBaseDocument.text_id == text_id, + ResourceBaseDocument.level == index, with_children=True, ).delete() - # update all existing layers with level > index - await LayerBaseDocument.find( - LayerBaseDocument.text_id == text_id, - LayerBaseDocument.level > index, + # update all existing resources with level > index + await ResourceBaseDocument.find( + ResourceBaseDocument.text_id == text_id, + ResourceBaseDocument.level > index, with_children=True, - ).inc({LayerBaseDocument.level: -1}) + ).inc({ResourceBaseDocument.level: -1}) # delete all existing nodes with level == index await NodeDocument.find( @@ -409,7 +409,7 @@ async def delete_level( # update all existing nodes with level >= index await NodeDocument.find( NodeDocument.text_id == text_id, NodeDocument.level >= index - ).inc({LayerBaseDocument.level: -1}) + ).inc({ResourceBaseDocument.level: -1}) # update text itself text_doc.levels.pop(index) @@ -448,18 +448,18 @@ async def delete_text( status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot delete the only text", ) - # get data layers associated with target text - layers = await LayerBaseDocument.find( - LayerBaseDocument.text_id == text_id, with_children=True + # get resources associated with target text + resources = await ResourceBaseDocument.find( + ResourceBaseDocument.text_id == text_id, with_children=True ).to_list() - # delete data units of all layers associated with target text + # delete data units of all resources associated with target text await UnitBaseDocument.find( - In(UnitBaseDocument.layer_id, [layer.id for layer in layers]), + In(UnitBaseDocument.resource_id, [resource.id for resource in resources]), with_children=True, ).delete_many() - # delete data layers associated with target text - await LayerBaseDocument.find( - LayerBaseDocument.text_id == text_id, with_children=True + # delete resources associated with target text + await ResourceBaseDocument.find( + ResourceBaseDocument.text_id == text_id, with_children=True ).delete_many() # delete structure nodes associated with target text await NodeDocument.find( diff --git a/Tekst-API/tekst/routers/units.py b/Tekst-API/tekst/routers/units.py index 59246a67..464bd673 100644 --- a/Tekst-API/tekst/routers/units.py +++ b/Tekst-API/tekst/routers/units.py @@ -5,16 +5,16 @@ from fastapi import APIRouter, HTTPException, Path, Query, status from tekst.auth import OptionalUserDep, UserDep -from tekst.layer_types import ( +from tekst.models.resource import ResourceBaseDocument +from tekst.models.unit import UnitBaseDocument +from tekst.resource_types import ( AnyUnitCreateBody, AnyUnitDocument, AnyUnitRead, AnyUnitReadBody, AnyUnitUpdateBody, - layer_types_mgr, + resource_types_mgr, ) -from tekst.models.layer import LayerBaseDocument -from tekst.models.unit import UnitBaseDocument # initialize unit router @@ -32,19 +32,19 @@ responses={status.HTTP_201_CREATED: {"description": "Created"}}, ) async def create_unit(unit: AnyUnitCreateBody, user: UserDep) -> AnyUnitDocument: - # check if the layer this unit belongs to is writable by user - if not await LayerBaseDocument.find( - LayerBaseDocument.id == unit.layer_id, - LayerBaseDocument.allowed_to_write(user), + # check if the resource this unit belongs to is writable by user + if not await ResourceBaseDocument.find( + ResourceBaseDocument.id == unit.resource_id, + ResourceBaseDocument.allowed_to_write(user), with_children=True, ).exists(): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, - detail="No write access for units belonging to this layer", + detail="No write access for units belonging to this resource", ) # check for duplicates if await UnitBaseDocument.find_one( - UnitBaseDocument.layer_id == unit.layer_id, + UnitBaseDocument.resource_id == unit.resource_id, UnitBaseDocument.node_id == unit.node_id, with_children=True, ).exists(): @@ -54,7 +54,7 @@ async def create_unit(unit: AnyUnitCreateBody, user: UserDep) -> AnyUnitDocument ) return ( - await layer_types_mgr.get(unit.layer_type) + await resource_types_mgr.get(unit.resource_type) .unit_model() .document_model() .model_from(unit) @@ -68,15 +68,15 @@ async def get_unit( ) -> AnyUnitDocument: """A generic route for retrieving a unit by ID from the database""" unit_doc = await UnitBaseDocument.get(unit_id, with_children=True) - # check if the layer this unit belongs to is readable by user - layer_read_allowed = unit_doc and ( - await LayerBaseDocument.find_one( - LayerBaseDocument.id == unit_doc.layer_id, - await LayerBaseDocument.allowed_to_read(user), + # check if the resource this unit belongs to is readable by user + resource_read_allowed = unit_doc and ( + await ResourceBaseDocument.find_one( + ResourceBaseDocument.id == unit_doc.resource_id, + await ResourceBaseDocument.allowed_to_read(user), with_children=True, ).exists() ) - if not unit_doc or not layer_read_allowed: + if not unit_doc or not resource_read_allowed: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Could not find unit with ID {unit_id}", @@ -96,22 +96,22 @@ async def update_unit( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Unit {unit_id} doesn't exist", ) - # check if unit's layer ID matches updates' layer ID (if it has one specified) - if updates.layer_id and unit_doc.layer_id != updates.layer_id: + # check if unit's resource ID matches updates' resource ID (if it has one specified) + if updates.resource_id and unit_doc.resource_id != updates.resource_id: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, - detail="Referenced layer ID in unit and updates doesn't match", + detail="Referenced resource ID in unit and updates doesn't match", ) - # check if the layer this unit belongs to is writable by user - layer_write_allowed = await LayerBaseDocument.find_one( - LayerBaseDocument.id == unit_doc.layer_id, - LayerBaseDocument.allowed_to_write(user), + # check if the resource this unit belongs to is writable by user + resource_write_allowed = await ResourceBaseDocument.find_one( + ResourceBaseDocument.id == unit_doc.resource_id, + ResourceBaseDocument.allowed_to_write(user), with_children=True, ).exists() - if not layer_write_allowed: + if not resource_write_allowed: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, - detail=f"No write access for units of layer {unit_doc.layer_id}", + detail=f"No write access for units of resource {unit_doc.resource_id}", ) # apply updates await unit_doc.apply(updates.model_dump(exclude_unset=True)) @@ -121,11 +121,11 @@ async def update_unit( @router.get("", response_model=list[AnyUnitReadBody], status_code=status.HTTP_200_OK) async def find_units( user: OptionalUserDep, - layer_ids: Annotated[ + resource_ids: Annotated[ list[PydanticObjectId], Query( - alias="layerId", - description="ID (or list of IDs) of layer(s) to return unit data for", + alias="resourceId", + description="ID (or list of IDs) of resource(s) to return unit data for", ), ] = [], node_ids: Annotated[ @@ -138,23 +138,26 @@ async def find_units( limit: Annotated[int, Query(description="Return at most items")] = 1000, ) -> list[AnyUnitRead]: """ - Returns a list of all data layer units matching the given criteria. + Returns a list of all resource units matching the given criteria. - Respects restricted layers and inactive texts. + Respects restricted resources 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. + returned unit objects cannot be typed to their precise resource unit type. """ - readable_layers = await LayerBaseDocument.find( - await LayerBaseDocument.allowed_to_read(user), + readable_resources = await ResourceBaseDocument.find( + await ResourceBaseDocument.allowed_to_read(user), with_children=True, ).to_list() unit_docs = ( await UnitBaseDocument.find( - In(UnitBaseDocument.layer_id, layer_ids) if layer_ids else {}, + In(UnitBaseDocument.resource_id, resource_ids) if resource_ids else {}, In(UnitBaseDocument.node_id, node_ids) if node_ids else {}, - In(UnitBaseDocument.layer_id, [layer.id for layer in readable_layers]), + In( + UnitBaseDocument.resource_id, + [resource.id for resource in readable_resources], + ), with_children=True, ) .limit(limit) @@ -162,7 +165,7 @@ async def find_units( ) return [ - layer_types_mgr.get(unit_doc.layer_type) + resource_types_mgr.get(unit_doc.resource_type) .unit_model() .read_model()(**unit_doc.model_dump()) for unit_doc in unit_docs diff --git a/Tekst-API/tekst/sample_data/__init__.py b/Tekst-API/tekst/sample_data/__init__.py index fe55a932..b4d51fb8 100644 --- a/Tekst-API/tekst/sample_data/__init__.py +++ b/Tekst-API/tekst/sample_data/__init__.py @@ -16,7 +16,7 @@ async def insert_sample_data(): if environ.get("TESTING", False): return - target_collections = ("texts", "nodes", "layers", "units", "settings") + target_collections = ("texts", "nodes", "resources", "units", "settings") db = get_db_client()[_cfg.db_name] # check if any of the target collections contains data for collection in target_collections: diff --git a/Tekst-API/tekst/sample_data/db/layers.json b/Tekst-API/tekst/sample_data/db/resources.json similarity index 88% rename from Tekst-API/tekst/sample_data/db/layers.json rename to Tekst-API/tekst/sample_data/db/resources.json index 05863215..0dfda4ba 100644 --- a/Tekst-API/tekst/sample_data/db/layers.json +++ b/Tekst-API/tekst/sample_data/db/resources.json @@ -1,7 +1,7 @@ [ { "_id": { "$oid": "654b825533ee5737b297f8f3" }, - "_class_id": "LayerBaseDocument.PlaintextLayerDocument", + "_class_id": "ResourceBaseDocument.PlaintextResourceDocument", "title": "Originalfassung", "description": [ { @@ -11,7 +11,7 @@ ], "text_id": { "$oid": "654b825533ee5737b297f8e3" }, "level": 1, - "layer_type": "plaintext", + "resource_type": "plaintext", "owner_id": null, "shared_read": [], "shared_write": [], @@ -47,11 +47,11 @@ }, { "_id": { "$oid": "654ba3a6ec7833e469dde77a" }, - "_class_id": "LayerBaseDocument.PlaintextLayerDocument", + "_class_id": "ResourceBaseDocument.PlaintextResourceDocument", "title": "Japanese Version", "text_id": { "$oid": "654ba1f3ec7833e469dde765" }, "level": 0, - "layer_type": "plaintext", + "resource_type": "plaintext", "shared_read": [], "shared_write": [], "proposed": false, @@ -81,11 +81,11 @@ }, { "_id": { "$oid": "654ba525ec7833e469dde77e" }, - "_class_id": "LayerBaseDocument.PlaintextLayerDocument", + "_class_id": "ResourceBaseDocument.PlaintextResourceDocument", "title": "English Translation", "text_id": { "$oid": "654ba1f3ec7833e469dde765" }, "level": 0, - "layer_type": "plaintext", + "resource_type": "plaintext", "shared_read": [], "shared_write": [], "proposed": false, @@ -106,11 +106,11 @@ }, { "_id": { "$oid": "654ba678ec7833e469dde782" }, - "_class_id": "LayerBaseDocument.PlaintextLayerDocument", + "_class_id": "ResourceBaseDocument.PlaintextResourceDocument", "title": "Romanic Transliteration", "text_id": { "$oid": "654ba1f3ec7833e469dde765" }, "level": 0, - "layer_type": "plaintext", + "resource_type": "plaintext", "shared_read": [], "shared_write": [], "proposed": false, diff --git a/Tekst-API/tekst/sample_data/db/settings.json b/Tekst-API/tekst/sample_data/db/settings.json index 5f3ad9b5..0b32dbbb 100644 --- a/Tekst-API/tekst/sample_data/db/settings.json +++ b/Tekst-API/tekst/sample_data/db/settings.json @@ -11,7 +11,7 @@ "info_contact_url": null, "default_text_id": "654ba1f3ec7833e469dde765", "nav_info_entry": [], - "layer_categories": [ + "resource_categories": [ { "key": "versions", "translations": [ @@ -27,7 +27,7 @@ ] } ], - "show_layer_category_headings": true, + "show_resource_category_headings": true, "always_show_text_info": true, "show_header_info": true, "show_footer_info": true, diff --git a/Tekst-API/tekst/sample_data/db/units.json b/Tekst-API/tekst/sample_data/db/units.json index 94100de6..de034736 100644 --- a/Tekst-API/tekst/sample_data/db/units.json +++ b/Tekst-API/tekst/sample_data/db/units.json @@ -2,8 +2,8 @@ { "_id": { "$oid": "654b825533ee5737b297f8f4" }, "_class_id": "UnitBaseDocument.PlaintextUnitDocument", - "layer_id": { "$oid": "654b825533ee5737b297f8f3" }, - "layer_type": "plaintext", + "resource_id": { "$oid": "654b825533ee5737b297f8f3" }, + "resource_type": "plaintext", "node_id": { "$oid": "654b825533ee5737b297f8e5" }, "meta": null, "text": "Fuchs, du hast die Gans gestohlen," @@ -11,8 +11,8 @@ { "_id": { "$oid": "654b825533ee5737b297f8f5" }, "_class_id": "UnitBaseDocument.PlaintextUnitDocument", - "layer_id": { "$oid": "654b825533ee5737b297f8f3" }, - "layer_type": "plaintext", + "resource_id": { "$oid": "654b825533ee5737b297f8f3" }, + "resource_type": "plaintext", "node_id": { "$oid": "654b825533ee5737b297f8e6" }, "meta": null, "text": "|: gib sie wieder her! :|" @@ -20,8 +20,8 @@ { "_id": { "$oid": "654b825533ee5737b297f8f6" }, "_class_id": "UnitBaseDocument.PlaintextUnitDocument", - "layer_id": { "$oid": "654b825533ee5737b297f8f3" }, - "layer_type": "plaintext", + "resource_id": { "$oid": "654b825533ee5737b297f8f3" }, + "resource_type": "plaintext", "node_id": { "$oid": "654b825533ee5737b297f8e7" }, "meta": null, "text": "|: Sonst wird sie der Jäger holen" @@ -29,8 +29,8 @@ { "_id": { "$oid": "654b825533ee5737b297f8f7" }, "_class_id": "UnitBaseDocument.PlaintextUnitDocument", - "layer_id": { "$oid": "654b825533ee5737b297f8f3" }, - "layer_type": "plaintext", + "resource_id": { "$oid": "654b825533ee5737b297f8f3" }, + "resource_type": "plaintext", "node_id": { "$oid": "654b825533ee5737b297f8e8" }, "meta": null, "text": "mit dem Schießgewehr. :|" @@ -38,8 +38,8 @@ { "_id": { "$oid": "654b825533ee5737b297f8f8" }, "_class_id": "UnitBaseDocument.PlaintextUnitDocument", - "layer_id": { "$oid": "654b825533ee5737b297f8f3" }, - "layer_type": "plaintext", + "resource_id": { "$oid": "654b825533ee5737b297f8f3" }, + "resource_type": "plaintext", "node_id": { "$oid": "654b825533ee5737b297f8ea" }, "meta": null, "text": "Seine große, lange Flinte" @@ -47,8 +47,8 @@ { "_id": { "$oid": "654b825533ee5737b297f8f9" }, "_class_id": "UnitBaseDocument.PlaintextUnitDocument", - "layer_id": { "$oid": "654b825533ee5737b297f8f3" }, - "layer_type": "plaintext", + "resource_id": { "$oid": "654b825533ee5737b297f8f3" }, + "resource_type": "plaintext", "node_id": { "$oid": "654b825533ee5737b297f8eb" }, "meta": null, "text": "|: schießt auf dich den Schrot, :|" @@ -56,8 +56,8 @@ { "_id": { "$oid": "654b825533ee5737b297f8fa" }, "_class_id": "UnitBaseDocument.PlaintextUnitDocument", - "layer_id": { "$oid": "654b825533ee5737b297f8f3" }, - "layer_type": "plaintext", + "resource_id": { "$oid": "654b825533ee5737b297f8f3" }, + "resource_type": "plaintext", "node_id": { "$oid": "654b825533ee5737b297f8ec" }, "meta": null, "text": "|: dass dich färbt die rote Tinte" @@ -65,8 +65,8 @@ { "_id": { "$oid": "654b825533ee5737b297f8fb" }, "_class_id": "UnitBaseDocument.PlaintextUnitDocument", - "layer_id": { "$oid": "654b825533ee5737b297f8f3" }, - "layer_type": "plaintext", + "resource_id": { "$oid": "654b825533ee5737b297f8f3" }, + "resource_type": "plaintext", "node_id": { "$oid": "654b825533ee5737b297f8ed" }, "meta": null, "text": "und dann bist du tot. :|" @@ -74,8 +74,8 @@ { "_id": { "$oid": "654b825533ee5737b297f8fc" }, "_class_id": "UnitBaseDocument.PlaintextUnitDocument", - "layer_id": { "$oid": "654b825533ee5737b297f8f3" }, - "layer_type": "plaintext", + "resource_id": { "$oid": "654b825533ee5737b297f8f3" }, + "resource_type": "plaintext", "node_id": { "$oid": "654b825533ee5737b297f8ef" }, "meta": null, "text": "Liebes Füchslein, lass dir raten," @@ -83,8 +83,8 @@ { "_id": { "$oid": "654b825533ee5737b297f8fd" }, "_class_id": "UnitBaseDocument.PlaintextUnitDocument", - "layer_id": { "$oid": "654b825533ee5737b297f8f3" }, - "layer_type": "plaintext", + "resource_id": { "$oid": "654b825533ee5737b297f8f3" }, + "resource_type": "plaintext", "node_id": { "$oid": "654b825533ee5737b297f8f0" }, "meta": null, "text": "|: sei doch nur kein Dieb; :|" @@ -92,17 +92,17 @@ { "_id": { "$oid": "654b825533ee5737b297f8fe" }, "_class_id": "UnitBaseDocument.PlaintextUnitDocument", - "layer_id": { "$oid": "654b825533ee5737b297f8f3" }, + "resource_id": { "$oid": "654b825533ee5737b297f8f3" }, "node_id": { "$oid": "654b825533ee5737b297f8f1" }, - "layer_type": "plaintext", + "resource_type": "plaintext", "meta": null, "text": "|: nimm, du brauchst nicht Gänsebraten," }, { "_id": { "$oid": "654b825533ee5737b297f8ff" }, "_class_id": "UnitBaseDocument.PlaintextUnitDocument", - "layer_id": { "$oid": "654b825533ee5737b297f8f3" }, - "layer_type": "plaintext", + "resource_id": { "$oid": "654b825533ee5737b297f8f3" }, + "resource_type": "plaintext", "node_id": { "$oid": "654b825533ee5737b297f8f2" }, "meta": null, "text": "mit der Maus vorlieb. :|" @@ -110,8 +110,8 @@ { "_id": { "$oid": "654ba432ec7833e469dde77b" }, "_class_id": "UnitBaseDocument.PlaintextUnitDocument", - "layer_id": { "$oid": "654ba3a6ec7833e469dde77a" }, - "layer_type": "plaintext", + "resource_id": { "$oid": "654ba3a6ec7833e469dde77a" }, + "resource_type": "plaintext", "node_id": { "$oid": "654ba282ec7833e469dde766" }, "meta": null, "text": "古池や" @@ -119,8 +119,8 @@ { "_id": { "$oid": "654ba486ec7833e469dde77c" }, "_class_id": "UnitBaseDocument.PlaintextUnitDocument", - "layer_id": { "$oid": "654ba3a6ec7833e469dde77a" }, - "layer_type": "plaintext", + "resource_id": { "$oid": "654ba3a6ec7833e469dde77a" }, + "resource_type": "plaintext", "node_id": { "$oid": "654ba287ec7833e469dde767" }, "meta": null, "text": "蛙飛び込む" @@ -128,8 +128,8 @@ { "_id": { "$oid": "654ba49eec7833e469dde77d" }, "_class_id": "UnitBaseDocument.PlaintextUnitDocument", - "layer_id": { "$oid": "654ba3a6ec7833e469dde77a" }, - "layer_type": "plaintext", + "resource_id": { "$oid": "654ba3a6ec7833e469dde77a" }, + "resource_type": "plaintext", "node_id": { "$oid": "654ba288ec7833e469dde768" }, "meta": null, "text": "水の音" @@ -137,8 +137,8 @@ { "_id": { "$oid": "654ba553ec7833e469dde77f" }, "_class_id": "UnitBaseDocument.PlaintextUnitDocument", - "layer_id": { "$oid": "654ba525ec7833e469dde77e" }, - "layer_type": "plaintext", + "resource_id": { "$oid": "654ba525ec7833e469dde77e" }, + "resource_type": "plaintext", "node_id": { "$oid": "654ba288ec7833e469dde768" }, "meta": null, "text": "sound of water." @@ -146,8 +146,8 @@ { "_id": { "$oid": "654ba570ec7833e469dde780" }, "_class_id": "UnitBaseDocument.PlaintextUnitDocument", - "layer_id": { "$oid": "654ba525ec7833e469dde77e" }, - "layer_type": "plaintext", + "resource_id": { "$oid": "654ba525ec7833e469dde77e" }, + "resource_type": "plaintext", "node_id": { "$oid": "654ba287ec7833e469dde767" }, "meta": null, "text": "a frog jumps in," @@ -155,8 +155,8 @@ { "_id": { "$oid": "654ba587ec7833e469dde781" }, "_class_id": "UnitBaseDocument.PlaintextUnitDocument", - "layer_id": { "$oid": "654ba525ec7833e469dde77e" }, - "layer_type": "plaintext", + "resource_id": { "$oid": "654ba525ec7833e469dde77e" }, + "resource_type": "plaintext", "node_id": { "$oid": "654ba282ec7833e469dde766" }, "meta": null, "text": "The old pond-" @@ -164,8 +164,8 @@ { "_id": { "$oid": "654ba68eec7833e469dde783" }, "_class_id": "UnitBaseDocument.PlaintextUnitDocument", - "layer_id": { "$oid": "654ba678ec7833e469dde782" }, - "layer_type": "plaintext", + "resource_id": { "$oid": "654ba678ec7833e469dde782" }, + "resource_type": "plaintext", "node_id": { "$oid": "654ba282ec7833e469dde766" }, "meta": null, "text": "Furu ike ya" @@ -173,8 +173,8 @@ { "_id": { "$oid": "654ba6a7ec7833e469dde784" }, "_class_id": "UnitBaseDocument.PlaintextUnitDocument", - "layer_id": { "$oid": "654ba678ec7833e469dde782" }, - "layer_type": "plaintext", + "resource_id": { "$oid": "654ba678ec7833e469dde782" }, + "resource_type": "plaintext", "node_id": { "$oid": "654ba287ec7833e469dde767" }, "meta": null, "text": "kawazu tobikomu" @@ -182,8 +182,8 @@ { "_id": { "$oid": "654ba6b9ec7833e469dde785" }, "_class_id": "UnitBaseDocument.PlaintextUnitDocument", - "layer_id": { "$oid": "654ba678ec7833e469dde782" }, - "layer_type": "plaintext", + "resource_id": { "$oid": "654ba678ec7833e469dde782" }, + "resource_type": "plaintext", "node_id": { "$oid": "654ba288ec7833e469dde768" }, "meta": null, "text": "mizu no oto" diff --git a/Tekst-API/tekst/setup.py b/Tekst-API/tekst/setup.py index e4678151..d88f1367 100644 --- a/Tekst-API/tekst/setup.py +++ b/Tekst-API/tekst/setup.py @@ -2,8 +2,8 @@ from tekst.config import TekstConfig from tekst.db import init_odm from tekst.dependencies import get_db, get_db_client -from tekst.layer_types import init_layer_types_mgr from tekst.logging import log, setup_logging +from tekst.resource_types import init_resource_types_mgr from tekst.sample_data import insert_sample_data @@ -15,7 +15,7 @@ async def app_setup(cfg: TekstConfig): # if not cfg.email_smtp_server: # log.warning("No SMTP server configured") # pragma: no cover - init_layer_types_mgr() + init_resource_types_mgr() await init_odm(get_db(get_db_client(cfg), cfg)) await create_sample_users() # happens only when in DEV mode diff --git a/Tekst-API/tests/conftest.py b/Tekst-API/tests/conftest.py index 4026cc63..ccb495a5 100644 --- a/Tekst-API/tests/conftest.py +++ b/Tekst-API/tests/conftest.py @@ -111,7 +111,7 @@ async def test_client(test_app, config) -> AsyncClient: @pytest.fixture async def reset_db(get_db_client_override, config): - for collection in ("texts", "nodes", "layers", "units", "users"): + for collection in ("texts", "nodes", "resources", "units", "users"): await get_db_client_override[config.db_name][collection].delete_many({}) diff --git a/Tekst-API/tests/integration/test_api_node.py b/Tekst-API/tests/integration/test_api_node.py index 9edef603..32f92219 100644 --- a/Tekst-API/tests/integration/test_api_node.py +++ b/Tekst-API/tests/integration/test_api_node.py @@ -222,7 +222,7 @@ async def test_delete_node( register_test_user, get_session_cookie, ): - text_id = (await insert_sample_data("texts", "nodes", "layers"))["texts"][0] + text_id = (await insert_sample_data("texts", "nodes", "resources"))["texts"][0] # get node from db resp = await test_client.get( @@ -237,17 +237,17 @@ async def test_delete_node( superuser_data = await register_test_user(is_superuser=True) session_cookie = await get_session_cookie(superuser_data) - # get existing layer - resp = await test_client.get("/layers", params={"textId": text_id}) + # get existing resource + resp = await test_client.get("/resources", params={"textId": text_id}) assert resp.status_code == 200, status_fail_msg(200, resp) assert isinstance(resp.json(), list) assert len(resp.json()) > 0 - layer = resp.json()[0] + resource = resp.json()[0] - # create plaintext layer unit + # create plaintext resource unit payload = { - "layerId": layer["id"], - "layerType": "plaintext", + "resourceId": resource["id"], + "resourceType": "plaintext", "nodeId": node["id"], "text": "Ein Raabe geht im Feld spazieren.", "comment": "This is a comment", @@ -275,7 +275,7 @@ async def test_move_node( register_test_user, get_session_cookie, ): - text_id = (await insert_sample_data("texts", "nodes", "layers"))["texts"][0] + text_id = (await insert_sample_data("texts", "nodes", "resources"))["texts"][0] # create superuser superuser_data = await register_test_user(is_superuser=True) diff --git a/Tekst-API/tests/integration/test_api_layer.py b/Tekst-API/tests/integration/test_api_resource.py similarity index 60% rename from Tekst-API/tests/integration/test_api_layer.py rename to Tekst-API/tests/integration/test_api_resource.py index 15001a7b..dab71253 100644 --- a/Tekst-API/tests/integration/test_api_layer.py +++ b/Tekst-API/tests/integration/test_api_resource.py @@ -4,7 +4,7 @@ @pytest.mark.anyio -async def test_create_layer( +async def test_create_resource( api_path, test_client: AsyncClient, insert_sample_data, @@ -16,7 +16,7 @@ async def test_create_layer( user_data = await register_test_user() session_cookie = await get_session_cookie(user_data) payload = { - "title": "A test layer", + "title": "A test resource", "description": [ { "locale": "*", @@ -25,14 +25,14 @@ async def test_create_layer( ], "textId": text_id, "level": 0, - "layerType": "plaintext", + "resourceType": "plaintext", "ownerId": user_data["id"], } - resp = await test_client.post("/layers", json=payload, cookies=session_cookie) + resp = await test_client.post("/resources", json=payload, cookies=session_cookie) assert resp.status_code == 201, status_fail_msg(201, resp) assert "id" in resp.json() - assert resp.json()["title"] == "A test layer" + assert resp.json()["title"] == "A test resource" assert ( resp.json()["description"][0]["translation"] == "This is a string with some space chars" @@ -41,7 +41,7 @@ async def test_create_layer( @pytest.mark.anyio -async def test_create_layer_invalid( +async def test_create_resource_invalid( api_path, test_client: AsyncClient, insert_sample_data, @@ -54,18 +54,18 @@ async def test_create_layer_invalid( session_cookie = await get_session_cookie(user_data) payload = { - "title": "A test layer", + "title": "A test resource", "textId": "5eb7cfb05e32e07750a1756a", "level": 0, - "layerType": "plaintext", + "resourceType": "plaintext", } - resp = await test_client.post("/layers", json=payload, cookies=session_cookie) + resp = await test_client.post("/resources", json=payload, cookies=session_cookie) assert resp.status_code == 400, status_fail_msg(400, resp) @pytest.mark.anyio -async def test_update_layer( +async def test_update_resource( api_path, test_client: AsyncClient, insert_sample_data, @@ -73,39 +73,39 @@ async def test_update_layer( register_test_user, get_session_cookie, ): - text_id = (await insert_sample_data("texts", "nodes", "layers"))["texts"][0] + text_id = (await insert_sample_data("texts", "nodes", "resources"))["texts"][0] user_data = await register_test_user() session_cookie = await get_session_cookie(user_data) - # create new layer (because only owner can update(write)) + # create new resource (because only owner can update(write)) payload = { "title": "Foo Bar Baz", "textId": text_id, "level": 0, - "layerType": "plaintext", + "resourceType": "plaintext", "public": True, } - resp = await test_client.post("/layers", json=payload, cookies=session_cookie) + resp = await test_client.post("/resources", json=payload, cookies=session_cookie) assert resp.status_code == 201, status_fail_msg(201, resp) - layer_data = resp.json() - assert "id" in layer_data - assert "ownerId" in layer_data - assert layer_data.get("public") is False - # update layer - updates = {"title": "This Title Changed", "layerType": "plaintext"} + resource_data = resp.json() + assert "id" in resource_data + assert "ownerId" in resource_data + assert resource_data.get("public") is False + # update resource + updates = {"title": "This Title Changed", "resourceType": "plaintext"} resp = await test_client.patch( - f"/layers/{layer_data['id']}", + f"/resources/{resource_data['id']}", json=updates, cookies=session_cookie, ) assert resp.status_code == 200, status_fail_msg(200, resp) assert isinstance(resp.json(), dict) assert "id" in resp.json() - assert resp.json()["id"] == str(layer_data["id"]) + assert resp.json()["id"] == str(resource_data["id"]) assert resp.json()["title"] == updates["title"] # check if updating public/proposed has no effect (as intended) - updates = {"public": True, "proposed": True, "layerType": "plaintext"} + updates = {"public": True, "proposed": True, "resourceType": "plaintext"} resp = await test_client.patch( - f"/layers/{layer_data['id']}", + f"/resources/{resource_data['id']}", json=updates, cookies=session_cookie, ) @@ -116,17 +116,17 @@ async def test_update_layer( # logout resp = await test_client.post("/auth/cookie/logout") assert resp.status_code == 204, status_fail_msg(204, resp) - # update layer unauthorized - updates = {"title": "This Title Changed Again", "layerType": "plaintext"} + # update resource unauthorized + updates = {"title": "This Title Changed Again", "resourceType": "plaintext"} resp = await test_client.patch( - f"/layers/{layer_data['id']}", + f"/resources/{resource_data['id']}", json=updates, ) assert resp.status_code == 401, status_fail_msg(401, resp) @pytest.mark.anyio -async def test_create_layer_with_forged_owner_id( +async def test_create_resource_with_forged_owner_id( api_path, test_client: AsyncClient, insert_sample_data, @@ -137,43 +137,43 @@ async def test_create_layer_with_forged_owner_id( text_id = (await insert_sample_data("texts", "nodes"))["texts"][0] user_data = await register_test_user() session_cookie = await get_session_cookie(user_data) - # create new layer with made up owner ID + # create new resource with made up owner ID payload = { "title": "Foo Bar Baz", "textId": text_id, "level": 0, - "layerType": "plaintext", + "resourceType": "plaintext", "ownerId": "643d3cdc21efd6c46ae1527e", } - resp = await test_client.post("/layers", json=payload, cookies=session_cookie) + resp = await test_client.post("/resources", json=payload, cookies=session_cookie) assert resp.status_code == 201, status_fail_msg(201, resp) assert resp.json()["ownerId"] != payload["ownerId"] @pytest.mark.anyio -async def test_get_layer( +async def test_get_resource( api_path, test_client: AsyncClient, insert_sample_data, status_fail_msg ): - text_id = (await insert_sample_data("texts", "nodes", "layers"))["texts"][0] - # get existing layer id - resp = await test_client.get("/layers", params={"textId": text_id}) + text_id = (await insert_sample_data("texts", "nodes", "resources"))["texts"][0] + # get existing resource id + resp = await test_client.get("/resources", params={"textId": text_id}) assert resp.status_code == 200, status_fail_msg(200, resp) assert isinstance(resp.json(), list) assert len(resp.json()) > 0 assert isinstance(resp.json()[0], dict) assert "id" in resp.json()[0] - layer = resp.json()[0] - layer_id = layer["id"] - # get layer by id - resp = await test_client.get(f"/layers/{layer_id}") + resource = resp.json()[0] + resource_id = resource["id"] + # get resource by id + resp = await test_client.get(f"/resources/{resource_id}") assert resp.status_code == 200, status_fail_msg(200, resp) assert isinstance(resp.json(), dict) assert "id" in resp.json() - assert resp.json()["id"] == layer_id + assert resp.json()["id"] == resource_id @pytest.mark.anyio -async def test_access_private_layer( +async def test_access_private_resource( api_path, test_client: AsyncClient, insert_sample_data, @@ -181,11 +181,11 @@ async def test_access_private_layer( register_test_user, get_session_cookie, ): - inserted_ids = await insert_sample_data("texts", "nodes", "layers") + inserted_ids = await insert_sample_data("texts", "nodes", "resources") text_id = inserted_ids["texts"][0] - layer_id = inserted_ids["layers"][0] - # get all accessible layers - resp = await test_client.get("/layers", params={"textId": text_id}) + resource_id = inserted_ids["resources"][0] + # get all accessible resources + resp = await test_client.get("/resources", params={"textId": text_id}) assert resp.status_code == 200, status_fail_msg(200, resp) assert isinstance(resp.json(), list) accessible_unauthorized = len(resp.json()) @@ -194,26 +194,27 @@ async def test_access_private_layer( session_cookie = await get_session_cookie(user_data) # unpublish resp = await test_client.post( - f"/layers/{layer_id}/unpublish", cookies=session_cookie + f"/resources/{resource_id}/unpublish", cookies=session_cookie ) assert resp.status_code == 200, status_fail_msg(200, resp) # logout resp = await test_client.post("/auth/cookie/logout") assert resp.status_code == 204, status_fail_msg(204, resp) - # get all accessible layers again, unauthorized - resp = await test_client.get("/layers", params={"textId": text_id}) + # get all accessible resources again, unauthorized + resp = await test_client.get("/resources", params={"textId": text_id}) assert resp.status_code == 200, status_fail_msg(200, resp) assert isinstance(resp.json(), list) assert len(resp.json()) < accessible_unauthorized # this should be less now @pytest.mark.anyio -async def test_get_layers( +async def test_get_resources( api_path, test_client: AsyncClient, insert_sample_data, status_fail_msg ): - text_id = (await insert_sample_data("texts", "nodes", "layers"))["texts"][0] + text_id = (await insert_sample_data("texts", "nodes", "resources"))["texts"][0] resp = await test_client.get( - "/layers", params={"textId": text_id, "level": 1, "layerType": "plaintext"} + "/resources", + params={"textId": text_id, "level": 1, "resourceType": "plaintext"}, ) assert resp.status_code == 200, status_fail_msg(200, resp) assert isinstance(resp.json(), list) @@ -221,20 +222,20 @@ async def test_get_layers( assert isinstance(resp.json()[0], dict) assert "id" in resp.json()[0] - layer_id = resp.json()[0]["id"] + resource_id = resp.json()[0]["id"] - resp = await test_client.get(f"/layers/{layer_id}") + resp = await test_client.get(f"/resources/{resource_id}") assert resp.status_code == 200, status_fail_msg(200, resp) assert isinstance(resp.json(), dict) - assert "layerType" in resp.json() + assert "resourceType" in resp.json() # request invalid ID - resp = await test_client.get("/layers/foo") + resp = await test_client.get("/resources/foo") assert resp.status_code == 422, status_fail_msg(422, resp) @pytest.mark.anyio -async def test_propose_unpropose_publish_unpublish_layer( +async def test_propose_unpropose_publish_unpublish_resource( api_path, test_client: AsyncClient, insert_sample_data, @@ -242,84 +243,84 @@ async def test_propose_unpropose_publish_unpublish_layer( register_test_user, get_session_cookie, ): - text_id = (await insert_sample_data("texts", "nodes", "layers"))["texts"][0] + text_id = (await insert_sample_data("texts", "nodes", "resources"))["texts"][0] user_data = await register_test_user() session_cookie = await get_session_cookie(user_data) - # create new layer (because only owner can update(write)) + # create new resource (because only owner can update(write)) payload = { "title": "Foo Bar Baz", "textId": text_id, "level": 0, - "layerType": "plaintext", + "resourceType": "plaintext", "ownerId": user_data.get("id"), } - resp = await test_client.post("/layers", json=payload, cookies=session_cookie) + resp = await test_client.post("/resources", json=payload, cookies=session_cookie) assert resp.status_code == 201, status_fail_msg(201, resp) - layer_data = resp.json() - assert "id" in layer_data - assert "ownerId" in layer_data + resource_data = resp.json() + assert "id" in resource_data + assert "ownerId" in resource_data # become superuser user_data = await register_test_user(is_superuser=True, alternative=True) session_cookie = await get_session_cookie(user_data) - # publish unproposed layer + # publish unproposed resource resp = await test_client.post( - f"/layers/{layer_data['id']}/publish", + f"/resources/{resource_data['id']}/publish", cookies=session_cookie, ) assert resp.status_code == 400, status_fail_msg(400, resp) - # propose layer + # propose resource resp = await test_client.post( - f"/layers/{layer_data['id']}/propose", + f"/resources/{resource_data['id']}/propose", cookies=session_cookie, ) assert resp.status_code == 200, status_fail_msg(200, resp) - # get all accessible layers, check if ours is proposed - resp = await test_client.get("/layers", params={"textId": text_id}) + # get all accessible resources, check if ours is proposed + resp = await test_client.get("/resources", params={"textId": text_id}) assert resp.status_code == 200, status_fail_msg(200, resp) assert isinstance(resp.json(), list) - for layer in resp.json(): - if layer["id"] == layer_data["id"]: - assert layer["proposed"] - # propose layer again + for resource in resp.json(): + if resource["id"] == resource_data["id"]: + assert resource["proposed"] + # propose resource again resp = await test_client.post( - f"/layers/{layer_data['id']}/propose", + f"/resources/{resource_data['id']}/propose", cookies=session_cookie, ) assert resp.status_code == 400, status_fail_msg(400, resp) - # publish layer + # publish resource resp = await test_client.post( - f"/layers/{layer_data['id']}/publish", + f"/resources/{resource_data['id']}/publish", cookies=session_cookie, ) assert resp.status_code == 200, status_fail_msg(200, resp) - # unpublish layer + # unpublish resource resp = await test_client.post( - f"/layers/{layer_data['id']}/unpublish", + f"/resources/{resource_data['id']}/unpublish", cookies=session_cookie, ) assert resp.status_code == 200, status_fail_msg(200, resp) - # unpublish layer again + # unpublish resource again resp = await test_client.post( - f"/layers/{layer_data['id']}/unpublish", + f"/resources/{resource_data['id']}/unpublish", cookies=session_cookie, ) assert resp.status_code == 400, status_fail_msg(400, resp) - # propose layer again + # propose resource again resp = await test_client.post( - f"/layers/{layer_data['id']}/propose", + f"/resources/{resource_data['id']}/propose", cookies=session_cookie, ) assert resp.status_code == 200, status_fail_msg(200, resp) - # unpropose layer + # unpropose resource resp = await test_client.post( - f"/layers/{layer_data['id']}/unpropose", + f"/resources/{resource_data['id']}/unpropose", cookies=session_cookie, ) assert resp.status_code == 200, status_fail_msg(200, resp) @pytest.mark.anyio -async def test_delete_layer( +async def test_delete_resource( api_path, test_client: AsyncClient, insert_sample_data, @@ -327,52 +328,52 @@ async def test_delete_layer( register_test_user, get_session_cookie, ): - inserted_ids = await insert_sample_data("texts", "nodes", "layers") + inserted_ids = await insert_sample_data("texts", "nodes", "resources") text_id = inserted_ids["texts"][0] - layer_id = inserted_ids["layers"][0] - # get all accessible layers - resp = await test_client.get("/layers", params={"textId": text_id}) + resource_id = inserted_ids["resources"][0] + # get all accessible resources + resp = await test_client.get("/resources", params={"textId": text_id}) assert resp.status_code == 200, status_fail_msg(200, resp) assert isinstance(resp.json(), list) - layers_count = len(resp.json()) + resources_count = len(resp.json()) # register test superuser user_data = await register_test_user(is_superuser=True) session_cookie = await get_session_cookie(user_data) - # delete layer - resp = await test_client.delete(f"/layers/{layer_id}", cookies=session_cookie) + # delete resource + resp = await test_client.delete(f"/resources/{resource_id}", cookies=session_cookie) assert resp.status_code == 400, status_fail_msg(400, resp) - # unpublish layer + # unpublish resource resp = await test_client.post( - f"/layers/{layer_id}/unpublish", + f"/resources/{resource_id}/unpublish", cookies=session_cookie, ) - # delete layer - resp = await test_client.delete(f"/layers/{layer_id}", cookies=session_cookie) + # delete resource + resp = await test_client.delete(f"/resources/{resource_id}", cookies=session_cookie) assert resp.status_code == 204, status_fail_msg(204, resp) - # get all accessible layers again - resp = await test_client.get("/layers", params={"textId": text_id}) + # get all accessible resources again + resp = await test_client.get("/resources", params={"textId": text_id}) assert resp.status_code == 200, status_fail_msg(200, resp) assert isinstance(resp.json(), list) - assert len(resp.json()) == layers_count - 1 + assert len(resp.json()) == resources_count - 1 # @pytest.mark.anyio -# async def test_get_layer_template( +# async def test_get_resource_template( # api_path, test_client: AsyncClient, insert_sample_data # ): -# await insert_sample_data("texts", "nodes", "layers") -# # get all layers for text -# endpoint = f"/layers" +# await insert_sample_data("texts", "nodes", "resources") +# # get all resources for text +# endpoint = f"/resources" # resp = await test_client.get(endpoint, params={"textSlug": "rigveda"}) # assert resp.status_code == 200, status_fail_msg(200, resp) # assert isinstance(resp.json(), list) # assert len(resp.json()) > 0 # assert isinstance(resp.json()[0], dict) # assert "_id" in resp.json()[0] -# layer_id = resp.json()[0]["_id"] # remember layer ID -# # get template for layer -# endpoint = f"/layers/template" -# resp = await test_client.get(endpoint, params={"layerId": layer_id}) +# resource_id = resp.json()[0]["_id"] # remember resource ID +# # get template for resource +# endpoint = f"/resources/template" +# resp = await test_client.get(endpoint, params={"resourceId": resource_id}) # assert resp.status_code == 200, status_fail_msg(200, resp) # assert isinstance(resp.json(), dict) # assert "_unitSchema" in resp.json() @@ -380,10 +381,10 @@ async def test_delete_layer( # @pytest.mark.anyio -# async def test_get_layer_template_invalid_id( +# async def test_get_resource_template_invalid_id( # api_path, test_client: AsyncClient, insert_sample_data # ): -# await insert_sample_data("texts", "nodes", "layers") -# endpoint = f"/layers/template" -# resp = await test_client.get(endpoint, params={"layerId": "foo"}) +# await insert_sample_data("texts", "nodes", "resources") +# endpoint = f"/resources/template" +# resp = await test_client.get(endpoint, params={"resourceId": "foo"}) # assert resp.status_code == 400, status_fail_msg(400, resp) diff --git a/Tekst-API/tests/integration/test_api_unit.py b/Tekst-API/tests/integration/test_api_unit.py index 75816ee5..0191d331 100644 --- a/Tekst-API/tests/integration/test_api_unit.py +++ b/Tekst-API/tests/integration/test_api_unit.py @@ -12,24 +12,24 @@ async def test_create_unit( register_test_user, get_session_cookie, ): - text_id = (await insert_sample_data("texts", "nodes", "layers"))["texts"][0] + text_id = (await insert_sample_data("texts", "nodes", "resources"))["texts"][0] user_data = await register_test_user() session_cookie = await get_session_cookie(user_data) - # create new layer (because only owner can update(write)) + # create new resource (because only owner can update(write)) payload = { "title": "Foo Bar Baz", "textId": text_id, "level": 0, - "layerType": "plaintext", + "resourceType": "plaintext", "ownerId": user_data.get("id"), } - resp = await test_client.post("/layers", json=payload, cookies=session_cookie) + resp = await test_client.post("/resources", json=payload, cookies=session_cookie) assert resp.status_code == 201, status_fail_msg(201, resp) - layer_data = resp.json() - assert "id" in layer_data - assert "ownerId" in layer_data - assert layer_data["ownerId"] == user_data["id"] + resource_data = resp.json() + assert "id" in resource_data + assert "ownerId" in resource_data + assert resource_data["ownerId"] == user_data["id"] # get ID of existing test node resp = await test_client.get( @@ -41,10 +41,10 @@ async def test_create_unit( assert "id" in resp.json()[0] node_id = resp.json()[0]["id"] - # create plaintext layer unit + # create plaintext resource unit payload = { - "layerId": layer_data["id"], - "layerType": "plaintext", + "resourceId": resource_data["id"], + "resourceType": "plaintext", "nodeId": node_id, "text": "Ein Raabe geht im Feld spazieren.", "comment": "This is a comment", @@ -78,7 +78,7 @@ async def test_create_unit( # update unit resp = await test_client.patch( f"/units/{unit_id}", - json={"layerType": "plaintext", "text": "FOO BAR"}, + json={"resourceType": "plaintext", "text": "FOO BAR"}, cookies=session_cookie, ) assert resp.status_code == 200, status_fail_msg(200, resp) @@ -90,7 +90,7 @@ async def test_create_unit( # fail to update unit with invalid ID resp = await test_client.patch( "/units/637b9ad396d541a505e5439b", - json={"layerType": "plaintext", "text": "FOO BAR"}, + json={"resourceType": "plaintext", "text": "FOO BAR"}, cookies=session_cookie, ) assert resp.status_code == 400, status_fail_msg(400, resp) diff --git a/Tekst-API/tests/unit/test_models.py b/Tekst-API/tests/unit/test_models.py index 9302bbc6..e4d85a36 100644 --- a/Tekst-API/tests/unit/test_models.py +++ b/Tekst-API/tests/unit/test_models.py @@ -62,26 +62,26 @@ def test_model_field_casing(test_app): assert t.loc_delim == "bar" -def test_layer_description_validator(test_app): +def test_resource_description_validator(test_app): # desc with arbitrary whitespaces - from tekst.layer_types.plaintext import PlaintextLayer + from tekst.resource_types.plaintext import PlaintextResource - layer = PlaintextLayer( + resource = PlaintextResource( title="foo", text_id="5eb7cfb05e32e07750a1756a", level=0, - layer_type="plaintext", + resource_type="plaintext", description=[ {"locale": "enUS", "translation": "foo bar\t\t baz\n \ttest"} ], ) - assert layer.description[0]["translation"] == "foo bar baz test" + assert resource.description[0]["translation"] == "foo bar baz test" # desc = None - layer = PlaintextLayer( + resource = PlaintextResource( title="foo", text_id="5eb7cfb05e32e07750a1756a", level=0, - layer_type="plaintext", + resource_type="plaintext", ) - assert isinstance(layer.description, list) - assert len(layer.description) == 0 + assert isinstance(resource.description, list) + assert len(resource.description) == 0 diff --git a/Tekst-Web/src/api/index.ts b/Tekst-Web/src/api/index.ts index bca5213e..a0fa51d2 100644 --- a/Tekst-Web/src/api/index.ts +++ b/Tekst-Web/src/api/index.ts @@ -73,7 +73,7 @@ export function getFullUrl(path: string, query?: Record): URL { // export some common platform properties for use throughout codebase -export const layerTypes = ['plaintext', 'debug']; +export const resourceTypes = ['plaintext', 'debug']; export const prioritizedMetadataKeys = ['author', 'year', 'language']; // export components types for use throughout codebase @@ -110,7 +110,7 @@ export type PlatformStats = components['schemas']['PlatformStats']; export type PlatformData = components['schemas']['PlatformData']; export type PlatformSettingsRead = components['schemas']['PlatformSettingsRead']; export type PlatformSettingsUpdate = components['schemas']['PlatformSettingsUpdate']; -export type LayerNodeCoverage = components['schemas']['LayerNodeCoverage']; +export type ResourceNodeCoverage = components['schemas']['ResourceNodeCoverage']; // client segments @@ -119,34 +119,34 @@ export type ClientSegmentCreate = components['schemas']['ClientSegmentCreate']; export type ClientSegmentUpdate = components['schemas']['ClientSegmentUpdate']; export type ClientSegmentHead = components['schemas']['ClientSegmentHead']; -// data layers +// resources -export type PlaintextLayerCreate = components['schemas']['PlaintextLayerCreate']; -export type PlaintextLayerRead = components['schemas']['PlaintextLayerRead']; -export type PlaintextLayerUpdate = components['schemas']['PlaintextLayerUpdate']; -export type PlaintextLayerConfig = components['schemas']['PlaintextLayerConfig']; +export type PlaintextResourceCreate = components['schemas']['PlaintextResourceCreate']; +export type PlaintextResourceRead = components['schemas']['PlaintextResourceRead']; +export type PlaintextResourceUpdate = components['schemas']['PlaintextResourceUpdate']; +export type PlaintextResourceConfig = components['schemas']['PlaintextResourceConfig']; export type PlaintextUnitCreate = components['schemas']['PlaintextUnitCreate']; export type PlaintextUnitRead = components['schemas']['PlaintextUnitRead']; export type PlaintextUnitUpdate = components['schemas']['PlaintextUnitUpdate']; -export type DebugLayerCreate = components['schemas']['DebugLayerCreate']; -export type DebugLayerRead = components['schemas']['DebugLayerRead']; -export type DebugLayerUpdate = components['schemas']['DebugLayerUpdate']; -export type DebugLayerConfig = components['schemas']['DebugLayerConfig']; +export type DebugResourceCreate = components['schemas']['DebugResourceCreate']; +export type DebugResourceRead = components['schemas']['DebugResourceRead']; +export type DebugResourceUpdate = components['schemas']['DebugResourceUpdate']; +export type DebugResourceConfig = components['schemas']['DebugResourceConfig']; export type DebugUnitCreate = components['schemas']['DebugUnitCreate']; export type DebugUnitRead = components['schemas']['DebugUnitRead']; export type DebugUnitUpdate = components['schemas']['DebugUnitUpdate']; export type AnyUnitCreate = PlaintextUnitCreate | DebugUnitCreate; export type AnyUnitRead = PlaintextUnitRead | DebugUnitRead; -export type AnyLayerCreate = PlaintextLayerCreate | DebugLayerCreate; -export type AnyLayerRead = (PlaintextLayerRead | DebugLayerRead) & { +export type AnyResourceCreate = PlaintextResourceCreate | DebugResourceCreate; +export type AnyResourceRead = (PlaintextResourceRead | DebugResourceRead) & { active?: boolean; units?: AnyUnitRead[]; }; -export type AnyLayerUpdate = PlaintextLayerUpdate | DebugLayerUpdate; +export type AnyResourceUpdate = PlaintextResourceUpdate | DebugResourceUpdate; -// common data layer config types +// common resource config types -export type AnyLayerConfig = PlaintextLayerConfig | DebugLayerConfig; +export type AnyResourceConfig = PlaintextResourceConfig | DebugResourceConfig; export type DeepLLinksConfig = components['schemas']['DeepLLinksConfig']; diff --git a/Tekst-Web/src/api/schema.d.ts b/Tekst-Web/src/api/schema.d.ts index 26afbdf0..794fadfd 100644 --- a/Tekst-Web/src/api/schema.d.ts +++ b/Tekst-Web/src/api/schema.d.ts @@ -19,12 +19,12 @@ export interface paths { '/browse/unit-siblings': { /** * Get unit siblings - * @description Returns a list of all data layer units belonging to the data layer + * @description Returns a list of all resource units belonging to the resource * with the given ID, associated to nodes that are children of the parent node * with the given ID. * * As the resulting list may contain units of arbitrary type, the - * returned unit objects cannot be typed to their precise layer unit type. + * returned unit objects cannot be typed to their precise resource unit type. * Also, the returned unit objects have an additional property containing their * respective node's label, level and position. */ @@ -56,45 +56,9 @@ export interface paths { */ get: operations['getPathOptionsByRootId']; }; - '/browse/layers/{id}/coverage': { - /** Get layer coverage data */ - get: operations['getLayerCoverageData']; - }; - '/layers': { - /** - * Find layers - * @description Returns a list of all data layers matching the given criteria. - * - * As the resulting list of data layers may contain layers of different types, the - * returned layer objects cannot be typed to their precise layer type. - */ - get: operations['findLayers']; - /** Create layer */ - post: operations['createLayer']; - }; - '/layers/{id}': { - /** Get layer */ - get: operations['getLayer']; - /** Delete layer */ - delete: operations['deleteLayer']; - /** Update layer */ - patch: operations['updateLayer']; - }; - '/layers/{id}/propose': { - /** Propose layer */ - post: operations['proposeLayer']; - }; - '/layers/{id}/unpropose': { - /** Unpropose layer */ - post: operations['unproposeLayer']; - }; - '/layers/{id}/publish': { - /** Publish layer */ - post: operations['publishLayer']; - }; - '/layers/{id}/unpublish': { - /** Unpublish layer */ - post: operations['unpublishLayer']; + '/browse/resources/{id}/coverage': { + /** Get resource coverage data */ + get: operations['getResourceCoverageData']; }; '/nodes': { /** Find nodes */ @@ -168,6 +132,42 @@ export interface paths { /** Create segment */ post: operations['createSegment']; }; + '/resources': { + /** + * Find resources + * @description Returns a list of all resources matching the given criteria. + * + * As the resulting list of resources may contain resources of different types, the + * returned resource objects cannot be typed to their precise resource type. + */ + get: operations['findResources']; + /** Create resource */ + post: operations['createResource']; + }; + '/resources/{id}': { + /** Get resource */ + get: operations['getResource']; + /** Delete resource */ + delete: operations['deleteResource']; + /** Update resource */ + patch: operations['updateResource']; + }; + '/resources/{id}/propose': { + /** Propose resource */ + post: operations['proposeResource']; + }; + '/resources/{id}/unpropose': { + /** Unpropose resource */ + post: operations['unproposeResource']; + }; + '/resources/{id}/publish': { + /** Publish resource */ + post: operations['publishResource']; + }; + '/resources/{id}/unpublish': { + /** Unpublish resource */ + post: operations['unpublishResource']; + }; '/texts': { /** Get all texts */ get: operations['getAllTexts']; @@ -206,11 +206,11 @@ export interface paths { '/units': { /** * Find units - * @description Returns a list of all data layer units matching the given criteria. + * @description Returns a list of all resource units matching the given criteria. * - * Respects restricted layers and inactive texts. + * Respects restricted resources 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. + * returned unit objects cannot be typed to their precise resource unit type. */ get: operations['findUnits']; /** Create unit */ @@ -474,82 +474,81 @@ export interface components { /** Html */ html?: string | null; }; - /** DebugLayerConfig */ - DebugLayerConfig: { + /** DebugResourceConfig */ + DebugResourceConfig: { /** * Showonparentlevel - * @description Show combined contents of this layer on the parent level + * @description Show combined contents of this resource on the parent level * @default false */ showOnParentLevel?: boolean; - deeplLinks?: components['schemas']['DeepLLinksConfig']; }; - /** DebugLayerCreate */ - DebugLayerCreate: { + /** DebugResourceCreate */ + DebugResourceCreate: { /** * Title - * @description Title of this layer + * @description Title of this resource */ title: string; /** * Description - * @description Short, concise description of this data layer + * @description Short, concise description of this resource * @default [] */ - description?: components['schemas']['LayerDescriptionTranslation'][]; + description?: components['schemas']['ResourceDescriptionTranslation'][]; /** * Textid - * @description ID of the text this layer belongs to + * @description ID of the text this resource belongs to * @example 5eb7cf5a86d9755df3a6c593 */ textId: string; /** * Level - * @description Text level this layer belongs to + * @description Text level this resource belongs to */ level: number; /** - * Layertype + * Resourcetype * @constant */ - layerType: 'debug'; + resourceType: 'debug'; /** * Ownerid - * @description User owning this layer + * @description User owning this resource */ ownerId?: string | null; /** * Category - * @description Data layer category key + * @description Resource category key */ category?: string | null; /** * Sharedread - * @description Users with shared read access to this layer + * @description Users with shared read access to this resource * @default [] */ sharedRead?: string[]; /** * Sharedwrite - * @description Users with shared write access to this layer + * @description Users with shared write access to this resource * @default [] */ sharedWrite?: string[]; /** * Public - * @description Publication status of this layer + * @description Publication status of this resource * @default false */ public?: boolean; /** * Proposed - * @description Whether this layer has been proposed for publication + * @description Whether this resource has been proposed for publication * @default false */ proposed?: boolean; /** * Citation - * @description Citation details for this layer + * @description Citation details for this resource */ citation?: string | null; /** @@ -560,14 +559,19 @@ export interface components { meta?: components['schemas']['Metadate'][]; /** * Comment - * @description Plaintext, potentially multiline comment on this layer + * @description Plaintext, potentially multiline comment on this resource * @default [] */ - comment?: components['schemas']['LayerCommentTranslation'][]; - config?: components['schemas']['DebugLayerConfig']; + comment?: components['schemas']['ResourceCommentTranslation'][]; + /** + * @default { + * "showOnParentLevel": false + * } + */ + config?: components['schemas']['DebugResourceConfig']; }; - /** DebugLayerRead */ - DebugLayerRead: { + /** DebugResourceRead */ + DebugResourceRead: { /** * Id * @example 5eb7cf5a86d9755df3a6c593 @@ -575,85 +579,85 @@ export interface components { id: string; /** * Writable - * @description Whether this layer is writable for the requesting user + * @description Whether this resource is writable for the requesting user */ writable?: boolean | null; - /** @description Public user data for user owning this layer */ + /** @description Public user data for user owning this resource */ owner?: components['schemas']['UserReadPublic'] | null; /** * Sharedreadusers - * @description Public user data for users allowed to read this layer + * @description Public user data for users allowed to read this resource */ sharedReadUsers?: components['schemas']['UserReadPublic'][] | null; /** * Sharedwriteusers - * @description Public user data for users allowed to write this layer + * @description Public user data for users allowed to write this resource */ sharedWriteUsers?: components['schemas']['UserReadPublic'][] | null; /** * Title - * @description Title of this layer + * @description Title of this resource */ title: string; /** * Description - * @description Short, concise description of this data layer + * @description Short, concise description of this resource * @default [] */ - description?: components['schemas']['LayerDescriptionTranslation'][]; + description?: components['schemas']['ResourceDescriptionTranslation'][]; /** * Textid - * @description ID of the text this layer belongs to + * @description ID of the text this resource belongs to * @example 5eb7cf5a86d9755df3a6c593 */ textId: string; /** * Level - * @description Text level this layer belongs to + * @description Text level this resource belongs to */ level: number; /** - * Layertype + * Resourcetype * @constant */ - layerType: 'debug'; + resourceType: 'debug'; /** * Ownerid - * @description User owning this layer + * @description User owning this resource */ ownerId?: string | null; /** * Category - * @description Data layer category key + * @description Resource category key */ category?: string | null; /** * Sharedread - * @description Users with shared read access to this layer + * @description Users with shared read access to this resource * @default [] */ sharedRead?: string[]; /** * Sharedwrite - * @description Users with shared write access to this layer + * @description Users with shared write access to this resource * @default [] */ sharedWrite?: string[]; /** * Public - * @description Publication status of this layer + * @description Publication status of this resource * @default false */ public?: boolean; /** * Proposed - * @description Whether this layer has been proposed for publication + * @description Whether this resource has been proposed for publication * @default false */ proposed?: boolean; /** * Citation - * @description Citation details for this layer + * @description Citation details for this resource */ citation?: string | null; /** @@ -664,69 +668,74 @@ export interface components { meta?: components['schemas']['Metadate'][]; /** * Comment - * @description Plaintext, potentially multiline comment on this layer + * @description Plaintext, potentially multiline comment on this resource * @default [] */ - comment?: components['schemas']['LayerCommentTranslation'][]; - config?: components['schemas']['DebugLayerConfig']; + comment?: components['schemas']['ResourceCommentTranslation'][]; + /** + * @default { + * "showOnParentLevel": false + * } + */ + config?: components['schemas']['DebugResourceConfig']; [key: string]: unknown; }; - /** DebugLayerUpdate */ - DebugLayerUpdate: { + /** DebugResourceUpdate */ + DebugResourceUpdate: { /** Title */ title?: string | null; /** * Description - * @description Short, concise description of this data layer + * @description Short, concise description of this resource * @default [] */ - description?: components['schemas']['LayerDescriptionTranslation'][]; + description?: components['schemas']['ResourceDescriptionTranslation'][]; /** Textid */ textId?: string | null; /** Level */ level?: number | null; /** - * Layertype + * Resourcetype * @constant */ - layerType: 'debug'; + resourceType: 'debug'; /** * Ownerid - * @description User owning this layer + * @description User owning this resource */ ownerId?: string | null; /** * Category - * @description Data layer category key + * @description Resource category key */ category?: string | null; /** * Sharedread - * @description Users with shared read access to this layer + * @description Users with shared read access to this resource * @default [] */ sharedRead?: string[]; /** * Sharedwrite - * @description Users with shared write access to this layer + * @description Users with shared write access to this resource * @default [] */ sharedWrite?: string[]; /** * Public - * @description Publication status of this layer + * @description Publication status of this resource * @default false */ public?: boolean; /** * Proposed - * @description Whether this layer has been proposed for publication + * @description Whether this resource has been proposed for publication * @default false */ proposed?: boolean; /** * Citation - * @description Citation details for this layer + * @description Citation details for this resource */ citation?: string | null; /** @@ -737,25 +746,30 @@ export interface components { meta?: components['schemas']['Metadate'][]; /** * Comment - * @description Plaintext, potentially multiline comment on this layer + * @description Plaintext, potentially multiline comment on this resource * @default [] */ - comment?: components['schemas']['LayerCommentTranslation'][]; - config?: components['schemas']['DebugLayerConfig']; + comment?: components['schemas']['ResourceCommentTranslation'][]; + /** + * @default { + * "showOnParentLevel": false + * } + */ + config?: components['schemas']['DebugResourceConfig']; }; /** DebugUnitCreate */ DebugUnitCreate: { /** - * Layerid - * @description Data layer ID + * Resourceid + * @description Resource ID * @example 5eb7cf5a86d9755df3a6c593 */ - layerId: string; + resourceId: string; /** - * Layertype + * Resourcetype * @constant */ - layerType: 'debug'; + resourceType: 'debug'; /** * Nodeid * @description Parent text node ID @@ -781,16 +795,16 @@ export interface components { */ id: string; /** - * Layerid - * @description Data layer ID + * Resourceid + * @description Resource ID * @example 5eb7cf5a86d9755df3a6c593 */ - layerId: string; + resourceId: string; /** - * Layertype + * Resourcetype * @constant */ - layerType: 'debug'; + resourceType: 'debug'; /** * Nodeid * @description Parent text node ID @@ -811,13 +825,13 @@ export interface components { }; /** DebugUnitUpdate */ DebugUnitUpdate: { - /** Layerid */ - layerId?: string | null; + /** Resourceid */ + resourceId?: string | null; /** - * Layertype + * Resourcetype * @constant */ - layerType: 'debug'; + resourceType: 'debug'; /** Nodeid */ nodeId?: string | null; /** @@ -934,47 +948,6 @@ export interface components { /** Detail */ detail?: components['schemas']['ValidationError'][]; }; - /** LayerCategory */ - 'LayerCategory-Input': { - /** Key */ - key: string; - /** Translations */ - translations: components['schemas']['LayerCategoryTranslation'][]; - }; - /** LayerCategory */ - 'LayerCategory-Output': { - /** Key */ - key: string; - /** Translations */ - translations: components['schemas']['LayerCategoryTranslation'][]; - }; - /** LayerCategoryTranslation */ - LayerCategoryTranslation: { - locale: components['schemas']['Locale']; - /** Translation */ - translation: string; - }; - /** LayerCommentTranslation */ - LayerCommentTranslation: { - locale: components['schemas']['Locale']; - /** Translation */ - translation: string; - }; - /** LayerDescriptionTranslation */ - LayerDescriptionTranslation: { - locale: components['schemas']['Locale']; - /** Translation */ - translation: string; - }; - /** LayerNodeCoverage */ - LayerNodeCoverage: { - /** Label */ - label: string; - /** Position */ - position: number; - /** Covered */ - covered: boolean; - }; /** @enum {string} */ Locale: '*' | 'deDE' | 'enUS'; /** Metadate */ @@ -1073,11 +1046,11 @@ export interface components { /** Label */ label?: string | null; }; - /** PlaintextLayerConfig */ - PlaintextLayerConfig: { + /** PlaintextResourceConfig */ + PlaintextResourceConfig: { /** * Showonparentlevel - * @description Show combined contents of this layer on the parent level + * @description Show combined contents of this resource on the parent level * @default false */ showOnParentLevel?: boolean; @@ -1093,72 +1066,72 @@ export interface components { */ deeplLinks?: components['schemas']['DeepLLinksConfig']; }; - /** PlaintextLayerCreate */ - PlaintextLayerCreate: { + /** PlaintextResourceCreate */ + PlaintextResourceCreate: { /** * Title - * @description Title of this layer + * @description Title of this resource */ title: string; /** * Description - * @description Short, concise description of this data layer + * @description Short, concise description of this resource * @default [] */ - description?: components['schemas']['LayerDescriptionTranslation'][]; + description?: components['schemas']['ResourceDescriptionTranslation'][]; /** * Textid - * @description ID of the text this layer belongs to + * @description ID of the text this resource belongs to * @example 5eb7cf5a86d9755df3a6c593 */ textId: string; /** * Level - * @description Text level this layer belongs to + * @description Text level this resource belongs to */ level: number; /** - * Layertype + * Resourcetype * @constant */ - layerType: 'plaintext'; + resourceType: 'plaintext'; /** * Ownerid - * @description User owning this layer + * @description User owning this resource */ ownerId?: string | null; /** * Category - * @description Data layer category key + * @description Resource category key */ category?: string | null; /** * Sharedread - * @description Users with shared read access to this layer + * @description Users with shared read access to this resource * @default [] */ sharedRead?: string[]; /** * Sharedwrite - * @description Users with shared write access to this layer + * @description Users with shared write access to this resource * @default [] */ sharedWrite?: string[]; /** * Public - * @description Publication status of this layer + * @description Publication status of this resource * @default false */ public?: boolean; /** * Proposed - * @description Whether this layer has been proposed for publication + * @description Whether this resource has been proposed for publication * @default false */ proposed?: boolean; /** * Citation - * @description Citation details for this layer + * @description Citation details for this resource */ citation?: string | null; /** @@ -1169,10 +1142,10 @@ export interface components { meta?: components['schemas']['Metadate'][]; /** * Comment - * @description Plaintext, potentially multiline comment on this layer + * @description Plaintext, potentially multiline comment on this resource * @default [] */ - comment?: components['schemas']['LayerCommentTranslation'][]; + comment?: components['schemas']['ResourceCommentTranslation'][]; /** * @default { * "showOnParentLevel": false, @@ -1186,10 +1159,10 @@ export interface components { * } * } */ - config?: components['schemas']['PlaintextLayerConfig']; + config?: components['schemas']['PlaintextResourceConfig']; }; - /** PlaintextLayerRead */ - PlaintextLayerRead: { + /** PlaintextResourceRead */ + PlaintextResourceRead: { /** * Id * @example 5eb7cf5a86d9755df3a6c593 @@ -1197,85 +1170,85 @@ export interface components { id: string; /** * Writable - * @description Whether this layer is writable for the requesting user + * @description Whether this resource is writable for the requesting user */ writable?: boolean | null; - /** @description Public user data for user owning this layer */ + /** @description Public user data for user owning this resource */ owner?: components['schemas']['UserReadPublic'] | null; /** * Sharedreadusers - * @description Public user data for users allowed to read this layer + * @description Public user data for users allowed to read this resource */ sharedReadUsers?: components['schemas']['UserReadPublic'][] | null; /** * Sharedwriteusers - * @description Public user data for users allowed to write this layer + * @description Public user data for users allowed to write this resource */ sharedWriteUsers?: components['schemas']['UserReadPublic'][] | null; /** * Title - * @description Title of this layer + * @description Title of this resource */ title: string; /** * Description - * @description Short, concise description of this data layer + * @description Short, concise description of this resource * @default [] */ - description?: components['schemas']['LayerDescriptionTranslation'][]; + description?: components['schemas']['ResourceDescriptionTranslation'][]; /** * Textid - * @description ID of the text this layer belongs to + * @description ID of the text this resource belongs to * @example 5eb7cf5a86d9755df3a6c593 */ textId: string; /** * Level - * @description Text level this layer belongs to + * @description Text level this resource belongs to */ level: number; /** - * Layertype + * Resourcetype * @constant */ - layerType: 'plaintext'; + resourceType: 'plaintext'; /** * Ownerid - * @description User owning this layer + * @description User owning this resource */ ownerId?: string | null; /** * Category - * @description Data layer category key + * @description Resource category key */ category?: string | null; /** * Sharedread - * @description Users with shared read access to this layer + * @description Users with shared read access to this resource * @default [] */ sharedRead?: string[]; /** * Sharedwrite - * @description Users with shared write access to this layer + * @description Users with shared write access to this resource * @default [] */ sharedWrite?: string[]; /** * Public - * @description Publication status of this layer + * @description Publication status of this resource * @default false */ public?: boolean; /** * Proposed - * @description Whether this layer has been proposed for publication + * @description Whether this resource has been proposed for publication * @default false */ proposed?: boolean; /** * Citation - * @description Citation details for this layer + * @description Citation details for this resource */ citation?: string | null; /** @@ -1286,10 +1259,10 @@ export interface components { meta?: components['schemas']['Metadate'][]; /** * Comment - * @description Plaintext, potentially multiline comment on this layer + * @description Plaintext, potentially multiline comment on this resource * @default [] */ - comment?: components['schemas']['LayerCommentTranslation'][]; + comment?: components['schemas']['ResourceCommentTranslation'][]; /** * @default { * "showOnParentLevel": false, @@ -1303,65 +1276,65 @@ export interface components { * } * } */ - config?: components['schemas']['PlaintextLayerConfig']; + config?: components['schemas']['PlaintextResourceConfig']; [key: string]: unknown; }; - /** PlaintextLayerUpdate */ - PlaintextLayerUpdate: { + /** PlaintextResourceUpdate */ + PlaintextResourceUpdate: { /** Title */ title?: string | null; /** * Description - * @description Short, concise description of this data layer + * @description Short, concise description of this resource * @default [] */ - description?: components['schemas']['LayerDescriptionTranslation'][]; + description?: components['schemas']['ResourceDescriptionTranslation'][]; /** Textid */ textId?: string | null; /** Level */ level?: number | null; /** - * Layertype + * Resourcetype * @constant */ - layerType: 'plaintext'; + resourceType: 'plaintext'; /** * Ownerid - * @description User owning this layer + * @description User owning this resource */ ownerId?: string | null; /** * Category - * @description Data layer category key + * @description Resource category key */ category?: string | null; /** * Sharedread - * @description Users with shared read access to this layer + * @description Users with shared read access to this resource * @default [] */ sharedRead?: string[]; /** * Sharedwrite - * @description Users with shared write access to this layer + * @description Users with shared write access to this resource * @default [] */ sharedWrite?: string[]; /** * Public - * @description Publication status of this layer + * @description Publication status of this resource * @default false */ public?: boolean; /** * Proposed - * @description Whether this layer has been proposed for publication + * @description Whether this resource has been proposed for publication * @default false */ proposed?: boolean; /** * Citation - * @description Citation details for this layer + * @description Citation details for this resource */ citation?: string | null; /** @@ -1372,10 +1345,10 @@ export interface components { meta?: components['schemas']['Metadate'][]; /** * Comment - * @description Plaintext, potentially multiline comment on this layer + * @description Plaintext, potentially multiline comment on this resource * @default [] */ - comment?: components['schemas']['LayerCommentTranslation'][]; + comment?: components['schemas']['ResourceCommentTranslation'][]; /** * @default { * "showOnParentLevel": false, @@ -1389,21 +1362,21 @@ export interface components { * } * } */ - config?: components['schemas']['PlaintextLayerConfig']; + config?: components['schemas']['PlaintextResourceConfig']; }; /** PlaintextUnitCreate */ PlaintextUnitCreate: { /** - * Layerid - * @description Data layer ID + * Resourceid + * @description Resource ID * @example 5eb7cf5a86d9755df3a6c593 */ - layerId: string; + resourceId: string; /** - * Layertype + * Resourcetype * @constant */ - layerType: 'plaintext'; + resourceType: 'plaintext'; /** * Nodeid * @description Parent text node ID @@ -1429,16 +1402,16 @@ export interface components { */ id: string; /** - * Layerid - * @description Data layer ID + * Resourceid + * @description Resource ID * @example 5eb7cf5a86d9755df3a6c593 */ - layerId: string; + resourceId: string; /** - * Layertype + * Resourcetype * @constant */ - layerType: 'plaintext'; + resourceType: 'plaintext'; /** * Nodeid * @description Parent text node ID @@ -1459,13 +1432,13 @@ export interface components { }; /** PlaintextUnitUpdate */ PlaintextUnitUpdate: { - /** Layerid */ - layerId?: string | null; + /** Resourceid */ + resourceId?: string | null; /** - * Layertype + * Resourcetype * @constant */ - layerType: 'plaintext'; + resourceType: 'plaintext'; /** Nodeid */ nodeId?: string | null; /** @@ -1610,17 +1583,17 @@ export interface components { */ navInfoEntry?: components['schemas']['PlatformNavInfoEntryTranslation'][]; /** - * Layercategories - * @description Layer categories to categorize layers in + * Resourcecategories + * @description Resource categories to categorize resources in * @default [] */ - layerCategories?: components['schemas']['LayerCategory-Output'][]; + resourceCategories?: components['schemas']['ResourceCategory-Output'][]; /** - * Showlayercategoryheadings - * @description Show layer category headings in browse view + * Showresourcecategoryheadings + * @description Show resource category headings in browse view * @default true */ - showLayerCategoryHeadings?: boolean; + showResourceCategoryHeadings?: boolean; /** * Alwaysshowtextinfo * @description Always show text info and selector in header, even on non-text-specific pages @@ -1692,17 +1665,17 @@ export interface components { */ navInfoEntry?: components['schemas']['PlatformNavInfoEntryTranslation'][]; /** - * Layercategories - * @description Layer categories to categorize layers in + * Resourcecategories + * @description Resource categories to categorize resources in * @default [] */ - layerCategories?: components['schemas']['LayerCategory-Input'][]; + resourceCategories?: components['schemas']['ResourceCategory-Input'][]; /** - * Showlayercategoryheadings - * @description Show layer category headings in browse view + * Showresourcecategoryheadings + * @description Show resource category headings in browse view * @default true */ - showLayerCategoryHeadings?: boolean; + showResourceCategoryHeadings?: boolean; /** * Alwaysshowtextinfo * @description Always show text info and selector in header, even on non-text-specific pages @@ -1732,6 +1705,47 @@ export interface components { /** Texts */ texts: components['schemas']['TextStats'][]; }; + /** ResourceCategory */ + 'ResourceCategory-Input': { + /** Key */ + key: string; + /** Translations */ + translations: components['schemas']['ResourceCategoryTranslation'][]; + }; + /** ResourceCategory */ + 'ResourceCategory-Output': { + /** Key */ + key: string; + /** Translations */ + translations: components['schemas']['ResourceCategoryTranslation'][]; + }; + /** ResourceCategoryTranslation */ + ResourceCategoryTranslation: { + locale: components['schemas']['Locale']; + /** Translation */ + translation: string; + }; + /** ResourceCommentTranslation */ + ResourceCommentTranslation: { + locale: components['schemas']['Locale']; + /** Translation */ + translation: string; + }; + /** ResourceDescriptionTranslation */ + ResourceDescriptionTranslation: { + locale: components['schemas']['Locale']; + /** Translation */ + translation: string; + }; + /** ResourceNodeCoverage */ + ResourceNodeCoverage: { + /** Label */ + label: string; + /** Position */ + position: number; + /** Covered */ + covered: boolean; + }; /** TextCreate */ TextCreate: { /** @@ -1866,10 +1880,10 @@ export interface components { id: string; /** Nodescount */ nodesCount: number; - /** Layerscount */ - layersCount: number; - /** Layertypes */ - layerTypes: { + /** Resourcescount */ + resourcesCount: number; + /** Resourcetypes */ + resourceTypes: { [key: string]: number; }; }; @@ -2119,20 +2133,20 @@ export interface operations { }; /** * Get unit siblings - * @description Returns a list of all data layer units belonging to the data layer + * @description Returns a list of all resource units belonging to the resource * with the given ID, associated to nodes that are children of the parent node * with the given ID. * * As the resulting list may contain units of arbitrary type, the - * returned unit objects cannot be typed to their precise layer unit type. + * returned unit objects cannot be typed to their precise resource unit type. * Also, the returned unit objects have an additional property containing their * respective node's label, level and position. */ getUnitSiblings: { parameters: { query: { - /** @description ID of layer the requested units belong to */ - layerId: string; + /** @description ID of resource the requested units belong to */ + resourceId: string; /** @description ID of node for which siblings to get associated units for */ parentNodeId?: string | null; }; @@ -2253,8 +2267,8 @@ export interface operations { }; }; }; - /** Get layer coverage data */ - getLayerCoverageData: { + /** Get resource coverage data */ + getResourceCoverageData: { parameters: { path: { id: string; @@ -2264,7 +2278,7 @@ export interface operations { /** @description Successful Response */ 200: { content: { - 'application/json': components['schemas']['LayerNodeCoverage'][]; + 'application/json': components['schemas']['ResourceNodeCoverage'][]; }; }; /** @description Not found */ @@ -2279,19 +2293,14 @@ export interface operations { }; }; }; - /** - * Find layers - * @description Returns a list of all data layers matching the given criteria. - * - * As the resulting list of data layers may contain layers of different types, the - * returned layer objects cannot be typed to their precise layer type. - */ - findLayers: { + /** Find nodes */ + findNodes: { parameters: { query: { textId: string; level?: number; - layerType?: string; + position?: number; + parentId?: string; limit?: number; }; }; @@ -2299,10 +2308,7 @@ export interface operations { /** @description Successful Response */ 200: { content: { - 'application/json': ( - | components['schemas']['DebugLayerRead'] - | components['schemas']['PlaintextLayerRead'] - )[]; + 'application/json': components['schemas']['NodeRead'][]; }; }; /** @description Not found */ @@ -2317,22 +2323,22 @@ export interface operations { }; }; }; - /** Create layer */ - createLayer: { + /** + * Create node + * @description Creates a new node. The position will be automatically set to the last position + * of the node's parent (or the first parent before that has children). + */ + createNode: { requestBody: { content: { - 'application/json': - | components['schemas']['DebugLayerCreate'] - | components['schemas']['PlaintextLayerCreate']; + 'application/json': components['schemas']['NodeCreate']; }; }; responses: { - /** @description Created */ + /** @description Successful Response */ 201: { content: { - 'application/json': - | components['schemas']['DebugLayerRead'] - | components['schemas']['PlaintextLayerRead']; + 'application/json': components['schemas']['NodeRead']; }; }; /** @description Not found */ @@ -2347,20 +2353,20 @@ export interface operations { }; }; }; - /** Get layer */ - getLayer: { + /** Get children */ + getChildren: { parameters: { - path: { - id: string; + query?: { + parentId?: string | null; + textId?: string | null; + limit?: number; }; }; responses: { /** @description Successful Response */ 200: { content: { - 'application/json': - | components['schemas']['DebugLayerRead'] - | components['schemas']['PlaintextLayerRead']; + 'application/json': components['schemas']['NodeRead'][]; }; }; /** @description Not found */ @@ -2375,8 +2381,8 @@ export interface operations { }; }; }; - /** Delete layer */ - deleteLayer: { + /** Get node */ + getNode: { parameters: { path: { id: string; @@ -2384,8 +2390,10 @@ export interface operations { }; responses: { /** @description Successful Response */ - 204: { - content: never; + 200: { + content: { + 'application/json': components['schemas']['NodeRead']; + }; }; /** @description Not found */ 404: { @@ -2399,27 +2407,21 @@ export interface operations { }; }; }; - /** Update layer */ - updateLayer: { + /** + * Delete node + * @description Deletes the specified node. Also deletes any associated units, child nodes and units associated with child nodes. + */ + deleteNode: { parameters: { path: { id: string; }; }; - requestBody: { - content: { - 'application/json': - | components['schemas']['DebugLayerUpdate'] - | components['schemas']['PlaintextLayerUpdate']; - }; - }; responses: { /** @description Successful Response */ 200: { content: { - 'application/json': - | components['schemas']['DebugLayerRead'] - | components['schemas']['PlaintextLayerRead']; + 'application/json': components['schemas']['DeleteNodeResult']; }; }; /** @description Not found */ @@ -2434,20 +2436,23 @@ export interface operations { }; }; }; - /** Propose layer */ - proposeLayer: { + /** Update node */ + updateNode: { parameters: { path: { id: string; }; }; + requestBody: { + content: { + 'application/json': components['schemas']['NodeUpdate']; + }; + }; responses: { /** @description Successful Response */ 200: { content: { - 'application/json': - | components['schemas']['DebugLayerRead'] - | components['schemas']['PlaintextLayerRead']; + 'application/json': components['schemas']['NodeRead']; }; }; /** @description Not found */ @@ -2462,20 +2467,26 @@ export interface operations { }; }; }; - /** Unpropose layer */ - unproposeLayer: { + /** + * Move node + * @description Moves the specified node to a new position on its structure level. + */ + moveNode: { parameters: { path: { id: string; }; }; + requestBody: { + content: { + 'application/json': components['schemas']['MoveNodeRequestBody']; + }; + }; responses: { /** @description Successful Response */ 200: { content: { - 'application/json': - | components['schemas']['DebugLayerRead'] - | components['schemas']['PlaintextLayerRead']; + 'application/json': components['schemas']['NodeRead']; }; }; /** @description Not found */ @@ -2490,48 +2501,39 @@ export interface operations { }; }; }; - /** Publish layer */ - publishLayer: { - parameters: { - path: { - id: string; - }; - }; + /** + * Get platform data + * @description Returns data the client needs to initialize + */ + getPlatformData: { responses: { /** @description Successful Response */ 200: { content: { - 'application/json': - | components['schemas']['DebugLayerRead'] - | components['schemas']['PlaintextLayerRead']; + 'application/json': components['schemas']['PlatformData']; }; }; /** @description Not found */ 404: { content: never; }; - /** @description Validation Error */ - 422: { - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; }; }; - /** Unpublish layer */ - unpublishLayer: { + /** + * Get public user info + * @description Returns public information on the user with the specified username or ID + */ + getPublicUserInfo: { parameters: { path: { - id: string; + usernameOrId: string; }; }; responses: { /** @description Successful Response */ 200: { content: { - 'application/json': - | components['schemas']['DebugLayerRead'] - | components['schemas']['PlaintextLayerRead']; + 'application/json': components['schemas']['UserReadPublic']; }; }; /** @description Not found */ @@ -2546,22 +2548,24 @@ export interface operations { }; }; }; - /** Find nodes */ - findNodes: { + /** + * Find public users + * @description Returns a list of public users matching the given query. + * + * Only returns active user accounts. The query is considered to match a full token + * (e.g. first name, last name, username, a word in the affiliation field). + */ + findPublicUsers: { parameters: { query: { - textId: string; - level?: number; - position?: number; - parentId?: string; - limit?: number; + q: string | null; }; }; responses: { /** @description Successful Response */ 200: { content: { - 'application/json': components['schemas']['NodeRead'][]; + 'application/json': components['schemas']['UserReadPublic'][]; }; }; /** @description Not found */ @@ -2576,22 +2580,18 @@ export interface operations { }; }; }; - /** - * Create node - * @description Creates a new node. The position will be automatically set to the last position - * of the node's parent (or the first parent before that has children). - */ - createNode: { + /** Update platform settings */ + updatePlatformSettings: { requestBody: { content: { - 'application/json': components['schemas']['NodeCreate']; + 'application/json': components['schemas']['PlatformSettingsUpdate']; }; }; responses: { /** @description Successful Response */ - 201: { + 200: { content: { - 'application/json': components['schemas']['NodeRead']; + 'application/json': components['schemas']['PlatformSettingsRead']; }; }; /** @description Not found */ @@ -2606,20 +2606,18 @@ export interface operations { }; }; }; - /** Get children */ - getChildren: { + /** Get segment */ + getSegment: { parameters: { - query?: { - parentId?: string | null; - textId?: string | null; - limit?: number; + path: { + id: string; }; }; responses: { /** @description Successful Response */ 200: { content: { - 'application/json': components['schemas']['NodeRead'][]; + 'application/json': components['schemas']['ClientSegmentRead']; }; }; /** @description Not found */ @@ -2634,8 +2632,8 @@ export interface operations { }; }; }; - /** Get node */ - getNode: { + /** Delete segment */ + deleteSegment: { parameters: { path: { id: string; @@ -2643,10 +2641,8 @@ export interface operations { }; responses: { /** @description Successful Response */ - 200: { - content: { - 'application/json': components['schemas']['NodeRead']; - }; + 204: { + content: never; }; /** @description Not found */ 404: { @@ -2660,21 +2656,23 @@ export interface operations { }; }; }; - /** - * Delete node - * @description Deletes the specified node. Also deletes any associated units, child nodes and units associated with child nodes. - */ - deleteNode: { + /** Update segment */ + updateSegment: { parameters: { path: { id: string; }; }; + requestBody: { + content: { + 'application/json': components['schemas']['ClientSegmentUpdate']; + }; + }; responses: { /** @description Successful Response */ 200: { content: { - 'application/json': components['schemas']['DeleteNodeResult']; + 'application/json': components['schemas']['ClientSegmentRead']; }; }; /** @description Not found */ @@ -2689,23 +2687,18 @@ export interface operations { }; }; }; - /** Update node */ - updateNode: { - parameters: { - path: { - id: string; - }; - }; + /** Create segment */ + createSegment: { requestBody: { content: { - 'application/json': components['schemas']['NodeUpdate']; + 'application/json': components['schemas']['ClientSegmentCreate']; }; }; responses: { /** @description Successful Response */ - 200: { + 201: { content: { - 'application/json': components['schemas']['NodeRead']; + 'application/json': components['schemas']['ClientSegmentRead']; }; }; /** @description Not found */ @@ -2721,25 +2714,29 @@ export interface operations { }; }; /** - * Move node - * @description Moves the specified node to a new position on its structure level. + * Find resources + * @description Returns a list of all resources matching the given criteria. + * + * As the resulting list of resources may contain resources of different types, the + * returned resource objects cannot be typed to their precise resource type. */ - moveNode: { + findResources: { parameters: { - path: { - id: string; - }; - }; - requestBody: { - content: { - 'application/json': components['schemas']['MoveNodeRequestBody']; + query: { + textId: string; + level?: number; + resourceType?: string; + limit?: number; }; }; responses: { /** @description Successful Response */ 200: { content: { - 'application/json': components['schemas']['NodeRead']; + 'application/json': ( + | components['schemas']['DebugResourceRead'] + | components['schemas']['PlaintextResourceRead'] + )[]; }; }; /** @description Not found */ @@ -2754,39 +2751,50 @@ export interface operations { }; }; }; - /** - * Get platform data - * @description Returns data the client needs to initialize - */ - getPlatformData: { + /** Create resource */ + createResource: { + requestBody: { + content: { + 'application/json': + | components['schemas']['DebugResourceCreate'] + | components['schemas']['PlaintextResourceCreate']; + }; + }; responses: { - /** @description Successful Response */ - 200: { + /** @description Created */ + 201: { content: { - 'application/json': components['schemas']['PlatformData']; + 'application/json': + | components['schemas']['DebugResourceRead'] + | components['schemas']['PlaintextResourceRead']; }; }; /** @description Not found */ 404: { content: never; }; + /** @description Validation Error */ + 422: { + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; }; }; - /** - * Get public user info - * @description Returns public information on the user with the specified username or ID - */ - getPublicUserInfo: { + /** Get resource */ + getResource: { parameters: { path: { - usernameOrId: string; + id: string; }; }; responses: { /** @description Successful Response */ 200: { content: { - 'application/json': components['schemas']['UserReadPublic']; + 'application/json': + | components['schemas']['DebugResourceRead'] + | components['schemas']['PlaintextResourceRead']; }; }; /** @description Not found */ @@ -2801,25 +2809,17 @@ export interface operations { }; }; }; - /** - * Find public users - * @description Returns a list of public users matching the given query. - * - * Only returns active user accounts. The query is considered to match a full token - * (e.g. first name, last name, username, a word in the affiliation field). - */ - findPublicUsers: { + /** Delete resource */ + deleteResource: { parameters: { - query: { - q: string | null; + path: { + id: string; }; }; responses: { /** @description Successful Response */ - 200: { - content: { - 'application/json': components['schemas']['UserReadPublic'][]; - }; + 204: { + content: never; }; /** @description Not found */ 404: { @@ -2833,18 +2833,27 @@ export interface operations { }; }; }; - /** Update platform settings */ - updatePlatformSettings: { + /** Update resource */ + updateResource: { + parameters: { + path: { + id: string; + }; + }; requestBody: { content: { - 'application/json': components['schemas']['PlatformSettingsUpdate']; + 'application/json': + | components['schemas']['DebugResourceUpdate'] + | components['schemas']['PlaintextResourceUpdate']; }; }; responses: { /** @description Successful Response */ 200: { content: { - 'application/json': components['schemas']['PlatformSettingsRead']; + 'application/json': + | components['schemas']['DebugResourceRead'] + | components['schemas']['PlaintextResourceRead']; }; }; /** @description Not found */ @@ -2859,8 +2868,8 @@ export interface operations { }; }; }; - /** Get segment */ - getSegment: { + /** Propose resource */ + proposeResource: { parameters: { path: { id: string; @@ -2870,7 +2879,9 @@ export interface operations { /** @description Successful Response */ 200: { content: { - 'application/json': components['schemas']['ClientSegmentRead']; + 'application/json': + | components['schemas']['DebugResourceRead'] + | components['schemas']['PlaintextResourceRead']; }; }; /** @description Not found */ @@ -2885,8 +2896,8 @@ export interface operations { }; }; }; - /** Delete segment */ - deleteSegment: { + /** Unpropose resource */ + unproposeResource: { parameters: { path: { id: string; @@ -2894,8 +2905,12 @@ export interface operations { }; responses: { /** @description Successful Response */ - 204: { - content: never; + 200: { + content: { + 'application/json': + | components['schemas']['DebugResourceRead'] + | components['schemas']['PlaintextResourceRead']; + }; }; /** @description Not found */ 404: { @@ -2909,23 +2924,20 @@ export interface operations { }; }; }; - /** Update segment */ - updateSegment: { + /** Publish resource */ + publishResource: { parameters: { path: { id: string; }; }; - requestBody: { - content: { - 'application/json': components['schemas']['ClientSegmentUpdate']; - }; - }; responses: { /** @description Successful Response */ 200: { content: { - 'application/json': components['schemas']['ClientSegmentRead']; + 'application/json': + | components['schemas']['DebugResourceRead'] + | components['schemas']['PlaintextResourceRead']; }; }; /** @description Not found */ @@ -2940,18 +2952,20 @@ export interface operations { }; }; }; - /** Create segment */ - createSegment: { - requestBody: { - content: { - 'application/json': components['schemas']['ClientSegmentCreate']; + /** Unpublish resource */ + unpublishResource: { + parameters: { + path: { + id: string; }; }; responses: { /** @description Successful Response */ - 201: { + 200: { content: { - 'application/json': components['schemas']['ClientSegmentRead']; + 'application/json': + | components['schemas']['DebugResourceRead'] + | components['schemas']['PlaintextResourceRead']; }; }; /** @description Not found */ @@ -3226,17 +3240,17 @@ export interface operations { }; /** * Find units - * @description Returns a list of all data layer units matching the given criteria. + * @description Returns a list of all resource units matching the given criteria. * - * Respects restricted layers and inactive texts. + * Respects restricted resources 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. + * returned unit objects cannot be typed to their precise resource unit type. */ findUnits: { parameters: { query?: { - /** @description ID (or list of IDs) of layer(s) to return unit data for */ - layerId?: string[]; + /** @description ID (or list of IDs) of resource(s) to return unit data for */ + resourceId?: string[]; /** @description ID (or list of IDs) of node(s) to return unit data for */ nodeId?: string[]; /** @description Return at most items */ diff --git a/Tekst-Web/src/components/LayerListItem.vue b/Tekst-Web/src/components/ResourceListItem.vue similarity index 61% rename from Tekst-Web/src/components/LayerListItem.vue rename to Tekst-Web/src/components/ResourceListItem.vue index dccd48d1..1eebe269 100644 --- a/Tekst-Web/src/components/LayerListItem.vue +++ b/Tekst-Web/src/components/ResourceListItem.vue @@ -1,9 +1,9 @@