From 24e34ddc20a4f3978b57b539db711afae34d0e7c Mon Sep 17 00:00:00 2001 From: Marcin Procyk Date: Wed, 17 Nov 2021 15:38:32 +0100 Subject: [PATCH] refactor(lists+groups): improve value objects + remove new lists implementation (DEV-160) (#1932) * refactor group create payload * separate group route value object * add group iri value object * use project IRI in group create patload + add group value object error messages & test * updated groups value objects and tests * add value objects to groups update payload * move optional params to value objects + replace either by zio.Validation * refactor(list): replace Either with zio.Validation + move option to value objects * refactor projectIri value object * fix node creation * add validation to payloads creation in lists and groups * remove doubled checks and related tests * disable new list route + separate NodeApiRequestADM to root & child * list route factory + new list route implementation * refactor list naming * make parentNodeIri required for ListChildNodeCreatePayloadADM * fix tests * refactor lists get methods * update lists docs * fix update single lists properties + groups cleanup * fix client-test-data * bring back GET methods * fix file extension * fix testdata --- docs/03-apis/api-admin/groups.md | 2 +- docs/03-apis/api-admin/index.md | 1 - docs/03-apis/api-admin/lists.md | 264 ++-- .../lists_new-list-admin-routes_v1.md | 318 ----- mkdocs.yml | 1 - webapi/scripts/expected-client-test-data.txt | 28 +- webapi/src/main/resources/application.conf | 15 - .../webapi/messages/StringFormatter.scala | 15 +- .../groupsmessages/GroupPayloadsADM.scala | 38 - .../GroupsErrorMessagesADM.scala | 15 + .../groupsmessages/GroupsMessagesADM.scala | 31 +- .../groupsmessages/GroupsPayloadsADM.scala | 31 + .../listsmessages/ListPayloadsADM.scala | 36 +- .../listsmessages/ListsErrorMessagesADM.scala | 1 + .../listsmessages/ListsMessagesADM.scala | 81 +- .../valueObjects/GroupsValueObjectsADM.scala | 120 ++ .../valueObjects/IriValueObjectsADM.scala | 37 + .../valueObjects/ListsValueObjectsADM.scala | 154 ++- .../valueObjects/ValueObjectsADM.scala | 2 +- .../org/knora/webapi/responders/BUILD.bazel | 2 + .../responders/admin/GroupsResponderADM.scala | 85 +- .../responders/admin/ListsResponderADM.scala | 132 +- .../webapi/routing/SwaggerApiDocsRoute.scala | 1 - .../webapi/routing/admin/GroupsRouteADM.scala | 252 ++-- .../webapi/routing/admin/ListsRouteADM.scala | 4 +- .../routing/admin/ProjectsRouteADM.scala | 1 + .../lists/ListsRouteADMFeatureFactory.scala | 41 - .../admin/lists/NewListsRouteADMFeature.scala | 456 ------- .../admin/lists/OldListsRouteADMFeature.scala | 450 +++---- .../admin/lists/UpdateListItemsRouteADM.scala | 44 +- .../webapi/e2e/admin/GroupsADME2ESpec.scala | 7 - .../knora/webapi/e2e/admin/lists/BUILD.bazel | 19 - .../NewListsRoutesADMFeatureE2ESpec.scala | 1078 ----------------- .../OldListsRouteADMFeatureE2ESpec.scala | 47 +- .../UpdateListItemsRouteADME2ESpec.scala | 22 +- .../responder/groupsmessages/BUILD.bazel | 22 - .../GroupsMessagesADMSpec.scala | 43 - .../listsmessages/ListsMessagesADMSpec.scala | 67 +- .../admin/responder/valueObjects/BUILD.bazel | 2 + .../GroupsValueObjectsADMSpec.scala | 112 ++ .../valueObjects/IriValueObjectsADMSpec.scala | 44 + .../ListsValueObjectsADMSpec.scala | 107 +- .../admin/GroupsResponderADMSpec.scala | 88 +- .../admin/ListsResponderADMSpec.scala | 143 +-- 44 files changed, 1238 insertions(+), 3221 deletions(-) delete mode 100644 docs/03-apis/api-admin/lists_new-list-admin-routes_v1.md delete mode 100644 webapi/src/main/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupPayloadsADM.scala create mode 100644 webapi/src/main/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupsErrorMessagesADM.scala create mode 100644 webapi/src/main/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupsPayloadsADM.scala create mode 100644 webapi/src/main/scala/org/knora/webapi/messages/admin/responder/valueObjects/GroupsValueObjectsADM.scala create mode 100644 webapi/src/main/scala/org/knora/webapi/messages/admin/responder/valueObjects/IriValueObjectsADM.scala delete mode 100644 webapi/src/main/scala/org/knora/webapi/routing/admin/lists/ListsRouteADMFeatureFactory.scala delete mode 100644 webapi/src/main/scala/org/knora/webapi/routing/admin/lists/NewListsRouteADMFeature.scala delete mode 100644 webapi/src/test/scala/org/knora/webapi/e2e/admin/lists/NewListsRoutesADMFeatureE2ESpec.scala delete mode 100644 webapi/src/test/scala/org/knora/webapi/messages/admin/responder/groupsmessages/BUILD.bazel delete mode 100644 webapi/src/test/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupsMessagesADMSpec.scala create mode 100644 webapi/src/test/scala/org/knora/webapi/messages/admin/responder/valueObjects/GroupsValueObjectsADMSpec.scala create mode 100644 webapi/src/test/scala/org/knora/webapi/messages/admin/responder/valueObjects/IriValueObjectsADMSpec.scala diff --git a/docs/03-apis/api-admin/groups.md b/docs/03-apis/api-admin/groups.md index 37180e5ded..f7a72ec5c4 100644 --- a/docs/03-apis/api-admin/groups.md +++ b/docs/03-apis/api-admin/groups.md @@ -87,7 +87,7 @@ hasProjectRestrictedGroupAdminPermission (for this group) - PUT: `/admin/groups//status` - BODY: -``` +```json { "status": false } diff --git a/docs/03-apis/api-admin/index.md b/docs/03-apis/api-admin/index.md index 831849d97d..bd6b228a55 100644 --- a/docs/03-apis/api-admin/index.md +++ b/docs/03-apis/api-admin/index.md @@ -13,6 +13,5 @@ The Knora admin API makes it possible to administer Knora projects, users, user - [Projects Endpoint](projects.md) - [Groups Endpoint](groups.md) - [Lists Endpoint](lists.md) -- [New Lists Endpoint](lists_new-list-admin-routes_v1.md) - [Permissions Endpoint](permissions.md) - [Stores Endpoint](stores.md) diff --git a/docs/03-apis/api-admin/lists.md b/docs/03-apis/api-admin/lists.md index d3861e52ae..26149fa305 100644 --- a/docs/03-apis/api-admin/lists.md +++ b/docs/03-apis/api-admin/lists.md @@ -10,23 +10,23 @@ **List Item Operations:** - `GET: /admin/lists[?projectIri=]` : return all lists optionally filtered by project -- `GET: /admin/lists/` : return complete list with all children if IRI of the list (i.e. root node) is given. -If IRI of the child node is given, return the node with its immediate children. +- `GET: /admin/lists/` : return complete list with all children if IRI of the list (i.e. root node) is given +If IRI of the child node is given, return the node with its immediate children - `GET: /admin/lists/infos/` : return list information (without children) - `GET: /admin/lists/nodes/` : return list node information (without children) +- `GET: /admin/lists//info` : return list basic information (without children) - `POST: /admin/lists` : create new list - `POST: /admin/lists/` : create new child node under the supplied parent node IRI -- `PUT: /admin/lists/` : update node information (root or child.) -- `PUT: /admin/lists//name` : update the name of the node (root or child). -- `PUT: /admin/lists//labels` : update labels of the node (root or child). -- `PUT: /admin/lists//comments` : update comments of the node (root or child). +- `PUT: /admin/lists/` : update node information (root or child) +- `PUT: /admin/lists//name` : update the name of the node (root or child) +- `PUT: /admin/lists//labels` : update labels of the node (root or child) +- `PUT: /admin/lists//comments` : update comments of the node (root or child) - `PUT: /admin/lists//position` : update position of a child node within its current parent or by changing its -parent node. +parent node -- `DELETE: /admin/lists/` : delete a list (i.e. root node) or a child node and -all its children, if not used +- `DELETE: /admin/lists/` : delete a list (i.e. root node) or a child node and all its children, if not used ## List Item Operations @@ -39,20 +39,39 @@ all its children, if not used ### Get list - Required permission: none - - Return complete `list` including basic information of the list, `listinfo`, and all its children. + - Return complete `list` (or `node`) including basic information of the list (or child node), `listinfo` (or `nodeinfo`), +and all its children - GET: `/admin/lists/` +### Get list's information + +- Required permission: none +- Return list information, `listinfo` (without children). +- GET: `/admin/lists/infos/` + +### Get list node Information + +- Required permission: none +- Return node information, `nodeinfo`, (without children). +- GET: `/admin/lists/nodes/` + +### Get list's information (merged) + +- Required permission: none +- Return list (or node) basic information, `listinfo` (or `nodeinfo`), without its children +- GET: `/admin/lists//info` ### Create new list - Required permission: SystemAdmin / ProjectAdmin + - Required fields: `projectIri`, `labels`, `comments` - POST: `/admin/lists` - BODY: ```json { "projectIri": "someprojectiri", - "labels": [{ "value": "Neue Liste", "language": "de"}], + "labels": [{ "value": "New list", "language": "en"}], "comments": [] } ``` @@ -64,8 +83,8 @@ Additionally, each list can have an optional custom IRI (of [Knora IRI](../api-v "id": "http://rdfh.ch/lists/0001/yWQEGXl53Z4C4DYJ-S2c5A", "projectIri": "http://rdfh.ch/projects/0001", "name": "a new list", - "labels": [{ "value": "Neue Liste mit IRI", "language": "de"}], - "comments": [] + "labels": [{ "value": "New list with IRI", "language": "en"}], + "comments": [{ "value": "New comment", "language": "en"}] } ``` @@ -75,13 +94,13 @@ The response will contain the basic information of the list, `listinfo` and an e "list": { "children": [], "listinfo": { - "comments": [], + "comments": [{ "value": "New comment", "language": "en"}], "id": "http://rdfh.ch/lists/0001/yWQEGXl53Z4C4DYJ-S2c5A", "isRootNode": true, "labels": [ { - "value": "Neue Liste mit IRI", - "language": "de" + "value": "New list with IRI", + "language": "en" } ], "name": "a new list", @@ -90,18 +109,79 @@ The response will contain the basic information of the list, `listinfo` and an e } } ``` -### Get list's information - - Required permission: none - - Return list information, `listinfo` (without children). - - GET: `/admin/lists/infos/` +### Create new child node + +- Required permission: SystemAdmin / ProjectAdmin +- Required fields: `parentNodeIri`, `projectIri`, `labels`, +- Appends a new child node under the supplied nodeIri. If the supplied nodeIri + is the listIri, then a new child node is appended to the top level. If a position is given + for the new child node, the node will be created and inserted in the specified position, otherwise + the node is appended to the end of parent's children. +- POST: `/admin/lists/` +- BODY: + +```json + { + "parentNodeIri": "http://rdfh.ch/lists/0001/yWQEGXl53Z4C4DYJ-S2c5A", + "projectIri": "http://rdfh.ch/projects/0001", + "name": "a child", + "labels": [{ "value": "New List Node", "language": "en"}] + } +``` + +Additionally, each child node can have an optional custom IRI (of [Knora IRI](../api-v2/knora-iris.md#iris-for-data) form) specified by the `id` in the request body as below: + +```json +{ "id": "http://rdfh.ch/lists/0001/8u37MxBVMbX3XQ8-d31x6w", + "parentNodeIri": "http://rdfh.ch/lists/0001/yWQEGXl53Z4C4DYJ-S2c5A", + "projectIri": "http://rdfh.ch/projects/0001", + "name": "a child", + "labels": [{ "value": "New List Node", "language": "en"}] +} +``` + +The response will contain the basic information of the node, `nodeinfo`, as below: +```json +{ + "nodeinfo": { + "comments": [], + "hasRootNode": "http://rdfh.ch/lists/0001/yWQEGXl53Z4C4DYJ-S2c5A", + "id": "http://rdfh.ch/lists/0001/8u37MxBVMbX3XQ8-d31x6w", + "labels": [ + { + "value": "New List Node", + "language": "en" + } + ], + "name": "a new child", + "position": 1 + } +} +``` +The new node can be created and inserted in a specific position which must be given in the payload as shown below. If necessary, +according to the given position, the sibling nodes will be shifted. Note that `position` cannot have a value higher than the +number of existing children. + +```json +{ "parentNodeIri": "http://rdfh.ch/lists/0001/yWQEGXl53Z4C4DYJ-S2c5A", + "projectIri": "http://rdfh.ch/projects/0001", + "name": "Inserted new child", + "position": 0, + "labels": [{ "value": "New List Node", "language": "en"}] +} +``` + +In case the new node should be appended to the list of current children, either `position: -1` must be given in the +payload or the `position` parameter must be left out of the payload. -### Update list's information -The basic information of a list such as its labels, comments, name, or all of them can be updated. The parameters that +### Update list's or node's information +The basic information of a list (or node) such as its labels, comments, name, or all of them can be updated. The parameters that must be updated together with the new value must be given in the JSON body of the request together with the IRI of the list and the IRI of the project it belongs to. - Required permission: SystemAdmin / ProjectAdmin + - Required fields: `listIri`, `projectIri` - Update list information - PUT: `/admin/lists/` - BODY: @@ -115,7 +195,7 @@ list and the IRI of the project it belongs to. } ``` -The response will contain the basic information of the list, `listinfo`, without its children, as below: +The response will contain the basic information of the list, `listinfo` (or `nodeinfo`), without its children, as below: ```json { "listinfo": { @@ -193,144 +273,6 @@ There is no need to specify the project IRI because it is automatically extracte ``` There is no need to specify the project IRI because it is automatically extracted using the given ``. -### Get node - - - Required permission: none - - Return complete `node` including basic information of the list, `nodeinfo`, and all its immediate children. - - GET: `/admin/lists/` - -### Get List Node Information - - - Required permission: none - - Return node information, `nodeinfo`, (without children). - - GET: `/admin/lists/nodes/` - - -### Create new child node - - - Required permission: SystemAdmin / ProjectAdmin - - Appends a new child node under the supplied nodeIri. If the supplied nodeIri - is the listIri, then a new child node is appended to the top level. If a position is given - for the new child node, the node will be created and inserted in the specified position, otherwise - the node is appended to the end of parent's children. - - POST: `/admin/lists/` - - BODY: - -```json - { - "parentNodeIri": "http://rdfh.ch/lists/0001/yWQEGXl53Z4C4DYJ-S2c5A", - "projectIri": "http://rdfh.ch/projects/0001", - "name": "a child", - "labels": [{ "value": "New List Node", "language": "en"}], - "comments": [] - } -``` - -Additionally, each child node can have an optional custom IRI (of [Knora IRI](../api-v2/knora-iris.md#iris-for-data) form) specified by the `id` in the request body as below: - -```json -{ "id": "http://rdfh.ch/lists/0001/8u37MxBVMbX3XQ8-d31x6w", - "parentNodeIri": "http://rdfh.ch/lists/0001/yWQEGXl53Z4C4DYJ-S2c5A", - "projectIri": "http://rdfh.ch/projects/0001", - "name": "a child", - "labels": [{ "value": "New List Node", "language": "en"}], - "comments": [] -} -``` - -The response will contain the basic information of the node, `nodeinfo`, as below: -```json -{ - "nodeinfo": { - "comments": [], - "hasRootNode": "http://rdfh.ch/lists/0001/yWQEGXl53Z4C4DYJ-S2c5A", - "id": "http://rdfh.ch/lists/0001/8u37MxBVMbX3XQ8-d31x6w", - "labels": [ - { - "value": "New List Node", - "language": "en" - } - ], - "name": "a new child", - "position": 1 - } -} -``` -The new node can be created and inserted in a specific position which must be given in the payload as shown below. If necessary, -according to the given position, the sibling nodes will be shifted. Note that `position` cannot have a value higher than the -number of existing children. - -```json -{ "parentNodeIri": "http://rdfh.ch/lists/0001/yWQEGXl53Z4C4DYJ-S2c5A", - "projectIri": "http://rdfh.ch/projects/0001", - "name": "Inserted new child", - "position": 0, - "labels": [{ "value": "New List Node", "language": "en"}], - "comments": [] -} -``` - -In case the new node should be appended to the list of current children, either `position: -1` must be given in the -payload or the `position` parameter must be left out of the payload. - -### Update node's information -The basic information of a node such as its labels, comments, name, or all of them can be updated. The parameters that -must be updated together with the new value must be given in the JSON body of the request together with the IRI of the -node and the IRI of the project it belongs to. - - - Required permission: SystemAdmin / ProjectAdmin - - Update node information - - PUT: `/admin/lists/` - - BODY: - -```json - { "listIri": "http://rdfh.ch/lists/0001/8u37MxBVMbX3XQ8-d31x6w", - "projectIri": "http://rdfh.ch/projects/0001", - "name": "new node name", - "labels": [{ "value": "new node label", "language": "en"}], - "comments": [{ "value": "new node comment", "language": "en"}] - } -``` - -The response will contain the basic information of the node as `nodeInfo` without its children, as below: - -```json -{ - "nodeinfo": { - "comments": [ - { - "value": "new node comment", - "language": "en" - } - ], - "hasRootNode": "http://rdfh.ch/lists/0001/yWQEGXl53Z4C4DYJ-S2c5A", - "id": "http://rdfh.ch/lists/0001/8u37MxBVMbX3XQ8-d31x6w", - "labels": [ - { - "value": "new node label", - "language": "en" - } - ], - "name": "new node name", - "position": 0 - } -} -``` - -If only name of the node must be updated, it can be given as below in the body of the request: - -```json - { - "listIri": "nodeIri", - "projectIri": "projectIri", - "name": "another name" - } -``` - -Alternatively, basic information of the child node can be updated individually as explained above (See -[update node name](#update-list-or-nodes-name), [update node labels](#update-list-or-nodes-labels), and -[update node comments](#update-list-or-nodes-comments)). - ### Repositioning a child node The position of an existing child node can be updated. The child node can be either repositioned within its diff --git a/docs/03-apis/api-admin/lists_new-list-admin-routes_v1.md b/docs/03-apis/api-admin/lists_new-list-admin-routes_v1.md deleted file mode 100644 index 331b27ffe1..0000000000 --- a/docs/03-apis/api-admin/lists_new-list-admin-routes_v1.md +++ /dev/null @@ -1,318 +0,0 @@ - - -# Lists Endpoint -## To use the routes in this endpoint the [feature toggle](../feature-toggles.md), `new-list-admin-routes:1` must - be activated. - -## Endpoint Overview - -**List Item Operations:** - -- `POST: /admin/lists` : create new list item (root or child node). To use activate: `new-list-admin-routes:1`. - -- `GET: /admin/lists[?projectIri=]` : return all lists optionally filtered by project - -- `GET: /admin/lists/` : if given a root node IRI, return complete list with all children. -Otherwise, if given a child node IRI, it returns node completely with its immediate children. -To use activate: `new-list-admin-routes:1`. - -- `GET: /admin/lists//info` : return information (without children) of the node whose IRI is given -(root or child). To use activate: `new-list-admin-routes:1`. - -- `PUT: /admin/lists/` : update information of the node (root or child). -- `PUT: /admin/lists//name` : update the name of the node (root or child). -- `PUT: /admin/lists//labels` : update labels of the node (root or child). -- `PUT: /admin/lists//comments` : update comments of the node (root or child). -- `PUT: /admin/lists//position` : update position of a child node within its current parent or by changing its -parent node. - -- `DELETE: /admin/lists/` : delete a list (i.e. root node) or a child node and -all its children, if not used -### Get lists - - - Required permission: none - - Return all lists optionally filtered by project - - GET: `/admin/lists[?projectIri=]` - -### Get list item (entire list or a node) -To use activate: `new-list-admin-routes:1`. - - Required permission: none - - Response: - - If the IRI of the list (i.e. root node) is given, return complete `list` including its basic information, `listinfo`, - and all children of the list. - - If the IRI of a child node is given, return the entire `node` including its basic information, `nodeinfo`, - with its immediate children. - - GET: `/admin/lists/` - - -### Get item information (entire list or a node) -To use activate: `new-list-admin-routes:1`. - - Required permission: none - - Response: - - If the IRI of the list (i.e. root node) is given, return basic information of the list, `listinfo` without children. - - If the IRI of a child node is given, return basic information of the node, `nodeinfo`, without its children. - - GET: `/admin/lists//info` - -### Create new item (entire list or a node) -To use activate: `new-list-admin-routes:1`. - - Required permission: SystemAdmin / ProjectAdmin - - POST: `/admin/lists` - - BODY: - -#### Create an entirely new list -The IRI of the project must be given in the body of the request to which the list -is supposed to be attached. -Further basic information about the list such as its `labels` and `comments` must also be provided. -Optionally, the request body can contain a `name` for the list which must be unique in the project. - -```json - { - "projectIri": "someprojectiri", - "labels": [{ "value": "Neue Liste", "language": "de"}], - "comments": [] - } -``` - -Additionally, each list can have an optional custom IRI (of [Knora IRI](../api-v2/knora-iris.md#iris-for-data) form) specified by the `id` in the request body as below: - -```json - { - "id": "http://rdfh.ch/lists/0001/yWQEGXl53Z4C4DYJ-S2c5A", - "projectIri": "http://rdfh.ch/projects/0001", - "name": "a new list", - "labels": [{ "value": "Neue Liste mit IRI", "language": "de"}], - "comments": [] - } -``` - -The response will contain the basic information of the list, `listinfo` and an empty list of its children, as below: - -```json -{ - "list": { - "children": [], - "listinfo": { - "comments": [], - "id": "http://rdfh.ch/lists/0001/yWQEGXl53Z4C4DYJ-S2c5A", - "isRootNode": true, - "labels": [ - { - "value": "Neue Liste mit IRI", - "language": "de" - } - ], - "name": "a new list", - "projectIri": "http://rdfh.ch/projects/0001" - } - } -} -``` - -#### Create a new child node -The IRI of its parent node must be given in the request body by `parentNodeIri`. -Furthermore, the request body should also contain the project IRI of the list and basic information of the node as below: - -```json - { - "parentNodeIri": "http://rdfh.ch/lists/0001/yWQEGXl53Z4C4DYJ-S2c5A", - "projectIri": "http://rdfh.ch/projects/0001", - "name": "a child", - "labels": [{ "value": "New List Node", "language": "en"}], - "comments": [] - } -``` - -Additionally, each child node can have an optional custom IRI (of [Knora IRI](../api-v2/knora-iris.md#iris-for-data) form) specified by the `id` in the request body as below: - -```json -{ "id": "http://rdfh.ch/lists/0001/8u37MxBVMbX3XQ8-d31x6w", - "parentNodeIri": "http://rdfh.ch/lists/0001/yWQEGXl53Z4C4DYJ-S2c5A", - "projectIri": "http://rdfh.ch/projects/0001", - "name": "a child", - "labels": [{ "value": "New List Node", "language": "en"}], - "comments": [] -} -``` - -The response will contain the basic information of the node, `nodeinfo`, as below: - -```json -{ - "nodeinfo": { - "comments": [], - "hasRootNode": "http://rdfh.ch/lists/0001/yWQEGXl53Z4C4DYJ-S2c5A", - "id": "http://rdfh.ch/lists/0001/8u37MxBVMbX3XQ8-d31x6w", - "labels": [ - { - "value": "New List Node", - "language": "en" - } - ], - "name": "a new child", - "position": 1 - } -} -``` - -The new node can be created and inserted in a specific position which must be given in the payload as shown below. If necessary, -according to the given position, the sibling nodes will be shifted. Note that `position` cannot have a value higher than -number of existing children. - -```json -{ "parentNodeIri": "http://rdfh.ch/lists/0001/yWQEGXl53Z4C4DYJ-S2c5A", - "projectIri": "http://rdfh.ch/projects/0001", - "name": "Inserted new child", - "position": 0, - "labels": [{ "value": "New List Node", "language": "en"}], - "comments": [] -} -``` - -In case the new node should be appended to the list of current children, either `position: -1` must be given in the -payload or the `position` parameter must be left out of the payload. - -### Update basic information (entire list or a node) -The basic information of a list or a node such as its labels, comments, name, or all of them can be updated. The parameters that -must be updated together with the new value must be given in the JSON body of the request together with the IRI of the -list item (root or child node) and the IRI of the project it belongs to. - - - Required permission: SystemAdmin / ProjectAdmin - - Update list information - - Response: - - If the IRI of the list (i.e. root node) is given, return basic information of the list, `listinfo` without children. - - If the IRI of a child node is given, return basic information of the node, `nodeinfo`, without its children. - - PUT: `/admin/lists/` - - BODY: - -```json - { - "listIri": "listIri", - "projectIri": "someprojectiri", - "name": "a new name", - "labels": [{ "value": "Neue geönderte Liste", "language": "de"}, { "value": "Changed list", "language": "en"}], - "comments": [{ "value": "Neuer Kommentar", "language": "de"}, { "value": "New comment", "language": "en"}] - } -``` -If only name of the list must be updated, it can be given as below in the body of the request: - -```json - { - "listIri": "listIri", - "projectIri": "someprojectiri", - "name": "another name" - } -``` - -Alternatively, basic information `name`, `labels`, or `comments` or a list or a child node can be updated individually -as explained below. - -### Update list or node's name - - - Required permission: SystemAdmin / ProjectAdmin - - Update name of the list (i.e. root node) or a child node whose IRI is specified by ``. - - PUT: `/admin/lists//name` - - BODY: - The new name of the node must be given in the body of the request as shown below: - ```json -{ - "name": "a new name" -} -``` -There is no need to specify the project IRI because it is automatically extracted using the given ``. - -### Update list or node's labels - - - Required permission: SystemAdmin / ProjectAdmin - - Update labels of the list (i.e. root node) or a child node whose IRI is specified by ``. - - PUT: `/admin/lists//labels` - - BODY: - The new set of labels of the node must be given in the body of the request as shown below: - ```json -{ - "labels": [{"language": "se", "value": "nya märkningen"}] -} -``` -There is no need to specify the project IRI because it is automatically extracted using the given ``. - -### Update list or node's comments - - - Required permission: SystemAdmin / ProjectAdmin - - Update comments of the list (i.e. root node) or a child node whose IRI is specified by ``. - - PUT: `/admin/lists//labels` - - BODY: - The new set of comments of the node must be given in the body of the request as shown below: - ```json -{ - "comments": [{"language": "se", "value": "nya kommentarer"}] -} -``` -There is no need to specify the project IRI because it is automatically extracted using the given ``. - -### Repositioning a child node - -The position of an existing child node can be updated. The child node can be either repositioned within its -current parent node, or can be added to another parent node in a specific position. The IRI of the parent node -and the new position of the child node must be given in the request body. - -If a node is supposed to be repositioned to the end of a parent node's children, give `position: -1`. - -Suppose a parent node `parentNode1` has five children in positions 0-4, to change the position of its child node -`childNode4` from its original position 3 to position 1 the request body should specify the IRI of its parent node -and the new position as below: -```json - { - "parentNodeIri": "", - "position": 1 - } -``` - -Then the node `childNode4` will be put in position 1, and its siblings will be shifted accordingly. The new position given -in the request body cannot be the same as the child node's original position. If `position: -1` is given, the node will -be moved to the end of children list, and its siblings will be shifted to left. In case of repositioning the node -within its current parent, the maximum permitted position is the length of its children list, i.e. in this example the -highest allowed position is 4. - -To reposition a child node `childNode4` to another parent node `parentNode2` in a specific position, for -example `position: 3`, the IRI of the new parent node and the position the node must be placed within children of -`parentNode2` must be given as: - -```json - { - "parentNodeIri": "", - "position": 3 - } -``` - -In this case, the `childNode4` is removed from the list of children of its old parent `parentNode1` and its old -siblings are shifted accordingly. Then the node `childNode4` is added to the specified new parent, i.e. `parentNode2`, in -the given position. The new siblings are shifted accordingly. - -Note that, the furthest the node can be placed is at the end of the list of the children of `parentNode2`. That means -if `parentNode2` had 3 children with positions 0-2, then `childNode4` can be placed in position 0-3 within children -of its new parent node. If the `position: -1` is given, the node will be appended to the end of new parent's children, -and new siblings will not be shifted. - -Values less than -1 are not permitted for parameter `position`. - -- Required permission: SystemAdmin / ProjectAdmin -- Response: returns the updated parent node with all its children. -- Put `/admin/lists//position` - -### Delete a list or a node -An entire list or a single node of it can be completely deleted, if not in use. Before deleting an entire list -(i.e. root node), the data and ontologies are checked for any usage of the list or its children. If not in use, the list -and all its children are deleted. - -Similarily, before deleting a single node of a list, it is verified that the node itself and none of its children are used. -If not in use, the node and all its children are deleted. Once a node is deleted, its parent node is updated by shifting the -remaining child nodes with respect to the position of the deleted node. - -- Required permission: SystemAdmin / ProjectAdmin -- Response: - - If the IRI of the list (i.e. root node) is given, the `iri` of the deleted list with a flag `deleted: true` is returned. - - If the IRI of a child node is given, the updated parent node is returned. - -- Delete `/admin/lists/` diff --git a/mkdocs.yml b/mkdocs.yml index f6496355a2..118b8251a1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -58,7 +58,6 @@ nav: - Projects Endpoint: 03-apis/api-admin/projects.md - Groups Endpoint: 03-apis/api-admin/groups.md - Lists Endpoint: 03-apis/api-admin/lists.md - - New Lists Endpoint: 03-apis/api-admin/lists_new-list-admin-routes_v1.md - Permissions Endpoint: 03-apis/api-admin/permissions.md - Stores Endpoint: 03-apis/api-admin/stores.md - Util API: diff --git a/webapi/scripts/expected-client-test-data.txt b/webapi/scripts/expected-client-test-data.txt index c062f45f1a..8a03e14513 100644 --- a/webapi/scripts/expected-client-test-data.txt +++ b/webapi/scripts/expected-client-test-data.txt @@ -28,6 +28,7 @@ test-data/admin/lists/delete-list-node-response.json test-data/admin/lists/delete-list-response.json test-data/admin/lists/get-anything-project-lists-response.json test-data/admin/lists/get-image-project-lists-response.json +test-data/admin/lists/get-list-info-response-new-merged-get-route.json test-data/admin/lists/get-list-info-response.json test-data/admin/lists/get-list-node-info-response.json test-data/admin/lists/get-list-response.json @@ -35,32 +36,7 @@ test-data/admin/lists/get-lists-response.json test-data/admin/lists/get-node-response.json test-data/admin/lists/insert-childNode-in-position-request.json test-data/admin/lists/insert-childNode-in-position-response.json -test-data/admin/lists/toggle_new-list-admin-routes_v1/ -test-data/admin/lists/toggle_new-list-admin-routes_v1/add-second-child-to-root-request.json -test-data/admin/lists/toggle_new-list-admin-routes_v1/add-second-child-to-root-response.json -test-data/admin/lists/toggle_new-list-admin-routes_v1/create-child-node-request.json -test-data/admin/lists/toggle_new-list-admin-routes_v1/create-child-node-response.json -test-data/admin/lists/toggle_new-list-admin-routes_v1/create-child-node-with-custom-IRI-request.json -test-data/admin/lists/toggle_new-list-admin-routes_v1/create-child-node-with-custom-IRI-response.json -test-data/admin/lists/toggle_new-list-admin-routes_v1/create-list-request.json -test-data/admin/lists/toggle_new-list-admin-routes_v1/create-list-response.json -test-data/admin/lists/toggle_new-list-admin-routes_v1/create-list-with-custom-IRI-request.json -test-data/admin/lists/toggle_new-list-admin-routes_v1/create-list-with-custom-IRI-response.json -test-data/admin/lists/toggle_new-list-admin-routes_v1/get-anything-project-lists-response.json -test-data/admin/lists/toggle_new-list-admin-routes_v1/get-image-project-lists-response.json -test-data/admin/lists/toggle_new-list-admin-routes_v1/get-list-info-response.json -test-data/admin/lists/toggle_new-list-admin-routes_v1/get-list-node-info-response.json -test-data/admin/lists/toggle_new-list-admin-routes_v1/get-list-response.json -test-data/admin/lists/toggle_new-list-admin-routes_v1/get-lists-response.json -test-data/admin/lists/toggle_new-list-admin-routes_v1/get-node-response.json -test-data/admin/lists/toggle_new-list-admin-routes_v1/update-list-info-comment-label-multiple-languages-request.json -test-data/admin/lists/toggle_new-list-admin-routes_v1/update-list-info-comment-label-multiple-languages-response.json -test-data/admin/lists/toggle_new-list-admin-routes_v1/update-list-info-request.json -test-data/admin/lists/toggle_new-list-admin-routes_v1/update-list-info-response.json -test-data/admin/lists/toggle_new-list-admin-routes_v1/update-list-name-request.json -test-data/admin/lists/toggle_new-list-admin-routes_v1/update-list-name-response.json -test-data/admin/lists/toggle_new-list-admin-routes_v1/update-node-info-name-request.json -test-data/admin/lists/toggle_new-list-admin-routes_v1/update-node-info-name-response.json +test-data/admin/lists/not-update-childNode-comments-request.json test-data/admin/lists/update-childNode-comments-request.json test-data/admin/lists/update-childNode-comments-response.json test-data/admin/lists/update-childNode-labels-request.json diff --git a/webapi/src/main/resources/application.conf b/webapi/src/main/resources/application.conf index 09a07bd7fe..ed8341b202 100644 --- a/webapi/src/main/resources/application.conf +++ b/webapi/src/main/resources/application.conf @@ -265,21 +265,6 @@ akka-http-cors { app { feature-toggles { - new-list-admin-routes { - description = "Replace the old list admin routes with new ones." - - available-versions = [ 1 ] - default-version = 1 - enabled-by-default = no - override-allowed = yes - expiration-date = "2021-12-01T00:00:00Z" - - developer-emails = [ - "Sepideh Alassi " - "Benjamin Geer " - ] - } - jena-rdf-library { description = "Use the Jena API for RDF processing. If turned off, use the RDF4J API." diff --git a/webapi/src/main/scala/org/knora/webapi/messages/StringFormatter.scala b/webapi/src/main/scala/org/knora/webapi/messages/StringFormatter.scala index 5c5f1f8ec7..5e860fb5ff 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/StringFormatter.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/StringFormatter.scala @@ -5,14 +5,6 @@ package org.knora.webapi.messages -import java.nio.ByteBuffer -import java.text.ParseException -import java.time._ -import java.time.format.DateTimeFormatter -import java.time.temporal.{ChronoField, TemporalAccessor} -import java.util.concurrent.ConcurrentHashMap -import java.util.{Base64, UUID} - import akka.actor.ActorRef import akka.event.LoggingAdapter import akka.http.scaladsl.util.FastFuture @@ -39,6 +31,13 @@ import org.knora.webapi.settings.KnoraSettingsImpl import org.knora.webapi.util.{Base64UrlCheckDigit, JavaUtil} import spray.json._ +import java.nio.ByteBuffer +import java.text.ParseException +import java.time._ +import java.time.format.DateTimeFormatter +import java.time.temporal.{ChronoField, TemporalAccessor} +import java.util.concurrent.ConcurrentHashMap +import java.util.{Base64, UUID} import scala.concurrent.{ExecutionContext, Future} import scala.util.control.Exception._ import scala.util.matching.Regex diff --git a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupPayloadsADM.scala b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupPayloadsADM.scala deleted file mode 100644 index 95112bef13..0000000000 --- a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupPayloadsADM.scala +++ /dev/null @@ -1,38 +0,0 @@ -package org.knora.webapi.messages.admin.responder.groupsmessages - -import org.knora.webapi.IRI -import org.knora.webapi.messages.admin.responder.valueObjects.{Name, Description, Selfjoin, Status} - -/** - * Group payload - */ -sealed abstract case class GroupCreatePayloadADM private ( - // TODO: shouldn't IRI be a value object too - since it's just String synonym? - id: Option[IRI], - name: Name, - descriptions: Description, - project: IRI, - status: Status, - selfjoin: Selfjoin -) - -object GroupCreatePayloadADM { - - /** The create constructor */ - def create( - id: Option[IRI], - name: Name, - descriptions: Description, - project: IRI, - status: Status, - selfjoin: Selfjoin - ): GroupCreatePayloadADM = - new GroupCreatePayloadADM( - id = id, - name = name, - descriptions = descriptions, - project = project, - status = status, - selfjoin = selfjoin - ) {} -} diff --git a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupsErrorMessagesADM.scala b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupsErrorMessagesADM.scala new file mode 100644 index 0000000000..70c9e99593 --- /dev/null +++ b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupsErrorMessagesADM.scala @@ -0,0 +1,15 @@ +/* + * Copyright © 2021 Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.knora.webapi.messages.admin.responder.groupsmessages + +object GroupsErrorMessagesADM { + val GROUP_IRI_MISSING_ERROR = "Group IRI cannot be empty." + val GROUP_IRI_INVALID_ERROR = "Group IRI is invalid." + val GROUP_NAME_MISSING_ERROR = "Group name cannot be empty." + val GROUP_NAME_INVALID_ERROR = "Group name is invalid." + val GROUP_DESCRIPTION_MISSING_ERROR = "Group description cannot be empty." + val GROUP_DESCRIPTION_INVALID_ERROR = "Group description is invalid." +} diff --git a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupsMessagesADM.scala b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupsMessagesADM.scala index 411c8419d5..cdc6377ed0 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupsMessagesADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupsMessagesADM.scala @@ -5,18 +5,18 @@ package org.knora.webapi.messages.admin.responder.groupsmessages -import java.util.UUID import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport import org.knora.webapi.IRI import org.knora.webapi.exceptions.BadRequestException import org.knora.webapi.feature.FeatureFactoryConfig -import org.knora.webapi.messages.StringFormatter import org.knora.webapi.messages.admin.responder.projectsmessages.{ProjectADM, ProjectsADMJsonProtocol} import org.knora.webapi.messages.admin.responder.usersmessages.UserADM import org.knora.webapi.messages.admin.responder.{KnoraRequestADM, KnoraResponseADM} import org.knora.webapi.messages.store.triplestoremessages.StringLiteralV2 import spray.json.{DefaultJsonProtocol, JsValue, JsonFormat, RootJsonFormat} +import java.util.UUID + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // API requests @@ -38,13 +38,7 @@ case class CreateGroupApiRequestADM( status: Boolean, selfjoin: Boolean ) extends GroupsADMJsonProtocol { - - implicit protected val stringFormatter: StringFormatter = StringFormatter.getGeneralInstance - def toJsValue: JsValue = createGroupApiRequestADMFormat.write(this) - - //check the custom Iri - stringFormatter.validateOptionalGroupIri(id, throw BadRequestException(s"Invalid group IRI")) } /** @@ -65,7 +59,7 @@ case class ChangeGroupApiRequestADM( status: Option[Boolean] = None, selfjoin: Option[Boolean] = None ) extends GroupsADMJsonProtocol { - +// TODO-mpro: once status is separate route then it can be removed private val parametersCount = List( name, descriptions, @@ -98,8 +92,6 @@ case class ChangeGroupApiRequestADM( */ sealed trait GroupsResponderRequestADM extends KnoraRequestADM -// Requests - /** * Get all information about all groups. * @@ -190,7 +182,7 @@ case class GroupCreateRequestADM( */ case class GroupChangeRequestADM( groupIri: IRI, - changeGroupRequest: ChangeGroupApiRequestADM, + changeGroupRequest: GroupUpdatePayloadADM, featureFactoryConfig: FeatureFactoryConfig, requestingUser: UserADM, apiRequestID: UUID @@ -307,21 +299,6 @@ case class GroupADM( */ case class GroupShortADM(id: IRI, name: String, descriptions: Seq[StringLiteralV2], status: Boolean, selfjoin: Boolean) -/** - * Payload used for updating of an existing group. - * - * @param name the name of the group. - * @param descriptions the descriptions of the group. - * @param status the group's status. - * @param selfjoin the group's self-join status. - */ -case class GroupUpdatePayloadADM( - name: Option[String] = None, - descriptions: Option[Seq[StringLiteralV2]] = None, - status: Option[Boolean] = None, - selfjoin: Option[Boolean] = None -) - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // JSON formatting diff --git a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupsPayloadsADM.scala b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupsPayloadsADM.scala new file mode 100644 index 0000000000..bdb09a125e --- /dev/null +++ b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupsPayloadsADM.scala @@ -0,0 +1,31 @@ +package org.knora.webapi.messages.admin.responder.groupsmessages + +import org.knora.webapi.messages.admin.responder.valueObjects._ + +/** + * Group create payload + */ +final case class GroupCreatePayloadADM( + id: Option[GroupIRI] = None, + name: GroupName, + descriptions: GroupDescriptions, + project: ProjectIRI, + status: GroupStatus, + selfjoin: GroupSelfJoin +) + +/** + * Payload used for updating of an existing group. + * + * @param name the name of the group. + * @param descriptions the descriptions of the group. + * @param status the group's status. + * @param selfjoin the group's self-join status. + */ +final case class GroupUpdatePayloadADM( + name: Option[GroupName] = None, + descriptions: Option[GroupDescriptions] = None, +// TODO-mpro: create separate payload for status update + status: Option[GroupStatus] = None, + selfjoin: Option[GroupSelfJoin] = None +) diff --git a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListPayloadsADM.scala b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListPayloadsADM.scala index e917c7ccce..fe6b83def0 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListPayloadsADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListPayloadsADM.scala @@ -1,41 +1,38 @@ package org.knora.webapi.messages.admin.responder.listsmessages -import org.knora.webapi.messages.admin.responder.valueObjects.{ - Comments, - Labels, - ListIRI, - ListName, - Position, - ProjectIRI -} +import org.knora.webapi.messages.admin.responder.valueObjects._ /** - * List (parent node, former root node) and Node (former child node) creation payloads + * List root node and child node creation payloads */ -sealed trait NodeCreatePayloadADM -object NodeCreatePayloadADM { - final case class ListCreatePayloadADM( +sealed trait ListNodeCreatePayloadADM +// TODO-mpro: +// 1. lack of consistency between parentNodeIri and hasRootNode in change payload - should be renamed to parentNodeIri +// 2. Rethink other field names if they are descriptive enough, e.g. id should be renamed to customIri or something similar +object ListNodeCreatePayloadADM { + final case class ListRootNodeCreatePayloadADM( id: Option[ListIRI] = None, projectIri: ProjectIRI, name: Option[ListName] = None, labels: Labels, comments: Comments - ) extends NodeCreatePayloadADM - final case class ChildNodeCreatePayloadADM( + ) extends ListNodeCreatePayloadADM + final case class ListChildNodeCreatePayloadADM( id: Option[ListIRI] = None, - parentNodeIri: Option[ListIRI] = None, + parentNodeIri: ListIRI, projectIri: ProjectIRI, name: Option[ListName] = None, position: Option[Position] = None, labels: Labels, comments: Option[Comments] = None - ) extends NodeCreatePayloadADM + ) extends ListNodeCreatePayloadADM } /** - * Node Info update payload + * List node update payload */ -final case class NodeInfoChangePayloadADM( +final case class ListNodeChangePayloadADM( +// TODO-mpro: listIri can be probably removed here or maybe from the route?? listIri: ListIRI, projectIri: ProjectIRI, hasRootNode: Option[ListIRI] = None, @@ -63,6 +60,5 @@ final case class NodeLabelsChangePayloadADM( * Node Comments update payload */ final case class NodeCommentsChangePayloadADM( -// TODO: remove Option here - comments: Option[Comments] = None + comments: Comments ) diff --git a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListsErrorMessagesADM.scala b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListsErrorMessagesADM.scala index 81e5ec16dd..42fec09558 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListsErrorMessagesADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListsErrorMessagesADM.scala @@ -10,6 +10,7 @@ object ListsErrorMessagesADM { val LIST_IRI_INVALID_ERROR = "List IRI cannot be empty." val LIST_NODE_IRI_MISSING_ERROR = "List node IRI cannot be empty." val LIST_NODE_IRI_INVALID_ERROR = "List node IRI is invalid." +// TODO-mpro: move project messages val PROJECT_IRI_MISSING_ERROR = "Project IRI cannot be empty." val PROJECT_IRI_INVALID_ERROR = "Project IRI is invalid." val LIST_NAME_MISSING_ERROR = "List name cannot be empty." diff --git a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListsMessagesADM.scala b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListsMessagesADM.scala index 747db919b6..8f53309547 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListsMessagesADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListsMessagesADM.scala @@ -5,14 +5,16 @@ package org.knora.webapi.messages.admin.responder.listsmessages -import java.util.UUID import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport import org.knora.webapi._ import org.knora.webapi.exceptions.BadRequestException import org.knora.webapi.feature.FeatureFactoryConfig import org.knora.webapi.messages.StringFormatter +import org.knora.webapi.messages.admin.responder.listsmessages.ListNodeCreatePayloadADM.{ + ListChildNodeCreatePayloadADM, + ListRootNodeCreatePayloadADM +} import org.knora.webapi.messages.admin.responder.listsmessages.ListsErrorMessagesADM._ -import org.knora.webapi.messages.admin.responder.listsmessages.NodeCreatePayloadADM.ChildNodeCreatePayloadADM import org.knora.webapi.messages.admin.responder.usersmessages._ import org.knora.webapi.messages.admin.responder.{KnoraRequestADM, KnoraResponseADM} import org.knora.webapi.messages.store.triplestoremessages.{ @@ -22,15 +24,36 @@ import org.knora.webapi.messages.store.triplestoremessages.{ } import spray.json._ +import java.util.UUID + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // API requests /** - * Represents an API request payload that asks the Knora API server to create a new list or child node. - * If the IRI of the parent node is given, the new node is attached to the parent node as a sublist node. + * Represents an API request payload that asks the Knora API server to create a new list root node. + * At least one label and comment need to be supplied. + * + * @param id the optional custom IRI of the list node. + * @param projectIri the IRI of the project. + * @param name the optional name of the list node. + * @param labels labels of the list node. + * @param comments comments of the list node. + */ +case class ListRootNodeCreateApiRequestADM( + id: Option[IRI] = None, + projectIri: IRI, + name: Option[String] = None, + labels: Seq[StringLiteralV2], + comments: Seq[StringLiteralV2] +) extends ListADMJsonProtocol { + def toJsValue: JsValue = createListRootNodeApiRequestADMFormat.write(this) +} + +/** + * Represents an API request payload that asks the Knora API server to create a new list child node. + * attached to given parent node as a sublist node. * If a specific position is given, insert the child node there. Otherwise, the newly created list node will be appended * to the end of the list of children. - * If no parent node IRI is given in the payload, a new list is created with this node as its root node. * At least one label needs to be supplied. * * @param id the optional custom IRI of the list node. @@ -41,16 +64,16 @@ import spray.json._ * @param labels labels of the list node. * @param comments comments of the list node. */ -case class CreateNodeApiRequestADM( +case class ListChildNodeCreateApiRequestADM( id: Option[IRI] = None, - parentNodeIri: Option[IRI] = None, + parentNodeIri: IRI, projectIri: IRI, name: Option[String] = None, position: Option[Int] = None, labels: Seq[StringLiteralV2], - comments: Seq[StringLiteralV2] + comments: Option[Seq[StringLiteralV2]] ) extends ListADMJsonProtocol { - def toJsValue: JsValue = createListNodeApiRequestADMFormat.write(this) + def toJsValue: JsValue = createListChildNodeApiRequestADMFormat.write(this) } /** @@ -64,7 +87,7 @@ case class CreateNodeApiRequestADM( * @param labels the labels. * @param comments the comments. */ -case class ChangeNodeInfoApiRequestADM( +case class ListNodeChangeApiRequestADM( listIri: IRI, projectIri: IRI, hasRootNode: Option[IRI] = None, @@ -183,13 +206,13 @@ case class NodePathGetRequestADM(iri: IRI, featureFactoryConfig: FeatureFactoryC /** * Requests the creation of a new list. * - * @param createRootNode the [[NodeCreatePayloadADM]] information used for creating the root node of the list. + * @param createRootNode the [[ListRootNodeCreatePayloadADM]] information used for creating the root node of the list. * @param featureFactoryConfig the feature factory configuration. * @param requestingUser the user creating the new list. * @param apiRequestID the ID of the API request. */ -case class ListCreateRequestADM( - createRootNode: NodeCreatePayloadADM, +case class ListRootNodeCreateRequestADM( + createRootNode: ListRootNodeCreatePayloadADM, featureFactoryConfig: FeatureFactoryConfig, requestingUser: UserADM, apiRequestID: UUID @@ -206,7 +229,7 @@ case class ListCreateRequestADM( */ case class NodeInfoChangeRequestADM( listIri: IRI, - changeNodeRequest: NodeInfoChangePayloadADM, + changeNodeRequest: ListNodeChangePayloadADM, featureFactoryConfig: FeatureFactoryConfig, requestingUser: UserADM, apiRequestID: UUID @@ -221,7 +244,7 @@ case class NodeInfoChangeRequestADM( * @param apiRequestID the ID of the API request. */ case class ListChildNodeCreateRequestADM( - createChildNodeRequest: ChildNodeCreatePayloadADM, + createChildNodeRequest: ListChildNodeCreatePayloadADM, featureFactoryConfig: FeatureFactoryConfig, requestingUser: UserADM, apiRequestID: UUID @@ -1241,10 +1264,30 @@ trait ListADMJsonProtocol extends SprayJsonSupport with DefaultJsonProtocol with } } - implicit val createListNodeApiRequestADMFormat: RootJsonFormat[CreateNodeApiRequestADM] = - jsonFormat(CreateNodeApiRequestADM, "id", "parentNodeIri", "projectIri", "name", "position", "labels", "comments") - implicit val changeListInfoApiRequestADMFormat: RootJsonFormat[ChangeNodeInfoApiRequestADM] = jsonFormat( - ChangeNodeInfoApiRequestADM, + implicit val createListRootNodeApiRequestADMFormat: RootJsonFormat[ListRootNodeCreateApiRequestADM] = + jsonFormat( + ListRootNodeCreateApiRequestADM, + "id", +// "parentNodeIri", + "projectIri", + "name", +// "position", + "labels", + "comments" + ) + implicit val createListChildNodeApiRequestADMFormat: RootJsonFormat[ListChildNodeCreateApiRequestADM] = + jsonFormat( + ListChildNodeCreateApiRequestADM, + "id", + "parentNodeIri", + "projectIri", + "name", + "position", + "labels", + "comments" + ) + implicit val changeListInfoApiRequestADMFormat: RootJsonFormat[ListNodeChangeApiRequestADM] = jsonFormat( + ListNodeChangeApiRequestADM, "listIri", "projectIri", "hasRootNode", diff --git a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/valueObjects/GroupsValueObjectsADM.scala b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/valueObjects/GroupsValueObjectsADM.scala new file mode 100644 index 0000000000..b52b48af13 --- /dev/null +++ b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/valueObjects/GroupsValueObjectsADM.scala @@ -0,0 +1,120 @@ +/* + * Copyright © 2021 Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.knora.webapi.messages.admin.responder.valueObjects + +import org.knora.webapi.exceptions.BadRequestException +import org.knora.webapi.messages.StringFormatter +import org.knora.webapi.messages.admin.responder.groupsmessages.GroupsErrorMessagesADM._ +import org.knora.webapi.messages.store.triplestoremessages.StringLiteralV2 +import zio.prelude.Validation + +/** + * GroupIRI value object. + */ +sealed abstract case class GroupIRI private (value: String) +object GroupIRI { self => + private val sf = StringFormatter.getGeneralInstance + + def make(value: String): Validation[Throwable, GroupIRI] = + if (value.isEmpty) { + Validation.fail(BadRequestException(GROUP_IRI_MISSING_ERROR)) + } else { + if (value.nonEmpty && !sf.isKnoraGroupIriStr(value)) { + Validation.fail(BadRequestException(GROUP_IRI_INVALID_ERROR)) + } else { + val validatedValue = Validation( + sf.validateAndEscapeIri(value, throw BadRequestException(GROUP_IRI_INVALID_ERROR)) + ) + + validatedValue.map(new GroupIRI(_) {}) + } + } + + def make(value: Option[String]): Validation[Throwable, Option[GroupIRI]] = + value match { + case Some(v) => self.make(v).map(Some(_)) + case None => Validation.succeed(None) + } +} + +/** + * GroupName value object. + */ +sealed abstract case class GroupName private (value: String) +object GroupName { self => + private val sf = StringFormatter.getGeneralInstance + + def make(value: String): Validation[Throwable, GroupName] = + if (value.isEmpty) { + Validation.fail(BadRequestException(GROUP_NAME_MISSING_ERROR)) + } else { + val validatedValue = Validation( + sf.toSparqlEncodedString(value, throw BadRequestException(GROUP_NAME_INVALID_ERROR)) + ) + + validatedValue.map(new GroupName(_) {}) + } + + def make(value: Option[String]): Validation[Throwable, Option[GroupName]] = + value match { + case Some(v) => self.make(v).map(Some(_)) + case None => Validation.succeed(None) + } +} + +/** + * GroupDescriptions value object. + */ +sealed abstract case class GroupDescriptions private (value: Seq[StringLiteralV2]) +object GroupDescriptions { self => + private val sf = StringFormatter.getGeneralInstance + + def make(value: Seq[StringLiteralV2]): Validation[Throwable, GroupDescriptions] = + if (value.isEmpty) { + Validation.fail(BadRequestException(GROUP_DESCRIPTION_MISSING_ERROR)) + } else { + val validatedDescriptions = Validation(value.map { description => + val validatedDescription = + sf.toSparqlEncodedString(description.value, throw BadRequestException(GROUP_DESCRIPTION_INVALID_ERROR)) + StringLiteralV2(value = validatedDescription, language = description.language) + }) + validatedDescriptions.map(new GroupDescriptions(_) {}) + } + + def make(value: Option[Seq[StringLiteralV2]]): Validation[Throwable, Option[GroupDescriptions]] = + value match { + case Some(v) => self.make(v).map(Some(_)) + case None => Validation.succeed(None) + } +} + +/** + * GroupStatus value object. + */ +sealed abstract case class GroupStatus private (value: Boolean) +object GroupStatus { self => + def make(value: Boolean): Validation[Throwable, GroupStatus] = + Validation.succeed(new GroupStatus(value) {}) + def make(value: Option[Boolean]): Validation[Throwable, Option[GroupStatus]] = + value match { + case Some(v) => self.make(v).map(Some(_)) + case None => Validation.succeed(None) + } +} + +/** + * GroupSelfJoin value object. + */ +sealed abstract case class GroupSelfJoin private (value: Boolean) +object GroupSelfJoin { self => + def make(value: Boolean): Validation[Throwable, GroupSelfJoin] = + Validation.succeed(new GroupSelfJoin(value) {}) + def make(value: Option[Boolean]): Validation[Throwable, Option[GroupSelfJoin]] = + value match { + case Some(v) => self.make(v).map(Some(_)) + case None => Validation.succeed(None) + } +} diff --git a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/valueObjects/IriValueObjectsADM.scala b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/valueObjects/IriValueObjectsADM.scala new file mode 100644 index 0000000000..f9dc7f7385 --- /dev/null +++ b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/valueObjects/IriValueObjectsADM.scala @@ -0,0 +1,37 @@ +/* + * Copyright © 2021 Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.knora.webapi.messages.admin.responder.valueObjects + +import org.knora.webapi.exceptions.BadRequestException +import org.knora.webapi.messages.StringFormatter +import org.knora.webapi.messages.admin.responder.listsmessages.ListsErrorMessagesADM.{ + PROJECT_IRI_INVALID_ERROR, + PROJECT_IRI_MISSING_ERROR +} +import zio.prelude.Validation + +/** + * ProjectIRI value object. + */ +sealed abstract case class ProjectIRI private (value: String) +object ProjectIRI { + val sf = StringFormatter.getGeneralInstance + + def make(value: String): Validation[Throwable, ProjectIRI] = + if (value.isEmpty) { + Validation.fail(BadRequestException(PROJECT_IRI_MISSING_ERROR)) + } else { + if (value.nonEmpty && !sf.isKnoraProjectIriStr(value)) { + Validation.fail(BadRequestException(PROJECT_IRI_INVALID_ERROR)) + } else { + val validatedValue = Validation( + sf.validateAndEscapeProjectIri(value, throw BadRequestException(PROJECT_IRI_INVALID_ERROR)) + ) + + validatedValue.map(new ProjectIRI(_) {}) + } + } +} diff --git a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/valueObjects/ListsValueObjectsADM.scala b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/valueObjects/ListsValueObjectsADM.scala index c8508597a2..f6f5d80b26 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/valueObjects/ListsValueObjectsADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/valueObjects/ListsValueObjectsADM.scala @@ -1,73 +1,42 @@ +/* + * Copyright © 2021 Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. + * SPDX-License-Identifier: Apache-2.0 + */ + package org.knora.webapi.messages.admin.responder.valueObjects import org.knora.webapi.exceptions.BadRequestException import org.knora.webapi.messages.StringFormatter -import org.knora.webapi.messages.admin.responder.listsmessages.ListsErrorMessagesADM.{ - COMMENT_INVALID_ERROR, - COMMENT_MISSING_ERROR, - INVALID_POSITION, - LABEL_INVALID_ERROR, - LABEL_MISSING_ERROR, - LIST_NAME_INVALID_ERROR, - LIST_NAME_MISSING_ERROR, - LIST_NODE_IRI_INVALID_ERROR, - LIST_NODE_IRI_MISSING_ERROR, - PROJECT_IRI_INVALID_ERROR, - PROJECT_IRI_MISSING_ERROR -} +import org.knora.webapi.messages.admin.responder.listsmessages.ListsErrorMessagesADM._ import org.knora.webapi.messages.store.triplestoremessages.StringLiteralV2 - -import scala.util.{Failure, Success, Try} +import zio.prelude.Validation /** * List ListIRI value object. */ sealed abstract case class ListIRI private (value: String) -object ListIRI { - val stringFormatter = StringFormatter.getGeneralInstance +object ListIRI { self => + val sf: StringFormatter = StringFormatter.getGeneralInstance - def create(value: String): Either[Throwable, ListIRI] = + def make(value: String): Validation[Throwable, ListIRI] = if (value.isEmpty) { - Left(BadRequestException(LIST_NODE_IRI_MISSING_ERROR)) + Validation.fail(BadRequestException(LIST_NODE_IRI_MISSING_ERROR)) } else { - if (value.nonEmpty && !stringFormatter.isKnoraListIriStr(value)) { - Left(BadRequestException(LIST_NODE_IRI_INVALID_ERROR)) + if (value.nonEmpty && !sf.isKnoraListIriStr(value)) { + Validation.fail(BadRequestException(LIST_NODE_IRI_INVALID_ERROR)) } else { - val validatedValue = Try( - stringFormatter.validateAndEscapeIri(value, throw BadRequestException(LIST_NODE_IRI_INVALID_ERROR)) + val validatedValue = Validation( + sf.validateAndEscapeIri(value, throw BadRequestException(LIST_NODE_IRI_INVALID_ERROR)) ) - validatedValue match { - case Success(iri) => Right(new ListIRI(iri) {}) - case Failure(_) => Left(BadRequestException(LIST_NODE_IRI_INVALID_ERROR)) - } + validatedValue.map(new ListIRI(_) {}) } } -} - -/** - * List ProjectIRI value object. - */ -sealed abstract case class ProjectIRI private (value: String) -object ProjectIRI { - val stringFormatter = StringFormatter.getGeneralInstance - - def create(value: String): Either[Throwable, ProjectIRI] = - if (value.isEmpty) { - Left(BadRequestException(PROJECT_IRI_MISSING_ERROR)) - } else { - if (value.nonEmpty && !stringFormatter.isKnoraProjectIriStr(value)) { - Left(BadRequestException(PROJECT_IRI_INVALID_ERROR)) - } else { - val validatedValue = Try( - stringFormatter.validateAndEscapeProjectIri(value, throw BadRequestException(PROJECT_IRI_INVALID_ERROR)) - ) - validatedValue match { - case Success(iri) => Right(new ProjectIRI(iri) {}) - case Failure(_) => Left(BadRequestException(PROJECT_IRI_INVALID_ERROR)) - } - } + def make(value: Option[String]): Validation[Throwable, Option[ListIRI]] = + value match { + case Some(v) => self.make(v).map(Some(_)) + case None => Validation.succeed(None) } } @@ -75,21 +44,24 @@ object ProjectIRI { * List ListName value object. */ sealed abstract case class ListName private (value: String) -object ListName { - val stringFormatter = StringFormatter.getGeneralInstance +object ListName { self => + val sf = StringFormatter.getGeneralInstance - def create(value: String): Either[Throwable, ListName] = + def make(value: String): Validation[Throwable, ListName] = if (value.isEmpty) { - Left(BadRequestException(LIST_NAME_MISSING_ERROR)) + Validation.fail(BadRequestException(LIST_NAME_MISSING_ERROR)) } else { - val validatedValue = Try( - stringFormatter.toSparqlEncodedString(value, throw BadRequestException(LIST_NAME_INVALID_ERROR)) + val validatedValue = Validation( + sf.toSparqlEncodedString(value, throw BadRequestException(LIST_NAME_INVALID_ERROR)) ) - validatedValue match { - case Success(name) => Right(new ListName(name) {}) - case Failure(_) => Left(BadRequestException(LIST_NAME_INVALID_ERROR)) - } + validatedValue.map(new ListName(_) {}) + } + + def make(value: Option[String]): Validation[Throwable, Option[ListName]] = + value match { + case Some(v) => self.make(v).map(Some(_)) + case None => Validation.succeed(None) } } @@ -97,12 +69,18 @@ object ListName { * List Position value object. */ sealed abstract case class Position private (value: Int) -object Position { - def create(value: Int): Either[Throwable, Position] = +object Position { self => + def make(value: Int): Validation[Throwable, Position] = if (value < -1) { - Left(BadRequestException(INVALID_POSITION)) + Validation.fail(BadRequestException(INVALID_POSITION)) } else { - Right(new Position(value) {}) + Validation.succeed(new Position(value) {}) + } + + def make(value: Option[Int]): Validation[Throwable, Option[Position]] = + value match { + case Some(v) => self.make(v).map(Some(_)) + case None => Validation.succeed(None) } } @@ -110,23 +88,26 @@ object Position { * List Labels value object. */ sealed abstract case class Labels private (value: Seq[StringLiteralV2]) -object Labels { - val stringFormatter = StringFormatter.getGeneralInstance +object Labels { self => + val sf = StringFormatter.getGeneralInstance - def create(value: Seq[StringLiteralV2]): Either[Throwable, Labels] = + def make(value: Seq[StringLiteralV2]): Validation[Throwable, Labels] = if (value.isEmpty) { - Left(BadRequestException(LABEL_MISSING_ERROR)) + Validation.fail(BadRequestException(LABEL_MISSING_ERROR)) } else { - val validatedLabels = Try(value.map { label => + val validatedLabels = Validation(value.map { label => val validatedLabel = - stringFormatter.toSparqlEncodedString(label.value, throw BadRequestException(LABEL_INVALID_ERROR)) + sf.toSparqlEncodedString(label.value, throw BadRequestException(LABEL_INVALID_ERROR)) StringLiteralV2(value = validatedLabel, language = label.language) }) - validatedLabels match { - case Success(valid) => Right(new Labels(valid) {}) - case Failure(_) => Left(BadRequestException(LABEL_INVALID_ERROR)) - } + validatedLabels.map(new Labels(_) {}) + } + + def make(value: Option[Seq[StringLiteralV2]]): Validation[Throwable, Option[Labels]] = + value match { + case Some(v) => self.make(v).map(Some(_)) + case None => Validation.succeed(None) } } @@ -134,22 +115,25 @@ object Labels { * List Comments value object. */ sealed abstract case class Comments private (value: Seq[StringLiteralV2]) -object Comments { - val stringFormatter = StringFormatter.getGeneralInstance +object Comments { self => + val sf = StringFormatter.getGeneralInstance - def create(value: Seq[StringLiteralV2]): Either[Throwable, Comments] = + def make(value: Seq[StringLiteralV2]): Validation[Throwable, Comments] = if (value.isEmpty) { - Left(BadRequestException(COMMENT_MISSING_ERROR)) + Validation.fail(BadRequestException(COMMENT_MISSING_ERROR)) } else { - val validatedComments = Try(value.map { comment => + val validatedComments = Validation(value.map { comment => val validatedComment = - stringFormatter.toSparqlEncodedString(comment.value, throw BadRequestException(COMMENT_INVALID_ERROR)) + sf.toSparqlEncodedString(comment.value, throw BadRequestException(COMMENT_INVALID_ERROR)) StringLiteralV2(value = validatedComment, language = comment.language) }) - validatedComments match { - case Success(valid) => Right(new Comments(valid) {}) - case Failure(_) => Left(BadRequestException(COMMENT_INVALID_ERROR)) - } + validatedComments.map(new Comments(_) {}) + } + + def make(value: Option[Seq[StringLiteralV2]]): Validation[Throwable, Option[Comments]] = + value match { + case Some(v) => self.make(v).map(Some(_)) + case None => Validation.succeed(None) } } diff --git a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/valueObjects/ValueObjectsADM.scala b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/valueObjects/ValueObjectsADM.scala index fd422202b4..a11d0003a5 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/valueObjects/ValueObjectsADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/valueObjects/ValueObjectsADM.scala @@ -8,7 +8,7 @@ import zio.prelude.Validation import scala.util.matching.Regex -// TODO: this is so far shared value object file, consider to slice it +// TODO-mpro: this is so far shared value object file, consider to slice it /** User value objects */ diff --git a/webapi/src/main/scala/org/knora/webapi/responders/BUILD.bazel b/webapi/src/main/scala/org/knora/webapi/responders/BUILD.bazel index 14e8dcfa32..d7cae6b4c2 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/BUILD.bazel +++ b/webapi/src/main/scala/org/knora/webapi/responders/BUILD.bazel @@ -27,6 +27,8 @@ scala_library( "@maven//:com_typesafe_akka_akka_stream_2_13", "@maven//:com_typesafe_play_twirl_api_2_13", "@maven//:com_typesafe_scala_logging_scala_logging_2_13", + "@maven//:dev_zio_zio_2_13", + "@maven//:dev_zio_zio_prelude_2_13", "@maven//:io_spray_spray_json_2_13", "@maven//:javax_json_javax_json_api", "@maven//:org_scala_lang_modules_scala_xml_2_13", diff --git a/webapi/src/main/scala/org/knora/webapi/responders/admin/GroupsResponderADM.scala b/webapi/src/main/scala/org/knora/webapi/responders/admin/GroupsResponderADM.scala index 2174b9d71d..6a54b704d4 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/admin/GroupsResponderADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/admin/GroupsResponderADM.scala @@ -5,8 +5,6 @@ package org.knora.webapi.responders.admin -import java.util.UUID - import akka.http.scaladsl.util.FastFuture import akka.pattern._ import org.knora.webapi._ @@ -16,6 +14,7 @@ import org.knora.webapi.messages.IriConversions._ import org.knora.webapi.messages.admin.responder.groupsmessages._ import org.knora.webapi.messages.admin.responder.projectsmessages.{ProjectADM, ProjectGetADM, ProjectIdentifierADM} import org.knora.webapi.messages.admin.responder.usersmessages._ +import org.knora.webapi.messages.admin.responder.valueObjects.GroupStatus import org.knora.webapi.messages.store.triplestoremessages._ import org.knora.webapi.messages.util.rdf.SparqlSelectResult import org.knora.webapi.messages.util.{KnoraSystemInstances, ResponderData} @@ -24,6 +23,7 @@ import org.knora.webapi.messages.{OntologyConstants, SmartIri} import org.knora.webapi.responders.Responder.handleUnexpectedMessage import org.knora.webapi.responders.{IriLocker, Responder} +import java.util.UUID import scala.concurrent.Future /** @@ -417,25 +417,27 @@ class GroupsResponderADM(responderData: ResponderData) extends Responder(respond apiRequestID: UUID ): Future[GroupOperationResponseADM] = for { - /* check if username or password are not empty */ - _ <- Future(if (createRequest.name.value.isEmpty) throw BadRequestException("Group name cannot be empty")) - _ = if (createRequest.project.isEmpty) throw BadRequestException("Project IRI cannot be empty") - /* check if the requesting user is allowed to create group */ - _ = if ( - !requestingUser.permissions.isProjectAdmin(createRequest.project) && !requestingUser.permissions.isSystemAdmin - ) { - // not a project admin and not a system admin - throw ForbiddenException("A new group can only be created by a project or system admin.") - } + _ <- Future( + if ( + !requestingUser.permissions + .isProjectAdmin(createRequest.project.value) && !requestingUser.permissions.isSystemAdmin + ) { + // not a project admin and not a system admin + throw ForbiddenException("A new group can only be created by a project or system admin.") + } + ) - nameExists <- groupByNameAndProjectExists(name = createRequest.name.value, projectIri = createRequest.project) + nameExists <- groupByNameAndProjectExists( + name = createRequest.name.value, + projectIri = createRequest.project.value + ) _ = if (nameExists) { throw DuplicateValueException(s"Group with the name '${createRequest.name.value}' already exists") } maybeProjectADM: Option[ProjectADM] <- (responderManager ? ProjectGetADM( - identifier = ProjectIdentifierADM(maybeIri = Some(createRequest.project)), + identifier = ProjectIdentifierADM(maybeIri = Some(createRequest.project.value)), featureFactoryConfig = featureFactoryConfig, requestingUser = KnoraSystemInstances.Users.SystemUser )).mapTo[Option[ProjectADM]] @@ -449,7 +451,7 @@ class GroupsResponderADM(responderData: ResponderData) extends Responder(respond } // check the custom IRI; if not given, create an unused IRI - customGroupIri: Option[SmartIri] = createRequest.id.map(iri => iri.toSmartIri) + customGroupIri: Option[SmartIri] = createRequest.id.map(_.value).map(iri => iri.toSmartIri) groupIri: IRI <- checkOrCreateEntityIri( customGroupIri, stringFormatter.makeRandomGroupIri(projectADM.shortcode) @@ -464,7 +466,7 @@ class GroupsResponderADM(responderData: ResponderData) extends Responder(respond groupClassIri = OntologyConstants.KnoraAdmin.UserGroup, name = createRequest.name.value, descriptions = createRequest.descriptions.value, - projectIri = createRequest.project, + projectIri = createRequest.project.value, status = createRequest.status.value, hasSelfJoinEnabled = createRequest.selfjoin.value ) @@ -508,7 +510,7 @@ class GroupsResponderADM(responderData: ResponderData) extends Responder(respond */ private def changeGroupBasicInformationRequestADM( groupIri: IRI, - changeGroupRequest: ChangeGroupApiRequestADM, + changeGroupRequest: GroupUpdatePayloadADM, featureFactoryConfig: FeatureFactoryConfig, requestingUser: UserADM, apiRequestID: UUID @@ -519,7 +521,7 @@ class GroupsResponderADM(responderData: ResponderData) extends Responder(respond */ def changeGroupTask( groupIri: IRI, - changeGroupRequest: ChangeGroupApiRequestADM, + changeGroupRequest: GroupUpdatePayloadADM, requestingUser: UserADM ): Future[GroupOperationResponseADM] = for { @@ -628,9 +630,14 @@ class GroupsResponderADM(responderData: ResponderData) extends Responder(respond throw ForbiddenException("Group's status can only be changed by a project or system admin.") } + maybeStatus: Option[GroupStatus] = changeGroupRequest.status match { + case Some(value) => Some(GroupStatus.make(value).fold(e => throw e.head, v => v)) + case None => None + } + /* create the update request */ groupUpdatePayload = GroupUpdatePayloadADM( - status = changeGroupRequest.status + status = maybeStatus ) // update group status @@ -704,7 +711,7 @@ class GroupsResponderADM(responderData: ResponderData) extends Responder(respond groupByNameAlreadyExists <- if (groupUpdatePayload.name.nonEmpty) { val newName = groupUpdatePayload.name.get - groupByNameAndProjectExists(newName, groupADM.project.id) + groupByNameAndProjectExists(newName.value, groupADM.project.id) } else { FastFuture.successful(false) } @@ -721,11 +728,11 @@ class GroupsResponderADM(responderData: ResponderData) extends Responder(respond adminNamedGraphIri = "http://www.knora.org/data/admin", triplestore = settings.triplestoreType, groupIri, - maybeName = groupUpdatePayload.name, - maybeDescriptions = groupUpdatePayload.descriptions, + maybeName = groupUpdatePayload.name.map(_.value), + maybeDescriptions = groupUpdatePayload.descriptions.map(_.value), maybeProject = None, // maybe later we want to allow moving of a group to another project - maybeStatus = groupUpdatePayload.status, - maybeSelfjoin = groupUpdatePayload.selfjoin + maybeStatus = groupUpdatePayload.status.map(_.value), + maybeSelfjoin = groupUpdatePayload.selfjoin.map(_.value) ) .toString ) @@ -746,36 +753,6 @@ class GroupsResponderADM(responderData: ResponderData) extends Responder(respond //_ = log.debug("updateProjectV1 - projectUpdatePayload: {} / updatedProject: {}", projectUpdatePayload, updatedProject) - _ = if (groupUpdatePayload.name.isDefined) { - if (updatedGroup.name != groupUpdatePayload.name.get) - throw UpdateNotPerformedException("Group's 'name' was not updated. Please report this as a possible bug.") - } - - _ = if (groupUpdatePayload.descriptions.isDefined) { - if (updatedGroup.descriptions != groupUpdatePayload.descriptions.get) - throw UpdateNotPerformedException( - "Group's 'descriptions' was not updated. Please report this as a possible bug." - ) - } - - /* - _ = if (groupUpdatePayload.project.isDefined) { - if (updatedGroup.project != groupUpdatePayload.project.get) throw UpdateNotPerformedException("Group's 'project' was not updated. Please report this as a possible bug.") - } - */ - - _ = if (groupUpdatePayload.status.isDefined) { - if (updatedGroup.status != groupUpdatePayload.status.get) - throw UpdateNotPerformedException("Group's 'status' was not updated. Please report this as a possible bug.") - } - - _ = if (groupUpdatePayload.selfjoin.isDefined) { - if (updatedGroup.selfjoin != groupUpdatePayload.selfjoin.get) - throw UpdateNotPerformedException( - "Group's 'selfjoin' status was not updated. Please report this as a possible bug." - ) - } - } yield GroupOperationResponseADM(group = updatedGroup) } diff --git a/webapi/src/main/scala/org/knora/webapi/responders/admin/ListsResponderADM.scala b/webapi/src/main/scala/org/knora/webapi/responders/admin/ListsResponderADM.scala index 5b6beaf167..a795d20e86 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/admin/ListsResponderADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/admin/ListsResponderADM.scala @@ -5,26 +5,29 @@ package org.knora.webapi.responders.admin -import java.util.UUID import akka.http.scaladsl.util.FastFuture import akka.pattern._ import org.knora.webapi._ import org.knora.webapi.exceptions._ import org.knora.webapi.feature.FeatureFactoryConfig import org.knora.webapi.messages.IriConversions._ +import org.knora.webapi.messages.admin.responder.listsmessages.ListNodeCreatePayloadADM.{ + ListChildNodeCreatePayloadADM, + ListRootNodeCreatePayloadADM +} import org.knora.webapi.messages.admin.responder.listsmessages.ListsErrorMessagesADM._ -import org.knora.webapi.messages.admin.responder.listsmessages.NodeCreatePayloadADM.ChildNodeCreatePayloadADM import org.knora.webapi.messages.admin.responder.listsmessages._ import org.knora.webapi.messages.admin.responder.projectsmessages.{ProjectADM, ProjectGetADM, ProjectIdentifierADM} import org.knora.webapi.messages.admin.responder.usersmessages._ +import org.knora.webapi.messages.admin.responder.valueObjects.{ListIRI, ListName, ProjectIRI} import org.knora.webapi.messages.store.triplestoremessages._ import org.knora.webapi.messages.util.rdf.SparqlSelectResult import org.knora.webapi.messages.util.{KnoraSystemInstances, ResponderData} import org.knora.webapi.messages.{OntologyConstants, SmartIri} import org.knora.webapi.responders.Responder.handleUnexpectedMessage import org.knora.webapi.responders.{IriLocker, Responder} -import org.knora.webapi.messages.admin.responder.valueObjects.{ListIRI, ListName, ProjectIRI} +import java.util.UUID import scala.annotation.tailrec import scala.concurrent.Future @@ -48,7 +51,7 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde listNodeInfoGetRequestADM(listIri, featureFactoryConfig, requestingUser) case NodePathGetRequestADM(iri, featureFactoryConfig, requestingUser) => nodePathGetAdminRequest(iri, requestingUser) - case ListCreateRequestADM(createRootNode, featureFactoryConfig, requestingUser, apiRequestID) => + case ListRootNodeCreateRequestADM(createRootNode, featureFactoryConfig, requestingUser, apiRequestID) => listCreateRequestADM(createRootNode, featureFactoryConfig, apiRequestID) case ListChildNodeCreateRequestADM(createChildNodeRequest, featureFactoryConfig, requestingUser, apiRequestID) => listChildNodeCreateRequestADM(createChildNodeRequest, featureFactoryConfig, apiRequestID) @@ -637,7 +640,6 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde * @return a [[ListChildNodeADM]]. */ def createChildNode(nodeIri: IRI, statements: Seq[(SubjectV2, Map[SmartIri, Seq[LiteralV2]])]): ListChildNodeADM = { - val propsMap: Map[SmartIri, Seq[LiteralV2]] = statements.filter(_._1 == IriSubjectV2(nodeIri)).head._2 val hasRootNode: IRI = propsMap @@ -834,17 +836,22 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde * @return a [newListNodeIri] */ private def createNode( - createNodeRequest: NodeCreatePayloadADM, + createNodeRequest: ListNodeCreatePayloadADM, featureFactoryConfig: FeatureFactoryConfig ): Future[IRI] = { // println("ZZZZZ-createNode", createNodeRequest) +// TODO-mpro: it's quickfix, refactor + val parentNode: Option[ListIRI] = createNodeRequest match { + case ListRootNodeCreatePayloadADM(_, _, _, _, _) => None + case ListChildNodeCreatePayloadADM(_, parentNodeIri, _, _, _, _, _) => Some(parentNodeIri) + } - val (id, parentNodeIri, projectIri, name, position) = createNodeRequest match { - case parent: NodeCreatePayloadADM.ListCreatePayloadADM => - (parent.id, None, parent.projectIri, parent.name, None) - case node: NodeCreatePayloadADM.ChildNodeCreatePayloadADM => - (node.id, node.parentNodeIri, node.projectIri, node.name, node.position) + val (id, projectIri, name, position) = createNodeRequest match { + case root: ListNodeCreatePayloadADM.ListRootNodeCreatePayloadADM => + (root.id, root.projectIri, root.name, None) + case child: ListNodeCreatePayloadADM.ListChildNodeCreatePayloadADM => + (child.id, child.projectIri, child.name, child.position) } def getPositionOfNewChild(children: Seq[ListChildNodeADM]): Int = { @@ -948,9 +955,9 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde // if parent node is known, find the root node of the list and the position of the new child node (newPosition: Option[Int], rootNodeIri: Option[IRI]) <- - if (parentNodeIri.nonEmpty) { + if (parentNode.nonEmpty) { getRootNodeAndPositionOfNewChild( - parentNodeIri = parentNodeIri.get.value, + parentNodeIri = parentNode.get.value, dataNamedGraph = dataNamedGraph, featureFactoryConfig = featureFactoryConfig ) @@ -965,13 +972,13 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde // Create the new list node depending on type createNewListSparqlString: String = createNodeRequest match { - case NodeCreatePayloadADM.ListCreatePayloadADM( + case ListNodeCreatePayloadADM.ListRootNodeCreatePayloadADM( _, projectIri, name, labels, comments - ) => { + ) => org.knora.webapi.messages.twirl.queries.sparql.admin.txt .createNewListNode( dataNamedGraph = dataNamedGraph, @@ -987,8 +994,7 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde maybeComments = Some(comments.value) ) .toString - } - case NodeCreatePayloadADM.ChildNodeCreatePayloadADM( + case ListNodeCreatePayloadADM.ListChildNodeCreatePayloadADM( _, parentNodeIri, projectIri, @@ -996,7 +1002,7 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde position, labels, comments - ) => { + ) => org.knora.webapi.messages.twirl.queries.sparql.admin.txt .createNewListNode( dataNamedGraph = dataNamedGraph, @@ -1004,7 +1010,7 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde listClassIri = OntologyConstants.KnoraBase.ListNode, projectIri = projectIri.value, nodeIri = newListNodeIri, - parentNodeIri = parentNodeIri.map(_.value), + parentNodeIri = Some(parentNodeIri.value), rootNodeIri = rootNodeIri, position = newPosition, maybeName = name.map(_.value), @@ -1012,7 +1018,6 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde maybeComments = comments.map(_.value) ) .toString - } } _ <- (storeManager ? SparqlUpdateRequest(createNewListSparqlString)).mapTo[SparqlUpdateResponse] @@ -1028,7 +1033,7 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde * @return a [[RootNodeInfoGetResponseADM]] */ private def listCreateRequestADM( - createRootRequest: NodeCreatePayloadADM, + createRootRequest: ListRootNodeCreatePayloadADM, featureFactoryConfig: FeatureFactoryConfig, apiRequestID: UUID ): Future[ListGetResponseADM] = { @@ -1039,12 +1044,11 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde * The actual task run with an IRI lock. */ def listCreateTask( - createRootRequest: NodeCreatePayloadADM, + createRootRequest: ListRootNodeCreatePayloadADM, featureFactoryConfig: FeatureFactoryConfig, apiRequestID: UUID ): Future[ListGetResponseADM] = for { - listRootIri <- createNode(createRootRequest, featureFactoryConfig) // Verify that the list was created. @@ -1086,7 +1090,7 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde */ private def nodeInfoChangeRequest( nodeIri: IRI, - changeNodeRequest: NodeInfoChangePayloadADM, + changeNodeRequest: ListNodeChangePayloadADM, featureFactoryConfig: FeatureFactoryConfig, apiRequestID: UUID ): Future[NodeInfoGetResponseADM] = { @@ -1096,14 +1100,12 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde */ def nodeInfoChangeTask( nodeIri: IRI, - changeNodeRequest: NodeInfoChangePayloadADM, + changeNodeRequest: ListNodeChangePayloadADM, featureFactoryConfig: FeatureFactoryConfig, apiRequestID: UUID ): Future[NodeInfoGetResponseADM] = for { - // _ <- Future(println("77777", nodeIri, changeNodeRequest.listIri)) - // check if nodeIRI in path and payload match _ <- Future( if (!nodeIri.equals(changeNodeRequest.listIri.value)) @@ -1151,7 +1153,7 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde * @return a [[ChildNodeInfoGetResponseADM]] */ private def listChildNodeCreateRequestADM( - createChildNodeRequest: ChildNodeCreatePayloadADM, + createChildNodeRequest: ListChildNodeCreatePayloadADM, featureFactoryConfig: FeatureFactoryConfig, apiRequestID: UUID ): Future[ChildNodeInfoGetResponseADM] = { @@ -1160,7 +1162,7 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde * The actual task run with an IRI lock. */ def listChildNodeCreateTask( - createChildNodeRequest: ChildNodeCreatePayloadADM, + createChildNodeRequest: ListChildNodeCreatePayloadADM, featureFactoryConfig: FeatureFactoryConfig, apiRequestID: UUID ): Future[ChildNodeInfoGetResponseADM] = @@ -1216,10 +1218,6 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde apiRequestID: UUID ): Future[NodeInfoGetResponseADM] = { - def verifyUpdatedNode(updatedNode: ListNodeInfoADM): Unit = - if (updatedNode.getName.nonEmpty && updatedNode.getName.get != changeNodeNameRequest.name.value) - throw UpdateNotPerformedException("Node's 'name' was not updated. Please report this as a possible bug.") - /** * The actual task run with an IRI lock. */ @@ -1231,7 +1229,6 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde apiRequestID: UUID ): Future[NodeInfoGetResponseADM] = for { - projectIri <- getProjectIriFromNode(nodeIri, featureFactoryConfig) // check if the requesting user is allowed to perform operation _ = if (!requestingUser.permissions.isProjectAdmin(projectIri) && !requestingUser.permissions.isSystemAdmin) { @@ -1240,9 +1237,9 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde } changeNodeNameSparqlString <- getUpdateNodeInfoSparqlStatement( - changeNodeInfoRequest = NodeInfoChangePayloadADM( - listIri = ListIRI.create(nodeIri).fold(e => throw e, v => v), - projectIri = ProjectIRI.create(projectIri).fold(e => throw e, v => v), + changeNodeInfoRequest = ListNodeChangePayloadADM( + listIri = ListIRI.make(nodeIri).fold(e => throw e.head, v => v), + projectIri = ProjectIRI.make(projectIri).fold(e => throw e.head, v => v), name = Some(changeNodeNameRequest.name) ), featureFactoryConfig = featureFactoryConfig @@ -1259,14 +1256,8 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde ) response = maybeNodeADM match { - case Some(rootNode: ListRootNodeInfoADM) => - verifyUpdatedNode(rootNode) - RootNodeInfoGetResponseADM(listinfo = rootNode) - - case Some(childNode: ListChildNodeInfoADM) => - verifyUpdatedNode(childNode) - ChildNodeInfoGetResponseADM(nodeinfo = childNode) - + case Some(rootNode: ListRootNodeInfoADM) => RootNodeInfoGetResponseADM(listinfo = rootNode) + case Some(childNode: ListChildNodeInfoADM) => ChildNodeInfoGetResponseADM(nodeinfo = childNode) case _ => throw UpdateNotPerformedException(s"Node $nodeIri was not updated. Please report this as a possible bug.") } @@ -1302,10 +1293,6 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde apiRequestID: UUID ): Future[NodeInfoGetResponseADM] = { - def verifyUpdatedNode(updatedNode: ListNodeInfoADM): Unit = - if (updatedNode.getLabels.stringLiterals.diff(changeNodeLabelsRequest.labels.value).nonEmpty) - throw UpdateNotPerformedException("Node's 'labels' were not updated. Please report this as a possible bug.") - /** * The actual task run with an IRI lock. */ @@ -1317,7 +1304,6 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde apiRequestID: UUID ): Future[NodeInfoGetResponseADM] = for { - projectIri <- getProjectIriFromNode(nodeIri, featureFactoryConfig) // check if the requesting user is allowed to perform operation @@ -1326,9 +1312,9 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde throw ForbiddenException(LIST_CHANGE_PERMISSION_ERROR) } changeNodeLabelsSparqlString <- getUpdateNodeInfoSparqlStatement( - changeNodeInfoRequest = NodeInfoChangePayloadADM( - listIri = ListIRI.create(nodeIri).fold(e => throw e, v => v), - projectIri = ProjectIRI.create(projectIri).fold(e => throw e, v => v), + changeNodeInfoRequest = ListNodeChangePayloadADM( + listIri = ListIRI.make(nodeIri).fold(e => throw e.head, v => v), + projectIri = ProjectIRI.make(projectIri).fold(e => throw e.head, v => v), labels = Some(changeNodeLabelsRequest.labels) ), featureFactoryConfig = featureFactoryConfig @@ -1344,14 +1330,8 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde ) response = maybeNodeADM match { - case Some(rootNode: ListRootNodeInfoADM) => - verifyUpdatedNode(rootNode) - RootNodeInfoGetResponseADM(listinfo = rootNode) - - case Some(childNode: ListChildNodeInfoADM) => - verifyUpdatedNode(childNode) - ChildNodeInfoGetResponseADM(nodeinfo = childNode) - + case Some(rootNode: ListRootNodeInfoADM) => RootNodeInfoGetResponseADM(listinfo = rootNode) + case Some(childNode: ListChildNodeInfoADM) => ChildNodeInfoGetResponseADM(nodeinfo = childNode) case _ => throw UpdateNotPerformedException(s"Node $nodeIri was not updated. Please report this as a possible bug.") } @@ -1386,9 +1366,6 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde requestingUser: UserADM, apiRequestID: UUID ): Future[NodeInfoGetResponseADM] = { - def verifyUpdatedNode(updatedNode: ListNodeInfoADM): Unit = - if (updatedNode.getComments.stringLiterals == changeNodeCommentsRequest.comments.map(_.value)) - throw UpdateNotPerformedException("Node's 'comments' were not updated. Please report this as a possible bug.") /** * The actual task run with an IRI lock. @@ -1401,7 +1378,6 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde apiRequestID: UUID ): Future[NodeInfoGetResponseADM] = for { - projectIri <- getProjectIriFromNode(nodeIri, featureFactoryConfig) // check if the requesting user is allowed to perform operation @@ -1411,10 +1387,10 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde } changeNodeCommentsSparqlString <- getUpdateNodeInfoSparqlStatement( - changeNodeInfoRequest = NodeInfoChangePayloadADM( - listIri = ListIRI.create(nodeIri).fold(e => throw e, v => v), - projectIri = ProjectIRI.create(projectIri).fold(e => throw e, v => v), - comments = changeNodeCommentsRequest.comments + changeNodeInfoRequest = ListNodeChangePayloadADM( + listIri = ListIRI.make(nodeIri).fold(e => throw e.head, v => v), + projectIri = ProjectIRI.make(projectIri).fold(e => throw e.head, v => v), + comments = Some(changeNodeCommentsRequest.comments) ), featureFactoryConfig = featureFactoryConfig ) @@ -1428,14 +1404,8 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde ) response = maybeNodeADM match { - case Some(rootNode: ListRootNodeInfoADM) => - verifyUpdatedNode(rootNode) - RootNodeInfoGetResponseADM(listinfo = rootNode) - - case Some(childNode: ListChildNodeInfoADM) => - verifyUpdatedNode(childNode) - ChildNodeInfoGetResponseADM(nodeinfo = childNode) - + case Some(rootNode: ListRootNodeInfoADM) => RootNodeInfoGetResponseADM(listinfo = rootNode) + case Some(childNode: ListChildNodeInfoADM) => ChildNodeInfoGetResponseADM(nodeinfo = childNode) case _ => throw UpdateNotPerformedException(s"Node $nodeIri was not updated. Please report this as a possible bug.") } @@ -1567,7 +1537,6 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde dataNamedGraph: IRI ): Future[Int] = for { - // get parent node with its immediate children maybeParentNode <- listNodeGetADM( nodeIri = parentIri, @@ -1734,7 +1703,6 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde apiRequestID: UUID ): Future[NodePositionChangeResponseADM] = for { - projectIri <- getProjectIriFromNode(nodeIri, featureFactoryConfig) // get data names graph of the project @@ -1960,7 +1928,6 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde apiRequestID: UUID ): Future[ListItemDeleteResponseADM] = for { - projectIri <- getProjectIriFromNode(nodeIri, featureFactoryConfig) // check if the requesting user is allowed to perform operation @@ -2125,7 +2092,7 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde * @return a [[String]]. */ private def getUpdateNodeInfoSparqlStatement( - changeNodeInfoRequest: NodeInfoChangePayloadADM, + changeNodeInfoRequest: ListNodeChangePayloadADM, featureFactoryConfig: FeatureFactoryConfig ): Future[String] = for { @@ -2312,7 +2279,6 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde */ protected def deleteNode(dataNamedGraph: IRI, nodeIri: IRI, isRootNode: Boolean): Future[Unit] = for { - // Generate SPARQL for erasing a node. sparqlDeleteNode: String <- Future( org.knora.webapi.messages.twirl.queries.sparql.admin.txt @@ -2409,7 +2375,6 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde featureFactoryConfig: FeatureFactoryConfig ): Future[Seq[ListChildNodeADM]] = for { - nodesTobeUpdated: Seq[ListChildNodeADM] <- Future( nodes.filter(node => node.position >= startPos && node.position <= endPos) ) @@ -2449,7 +2414,6 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde featureFactoryConfig: FeatureFactoryConfig ): Future[Unit] = for { - // Generate SPARQL for changing the parent node of the node. sparqlChangeParentNode: String <- Future( org.knora.webapi.messages.twirl.queries.sparql.admin.txt diff --git a/webapi/src/main/scala/org/knora/webapi/routing/SwaggerApiDocsRoute.scala b/webapi/src/main/scala/org/knora/webapi/routing/SwaggerApiDocsRoute.scala index bee921d39f..a85a56779f 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/SwaggerApiDocsRoute.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/SwaggerApiDocsRoute.scala @@ -22,7 +22,6 @@ class SwaggerApiDocsRoute(routeData: KnoraRouteData) extends KnoraRoute(routeDat // List all routes here override val apiClasses: Set[Class[_]] = Set( classOf[GroupsRouteADM], - classOf[NewListsRouteADMFeature], classOf[OldListsRouteADMFeature], classOf[DeleteListItemsRouteADM], classOf[PermissionsRouteADM], diff --git a/webapi/src/main/scala/org/knora/webapi/routing/admin/GroupsRouteADM.scala b/webapi/src/main/scala/org/knora/webapi/routing/admin/GroupsRouteADM.scala index f35a8778eb..d898909dd0 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/admin/GroupsRouteADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/admin/GroupsRouteADM.scala @@ -5,17 +5,18 @@ package org.knora.webapi.routing.admin -import java.util.UUID import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.{PathMatcher, Route} import io.swagger.annotations._ - -import javax.ws.rs.Path import org.knora.webapi.exceptions.BadRequestException import org.knora.webapi.feature.FeatureFactoryConfig import org.knora.webapi.messages.admin.responder.groupsmessages._ -import org.knora.webapi.messages.admin.responder.valueObjects.{Description, Name, Selfjoin, Status} +import org.knora.webapi.messages.admin.responder.valueObjects._ import org.knora.webapi.routing.{Authenticator, KnoraRoute, KnoraRouteData, RouteUtilADM} +import zio.prelude.Validation + +import java.util.UUID +import javax.ws.rs.Path object GroupsRouteADM { val GroupsBasePath: PathMatcher[Unit] = PathMatcher("admin" / "groups") @@ -36,26 +37,85 @@ class GroupsRouteADM(routeData: KnoraRouteData) override def makeRoute(featureFactoryConfig: FeatureFactoryConfig): Route = getGroups(featureFactoryConfig) ~ + getGroup(featureFactoryConfig) ~ + getGroupMembers(featureFactoryConfig) ~ createGroup(featureFactoryConfig) ~ - getGroupByIri(featureFactoryConfig) ~ updateGroup(featureFactoryConfig) ~ changeGroupStatus(featureFactoryConfig) ~ - deleteGroup(featureFactoryConfig) ~ - getGroupMembers(featureFactoryConfig) + deleteGroup(featureFactoryConfig) /** - * Returns all groups + * Returns all groups. */ private def getGroups(featureFactoryConfig: FeatureFactoryConfig): Route = path(GroupsBasePath) { - get { - /* return all groups */ - requestContext => + get { requestContext => + val requestMessage = for { + requestingUser <- getUserADM( + requestContext = requestContext, + featureFactoryConfig = featureFactoryConfig + ) + } yield GroupsGetRequestADM( + featureFactoryConfig = featureFactoryConfig, + requestingUser = requestingUser + ) + + RouteUtilADM.runJsonRoute( + requestMessageF = requestMessage, + requestContext = requestContext, + featureFactoryConfig = featureFactoryConfig, + settings = settings, + responderManager = responderManager, + log = log + ) + } + } + + /** + * Returns a single group identified by IRI. + */ + private def getGroup(featureFactoryConfig: FeatureFactoryConfig): Route = path(GroupsBasePath / Segment) { value => + get { requestContext => + val checkedGroupIri = + stringFormatter.validateAndEscapeIri(value, throw BadRequestException(s"Invalid custom group IRI $value")) + + val requestMessage = for { + requestingUser <- getUserADM( + requestContext = requestContext, + featureFactoryConfig = featureFactoryConfig + ) + } yield GroupGetRequestADM( + groupIri = checkedGroupIri, + featureFactoryConfig = featureFactoryConfig, + requestingUser = requestingUser + ) + + RouteUtilADM.runJsonRoute( + requestMessageF = requestMessage, + requestContext = requestContext, + featureFactoryConfig = featureFactoryConfig, + settings = settings, + responderManager = responderManager, + log = log + ) + } + } + + /** + * Returns all members of single group. + */ + private def getGroupMembers(featureFactoryConfig: FeatureFactoryConfig): Route = + path(GroupsBasePath / Segment / "members") { value => + get { requestContext => + val checkedGroupIri = + stringFormatter.validateAndEscapeIri(value, throw BadRequestException(s"Invalid group IRI $value")) + val requestMessage = for { requestingUser <- getUserADM( requestContext = requestContext, featureFactoryConfig = featureFactoryConfig ) - } yield GroupsGetRequestADM( + } yield GroupMembersGetRequestADM( + groupIri = checkedGroupIri, featureFactoryConfig = featureFactoryConfig, requestingUser = requestingUser ) @@ -68,34 +128,30 @@ class GroupsRouteADM(routeData: KnoraRouteData) responderManager = responderManager, log = log ) + } } - } /** - * Creates a group + * Creates a group. */ private def createGroup(featureFactoryConfig: FeatureFactoryConfig): Route = path(GroupsBasePath) { post { - /* create a new group */ entity(as[CreateGroupApiRequestADM]) { apiRequest => requestContext => - val groupCreatePayloadADM: GroupCreatePayloadADM = GroupCreatePayloadADM.create( - id = stringFormatter - .validateAndEscapeOptionalIri(apiRequest.id, throw BadRequestException(s"Invalid custom group IRI")), - name = Name.create(apiRequest.name).fold(e => throw e, v => v), - descriptions = Description.make(apiRequest.descriptions).fold(e => throw e.head, v => v), - project = stringFormatter - .validateAndEscapeProjectIri(apiRequest.project, throw BadRequestException(s"Invalid project IRI")), - status = Status.make(apiRequest.status).fold(e => throw e.head, v => v), - selfjoin = Selfjoin.make(apiRequest.selfjoin).fold(e => throw e.head, v => v) - ) + val id = GroupIRI.make(apiRequest.id) + val name = GroupName.make(apiRequest.name) + val descriptions = GroupDescriptions.make(apiRequest.descriptions) + val project = ProjectIRI.make(apiRequest.project) + val status = GroupStatus.make(apiRequest.status) + val selfjoin = GroupSelfJoin.make(apiRequest.selfjoin) + + val validatedGroupCreatePayload: Validation[Throwable, GroupCreatePayloadADM] = + Validation.validateWith(id, name, descriptions, project, status, selfjoin)(GroupCreatePayloadADM) val requestMessage = for { - requestingUser <- getUserADM( - requestContext = requestContext, - featureFactoryConfig = featureFactoryConfig - ) + payload <- toFuture(validatedGroupCreatePayload) + requestingUser <- getUserADM(requestContext, featureFactoryConfig) } yield GroupCreateRequestADM( - createRequest = groupCreatePayloadADM, + createRequest = payload, featureFactoryConfig = featureFactoryConfig, requestingUser = requestingUser, apiRequestID = UUID.randomUUID() @@ -114,44 +170,10 @@ class GroupsRouteADM(routeData: KnoraRouteData) } /** - * Returns a single group identified by IRI. - */ - private def getGroupByIri(featureFactoryConfig: FeatureFactoryConfig): Route = path(GroupsBasePath / Segment) { - value => - get { - /* returns a single group identified through iri */ - requestContext => - val checkedGroupIri = - stringFormatter.validateAndEscapeIri(value, throw BadRequestException(s"Invalid custom group IRI $value")) - - val requestMessage = for { - requestingUser <- getUserADM( - requestContext = requestContext, - featureFactoryConfig = featureFactoryConfig - ) - } yield GroupGetRequestADM( - groupIri = checkedGroupIri, - featureFactoryConfig = featureFactoryConfig, - requestingUser = requestingUser - ) - - RouteUtilADM.runJsonRoute( - requestMessageF = requestMessage, - requestContext = requestContext, - featureFactoryConfig = featureFactoryConfig, - settings = settings, - responderManager = responderManager, - log = log - ) - } - } - - /** - * Update basic group information. + * Updates basic group information. */ private def updateGroup(featureFactoryConfig: FeatureFactoryConfig): Route = path(GroupsBasePath / Segment) { value => put { - /* update a group identified by iri */ entity(as[ChangeGroupApiRequestADM]) { apiRequest => requestContext => val checkedGroupIri = stringFormatter.validateAndEscapeIri(value, throw BadRequestException(s"Invalid group IRI $value")) @@ -166,14 +188,20 @@ class GroupsRouteADM(routeData: KnoraRouteData) ) } + val name = GroupName.make(apiRequest.name) + val descriptions = GroupDescriptions.make(apiRequest.descriptions) + val status = GroupStatus.make(apiRequest.status) + val selfjoin = GroupSelfJoin.make(apiRequest.selfjoin) + + val validatedGroupUpdatePayload: Validation[Throwable, GroupUpdatePayloadADM] = + Validation.validateWith(name, descriptions, status, selfjoin)(GroupUpdatePayloadADM) + val requestMessage = for { - requestingUser <- getUserADM( - requestContext = requestContext, - featureFactoryConfig = featureFactoryConfig - ) + payload <- toFuture(validatedGroupUpdatePayload) + requestingUser <- getUserADM(requestContext, featureFactoryConfig) } yield GroupChangeRequestADM( groupIri = checkedGroupIri, - changeGroupRequest = apiRequest, + changeGroupRequest = payload, featureFactoryConfig = featureFactoryConfig, requestingUser = requestingUser, apiRequestID = UUID.randomUUID() @@ -192,12 +220,11 @@ class GroupsRouteADM(routeData: KnoraRouteData) } /** - * Update the group's status. + * Updates the group's status. */ private def changeGroupStatus(featureFactoryConfig: FeatureFactoryConfig): Route = path(GroupsBasePath / Segment / "status") { value => put { - /* change the status of a group identified by iri */ entity(as[ChangeGroupApiRequestADM]) { apiRequest => requestContext => val checkedGroupIri = stringFormatter.validateAndEscapeIri(value, throw BadRequestException(s"Invalid group IRI $value")) @@ -239,69 +266,34 @@ class GroupsRouteADM(routeData: KnoraRouteData) } /** - * Deletes a group (sets status to false) + * Deletes a group (sets status to false). */ private def deleteGroup(featureFactoryConfig: FeatureFactoryConfig): Route = path(GroupsBasePath / Segment) { value => - delete { - /* update group status to false */ - requestContext => - val checkedGroupIri = - stringFormatter.validateAndEscapeIri(value, throw BadRequestException(s"Invalid group IRI $value")) + delete { requestContext => + val checkedGroupIri = + stringFormatter.validateAndEscapeIri(value, throw BadRequestException(s"Invalid group IRI $value")) - val requestMessage = for { - requestingUser <- getUserADM( - requestContext = requestContext, - featureFactoryConfig = featureFactoryConfig - ) - } yield GroupChangeStatusRequestADM( - groupIri = checkedGroupIri, - changeGroupRequest = ChangeGroupApiRequestADM(status = Some(false)), - featureFactoryConfig = featureFactoryConfig, - requestingUser = requestingUser, - apiRequestID = UUID.randomUUID() - ) - - RouteUtilADM.runJsonRoute( - requestMessageF = requestMessage, + val requestMessage = for { + requestingUser <- getUserADM( requestContext = requestContext, - featureFactoryConfig = featureFactoryConfig, - settings = settings, - responderManager = responderManager, - log = log + featureFactoryConfig = featureFactoryConfig ) - } - } + } yield GroupChangeStatusRequestADM( + groupIri = checkedGroupIri, + changeGroupRequest = ChangeGroupApiRequestADM(status = Some(false)), + featureFactoryConfig = featureFactoryConfig, + requestingUser = requestingUser, + apiRequestID = UUID.randomUUID() + ) - /** - * Gets members of single group. - */ - private def getGroupMembers(featureFactoryConfig: FeatureFactoryConfig): Route = - path(GroupsBasePath / Segment / "members") { value => - get { - /* returns all members of the group identified through iri */ - requestContext => - val checkedGroupIri = - stringFormatter.validateAndEscapeIri(value, throw BadRequestException(s"Invalid group IRI $value")) - - val requestMessage = for { - requestingUser <- getUserADM( - requestContext = requestContext, - featureFactoryConfig = featureFactoryConfig - ) - } yield GroupMembersGetRequestADM( - groupIri = checkedGroupIri, - featureFactoryConfig = featureFactoryConfig, - requestingUser = requestingUser - ) - - RouteUtilADM.runJsonRoute( - requestMessageF = requestMessage, - requestContext = requestContext, - featureFactoryConfig = featureFactoryConfig, - settings = settings, - responderManager = responderManager, - log = log - ) - } + RouteUtilADM.runJsonRoute( + requestMessageF = requestMessage, + requestContext = requestContext, + featureFactoryConfig = featureFactoryConfig, + settings = settings, + responderManager = responderManager, + log = log + ) } + } } diff --git a/webapi/src/main/scala/org/knora/webapi/routing/admin/ListsRouteADM.scala b/webapi/src/main/scala/org/knora/webapi/routing/admin/ListsRouteADM.scala index 1605beacf6..fc41da428f 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/admin/ListsRouteADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/admin/ListsRouteADM.scala @@ -15,12 +15,12 @@ import org.knora.webapi.routing.{KnoraRoute, KnoraRouteData} * Provides an akka-http-routing function for API routes that deal with lists. */ class ListsRouteADM(routeData: KnoraRouteData) extends KnoraRoute(routeData) { - private val featureFactory: ListsRouteADMFeatureFactory = new ListsRouteADMFeatureFactory(routeData) + private val oldListRoute: OldListsRouteADMFeature = new OldListsRouteADMFeature(routeData) private val deleteNodeRoute: DeleteListItemsRouteADM = new DeleteListItemsRouteADM(routeData) private val updateNodeRoute: UpdateListItemsRouteADM = new UpdateListItemsRouteADM(routeData) override def makeRoute(featureFactoryConfig: FeatureFactoryConfig): Route = - featureFactory.makeRoute(featureFactoryConfig) ~ + oldListRoute.makeRoute(featureFactoryConfig) ~ deleteNodeRoute.makeRoute(featureFactoryConfig) ~ updateNodeRoute.makeRoute(featureFactoryConfig) } diff --git a/webapi/src/main/scala/org/knora/webapi/routing/admin/ProjectsRouteADM.scala b/webapi/src/main/scala/org/knora/webapi/routing/admin/ProjectsRouteADM.scala index 9075ece01b..ced29ed2e8 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/admin/ProjectsRouteADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/admin/ProjectsRouteADM.scala @@ -142,6 +142,7 @@ class ProjectsRouteADM(routeData: KnoraRouteData) stringFormatter .validateAndEscapeOptionalProjectIri(apiRequest.id, throw BadRequestException(s"Invalid project IRI")) ) +// TODO-mpro: why id, longname and logo are not options below? val shortname = Shortname.make(apiRequest.shortname) val shortcode = Shortcode.make(apiRequest.shortcode) val longname = Longname.make(apiRequest.longname) diff --git a/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/ListsRouteADMFeatureFactory.scala b/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/ListsRouteADMFeatureFactory.scala deleted file mode 100644 index b22ee0fc47..0000000000 --- a/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/ListsRouteADMFeatureFactory.scala +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright © 2021 Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.knora.webapi.routing.admin.lists - -import akka.http.scaladsl.server.Route -import org.knora.webapi.feature.{FeatureFactory, FeatureFactoryConfig} -import org.knora.webapi.routing.{KnoraRouteData, KnoraRouteFactory} - -/** - * A [[FeatureFactory]] that constructs list admin routes. - * - * @param routeData the [[KnoraRouteData]] to be used in constructing the routes. - */ -class ListsRouteADMFeatureFactory(routeData: KnoraRouteData) extends KnoraRouteFactory(routeData) with FeatureFactory { - - /** - * The old lists route feature. - */ - private val oldListsRouteADMFeature = new OldListsRouteADMFeature(routeData) - - /** - * The new lists route feature. - */ - private val newListsRouteADMFeature = new NewListsRouteADMFeature(routeData) - - /** - * Returns a lists route reflecting the specified feature factory configuration. - * - * @param featureFactoryConfig a [[FeatureFactoryConfig]]. - * @return a lists route. - */ - def makeRoute(featureFactoryConfig: FeatureFactoryConfig): Route = - if (featureFactoryConfig.getToggle("new-list-admin-routes").isEnabled) { - newListsRouteADMFeature.makeRoute(featureFactoryConfig) - } else { - oldListsRouteADMFeature.makeRoute(featureFactoryConfig) - } -} diff --git a/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/NewListsRouteADMFeature.scala b/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/NewListsRouteADMFeature.scala deleted file mode 100644 index 67a9b79e43..0000000000 --- a/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/NewListsRouteADMFeature.scala +++ /dev/null @@ -1,456 +0,0 @@ -/* - * Copyright © 2021 Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.knora.webapi.routing.admin.lists - -import java.util.UUID -import akka.http.scaladsl.server.Directives._ -import akka.http.scaladsl.server.{PathMatcher, Route} -import io.swagger.annotations._ - -import javax.ws.rs.Path -import org.knora.webapi.IRI -import org.knora.webapi.exceptions.{BadRequestException, ForbiddenException} -import org.knora.webapi.feature.{Feature, FeatureFactoryConfig} -import org.knora.webapi.messages.admin.responder.listsmessages.ListsErrorMessagesADM.LIST_CREATE_PERMISSION_ERROR -import org.knora.webapi.messages.admin.responder.listsmessages.NodeCreatePayloadADM.{ - ChildNodeCreatePayloadADM, - ListCreatePayloadADM -} -import org.knora.webapi.messages.admin.responder.listsmessages._ -import org.knora.webapi.messages.admin.responder.valueObjects.{ - Comments, - Labels, - ListIRI, - ListName, - Position, - ProjectIRI -} -import org.knora.webapi.routing.{Authenticator, KnoraRoute, KnoraRouteData, RouteUtilADM} - -import scala.concurrent.Future - -object NewListsRouteADMFeature { - val ListsBasePath: PathMatcher[Unit] = PathMatcher("admin" / "lists") -} - -/** - * A [[Feature]] that provides the new list admin API route. - * - * @param routeData the [[KnoraRouteData]] to be used in constructing the route. - */ -@Api(value = "lists (new endpoint)", produces = "application/json") -@Path("/admin/lists") -class NewListsRouteADMFeature(routeData: KnoraRouteData) - extends KnoraRoute(routeData) - with Feature - with Authenticator - with ListADMJsonProtocol { - - import NewListsRouteADMFeature._ - - def makeRoute(featureFactoryConfig: FeatureFactoryConfig): Route = - getLists(featureFactoryConfig) ~ - createListItem(featureFactoryConfig) ~ - getListItem(featureFactoryConfig) ~ - updateListItem(featureFactoryConfig) ~ - getNodeInfo(featureFactoryConfig) - - /* return all lists optionally filtered by project */ - @Path("/{IRI}") - @ApiOperation( - httpMethod = "GET", - response = classOf[ListsGetResponseADM], - value = "Get lists", - nickname = "newGetLists" - ) - @ApiResponses( - Array( - new ApiResponse(code = 500, message = "Internal server error") - ) - ) - @ApiImplicitParams( - Array( - new ApiImplicitParam( - name = "X-Knora-Feature-Toggles", - value = "new-list-admin-routes:1 = on/off", - required = true, - dataType = "string", - paramType = "header" - ), - new ApiImplicitParam( - name = "projectIri", - value = "IRI of the project", - required = true, - dataType = "string", - paramType = "query" - ) - ) - ) - /* return all lists optionally filtered by project */ - private def getLists(featureFactoryConfig: FeatureFactoryConfig): Route = path(ListsBasePath) { - get { - /* return all lists */ - parameters("projectIri".?) { maybeProjectIri: Option[IRI] => requestContext => - val projectIri = - stringFormatter.validateAndEscapeOptionalIri( - maybeProjectIri, - throw BadRequestException(s"Invalid param project IRI: $maybeProjectIri") - ) - - val requestMessage: Future[ListsGetRequestADM] = for { - requestingUser <- getUserADM(requestContext, featureFactoryConfig) - } yield ListsGetRequestADM( - projectIri = projectIri, - featureFactoryConfig = featureFactoryConfig, - requestingUser = requestingUser - ) - - RouteUtilADM.runJsonRoute( - requestMessageF = requestMessage, - requestContext = requestContext, - featureFactoryConfig = featureFactoryConfig, - settings = settings, - responderManager = responderManager, - log = log - ) - } - } - } - - /* create a new list item (root or child node)*/ - @ApiOperation( - value = "Add new list item", - nickname = "newAddListItem", - httpMethod = "POST", - response = classOf[ListGetResponseADM] - ) - @ApiImplicitParams( - Array( - new ApiImplicitParam( - name = "body", - value = "\"list\" item to create", - required = true, - dataTypeClass = classOf[CreateNodeApiRequestADM], - paramType = "body" - ), - new ApiImplicitParam( - name = "X-Knora-Feature-Toggles", - value = "new-list-admin-routes:1 = on/off", - required = true, - dataType = "string", - paramType = "header" - ) - ) - ) - @ApiResponses( - Array( - new ApiResponse(code = 500, message = "Internal server error") - ) - ) - private def createListItem(featureFactoryConfig: FeatureFactoryConfig): Route = path(ListsBasePath) { - post { - /* create a list item (root or child node) */ - entity(as[CreateNodeApiRequestADM]) { apiRequest => requestContext => - val maybeId: Option[ListIRI] = apiRequest.id match { - case Some(value) => Some(ListIRI.create(value).fold(e => throw e, v => v)) - case None => None - } - - val maybeParentNodeIri: Option[ListIRI] = apiRequest.parentNodeIri match { - case Some(value) => Some(ListIRI.create(value).fold(e => throw e, v => v)) - case None => None - } - - val maybeName: Option[ListName] = apiRequest.name match { - case Some(value) => Some(ListName.create(value).fold(e => throw e, v => v)) - case None => None - } - - val maybePosition: Option[Position] = apiRequest.position match { - case Some(value) => Some(Position.create(value).fold(e => throw e, v => v)) - case None => None - } - - val labels = Labels.create(apiRequest.labels).fold(e => throw e, v => v) - val projectIri = ProjectIRI.create(apiRequest.projectIri).fold(e => throw e, v => v) - - val requestMessage = for { - requestingUser <- getUserADM(requestContext, featureFactoryConfig) - - _ = if ( - !requestingUser.permissions.isProjectAdmin(projectIri.value) && !requestingUser.permissions.isSystemAdmin - ) { - // not project or a system admin - throw ForbiddenException(LIST_CREATE_PERMISSION_ERROR) - } - - // Is parent node IRI given in the payload? - createRequest = - if (apiRequest.parentNodeIri.isEmpty) { - // No, create a new list with given information of its root node. - - val comments = Comments.create(apiRequest.comments).fold(e => throw e, v => v) - - val createRootNodePayloadADM: ListCreatePayloadADM = ListCreatePayloadADM( - id = maybeId, - projectIri, - name = maybeName, - labels, - comments - ) - - ListCreateRequestADM( - createRootNode = createRootNodePayloadADM, - featureFactoryConfig = featureFactoryConfig, - requestingUser = requestingUser, - apiRequestID = UUID.randomUUID() - ) - } else { - // Yes, create a new child and attach it to the parent node. - - // allows to omit comments / send empty comments creating child node - val maybeComments = if (apiRequest.comments.isEmpty) { - None - } else { - Some(Comments.create(apiRequest.comments).fold(e => throw e, v => v)) - } - - val createChildNodePayloadADM: ChildNodeCreatePayloadADM = ChildNodeCreatePayloadADM( - id = maybeId, - parentNodeIri = maybeParentNodeIri, - projectIri, - name = maybeName, - position = maybePosition, - labels, - comments = maybeComments - ) - - ListChildNodeCreateRequestADM( - createChildNodeRequest = createChildNodePayloadADM, - featureFactoryConfig = featureFactoryConfig, - requestingUser = requestingUser, - apiRequestID = UUID.randomUUID() - ) - } - } yield createRequest - - RouteUtilADM.runJsonRoute( - requestMessageF = requestMessage, - requestContext = requestContext, - featureFactoryConfig = featureFactoryConfig, - settings = settings, - responderManager = responderManager, - log = log - ) - } - } - } - - /* get a node (root or child) */ - @Path("/{IRI}") - @ApiOperation( - value = "Get a list item", - nickname = "newGetlistItem", - httpMethod = "GET", - response = classOf[ListGetResponseADM] - ) - @ApiResponses( - Array( - new ApiResponse(code = 500, message = "Internal server error") - ) - ) - @ApiImplicitParams( - Array( - new ApiImplicitParam( - name = "X-Knora-Feature-Toggles", - value = "new-list-admin-routes:1 = on/off", - required = true, - dataType = "string", - paramType = "header" - ) - ) - ) - private def getListItem(featureFactoryConfig: FeatureFactoryConfig): Route = path(ListsBasePath / Segment) { iri => - get { - /* return a node, root or child, with all children */ - requestContext => - val listIri = - stringFormatter.validateAndEscapeIri(iri, throw BadRequestException(s"Invalid param list IRI: $iri")) - - val requestMessage: Future[ListGetRequestADM] = for { - requestingUser <- getUserADM(requestContext, featureFactoryConfig) - } yield ListGetRequestADM( - iri = listIri, - featureFactoryConfig = featureFactoryConfig, - requestingUser = requestingUser - ) - - RouteUtilADM.runJsonRoute( - requestMessageF = requestMessage, - requestContext = requestContext, - featureFactoryConfig = featureFactoryConfig, - settings = settings, - responderManager = responderManager, - log = log - ) - } - } - - /** - * update list - */ - @Path("/{IRI}") - @ApiOperation( - value = "Update basic node information", - nickname = "newPutListItem", - httpMethod = "PUT", - response = classOf[NodeInfoGetResponseADM] - ) - @ApiImplicitParams( - Array( - new ApiImplicitParam( - name = "body", - value = "\"list\" item to update", - required = true, - dataTypeClass = classOf[ChangeNodeInfoApiRequestADM], - paramType = "body" - ), - new ApiImplicitParam( - name = "X-Knora-Feature-Toggles", - value = "new-list-admin-routes:1 = on/off", - required = true, - dataType = "string", - paramType = "header" - ) - ) - ) - @ApiResponses( - Array( - new ApiResponse(code = 500, message = "Internal server error") - ) - ) - private def updateListItem(featureFactoryConfig: FeatureFactoryConfig): Route = path(ListsBasePath / Segment) { iri => - put { - /* update existing list node (either root or child) */ - entity(as[ChangeNodeInfoApiRequestADM]) { apiRequest => requestContext => - val listIri = ListIRI.create(apiRequest.listIri).fold(e => throw e, v => v) - val projectIri = ProjectIRI.create(apiRequest.projectIri).fold(e => throw e, v => v) - - val maybeHasRootNode: Option[ListIRI] = apiRequest.hasRootNode match { - case Some(value) => Some(ListIRI.create(value).fold(e => throw e, v => v)) - case None => None - } - - val maybeName: Option[ListName] = apiRequest.name match { - case Some(value) => Some(ListName.create(value).fold(e => throw e, v => v)) - case None => None - } - - val maybePosition: Option[Position] = apiRequest.position match { - case Some(value) => Some(Position.create(value).fold(e => throw e, v => v)) - case None => None - } - - val maybeLabels: Option[Labels] = apiRequest.labels match { - case Some(value) => Some(Labels.create(value).fold(e => throw e, v => v)) - case None => None - } - - val maybeComments: Option[Comments] = apiRequest.comments match { - case Some(value) => Some(Comments.create(value).fold(e => throw e, v => v)) - case None => None - } - - val changeNodeInfoPayloadADM: NodeInfoChangePayloadADM = NodeInfoChangePayloadADM( - listIri, - projectIri, - hasRootNode = maybeHasRootNode, - position = maybePosition, - name = maybeName, - labels = maybeLabels, - comments = maybeComments - ) - - val requestMessage: Future[NodeInfoChangeRequestADM] = for { - requestingUser <- getUserADM(requestContext, featureFactoryConfig) - // check if the requesting user is allowed to perform operation - _ = if ( - !requestingUser.permissions.isProjectAdmin(projectIri.value) && !requestingUser.permissions.isSystemAdmin - ) { - // not project or a system admin - throw ForbiddenException(LIST_CREATE_PERMISSION_ERROR) - } - } yield NodeInfoChangeRequestADM( - //TODO: why "listIri" property is doubled - here and inside "changeNodeRequest" - listIri = listIri.value, - changeNodeRequest = changeNodeInfoPayloadADM, - featureFactoryConfig = featureFactoryConfig, - requestingUser = requestingUser, - apiRequestID = UUID.randomUUID() - ) - - RouteUtilADM.runJsonRoute( - requestMessageF = requestMessage, - requestContext = requestContext, - featureFactoryConfig = featureFactoryConfig, - settings = settings, - responderManager = responderManager, - log = log - ) - } - } - } - - @Path("/{IRI}/info") - @ApiOperation( - value = "Get basic node information", - nickname = "newGetNodeInfo", - httpMethod = "PUT", - response = classOf[RootNodeInfoGetResponseADM] - ) - @ApiImplicitParams( - Array( - new ApiImplicitParam( - name = "X-Knora-Feature-Toggles", - value = "new-list-admin-routes:1 = on/off", - required = true, - dataType = "string", - paramType = "header" - ) - ) - ) - @ApiResponses( - Array( - new ApiResponse(code = 500, message = "Internal server error") - ) - ) - private def getNodeInfo(featureFactoryConfig: FeatureFactoryConfig): Route = path(ListsBasePath / Segment / "info") { - iri => - get { - /* return information about a node, root or child, without children */ - requestContext => - val listIri = - stringFormatter.validateAndEscapeIri(iri, throw BadRequestException(s"Invalid param list IRI: $iri")) - - val requestMessage: Future[ListNodeInfoGetRequestADM] = for { - requestingUser <- getUserADM(requestContext, featureFactoryConfig) - } yield ListNodeInfoGetRequestADM( - iri = listIri, - featureFactoryConfig = featureFactoryConfig, - requestingUser = requestingUser - ) - - RouteUtilADM.runJsonRoute( - requestMessageF = requestMessage, - requestContext = requestContext, - featureFactoryConfig = featureFactoryConfig, - settings = settings, - responderManager = responderManager, - log = log - ) - } - } -} diff --git a/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/OldListsRouteADMFeature.scala b/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/OldListsRouteADMFeature.scala index dece508ad4..1b05235c61 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/OldListsRouteADMFeature.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/OldListsRouteADMFeature.scala @@ -5,34 +5,27 @@ package org.knora.webapi.routing.admin.lists -import java.util.UUID import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.{PathMatcher, Route} import io.swagger.annotations._ - -import javax.ws.rs.Path import org.knora.webapi.IRI import org.knora.webapi.exceptions.{BadRequestException, ForbiddenException} import org.knora.webapi.feature.{Feature, FeatureFactoryConfig} +import org.knora.webapi.messages.admin.responder.listsmessages.ListNodeCreatePayloadADM.{ + ListChildNodeCreatePayloadADM, + ListRootNodeCreatePayloadADM +} import org.knora.webapi.messages.admin.responder.listsmessages.ListsErrorMessagesADM.{ LIST_CREATE_PERMISSION_ERROR, LIST_NODE_CREATE_PERMISSION_ERROR } -import org.knora.webapi.messages.admin.responder.listsmessages.NodeCreatePayloadADM.{ - ChildNodeCreatePayloadADM, - ListCreatePayloadADM -} import org.knora.webapi.messages.admin.responder.listsmessages._ -import org.knora.webapi.messages.admin.responder.valueObjects.{ - Comments, - Labels, - ListIRI, - ListName, - Position, - ProjectIRI -} +import org.knora.webapi.messages.admin.responder.valueObjects._ import org.knora.webapi.routing.{Authenticator, KnoraRoute, KnoraRouteData, RouteUtilADM} +import zio.prelude.Validation +import java.util.UUID +import javax.ws.rs.Path import scala.concurrent.Future object OldListsRouteADMFeature { @@ -56,24 +49,25 @@ class OldListsRouteADMFeature(routeData: KnoraRouteData) def makeRoute(featureFactoryConfig: FeatureFactoryConfig): Route = getLists(featureFactoryConfig) ~ - createList(featureFactoryConfig) ~ - getListOrNode(featureFactoryConfig) ~ - updateList(featureFactoryConfig) ~ - createListChildNode(featureFactoryConfig) ~ + getListNode(featureFactoryConfig) ~ + getListOrNodeInfo(featureFactoryConfig, "infos") ~ + getListOrNodeInfo(featureFactoryConfig, "nodes") ~ getListInfo(featureFactoryConfig) ~ - getListNodeInfo(featureFactoryConfig) + createListRootNode(featureFactoryConfig) ~ + createListChildNode(featureFactoryConfig) ~ + updateList(featureFactoryConfig) - /* return all lists optionally filtered by project */ @ApiOperation(value = "Get lists", nickname = "getlists", httpMethod = "GET", response = classOf[ListsGetResponseADM]) @ApiResponses( Array( new ApiResponse(code = 500, message = "Internal server error") ) ) - /* return all lists optionally filtered by project */ + /** + * Returns all lists optionally filtered by project. + */ private def getLists(featureFactoryConfig: FeatureFactoryConfig): Route = path(ListsBasePath) { get { - /* return all lists */ parameters("projectIri".?) { maybeProjectIri: Option[IRI] => requestContext => val projectIri = stringFormatter.validateAndEscapeOptionalIri( @@ -104,73 +98,57 @@ class OldListsRouteADMFeature(routeData: KnoraRouteData) } } - /* create a new list (root node) */ - @ApiOperation( - value = "Add new list", - nickname = "addList", - httpMethod = "POST", - response = classOf[ListGetResponseADM] - ) - @ApiImplicitParams( - Array( - new ApiImplicitParam( - name = "body", - value = "\"list\" to create", - required = true, - dataTypeClass = classOf[CreateNodeApiRequestADM], - paramType = "body" - ) - ) - ) + @Path("/{IRI}") + @ApiOperation(value = "Get a list", nickname = "getlist", httpMethod = "GET", response = classOf[ListGetResponseADM]) @ApiResponses( Array( new ApiResponse(code = 500, message = "Internal server error") ) ) - private def createList(featureFactoryConfig: FeatureFactoryConfig): Route = path(ListsBasePath) { - post { - /* create a list */ - entity(as[CreateNodeApiRequestADM]) { apiRequest => requestContext => - val maybeId: Option[ListIRI] = apiRequest.id match { - case Some(value) => Some(ListIRI.create(value).fold(e => throw e, v => v)) - case None => None - } - - val maybeName: Option[ListName] = apiRequest.name match { - case Some(value) => Some(ListName.create(value).fold(e => throw e, v => v)) - case None => None - } - - val projectIri = ProjectIRI.create(apiRequest.projectIri).fold(e => throw e, v => v) + /** + * Returns a list node, root or child, with children (if exist). + */ + private def getListNode(featureFactoryConfig: FeatureFactoryConfig): Route = path(ListsBasePath / Segment) { iri => + get { requestContext => + val listIri = + stringFormatter.validateAndEscapeIri(iri, throw BadRequestException(s"Invalid param list IRI: $iri")) - val createRootNodePayloadADM: ListCreatePayloadADM = ListCreatePayloadADM( - id = maybeId, - projectIri, - name = maybeName, - labels = Labels.create(apiRequest.labels).fold(e => throw e, v => v), - comments = Comments.create(apiRequest.comments).fold(e => throw e, v => v) + val requestMessage: Future[ListGetRequestADM] = for { + requestingUser <- getUserADM( + requestContext = requestContext, + featureFactoryConfig = featureFactoryConfig ) + } yield ListGetRequestADM( + iri = listIri, + featureFactoryConfig = featureFactoryConfig, + requestingUser = requestingUser + ) -// println("AAA-createList", createRootNodePayloadADM) - - val requestMessage: Future[ListCreateRequestADM] = for { - requestingUser <- getUserADM( - requestContext = requestContext, - featureFactoryConfig = featureFactoryConfig - ) + RouteUtilADM.runJsonRoute( + requestMessageF = requestMessage, + requestContext = requestContext, + featureFactoryConfig = featureFactoryConfig, + settings = settings, + responderManager = responderManager, + log = log + ) + } + } - // check if the requesting user is allowed to perform operation - _ = if ( - !requestingUser.permissions.isProjectAdmin(projectIri.value) && !requestingUser.permissions.isSystemAdmin - ) { - // not project or a system admin - throw ForbiddenException(LIST_CREATE_PERMISSION_ERROR) - } - } yield ListCreateRequestADM( - createRootNode = createRootNodePayloadADM, + /** + * Returns basic information about list node, root or child, w/o children (if exist). + */ + private def getListOrNodeInfo(featureFactoryConfig: FeatureFactoryConfig, routeSwitch: String): Route = + path(ListsBasePath / routeSwitch / Segment) { iri => + get { requestContext => + val listIri = + stringFormatter.validateAndEscapeIri(iri, throw BadRequestException(s"Invalid param list IRI: $iri")) + val requestMessage: Future[ListNodeInfoGetRequestADM] = for { + requestingUser <- getUserADM(requestContext, featureFactoryConfig) + } yield ListNodeInfoGetRequestADM( + iri = listIri, featureFactoryConfig = featureFactoryConfig, - requestingUser = requestingUser, - apiRequestID = UUID.randomUUID() + requestingUser = requestingUser ) RouteUtilADM.runJsonRoute( @@ -183,29 +161,20 @@ class OldListsRouteADMFeature(routeData: KnoraRouteData) ) } } - } - /* get a list */ - @Path("/{IRI}") - @ApiOperation(value = "Get a list", nickname = "getlist", httpMethod = "GET", response = classOf[ListGetResponseADM]) - @ApiResponses( - Array( - new ApiResponse(code = 500, message = "Internal server error") - ) - ) - private def getListOrNode(featureFactoryConfig: FeatureFactoryConfig): Route = path(ListsBasePath / Segment) { iri => - get { - /* return a list (a graph with all list nodes) */ - requestContext => + /** + * Returns basic information about a node, root or child, w/o children. + */ + private def getListInfo(featureFactoryConfig: FeatureFactoryConfig): Route = +// Brought from new lists route implementation, has the e functionality as getListOrNodeInfo + path(ListsBasePath / Segment / "info") { iri => + get { requestContext => val listIri = stringFormatter.validateAndEscapeIri(iri, throw BadRequestException(s"Invalid param list IRI: $iri")) - val requestMessage: Future[ListGetRequestADM] = for { - requestingUser <- getUserADM( - requestContext = requestContext, - featureFactoryConfig = featureFactoryConfig - ) - } yield ListGetRequestADM( + val requestMessage: Future[ListNodeInfoGetRequestADM] = for { + requestingUser <- getUserADM(requestContext, featureFactoryConfig) + } yield ListNodeInfoGetRequestADM( iri = listIri, featureFactoryConfig = featureFactoryConfig, requestingUser = requestingUser @@ -219,26 +188,22 @@ class OldListsRouteADMFeature(routeData: KnoraRouteData) responderManager = responderManager, log = log ) + } } - } - /** - * update list - */ - @Path("/{IRI}") @ApiOperation( - value = "Update basic list information", - nickname = "putList", - httpMethod = "PUT", - response = classOf[RootNodeInfoGetResponseADM] + value = "Add new list", + nickname = "addList", + httpMethod = "POST", + response = classOf[ListGetResponseADM] ) @ApiImplicitParams( Array( new ApiImplicitParam( name = "body", - value = "\"list\" to update", + value = "\"list\" to create", required = true, - dataTypeClass = classOf[ChangeNodeInfoApiRequestADM], + dataTypeClass = classOf[ListRootNodeCreateApiRequestADM], paramType = "body" ) ) @@ -248,60 +213,35 @@ class OldListsRouteADMFeature(routeData: KnoraRouteData) new ApiResponse(code = 500, message = "Internal server error") ) ) - private def updateList(featureFactoryConfig: FeatureFactoryConfig): Route = path(ListsBasePath / Segment) { iri => - put { - /* update existing list node (either root or child) */ - entity(as[ChangeNodeInfoApiRequestADM]) { apiRequest => requestContext => - val listIri = ListIRI.create(apiRequest.listIri).fold(e => throw e, v => v) - val projectIri = ProjectIRI.create(apiRequest.projectIri).fold(e => throw e, v => v) - - val maybeHasRootNode: Option[ListIRI] = apiRequest.hasRootNode match { - case Some(value) => Some(ListIRI.create(value).fold(e => throw e, v => v)) - case None => None - } - - val maybeName: Option[ListName] = apiRequest.name match { - case Some(value) => Some(ListName.create(value).fold(e => throw e, v => v)) - case None => None - } - - val maybePosition: Option[Position] = apiRequest.position match { - case Some(value) => Some(Position.create(value).fold(e => throw e, v => v)) - case None => None - } - - val maybeLabels: Option[Labels] = apiRequest.labels match { - case Some(value) => Some(Labels.create(value).fold(e => throw e, v => v)) - case None => None - } - - val maybeComments: Option[Comments] = apiRequest.comments match { - case Some(value) => Some(Comments.create(value).fold(e => throw e, v => v)) - case None => None - } - - val changeNodeInfoPayloadADM: NodeInfoChangePayloadADM = NodeInfoChangePayloadADM( - listIri, - projectIri, - hasRootNode = maybeHasRootNode, - position = maybePosition, - name = maybeName, - labels = maybeLabels, - comments = maybeComments - ) - - val requestMessage: Future[NodeInfoChangeRequestADM] = for { + /** + * Creates a new list (root node). + */ + private def createListRootNode(featureFactoryConfig: FeatureFactoryConfig): Route = path(ListsBasePath) { + post { + entity(as[ListRootNodeCreateApiRequestADM]) { apiRequest => requestContext => + val maybeId: Validation[Throwable, Option[ListIRI]] = ListIRI.make(apiRequest.id) + val projectIri: Validation[Throwable, ProjectIRI] = ProjectIRI.make(apiRequest.projectIri) + val maybeName: Validation[Throwable, Option[ListName]] = ListName.make(apiRequest.name) + val labels: Validation[Throwable, Labels] = Labels.make(apiRequest.labels) + val comments: Validation[Throwable, Comments] = Comments.make(apiRequest.comments) + val validatedListRootNodeCreatePayload: Validation[Throwable, ListRootNodeCreatePayloadADM] = + Validation.validateWith(maybeId, projectIri, maybeName, labels, comments)(ListRootNodeCreatePayloadADM) + + val requestMessage: Future[ListRootNodeCreateRequestADM] = for { + payload <- toFuture(validatedListRootNodeCreatePayload) requestingUser <- getUserADM(requestContext, featureFactoryConfig) + // check if the requesting user is allowed to perform operation _ = if ( - !requestingUser.permissions.isProjectAdmin(projectIri.value) && !requestingUser.permissions.isSystemAdmin + !requestingUser.permissions.isProjectAdmin( + projectIri.toOption.get.value + ) && !requestingUser.permissions.isSystemAdmin ) { // not project or a system admin - throw ForbiddenException(LIST_NODE_CREATE_PERMISSION_ERROR) + throw ForbiddenException(LIST_CREATE_PERMISSION_ERROR) } - } yield NodeInfoChangeRequestADM( - listIri = listIri.value, - changeNodeRequest = changeNodeInfoPayloadADM, + } yield ListRootNodeCreateRequestADM( + createRootNode = payload, featureFactoryConfig = featureFactoryConfig, requestingUser = requestingUser, apiRequestID = UUID.randomUUID() @@ -319,9 +259,6 @@ class OldListsRouteADMFeature(routeData: KnoraRouteData) } } - /** - * create a new child node - */ @Path("/{IRI}") @ApiOperation( value = "Add new node", @@ -335,7 +272,7 @@ class OldListsRouteADMFeature(routeData: KnoraRouteData) name = "body", value = "\"node\" to create", required = true, - dataTypeClass = classOf[CreateNodeApiRequestADM], + dataTypeClass = classOf[ListChildNodeCreateApiRequestADM], paramType = "body" ) ) @@ -345,65 +282,46 @@ class OldListsRouteADMFeature(routeData: KnoraRouteData) new ApiResponse(code = 500, message = "Internal server error") ) ) + /** + * Creates a new list child node. + */ private def createListChildNode(featureFactoryConfig: FeatureFactoryConfig): Route = path(ListsBasePath / Segment) { iri => post { - /* add node to existing list node. the existing list node can be either the root or a child */ - entity(as[CreateNodeApiRequestADM]) { apiRequest => requestContext => - val maybeId: Option[ListIRI] = apiRequest.id match { - case Some(value) => Some(ListIRI.create(value).fold(e => throw e, v => v)) - case None => None - } - - val maybeParentNodeIri: Option[ListIRI] = apiRequest.parentNodeIri match { - case Some(value) => Some(ListIRI.create(value).fold(e => throw e, v => v)) - case None => None - } - - val projectIri = ProjectIRI.create(apiRequest.projectIri).fold(e => throw e, v => v) - - val maybeName: Option[ListName] = apiRequest.name match { - case Some(value) => Some(ListName.create(value).fold(e => throw e, v => v)) - case None => None - } - - val maybePosition: Option[Position] = apiRequest.position match { - case Some(value) => Some(Position.create(value).fold(e => throw e, v => v)) - case None => None - } - - // allows to omit comments / send empty comments creating child node - val maybeComments = if (apiRequest.comments.isEmpty) { - None + entity(as[ListChildNodeCreateApiRequestADM]) { apiRequest => requestContext => + // check if requested ListIri matches the Iri passed in the route + val parentNodeIri: Validation[Throwable, ListIRI] = if (iri == apiRequest.parentNodeIri) { + ListIRI.make(apiRequest.parentNodeIri) } else { - Some(Comments.create(apiRequest.comments).fold(e => throw e, v => v)) + Validation.fail(throw BadRequestException("Route and payload parentNodeIri mismatch.")) } - val createChildNodeRequest: ChildNodeCreatePayloadADM = ChildNodeCreatePayloadADM( - id = maybeId, - parentNodeIri = maybeParentNodeIri, - projectIri, - name = maybeName, - position = maybePosition, - labels = Labels.create(apiRequest.labels).fold(e => throw e, v => v), - comments = maybeComments - ) + val id: Validation[Throwable, Option[ListIRI]] = ListIRI.make(apiRequest.id) + val projectIri: Validation[Throwable, ProjectIRI] = ProjectIRI.make(apiRequest.projectIri) + val name: Validation[Throwable, Option[ListName]] = ListName.make(apiRequest.name) + val position: Validation[Throwable, Option[Position]] = Position.make(apiRequest.position) + val labels: Validation[Throwable, Labels] = Labels.make(apiRequest.labels) + val comments: Validation[Throwable, Option[Comments]] = Comments.make(apiRequest.comments) + val validatedCreateChildNodePeyload: Validation[Throwable, ListChildNodeCreatePayloadADM] = + Validation.validateWith(id, parentNodeIri, projectIri, name, position, labels, comments)( + ListChildNodeCreatePayloadADM + ) val requestMessage: Future[ListChildNodeCreateRequestADM] = for { - requestingUser <- getUserADM( - requestContext = requestContext, - featureFactoryConfig = featureFactoryConfig - ) + payload <- toFuture(validatedCreateChildNodePeyload) + requestingUser <- getUserADM(requestContext, featureFactoryConfig) // check if the requesting user is allowed to perform operation _ = if ( - !requestingUser.permissions.isProjectAdmin(projectIri.value) && !requestingUser.permissions.isSystemAdmin + !requestingUser.permissions.isProjectAdmin( + projectIri.toOption.get.value + ) && !requestingUser.permissions.isSystemAdmin ) { // not project or a system admin throw ForbiddenException(LIST_CREATE_PERMISSION_ERROR) } } yield ListChildNodeCreateRequestADM( - createChildNodeRequest = createChildNodeRequest, + createChildNodeRequest = payload, featureFactoryConfig = featureFactoryConfig, requestingUser = requestingUser, apiRequestID = UUID.randomUUID() @@ -421,59 +339,83 @@ class OldListsRouteADMFeature(routeData: KnoraRouteData) } } - private def getListInfo(featureFactoryConfig: FeatureFactoryConfig): Route = path(ListsBasePath / "infos" / Segment) { - iri => - get { - /* return information about a list (without children) */ - requestContext => - val listIri = - stringFormatter.validateAndEscapeIri(iri, throw BadRequestException(s"Invalid param list IRI: $iri")) - val requestMessage: Future[ListNodeInfoGetRequestADM] = for { - requestingUser <- getUserADM(requestContext, featureFactoryConfig) - } yield ListNodeInfoGetRequestADM( - iri = listIri, - featureFactoryConfig = featureFactoryConfig, - requestingUser = requestingUser - ) + @Path("/{IRI}") + @ApiOperation( + value = "Update basic list information", + nickname = "putList", + httpMethod = "PUT", + response = classOf[RootNodeInfoGetResponseADM] + ) + @ApiImplicitParams( + Array( + new ApiImplicitParam( + name = "body", + value = "\"list\" to update", + required = true, + dataTypeClass = classOf[ListNodeChangeApiRequestADM], + paramType = "body" + ) + ) + ) + @ApiResponses( + Array( + new ApiResponse(code = 500, message = "Internal server error") + ) + ) + /** + * Updates existing list node, either root or child. + */ + private def updateList(featureFactoryConfig: FeatureFactoryConfig): Route = path(ListsBasePath / Segment) { iri => + put { + entity(as[ListNodeChangeApiRequestADM]) { apiRequest => requestContext => + // check if requested Iri matches the route Iri + val listIri: Validation[Throwable, ListIRI] = if (iri == apiRequest.listIri) { + ListIRI.make(apiRequest.listIri) + } else { + Validation.fail(throw BadRequestException("Route and payload listIri mismatch.")) + } - RouteUtilADM.runJsonRoute( - requestMessageF = requestMessage, - requestContext = requestContext, - featureFactoryConfig = featureFactoryConfig, - settings = settings, - responderManager = responderManager, - log = log - ) - } - } + val projectIri: Validation[Throwable, ProjectIRI] = ProjectIRI.make(apiRequest.projectIri) + val hasRootNode: Validation[Throwable, Option[ListIRI]] = ListIRI.make(apiRequest.hasRootNode) + val position: Validation[Throwable, Option[Position]] = Position.make(apiRequest.position) + val name: Validation[Throwable, Option[ListName]] = ListName.make(apiRequest.name) + val labels: Validation[Throwable, Option[Labels]] = Labels.make(apiRequest.labels) + val comments: Validation[Throwable, Option[Comments]] = Comments.make(apiRequest.comments) - private def getListNodeInfo(featureFactoryConfig: FeatureFactoryConfig): Route = - path(ListsBasePath / "nodes" / Segment) { iri => - get { - /* return information about a single node (without children) */ - requestContext => - val listIri = - stringFormatter.validateAndEscapeIri(iri, throw BadRequestException(s"Invalid param list IRI: $iri")) - - val requestMessage: Future[ListNodeInfoGetRequestADM] = for { - requestingUser <- getUserADM( - requestContext = requestContext, - featureFactoryConfig = featureFactoryConfig - ) - } yield ListNodeInfoGetRequestADM( - iri = listIri, - featureFactoryConfig = featureFactoryConfig, - requestingUser = requestingUser + val validatedChangeNodeInfoPayload: Validation[Throwable, ListNodeChangePayloadADM] = + Validation.validateWith(listIri, projectIri, hasRootNode, position, name, labels, comments)( + ListNodeChangePayloadADM ) - RouteUtilADM.runJsonRoute( - requestMessageF = requestMessage, - requestContext = requestContext, - featureFactoryConfig = featureFactoryConfig, - settings = settings, - responderManager = responderManager, - log = log - ) + val requestMessage: Future[NodeInfoChangeRequestADM] = for { + payload <- toFuture(validatedChangeNodeInfoPayload) + requestingUser <- getUserADM(requestContext, featureFactoryConfig) + // check if the requesting user is allowed to perform operation + _ = if ( + !requestingUser.permissions.isProjectAdmin( + projectIri.toOption.get.value + ) && !requestingUser.permissions.isSystemAdmin + ) { + // not project or a system admin + throw ForbiddenException(LIST_NODE_CREATE_PERMISSION_ERROR) + } + } yield NodeInfoChangeRequestADM( + listIri = listIri.toOption.get.value, + changeNodeRequest = payload, + featureFactoryConfig = featureFactoryConfig, + requestingUser = requestingUser, + apiRequestID = UUID.randomUUID() + ) + + RouteUtilADM.runJsonRoute( + requestMessageF = requestMessage, + requestContext = requestContext, + featureFactoryConfig = featureFactoryConfig, + settings = settings, + responderManager = responderManager, + log = log + ) } } + } } diff --git a/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/UpdateListItemsRouteADM.scala b/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/UpdateListItemsRouteADM.scala index 399af2f67f..1fd47b9784 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/UpdateListItemsRouteADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/UpdateListItemsRouteADM.scala @@ -5,18 +5,17 @@ package org.knora.webapi.routing.admin.lists -import java.util.UUID import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.{PathMatcher, Route} import io.swagger.annotations._ - -import javax.ws.rs.Path import org.knora.webapi.exceptions.BadRequestException import org.knora.webapi.feature.{Feature, FeatureFactoryConfig} import org.knora.webapi.messages.admin.responder.listsmessages._ import org.knora.webapi.messages.admin.responder.valueObjects.{Comments, Labels, ListName} import org.knora.webapi.routing.{Authenticator, KnoraRoute, KnoraRouteData, RouteUtilADM} +import java.util.UUID +import javax.ws.rs.Path import scala.concurrent.Future object UpdateListItemsRouteADM { @@ -42,9 +41,6 @@ class UpdateListItemsRouteADM(routeData: KnoraRouteData) updateNodeComments(featureFactoryConfig) ~ updateNodePosition(featureFactoryConfig) - /** - * update node name - */ @Path("/{IRI}/name") @ApiOperation( value = "Update Node Name", @@ -68,16 +64,18 @@ class UpdateListItemsRouteADM(routeData: KnoraRouteData) new ApiResponse(code = 500, message = "Internal server error") ) ) + /** + * Update name of an existing list node, either root or child. + */ private def updateNodeName(featureFactoryConfig: FeatureFactoryConfig): Route = path(ListsBasePath / Segment / "name") { iri => put { - /* update name of an existing list node (either root or child) */ entity(as[ChangeNodeNameApiRequestADM]) { apiRequest => requestContext => val nodeIri = stringFormatter.validateAndEscapeIri(iri, throw BadRequestException(s"Invalid param node IRI: $iri")) val namePayload: NodeNameChangePayloadADM = - NodeNameChangePayloadADM(ListName.create(apiRequest.name).fold(e => throw e, v => v)) + NodeNameChangePayloadADM(ListName.make(apiRequest.name).fold(e => throw e.head, v => v)) val requestMessage: Future[NodeNameChangeRequestADM] = for { requestingUser <- getUserADM(requestContext, featureFactoryConfig) @@ -101,9 +99,6 @@ class UpdateListItemsRouteADM(routeData: KnoraRouteData) } } - /** - * update node labels - */ @Path("/{IRI}/labels") @ApiOperation( value = "Update Node Labels", @@ -127,16 +122,18 @@ class UpdateListItemsRouteADM(routeData: KnoraRouteData) new ApiResponse(code = 500, message = "Internal server error") ) ) + /** + * Update labels of an existing list node, either root or child. + */ private def updateNodeLabels(featureFactoryConfig: FeatureFactoryConfig): Route = path(ListsBasePath / Segment / "labels") { iri => put { - /* update labels of an existing list node (either root or child) */ entity(as[ChangeNodeLabelsApiRequestADM]) { apiRequest => requestContext => val nodeIri = stringFormatter.validateAndEscapeIri(iri, throw BadRequestException(s"Invalid param node IRI: $iri")) val labelsPayload: NodeLabelsChangePayloadADM = - NodeLabelsChangePayloadADM(Labels.create(apiRequest.labels).fold(e => throw e, v => v)) + NodeLabelsChangePayloadADM(Labels.make(apiRequest.labels).fold(e => throw e.head, v => v)) val requestMessage: Future[NodeLabelsChangeRequestADM] = for { requestingUser <- getUserADM(requestContext, featureFactoryConfig) @@ -160,9 +157,6 @@ class UpdateListItemsRouteADM(routeData: KnoraRouteData) } } - /** - * update node comments - */ @Path("/{IRI}/comments") @ApiOperation( value = "Update Node Comments", @@ -186,19 +180,18 @@ class UpdateListItemsRouteADM(routeData: KnoraRouteData) new ApiResponse(code = 500, message = "Internal server error") ) ) + /** + * Updates comments of an existing list node, either root or child. + */ private def updateNodeComments(featureFactoryConfig: FeatureFactoryConfig): Route = path(ListsBasePath / Segment / "comments") { iri => put { - /* update comments of an existing list node (either root or child) */ entity(as[ChangeNodeCommentsApiRequestADM]) { apiRequest => requestContext => val nodeIri = stringFormatter.validateAndEscapeIri(iri, throw BadRequestException(s"Invalid param node IRI: $iri")) - val commentsPayload: NodeCommentsChangePayloadADM = if (apiRequest.comments.isEmpty) { - NodeCommentsChangePayloadADM(None) - } else { - NodeCommentsChangePayloadADM(Some(Comments.create(apiRequest.comments).fold(e => throw e, v => v))) - } + val commentsPayload: NodeCommentsChangePayloadADM = + NodeCommentsChangePayloadADM(Comments.make(apiRequest.comments).fold(e => throw e.head, v => v)) val requestMessage: Future[NodeCommentsChangeRequestADM] = for { requestingUser <- getUserADM(requestContext, featureFactoryConfig) @@ -222,9 +215,6 @@ class UpdateListItemsRouteADM(routeData: KnoraRouteData) } } - /** - * update node position - */ @Path("/{IRI}/position") @ApiOperation( value = "Update Node Position", @@ -248,10 +238,12 @@ class UpdateListItemsRouteADM(routeData: KnoraRouteData) new ApiResponse(code = 500, message = "Internal server error") ) ) + /** + * Updates position of an existing list child node. + */ private def updateNodePosition(featureFactoryConfig: FeatureFactoryConfig): Route = path(ListsBasePath / Segment / "position") { iri => put { - /* update labels of an existing list node (either root or child) */ entity(as[ChangeNodePositionApiRequestADM]) { apiRequest => requestContext => val nodeIri = stringFormatter.validateAndEscapeIri(iri, throw BadRequestException(s"Invalid param node IRI: $iri")) diff --git a/webapi/src/test/scala/org/knora/webapi/e2e/admin/GroupsADME2ESpec.scala b/webapi/src/test/scala/org/knora/webapi/e2e/admin/GroupsADME2ESpec.scala index 738dbff055..16543a97b1 100644 --- a/webapi/src/test/scala/org/knora/webapi/e2e/admin/GroupsADME2ESpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/e2e/admin/GroupsADME2ESpec.scala @@ -49,7 +49,6 @@ class GroupsADME2ESpec extends E2ESpec(GroupsADME2ESpec.config) with GroupsADMJs "The Groups Route ('admin/groups')" when { "used to query for group information" should { - "return all groups" in { val request = Get(baseApiUrl + s"/admin/groups") ~> addCredentials(BasicHttpCredentials(imagesUser01Email, testPass)) @@ -159,11 +158,9 @@ class GroupsADME2ESpec extends E2ESpec(GroupsADME2ESpec.config) with GroupsADMJs } "used to modify group information" should { - val newGroupIri = new MutableTestIri "CREATE a new group" in { - val createGroupRequest: String = s"""{ | "name": "NewGroup", @@ -217,7 +214,6 @@ class GroupsADME2ESpec extends E2ESpec(GroupsADME2ESpec.config) with GroupsADMJs } "UPDATE a group" in { - val updateGroupRequest: String = s"""{ | "name": "UpdatedGroupName", @@ -264,7 +260,6 @@ class GroupsADME2ESpec extends E2ESpec(GroupsADME2ESpec.config) with GroupsADMJs } "DELETE a group" in { - val groupIriEnc = java.net.URLEncoder.encode(newGroupIri.get, "utf-8") val request = Delete(baseApiUrl + "/admin/groups/" + groupIriEnc) ~> addCredentials( BasicHttpCredentials(imagesUser01Email, testPass) @@ -295,7 +290,6 @@ class GroupsADME2ESpec extends E2ESpec(GroupsADME2ESpec.config) with GroupsADMJs } "CHANGE status of a group" in { - val changeGroupStatusRequest: String = s"""{ | "status": true @@ -343,7 +337,6 @@ class GroupsADME2ESpec extends E2ESpec(GroupsADME2ESpec.config) with GroupsADMJs } "used to query members" should { - "return all members of a group" in { val request = Get(baseApiUrl + s"/admin/groups/$groupIriEnc/members") ~> addCredentials( BasicHttpCredentials(imagesUser01Email, testPass) diff --git a/webapi/src/test/scala/org/knora/webapi/e2e/admin/lists/BUILD.bazel b/webapi/src/test/scala/org/knora/webapi/e2e/admin/lists/BUILD.bazel index a630043c3b..23c8278cdc 100644 --- a/webapi/src/test/scala/org/knora/webapi/e2e/admin/lists/BUILD.bazel +++ b/webapi/src/test/scala/org/knora/webapi/e2e/admin/lists/BUILD.bazel @@ -22,25 +22,6 @@ scala_test( ] + BASE_TEST_DEPENDENCIES_WITH_JSON, ) -scala_test( - name = "NewListsRoutesADMFeatureE2ESpec", - size = "small", - srcs = [ - "NewListsRoutesADMFeatureE2ESpec.scala", - "//webapi/src/test/scala/org/knora/webapi/messages:SessionMessagesV1", - ], - data = [ - "//knora-ontologies", - "//test_data", - ], - jvm_flags = ["-Dconfig.resource=fuseki.conf"], - # unused_dependency_checker_mode = "warn", - deps = ALL_WEBAPI_MAIN_DEPENDENCIES + [ - "//webapi:main_library", - "//webapi:test_library", - ] + BASE_TEST_DEPENDENCIES_WITH_JSON, -) - scala_test( name = "DeleteListItemsRouteADME2ESpec", size = "small", diff --git a/webapi/src/test/scala/org/knora/webapi/e2e/admin/lists/NewListsRoutesADMFeatureE2ESpec.scala b/webapi/src/test/scala/org/knora/webapi/e2e/admin/lists/NewListsRoutesADMFeatureE2ESpec.scala deleted file mode 100644 index 2fe2c837eb..0000000000 --- a/webapi/src/test/scala/org/knora/webapi/e2e/admin/lists/NewListsRoutesADMFeatureE2ESpec.scala +++ /dev/null @@ -1,1078 +0,0 @@ -/* - * Copyright © 2021 Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.knora.webapi.e2e.admin.lists - -import akka.actor.ActorSystem -import akka.http.scaladsl.model._ -import akka.http.scaladsl.model.headers._ -import akka.http.scaladsl.testkit.RouteTestTimeout -import akka.http.scaladsl.unmarshalling.Unmarshal -import com.typesafe.config.{Config, ConfigFactory} -import org.knora.webapi.e2e.{ClientTestDataCollector, TestDataFileContent, TestDataFilePath} -import org.knora.webapi.feature.FeatureToggle -import org.knora.webapi.messages.admin.responder.listsmessages._ -import org.knora.webapi.messages.store.triplestoremessages.{RdfDataObject, StringLiteralV2, TriplestoreJsonProtocol} -import org.knora.webapi.messages.v1.responder.sessionmessages.SessionJsonProtocol -import org.knora.webapi.messages.v1.routing.authenticationmessages.CredentialsADM -import org.knora.webapi.sharedtestdata.{SharedListsTestDataADM, SharedTestDataADM} -import org.knora.webapi.util.{AkkaHttpUtils, MutableTestIri} -import org.knora.webapi.{E2ESpec, IRI} - -import scala.concurrent.Await -import scala.concurrent.duration._ - -object NewListsRouteADMFeatureE2ESpec { - val config: Config = ConfigFactory.parseString(""" - akka.loglevel = "DEBUG" - akka.stdout-loglevel = "DEBUG" - """.stripMargin) -} - -/** - * End-to-End (E2E) test specification for testing new lists endpoint. - */ -class NewListsRouteADMFeatureE2ESpec - extends E2ESpec(NewListsRouteADMFeatureE2ESpec.config) - with SessionJsonProtocol - with TriplestoreJsonProtocol - with ListADMJsonProtocol { - - implicit def default(implicit system: ActorSystem): RouteTestTimeout = RouteTestTimeout(5.seconds) - - // Directory path for generated client test data - private val clientTestDataPath: Seq[String] = Seq("admin", "lists", "toggle_new-list-admin-routes_v1") - - // Collects client test data - private val clientTestDataCollector = new ClientTestDataCollector(settings) - - override lazy val rdfDataObjects = List( - RdfDataObject(path = "test_data/demo_data/images-demo-data.ttl", name = "http://www.knora.org/data/00FF/images"), - RdfDataObject(path = "test_data/all_data/anything-data.ttl", name = "http://www.knora.org/data/0001/anything") - ) - - val rootCreds: CredentialsADM = CredentialsADM( - SharedTestDataADM.rootUser, - "test" - ) - - val normalUserCreds: CredentialsADM = CredentialsADM( - SharedTestDataADM.normalUser, - "test" - ) - - val anythingUserCreds: CredentialsADM = CredentialsADM( - SharedTestDataADM.anythingUser1, - "test" - ) - - val anythingAdminUserCreds: CredentialsADM = CredentialsADM( - SharedTestDataADM.anythingAdminUser, - "test" - ) - - private val treeListInfo: ListRootNodeInfoADM = SharedListsTestDataADM.treeListInfo - private val treeListNodes: Seq[ListChildNodeADM] = SharedListsTestDataADM.treeListChildNodes - private val customChildNodeIRI = "http://rdfh.ch/lists/0001/JbKZ-L_i5rTwHlv4dSNp4A" - - def addChildListNodeRequest(parentNodeIri: IRI, name: String, label: String, comment: String): String = - s"""{ - | "parentNodeIri": "$parentNodeIri", - | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", - | "name": "$name", - | "labels": [{ "value": "$label", "language": "en"}], - | "comments": [{ "value": "$comment", "language": "en"}] - |}""".stripMargin - - "The Lists Route (/admin/lists)" when { - "used to query information about lists" should { - "return all lists" in { - val request = Get(baseApiUrl + s"/admin/lists") - .addHeader(RawHeader(FeatureToggle.REQUEST_HEADER, "new-list-admin-routes:1=on")) ~> addCredentials( - BasicHttpCredentials(rootCreds.email, rootCreds.password) - ) - - val response: HttpResponse = singleAwaitingRequest(request) - // println(s"response: ${response.toString}") - - response.status should be(StatusCodes.OK) - - val lists: Seq[ListNodeInfoADM] = - AkkaHttpUtils.httpResponseToJson(response).fields("lists").convertTo[Seq[ListNodeInfoADM]] - - // log.debug("lists: {}", lists) - - lists.size should be(8) - clientTestDataCollector.addFile( - TestDataFileContent( - filePath = TestDataFilePath( - directoryPath = clientTestDataPath, - filename = "get-lists-response", - fileExtension = "json" - ), - text = responseToString(response) - ) - ) - } - - "return all lists belonging to the images project" in { - val request = Get(baseApiUrl + s"/admin/lists?projectIri=http%3A%2F%2Frdfh.ch%2Fprojects%2F00FF") - .addHeader(RawHeader(FeatureToggle.REQUEST_HEADER, "new-list-admin-routes:1=on")) ~> addCredentials( - rootCreds.basicHttpCredentials - ) - val response: HttpResponse = singleAwaitingRequest(request) - // log.debug(s"response: ${response.toString}") - - response.status should be(StatusCodes.OK) - - val lists: Seq[ListNodeInfoADM] = - AkkaHttpUtils.httpResponseToJson(response).fields("lists").convertTo[Seq[ListNodeInfoADM]] - - // log.debug("lists: {}", lists) - - lists.size should be(4) - - clientTestDataCollector.addFile( - TestDataFileContent( - filePath = TestDataFilePath( - directoryPath = clientTestDataPath, - filename = "get-image-project-lists-response", - fileExtension = "json" - ), - text = responseToString(response) - ) - ) - } - - "return all lists belonging to the anything project" in { - val request = Get(baseApiUrl + s"/admin/lists?projectIri=http%3A%2F%2Frdfh.ch%2Fprojects%2F0001") - .addHeader(RawHeader(FeatureToggle.REQUEST_HEADER, "new-list-admin-routes:1=on")) ~> addCredentials( - rootCreds.basicHttpCredentials - ) - val response: HttpResponse = singleAwaitingRequest(request) - // log.debug(s"response: ${response.toString}") - - response.status should be(StatusCodes.OK) - - val lists: Seq[ListNodeInfoADM] = - AkkaHttpUtils.httpResponseToJson(response).fields("lists").convertTo[Seq[ListNodeInfoADM]] - - // log.debug("lists: {}", lists) - - lists.size should be(3) - - clientTestDataCollector.addFile( - TestDataFileContent( - filePath = TestDataFilePath( - directoryPath = clientTestDataPath, - filename = "get-anything-project-lists-response", - fileExtension = "json" - ), - text = responseToString(response) - ) - ) - } - - "return basic list information" in { - val request = Get(baseApiUrl + s"/admin/lists/http%3A%2F%2Frdfh.ch%2Flists%2F0001%2FtreeList/info") - .addHeader(RawHeader(FeatureToggle.REQUEST_HEADER, "new-list-admin-routes:1=on")) ~> addCredentials( - rootCreds.basicHttpCredentials - ) - val response: HttpResponse = singleAwaitingRequest(request) - // log.debug(s"response: ${response.toString}") - - response.status should be(StatusCodes.OK) - - val receivedListInfo: ListRootNodeInfoADM = - AkkaHttpUtils.httpResponseToJson(response).fields("listinfo").convertTo[ListRootNodeInfoADM] - - val expectedListInfo: ListRootNodeInfoADM = SharedListsTestDataADM.treeListInfo - - receivedListInfo.sorted should be(expectedListInfo.sorted) - - clientTestDataCollector.addFile( - TestDataFileContent( - filePath = TestDataFilePath( - directoryPath = clientTestDataPath, - filename = "get-list-info-response", - fileExtension = "json" - ), - text = responseToString(response) - ) - ) - } - - "return a complete list" in { - val request = Get(baseApiUrl + s"/admin/lists/http%3A%2F%2Frdfh.ch%2Flists%2F0001%2FtreeList") - .addHeader(RawHeader(FeatureToggle.REQUEST_HEADER, "new-list-admin-routes:1=on")) ~> addCredentials( - rootCreds.basicHttpCredentials - ) - val response: HttpResponse = singleAwaitingRequest(request) - // println(s"response: ${response.toString}") - - response.status should be(StatusCodes.OK) - - val receivedList: ListADM = AkkaHttpUtils.httpResponseToJson(response).fields("list").convertTo[ListADM] - receivedList.listinfo.sorted should be(treeListInfo.sorted) - receivedList.children.map(_.sorted) should be(treeListNodes.map(_.sorted)) - - clientTestDataCollector.addFile( - TestDataFileContent( - filePath = TestDataFilePath( - directoryPath = clientTestDataPath, - filename = "get-list-response", - fileExtension = "json" - ), - text = responseToString(response) - ) - ) - } - - "return node info without children" in { - val request = Get(baseApiUrl + s"/admin/lists/http%3A%2F%2Frdfh.ch%2Flists%2F0001%2FtreeList01/info") - .addHeader(RawHeader(FeatureToggle.REQUEST_HEADER, "new-list-admin-routes:1=on")) ~> addCredentials( - rootCreds.basicHttpCredentials - ) - val response: HttpResponse = singleAwaitingRequest(request) - // log.debug(s"response: ${response.toString}") - - response.status should be(StatusCodes.OK) - - val receivedListInfo: ListChildNodeInfoADM = - AkkaHttpUtils.httpResponseToJson(response).fields("nodeinfo").convertTo[ListChildNodeInfoADM] - - val expectedListInfo: ListChildNodeInfoADM = SharedListsTestDataADM.treeListNode01Info - - receivedListInfo.sorted should be(expectedListInfo.sorted) - - clientTestDataCollector.addFile( - TestDataFileContent( - filePath = TestDataFilePath( - directoryPath = clientTestDataPath, - filename = "get-list-node-info-response", - fileExtension = "json" - ), - text = responseToString(response) - ) - ) - } - - "return a complete node with children" in { - val request = Get(baseApiUrl + s"/admin/lists/http%3A%2F%2Frdfh.ch%2Flists%2F0001%2FtreeList03") - .addHeader(RawHeader(FeatureToggle.REQUEST_HEADER, "new-list-admin-routes:1=on")) ~> addCredentials( - rootCreds.basicHttpCredentials - ) - val response: HttpResponse = singleAwaitingRequest(request) - response.status should be(StatusCodes.OK) - - val receivedNode: NodeADM = AkkaHttpUtils.httpResponseToJson(response).fields("node").convertTo[NodeADM] - receivedNode.nodeinfo.id should be("http://rdfh.ch/lists/0001/treeList03") - receivedNode.nodeinfo.name should be(Some("Tree list node 03")) - receivedNode.children.size should be(2) - - clientTestDataCollector.addFile( - TestDataFileContent( - filePath = TestDataFilePath( - directoryPath = clientTestDataPath, - filename = "get-node-response", - fileExtension = "json" - ), - text = responseToString(response) - ) - ) - - } - } - - "given a custom Iri" should { - "create a list with the provided custom Iri" in { - val createListWithCustomIriRequest: String = - s"""{ - | "id": "${SharedTestDataADM.customListIRI}", - | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", - | "labels": [{ "value": "New list with a custom IRI", "language": "en"}], - | "comments": [{ "value": "XXXXX", "language": "en"}] - |}""".stripMargin - - clientTestDataCollector.addFile( - TestDataFileContent( - filePath = TestDataFilePath( - directoryPath = clientTestDataPath, - filename = "create-list-with-custom-IRI-request", - fileExtension = "json" - ), - text = createListWithCustomIriRequest - ) - ) - val request = Post( - baseApiUrl + s"/admin/lists", - HttpEntity(ContentTypes.`application/json`, createListWithCustomIriRequest) - ) - .addHeader(RawHeader(FeatureToggle.REQUEST_HEADER, "new-list-admin-routes:1=on")) ~> addCredentials( - anythingAdminUserCreds.basicHttpCredentials - ) - val response: HttpResponse = singleAwaitingRequest(request) - response.status should be(StatusCodes.OK) - - val receivedList: ListADM = AkkaHttpUtils.httpResponseToJson(response).fields("list").convertTo[ListADM] - - val listInfo = receivedList.listinfo - listInfo.id should be(SharedTestDataADM.customListIRI) - - val labels: Seq[StringLiteralV2] = listInfo.labels.stringLiterals - labels.size should be(1) - labels.head should be(StringLiteralV2(value = "New list with a custom IRI", language = Some("en"))) - - clientTestDataCollector.addFile( - TestDataFileContent( - filePath = TestDataFilePath( - directoryPath = clientTestDataPath, - filename = "create-list-with-custom-IRI-response", - fileExtension = "json" - ), - text = responseToString(response) - ) - ) - } - - "return a DuplicateValueException during list creation when the supplied list IRI is not unique" in { - // duplicate list IRI - val params = - s""" - |{ - | "id": "${SharedTestDataADM.customListIRI}", - | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", - | "labels": [{ "value": "New List", "language": "en"}], - | "comments": [{ "value": "XXXXX", "language": "en"}] - |} - """.stripMargin - - val request = Post(baseApiUrl + s"/admin/lists", HttpEntity(ContentTypes.`application/json`, params)) - .addHeader(RawHeader(FeatureToggle.REQUEST_HEADER, "new-list-admin-routes:1=on")) ~> addCredentials( - anythingAdminUserCreds.basicHttpCredentials - ) - val response: HttpResponse = singleAwaitingRequest(request) - response.status should be(StatusCodes.BadRequest) - - val errorMessage: String = Await.result(Unmarshal(response.entity).to[String], 1.second) - val invalidIri: Boolean = - errorMessage.contains(s"IRI: '${SharedTestDataADM.customListIRI}' already exists, try another one.") - invalidIri should be(true) - } - - "add a child with a custom IRI" in { - val createChildNodeWithCustomIriRequest = - s""" - |{ "id": "$customChildNodeIRI", - | "parentNodeIri": "${SharedTestDataADM.customListIRI}", - | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", - | "name": "node with a custom IRI", - | "labels": [{ "value": "New List Node", "language": "en"}], - | "comments": [{ "value": "XXXXX", "language": "en"}] - |}""".stripMargin - - clientTestDataCollector.addFile( - TestDataFileContent( - filePath = TestDataFilePath( - directoryPath = clientTestDataPath, - filename = "create-child-node-with-custom-IRI-request", - fileExtension = "json" - ), - text = createChildNodeWithCustomIriRequest - ) - ) - - val request = Post( - baseApiUrl + s"/admin/lists", - HttpEntity(ContentTypes.`application/json`, createChildNodeWithCustomIriRequest) - ) - .addHeader(RawHeader(FeatureToggle.REQUEST_HEADER, "new-list-admin-routes:1=on")) ~> addCredentials( - anythingAdminUserCreds.basicHttpCredentials - ) - val response: HttpResponse = singleAwaitingRequest(request) - // println(s"response: ${response.toString}") - response.status should be(StatusCodes.OK) - - val received: ListNodeInfoADM = - AkkaHttpUtils.httpResponseToJson(response).fields("nodeinfo").convertTo[ListNodeInfoADM] - - // check correct node info - val childNodeInfo = received match { - case info: ListChildNodeInfoADM => info - case something => fail(s"expecting ListChildNodeInfoADM but got ${something.getClass.toString} instead.") - } - childNodeInfo.id should be(customChildNodeIRI) - - clientTestDataCollector.addFile( - TestDataFileContent( - filePath = TestDataFilePath( - directoryPath = clientTestDataPath, - filename = "create-child-node-with-custom-IRI-response", - fileExtension = "json" - ), - text = responseToString(response) - ) - ) - } - } - - "used to modify list information" should { - val newListIri = new MutableTestIri - val firstChildIri = new MutableTestIri - val secondChildIri = new MutableTestIri - val thirdChildIri = new MutableTestIri - - "create a list" in { - val createListRequest: String = - s"""{ - | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", - | "labels": [{ "value": "Neue Liste", "language": "de"}], - | "comments": [{ "value": "XXXXX", "language": "en"}] - |}""".stripMargin - - clientTestDataCollector.addFile( - TestDataFileContent( - filePath = TestDataFilePath( - directoryPath = clientTestDataPath, - filename = "create-list-request", - fileExtension = "json" - ), - text = createListRequest - ) - ) - val request = Post(baseApiUrl + s"/admin/lists", HttpEntity(ContentTypes.`application/json`, createListRequest)) - .addHeader(RawHeader(FeatureToggle.REQUEST_HEADER, "new-list-admin-routes:1=on")) ~> addCredentials( - anythingAdminUserCreds.basicHttpCredentials - ) - val response: HttpResponse = singleAwaitingRequest(request) - // log.debug(s"response: ${response.toString}") - response.status should be(StatusCodes.OK) - - val receivedList: ListADM = AkkaHttpUtils.httpResponseToJson(response).fields("list").convertTo[ListADM] - - val listInfo = receivedList.listinfo - listInfo.projectIri should be(SharedTestDataADM.ANYTHING_PROJECT_IRI) - - val labels: Seq[StringLiteralV2] = listInfo.labels.stringLiterals - labels.size should be(1) - labels.head should be(StringLiteralV2(value = "Neue Liste", language = Some("de"))) - - val comments = receivedList.listinfo.comments.stringLiterals - comments.isEmpty should be(false) - - val children = receivedList.children - children.size should be(0) - - clientTestDataCollector.addFile( - TestDataFileContent( - filePath = TestDataFilePath( - directoryPath = clientTestDataPath, - filename = "create-list-response", - fileExtension = "json" - ), - text = responseToString(response) - ) - ) - // store list IRI for next test - newListIri.set(listInfo.id) - } - - "return a ForbiddenException if the user creating the list is not project or system admin" in { - val params = - s""" - |{ - | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", - | "labels": [{ "value": "Neue Liste", "language": "de"}], - | "comments": [{ "value": "XXXXX", "language": "en"}] - |} - """.stripMargin - - val request = Post(baseApiUrl + s"/admin/lists", HttpEntity(ContentTypes.`application/json`, params)) - .addHeader(RawHeader(FeatureToggle.REQUEST_HEADER, "new-list-admin-routes:1=on")) ~> addCredentials( - anythingUserCreds.basicHttpCredentials - ) - val response: HttpResponse = singleAwaitingRequest(request) - // log.debug(s"response: ${response.toString}") - response.status should be(StatusCodes.Forbidden) - } - - "return a BadRequestException during list creation when payload is not correct" in { - // no project IRI - val params01 = - s""" - |{ - | "projectIri": "", - | "labels": [{ "value": "Neue Liste", "language": "de"}], - | "comments": [{ "value": "XXXXX", "language": "en"}] - |} - """.stripMargin - - val request01 = Post(baseApiUrl + s"/admin/lists", HttpEntity(ContentTypes.`application/json`, params01)) - .addHeader(RawHeader(FeatureToggle.REQUEST_HEADER, "new-list-admin-routes:1=on")) - val response01: HttpResponse = singleAwaitingRequest(request01) - // println(s"response: ${response01.toString}") - response01.status should be(StatusCodes.BadRequest) - - // invalid project IRI - val params02 = - s""" - |{ - | "projectIri": "notvalidIRI", - | "labels": [{ "value": "Neue Liste", "language": "de"}], - | "comments": [{ "value": "XXXXX", "language": "en"}] - |} - """.stripMargin - - val request02 = Post(baseApiUrl + s"/admin/lists", HttpEntity(ContentTypes.`application/json`, params02)) - .addHeader(RawHeader(FeatureToggle.REQUEST_HEADER, "new-list-admin-routes:1=on")) - val response02: HttpResponse = singleAwaitingRequest(request02) - // println(s"response: ${response02.toString}") - response02.status should be(StatusCodes.BadRequest) - - // missing label - val params03 = - s""" - |{ - | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", - | "labels": [], - | "comments": [{ "value": "XXXXX", "language": "en"}] - |} - """.stripMargin - - val request03 = Post(baseApiUrl + s"/admin/lists", HttpEntity(ContentTypes.`application/json`, params03)) - .addHeader(RawHeader(FeatureToggle.REQUEST_HEADER, "new-list-admin-routes:1=on")) - val response03: HttpResponse = singleAwaitingRequest(request03) - // println(s"response: ${response03.toString}") - response03.status should be(StatusCodes.BadRequest) - - } - - "update basic list information using special characters" in { - val updateListInfo: String = - s"""{ - | "listIri": "${newListIri.get}", - | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", - | "name": "neue Name mit A'postroph", - | "labels": [{ "value": "Neue geänderte Liste mit A'postroph", "language": "de"}, { "value": "Changed list with a'postrophe", "language": "en"}], - | "comments": [{ "value": "Neuer Kommentar mit A'postroph", "language": "de"}, { "value": "New comment with a'postrophe", "language": "en"}] - |}""".stripMargin - - clientTestDataCollector.addFile( - TestDataFileContent( - filePath = TestDataFilePath( - directoryPath = clientTestDataPath, - filename = "update-list-info-request", - fileExtension = "json" - ), - text = updateListInfo - ) - ) - val encodedListUrl = java.net.URLEncoder.encode(newListIri.get, "utf-8") - - val request = Put( - baseApiUrl + s"/admin/lists/" + encodedListUrl, - HttpEntity(ContentTypes.`application/json`, updateListInfo) - ) - .addHeader(RawHeader(FeatureToggle.REQUEST_HEADER, "new-list-admin-routes:1=on")) ~> addCredentials( - anythingAdminUserCreds.basicHttpCredentials - ) - val response: HttpResponse = singleAwaitingRequest(request) - // log.debug(s"response: ${response.toString}") - response.status should be(StatusCodes.OK) - - val receivedListInfo: ListRootNodeInfoADM = - AkkaHttpUtils.httpResponseToJson(response).fields("listinfo").convertTo[ListRootNodeInfoADM] - - receivedListInfo.projectIri should be(SharedTestDataADM.ANYTHING_PROJECT_IRI) - - val labels: Seq[StringLiteralV2] = receivedListInfo.labels.stringLiterals - labels.size should be(2) - - val comments = receivedListInfo.comments.stringLiterals - comments.size should be(2) - - clientTestDataCollector.addFile( - TestDataFileContent( - filePath = TestDataFilePath( - directoryPath = clientTestDataPath, - filename = "update-list-info-response", - fileExtension = "json" - ), - text = responseToString(response) - ) - ) - } - - "update basic list information with a new name" in { - val updateListName = - s"""{ - | "listIri": "${newListIri.get}", - | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", - | "name": "a totally new name" - |}""".stripMargin - - clientTestDataCollector.addFile( - TestDataFileContent( - filePath = TestDataFilePath( - directoryPath = clientTestDataPath, - filename = "update-list-name-request", - fileExtension = "json" - ), - text = updateListName - ) - ) - val encodedListUrl = java.net.URLEncoder.encode(newListIri.get, "utf-8") - - val request = Put( - baseApiUrl + s"/admin/lists/" + encodedListUrl, - HttpEntity(ContentTypes.`application/json`, updateListName) - ) - .addHeader(RawHeader(FeatureToggle.REQUEST_HEADER, "new-list-admin-routes:1=on")) ~> addCredentials( - anythingAdminUserCreds.basicHttpCredentials - ) - val response: HttpResponse = singleAwaitingRequest(request) - // log.debug(s"response: ${response.toString}") - response.status should be(StatusCodes.OK) - - val receivedListInfo: ListRootNodeInfoADM = - AkkaHttpUtils.httpResponseToJson(response).fields("listinfo").convertTo[ListRootNodeInfoADM] - - receivedListInfo.projectIri should be(SharedTestDataADM.ANYTHING_PROJECT_IRI) - - receivedListInfo.name should be(Some("a totally new name")) - - clientTestDataCollector.addFile( - TestDataFileContent( - filePath = TestDataFilePath( - directoryPath = clientTestDataPath, - filename = "update-list-name-response", - fileExtension = "json" - ), - text = responseToString(response) - ) - ) - } - - "update basic list information with repeated comment and label in different languages" in { - val updateListInfoWithRepeatedCommentAndLabelValuesRequest: String = - s"""{ - | "listIri": "http://rdfh.ch/lists/0001/treeList", - | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", - | "labels": [ - | {"language": "en", "value": "Test List"}, - | {"language": "se", "value": "Test List"} - | ], - | "comments": [ - | {"language": "en", "value": "test"}, - | {"language": "de", "value": "test"}, - | {"language": "fr", "value": "test"}, - | {"language": "it", "value": "test"} - | ] - |}""".stripMargin - - clientTestDataCollector.addFile( - TestDataFileContent( - filePath = TestDataFilePath( - directoryPath = clientTestDataPath, - filename = "update-list-info-comment-label-multiple-languages-request", - fileExtension = "json" - ), - text = updateListInfoWithRepeatedCommentAndLabelValuesRequest - ) - ) - - val encodedListUrl = java.net.URLEncoder.encode("http://rdfh.ch/lists/0001/treeList", "utf-8") - - val request = Put( - baseApiUrl + s"/admin/lists/" + encodedListUrl, - HttpEntity(ContentTypes.`application/json`, updateListInfoWithRepeatedCommentAndLabelValuesRequest) - ) - .addHeader(RawHeader(FeatureToggle.REQUEST_HEADER, "new-list-admin-routes:1=on")) ~> addCredentials( - anythingAdminUserCreds.basicHttpCredentials - ) - val response: HttpResponse = singleAwaitingRequest(request) - // log.debug(s"response: ${response.toString}") - response.status should be(StatusCodes.OK) - - val receivedListInfo: ListRootNodeInfoADM = - AkkaHttpUtils.httpResponseToJson(response).fields("listinfo").convertTo[ListRootNodeInfoADM] - - receivedListInfo.projectIri should be(SharedTestDataADM.ANYTHING_PROJECT_IRI) - - val labels: Seq[StringLiteralV2] = receivedListInfo.labels.stringLiterals - labels.size should be(2) - - val comments = receivedListInfo.comments.stringLiterals - comments.size should be(4) - - clientTestDataCollector.addFile( - TestDataFileContent( - filePath = TestDataFilePath( - directoryPath = clientTestDataPath, - filename = "update-list-info-comment-label-multiple-languages-response", - fileExtension = "json" - ), - text = responseToString(response) - ) - ) - } - - "return a ForbiddenException if the user updating the list is not project or system admin" in { - val params = - s""" - |{ - | "listIri": "${newListIri.get}", - | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", - | "labels": [{ "value": "Neue geönderte Liste", "language": "de"}, { "value": "Changed list", "language": "en"}], - | "comments": [{ "value": "Neuer Kommentar", "language": "de"}, { "value": "New comment", "language": "en"}] - |} - """.stripMargin - - val encodedListUrl = java.net.URLEncoder.encode(newListIri.get, "utf-8") - - val request = - Put(baseApiUrl + s"/admin/lists/" + encodedListUrl, HttpEntity(ContentTypes.`application/json`, params)) - .addHeader(RawHeader(FeatureToggle.REQUEST_HEADER, "new-list-admin-routes:1=on")) ~> addCredentials( - anythingUserCreds.basicHttpCredentials - ) - val response: HttpResponse = singleAwaitingRequest(request) - // log.debug(s"response: ${response.toString}") - response.status should be(StatusCodes.Forbidden) - } - - "return a BadRequestException during list change when payload is not correct" in { - val encodedListUrl = java.net.URLEncoder.encode(newListIri.get, "utf-8") - - // empty list IRI - val params01 = - s""" - |{ - | "listIri": "", - | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", - | "labels": [{ "value": "Neue geönderte Liste", "language": "de"}, { "value": "Changed list", "language": "en"}], - | "comments": [{ "value": "Neuer Kommentar", "language": "de"}, { "value": "New comment", "language": "en"}] - |} - """.stripMargin - - val request01 = - Put(baseApiUrl + s"/admin/lists/" + encodedListUrl, HttpEntity(ContentTypes.`application/json`, params01)) - .addHeader(RawHeader(FeatureToggle.REQUEST_HEADER, "new-list-admin-routes:1=on")) ~> addCredentials( - anythingAdminUserCreds.basicHttpCredentials - ) - val response01: HttpResponse = singleAwaitingRequest(request01) - // log.debug(s"response: ${response.toString}") - response01.status should be(StatusCodes.BadRequest) - - // empty project - val params02 = - s""" - |{ - | "listIri": "${newListIri.get}", - | "projectIri": "", - | "labels": [{ "value": "Neue geönderte Liste", "language": "de"}, { "value": "Changed list", "language": "en"}], - | "comments": [{ "value": "Neuer Kommentar", "language": "de"}, { "value": "New comment", "language": "en"}] - |} - """.stripMargin - - val request02 = - Put(baseApiUrl + s"/admin/lists/" + encodedListUrl, HttpEntity(ContentTypes.`application/json`, params02)) - .addHeader(RawHeader(FeatureToggle.REQUEST_HEADER, "new-list-admin-routes:1=on")) ~> addCredentials( - anythingAdminUserCreds.basicHttpCredentials - ) - val response02: HttpResponse = singleAwaitingRequest(request02) - // log.debug(s"response: ${response.toString}") - response02.status should be(StatusCodes.BadRequest) - - // empty parameters - val params03 = - s""" - |{ - | "listIri": "${newListIri.get}", - | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", - | "labels": [], - | "comments": [{ "value": "XXXXX", "language": "en"}] - |} - """.stripMargin - - val request03 = - Put(baseApiUrl + s"/admin/lists/" + encodedListUrl, HttpEntity(ContentTypes.`application/json`, params03)) - .addHeader(RawHeader(FeatureToggle.REQUEST_HEADER, "new-list-admin-routes:1=on")) ~> addCredentials( - anythingAdminUserCreds.basicHttpCredentials - ) - val response03: HttpResponse = singleAwaitingRequest(request03) - // log.debug(s"response: ${response.toString}") - response03.status should be(StatusCodes.BadRequest) - - } - - "add child to list - to the root node" in { - val name = "first" - val label = "New First Child List Node Value" - val comment = "New First Child List Node Comment" - - val addChildToRoot = addChildListNodeRequest( - parentNodeIri = newListIri.get, - name = name, - label = label, - comment = comment - ) - - clientTestDataCollector.addFile( - TestDataFileContent( - filePath = TestDataFilePath( - directoryPath = clientTestDataPath, - filename = "create-child-node-request", - fileExtension = "json" - ), - text = addChildToRoot - ) - ) - - val request = Post(baseApiUrl + s"/admin/lists", HttpEntity(ContentTypes.`application/json`, addChildToRoot)) - .addHeader(RawHeader(FeatureToggle.REQUEST_HEADER, "new-list-admin-routes:1=on")) ~> addCredentials( - anythingAdminUserCreds.basicHttpCredentials - ) - val response: HttpResponse = singleAwaitingRequest(request) - // println(s"response: ${response.toString}") - response.status should be(StatusCodes.OK) - - val received: ListNodeInfoADM = - AkkaHttpUtils.httpResponseToJson(response).fields("nodeinfo").convertTo[ListNodeInfoADM] - - // check correct node info - val childNodeInfo = received match { - case info: ListChildNodeInfoADM => info - case something => fail(s"expecting ListChildNodeInfoADM but got ${something.getClass.toString} instead.") - } - - // check labels - val labels: Seq[StringLiteralV2] = childNodeInfo.labels.stringLiterals - labels.size should be(1) - labels.sorted should be(Seq(StringLiteralV2(value = label, language = Some("en")))) - - // check comments - val comments = childNodeInfo.comments.stringLiterals - comments.size should be(1) - comments.sorted should be(Seq(StringLiteralV2(value = comment, language = Some("en")))) - - // check position - val position = childNodeInfo.position - position should be(0) - - // check has root node - val rootNode = childNodeInfo.hasRootNode - rootNode should be(newListIri.get) - clientTestDataCollector.addFile( - TestDataFileContent( - filePath = TestDataFilePath( - directoryPath = clientTestDataPath, - filename = "create-child-node-response", - fileExtension = "json" - ), - text = responseToString(response) - ) - ) - firstChildIri.set(childNodeInfo.id) - } - - "add second child to list - to the root node" in { - val name = "second" - val label = "New Second Child List Node Value" - val comment = "New Second Child List Node Comment" - - val addSecondChildToRoot = addChildListNodeRequest( - parentNodeIri = newListIri.get, - name = name, - label = label, - comment = comment - ) - clientTestDataCollector.addFile( - TestDataFileContent( - filePath = TestDataFilePath( - directoryPath = clientTestDataPath, - filename = "add-second-child-to-root-request", - fileExtension = "json" - ), - text = addSecondChildToRoot - ) - ) - val request = - Post(baseApiUrl + s"/admin/lists", HttpEntity(ContentTypes.`application/json`, addSecondChildToRoot)) - .addHeader(RawHeader(FeatureToggle.REQUEST_HEADER, "new-list-admin-routes:1=on")) ~> addCredentials( - anythingAdminUserCreds.basicHttpCredentials - ) - val response: HttpResponse = singleAwaitingRequest(request) - // println(s"response: ${response.toString}") - response.status should be(StatusCodes.OK) - - val received: ListNodeInfoADM = - AkkaHttpUtils.httpResponseToJson(response).fields("nodeinfo").convertTo[ListNodeInfoADM] - - // check correct node info - val childNodeInfo = received match { - case info: ListChildNodeInfoADM => info - case something => fail(s"expecting ListChildNodeInfoADM but got ${something.getClass.toString} instead.") - } - - // check labels - val labels: Seq[StringLiteralV2] = childNodeInfo.labels.stringLiterals - labels.size should be(1) - labels.sorted should be(Seq(StringLiteralV2(value = label, language = Some("en")))) - - // check comments - val comments = childNodeInfo.comments.stringLiterals - comments.size should be(1) - comments.sorted should be(Seq(StringLiteralV2(value = comment, language = Some("en")))) - - // check position - val position = childNodeInfo.position - position should be(1) - - // check has root node - val rootNode = childNodeInfo.hasRootNode - rootNode should be(newListIri.get) - - secondChildIri.set(childNodeInfo.id) - - clientTestDataCollector.addFile( - TestDataFileContent( - filePath = TestDataFilePath( - directoryPath = clientTestDataPath, - filename = "add-second-child-to-root-response", - fileExtension = "json" - ), - text = responseToString(response) - ) - ) - } - - "add child to second child node" in { - val name = "third" - val label = "New Third Child List Node Value" - val comment = "New Third Child List Node Comment" - - val addChildToSecondChild = addChildListNodeRequest( - parentNodeIri = secondChildIri.get, - name = name, - label = label, - comment = comment - ) - clientTestDataCollector.addFile( - TestDataFileContent( - filePath = TestDataFilePath( - directoryPath = clientTestDataPath, - filename = "add-second-child-to-root-request", - fileExtension = "json" - ), - text = addChildToSecondChild - ) - ) - val request = - Post(baseApiUrl + s"/admin/lists", HttpEntity(ContentTypes.`application/json`, addChildToSecondChild)) - .addHeader(RawHeader(FeatureToggle.REQUEST_HEADER, "new-list-admin-routes:1=on")) ~> addCredentials( - anythingAdminUserCreds.basicHttpCredentials - ) - val response: HttpResponse = singleAwaitingRequest(request) - // println(s"response: ${response.toString}") - response.status should be(StatusCodes.OK) - - val received: ListNodeInfoADM = - AkkaHttpUtils.httpResponseToJson(response).fields("nodeinfo").convertTo[ListNodeInfoADM] - - // check correct node info - val childNodeInfo = received match { - case info: ListChildNodeInfoADM => info - case something => fail(s"expecting ListChildNodeInfoADM but got ${something.getClass.toString} instead.") - } - - // check labels - val labels: Seq[StringLiteralV2] = childNodeInfo.labels.stringLiterals - labels.size should be(1) - labels.sorted should be(Seq(StringLiteralV2(value = label, language = Some("en")))) - - // check comments - val comments = childNodeInfo.comments.stringLiterals - comments.size should be(1) - comments.sorted should be(Seq(StringLiteralV2(value = comment, language = Some("en")))) - - // check position - val position = childNodeInfo.position - position should be(0) - - // check has root node - val rootNode = childNodeInfo.hasRootNode - rootNode should be(newListIri.get) - - thirdChildIri.set(childNodeInfo.id) - - clientTestDataCollector.addFile( - TestDataFileContent( - filePath = TestDataFilePath( - directoryPath = clientTestDataPath, - filename = "add-second-child-to-root-response", - fileExtension = "json" - ), - text = responseToString(response) - ) - ) - } - - "update node information of node that has custom IRI with a new name" in { - val newName = "modified third child" - val updateNodeName = - s"""{ - | "listIri": "$customChildNodeIRI", - | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", - | "name": "${newName}" - |}""".stripMargin - - clientTestDataCollector.addFile( - TestDataFileContent( - filePath = TestDataFilePath( - directoryPath = clientTestDataPath, - filename = "update-node-info-name-request", - fileExtension = "json" - ), - text = updateNodeName - ) - ) - - val encodedListUrl = java.net.URLEncoder.encode(customChildNodeIRI, "utf-8") - - val request = Put( - baseApiUrl + s"/admin/lists/" + encodedListUrl, - HttpEntity(ContentTypes.`application/json`, updateNodeName) - ) - .addHeader(RawHeader(FeatureToggle.REQUEST_HEADER, "new-list-admin-routes:1=on")) ~> addCredentials( - anythingAdminUserCreds.basicHttpCredentials - ) - val response: HttpResponse = singleAwaitingRequest(request) - - response.status should be(StatusCodes.OK) - - val receivedNodeInfo: ListChildNodeInfoADM = - AkkaHttpUtils.httpResponseToJson(response).fields("nodeinfo").convertTo[ListChildNodeInfoADM] - receivedNodeInfo.name.get should be(newName) - - clientTestDataCollector.addFile( - TestDataFileContent( - filePath = TestDataFilePath( - directoryPath = clientTestDataPath, - filename = "update-node-info-name-response", - fileExtension = "json" - ), - text = responseToString(response) - ) - ) - } - - "add flat nodes" ignore {} - - "add hierarchical nodes" ignore {} - - "change node order" ignore {} - } - } -} diff --git a/webapi/src/test/scala/org/knora/webapi/e2e/admin/lists/OldListsRouteADMFeatureE2ESpec.scala b/webapi/src/test/scala/org/knora/webapi/e2e/admin/lists/OldListsRouteADMFeatureE2ESpec.scala index 06e51cd1fc..ddd50ee873 100644 --- a/webapi/src/test/scala/org/knora/webapi/e2e/admin/lists/OldListsRouteADMFeatureE2ESpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/e2e/admin/lists/OldListsRouteADMFeatureE2ESpec.scala @@ -85,9 +85,7 @@ class OldListsRouteADMFeatureE2ESpec |}""".stripMargin "The Lists Route (/admin/lists)" when { - "used to query information about lists" should { - "return all lists" in { val request = Get(baseApiUrl + s"/admin/lists") ~> addCredentials(BasicHttpCredentials(rootCreds.email, rootCreds.password)) @@ -169,7 +167,7 @@ class OldListsRouteADMFeatureE2ESpec ) } - "return basic list information" in { + "return basic list information (w/o children)" in { val request = Get( baseApiUrl + s"/admin/lists/infos/http%3A%2F%2Frdfh.ch%2Flists%2F0001%2FtreeList" ) ~> addCredentials(rootCreds.basicHttpCredentials) @@ -197,6 +195,35 @@ class OldListsRouteADMFeatureE2ESpec ) } + "return basic list information (w/o children) for new merged GET route" in { + // the same test as above, testing the new route + val request = Get( + baseApiUrl + s"/admin/lists/http%3A%2F%2Frdfh.ch%2Flists%2F0001%2FtreeList/info" + ) ~> addCredentials(rootCreds.basicHttpCredentials) + val response: HttpResponse = singleAwaitingRequest(request) + // log.debug(s"response: ${response.toString}") + + response.status should be(StatusCodes.OK) + + val receivedListInfo: ListRootNodeInfoADM = + AkkaHttpUtils.httpResponseToJson(response).fields("listinfo").convertTo[ListRootNodeInfoADM] + + val expectedListInfo: ListRootNodeInfoADM = SharedListsTestDataADM.treeListInfo + + receivedListInfo.sorted should be(expectedListInfo.sorted) + + clientTestDataCollector.addFile( + TestDataFileContent( + filePath = TestDataFilePath( + directoryPath = clientTestDataPath, + filename = "get-list-info-response-new-merged-get-route", + fileExtension = "json" + ), + text = responseToString(response) + ) + ) + } + "return a complete list" in { val request = Get( baseApiUrl + s"/admin/lists/http%3A%2F%2Frdfh.ch%2Flists%2F0001%2FtreeList" @@ -222,7 +249,7 @@ class OldListsRouteADMFeatureE2ESpec ) } - "return node info without children" in { + "return node info w/o children" in { val request = Get( baseApiUrl + s"/admin/lists/nodes/http%3A%2F%2Frdfh.ch%2Flists%2F0001%2FtreeList01" ) ~> addCredentials(rootCreds.basicHttpCredentials) @@ -276,7 +303,6 @@ class OldListsRouteADMFeatureE2ESpec } "given a custom Iri" should { - "create a list with the provided custom Iri" in { val createListWithCustomIriRequest: String = s"""{ @@ -325,7 +351,6 @@ class OldListsRouteADMFeatureE2ESpec } "return a DuplicateValueException during list creation when the supplied list IRI is not unique" in { - // duplicate list IRI val params = s""" @@ -351,7 +376,6 @@ class OldListsRouteADMFeatureE2ESpec } "add a child with a custom IRI" in { - val createChildNodeWithCustomIriRequest = s""" |{ "id": "$customChildNodeIRI", @@ -407,7 +431,6 @@ class OldListsRouteADMFeatureE2ESpec } "used to modify list information" should { - val newListIri = new MutableTestIri val firstChildIri = new MutableTestIri val secondChildIri = new MutableTestIri @@ -488,7 +511,6 @@ class OldListsRouteADMFeatureE2ESpec } "return a BadRequestException during list creation when payload is not correct" in { - // no project IRI val params01 = s""" @@ -537,7 +559,6 @@ class OldListsRouteADMFeatureE2ESpec } "update basic list information" in { - val updateListInfo: String = s"""{ | "listIri": "${newListIri.get}", @@ -636,7 +657,6 @@ class OldListsRouteADMFeatureE2ESpec } "update basic list information with repeated comment and label in different languages" in { - val updateListInfoWithRepeatedCommentAndLabelValuesRequest: String = s"""{ | "listIri": "http://rdfh.ch/lists/0001/treeList", @@ -720,7 +740,6 @@ class OldListsRouteADMFeatureE2ESpec } "return a BadRequestException during list change when payload is not correct" in { - val encodedListUrl = java.net.URLEncoder.encode(newListIri.get, "utf-8") // empty list IRI @@ -783,7 +802,6 @@ class OldListsRouteADMFeatureE2ESpec } "add child to list - to the root node" in { - val encodedListUrl = java.net.URLEncoder.encode(newListIri.get, "utf-8") val name = "first" @@ -856,7 +874,6 @@ class OldListsRouteADMFeatureE2ESpec } "add second child to list - to the root node" in { - val encodedListUrl = java.net.URLEncoder.encode(newListIri.get, "utf-8") val name = "second" @@ -929,7 +946,6 @@ class OldListsRouteADMFeatureE2ESpec } "insert new child in a specific position" in { - val encodedListUrl = java.net.URLEncoder.encode(newListIri.get, "utf-8") val name = "child with position" @@ -1006,7 +1022,6 @@ class OldListsRouteADMFeatureE2ESpec } "add child to second child node" in { - val encodedListUrl = java.net.URLEncoder.encode(secondChildIri.get, "utf-8") val name = "third" diff --git a/webapi/src/test/scala/org/knora/webapi/e2e/admin/lists/UpdateListItemsRouteADME2ESpec.scala b/webapi/src/test/scala/org/knora/webapi/e2e/admin/lists/UpdateListItemsRouteADME2ESpec.scala index 59e802d7ba..a3eb7f2541 100644 --- a/webapi/src/test/scala/org/knora/webapi/e2e/admin/lists/UpdateListItemsRouteADME2ESpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/e2e/admin/lists/UpdateListItemsRouteADME2ESpec.scala @@ -207,6 +207,7 @@ class UpdateListItemsRouteADME2ESpec ) ) } + "not delete root node comments" in { val deleteComments = s"""{ @@ -219,16 +220,8 @@ class UpdateListItemsRouteADME2ESpec HttpEntity(ContentTypes.`application/json`, deleteComments) ) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) val response: HttpResponse = singleAwaitingRequest(request) - log.debug(s"response: ${response.toString}") - response.status should be(StatusCodes.OK) - val receivedListInfo: ListRootNodeInfoADM = - AkkaHttpUtils.httpResponseToJson(response).fields("listinfo").convertTo[ListRootNodeInfoADM] - - receivedListInfo.projectIri should be(SharedTestDataADM.ANYTHING_PROJECT_IRI) - - val comments: Seq[StringLiteralV2] = receivedListInfo.comments.stringLiterals - comments.size should be(1) - comments should contain(StringLiteralV2(value = "nya kommentarer", language = Some("se"))) +// log.debug(s"response: ${response.toString}") + response.status should be(StatusCodes.BadRequest) } } @@ -377,7 +370,7 @@ class UpdateListItemsRouteADME2ESpec TestDataFileContent( filePath = TestDataFilePath( directoryPath = clientTestDataPath, - filename = "update-childNode-comments-request", + filename = "not-update-childNode-comments-request", fileExtension = "json" ), text = deleteNodeComments @@ -392,12 +385,7 @@ class UpdateListItemsRouteADME2ESpec ) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) val response: HttpResponse = singleAwaitingRequest(request) - response.status should be(StatusCodes.OK) - - val receivedNodeInfo: ListChildNodeInfoADM = - AkkaHttpUtils.httpResponseToJson(response).fields("nodeinfo").convertTo[ListChildNodeInfoADM] - val comments: Seq[StringLiteralV2] = receivedNodeInfo.comments.stringLiterals - comments.size should be(1) + response.status should be(StatusCodes.BadRequest) } "not update the position of a node if given IRI is invalid" in { diff --git a/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/groupsmessages/BUILD.bazel b/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/groupsmessages/BUILD.bazel deleted file mode 100644 index f239bbb734..0000000000 --- a/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/groupsmessages/BUILD.bazel +++ /dev/null @@ -1,22 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load("@io_bazel_rules_scala//scala:scala.bzl", "scala_test") -load("//third_party:dependencies.bzl", "ALL_WEBAPI_MAIN_DEPENDENCIES", "BASE_TEST_DEPENDENCIES_WITH_JSON") - -scala_test( - name = "GroupsMessagesADMSpec", - size = "small", # 60s - srcs = [ - "GroupsMessagesADMSpec.scala", - ], - data = [ - "//knora-ontologies", - "//test_data", - ], - jvm_flags = ["-Dconfig.resource=fuseki.conf"], - # unused_dependency_checker_mode = "warn", - deps = ALL_WEBAPI_MAIN_DEPENDENCIES + [ - "//webapi:main_library", - "//webapi:test_library", - ] + BASE_TEST_DEPENDENCIES_WITH_JSON, -) diff --git a/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupsMessagesADMSpec.scala b/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupsMessagesADMSpec.scala deleted file mode 100644 index 5c65069587..0000000000 --- a/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupsMessagesADMSpec.scala +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright © 2021 Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.knora.webapi.messages.admin.responder.groupsmessages - -import com.typesafe.config.ConfigFactory -import org.knora.webapi.CoreSpec -import org.knora.webapi.exceptions.BadRequestException -import org.knora.webapi.messages.store.triplestoremessages.StringLiteralV2 -import org.knora.webapi.sharedtestdata.SharedTestDataADM - -object GroupsMessagesADMSpec { - val config = ConfigFactory.parseString(""" - akka.loglevel = "DEBUG" - akka.stdout-loglevel = "DEBUG" - """.stripMargin) -} - -/** - * This spec is used to test 'GroupAdminMessages'. - */ -class GroupsMessagesADMSpec extends CoreSpec(GroupsMessagesADMSpec.config) { - - "The CreateGroupsApiRequestADM case class" should { - - "return 'BadRequest' if the supplied 'id' is not a valid IRI" in { - - val caught = intercept[BadRequestException]( - CreateGroupApiRequestADM( - id = Some("invalid-group-IRI"), - name = "NewGroupWithInvalidCustomIri", - descriptions = Seq(StringLiteralV2("A new group created with an invalid custom IRI")), - project = SharedTestDataADM.IMAGES_PROJECT_IRI, - status = true, - selfjoin = false - ) - ) - assert(caught.getMessage === "Invalid group IRI") - } - } -} diff --git a/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListsMessagesADMSpec.scala b/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListsMessagesADMSpec.scala index fdd2827ccc..e5aef62631 100644 --- a/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListsMessagesADMSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListsMessagesADMSpec.scala @@ -5,17 +5,18 @@ package org.knora.webapi.messages.admin.responder.listsmessages -import java.util.UUID import com.typesafe.config.ConfigFactory import org.knora.webapi.CoreSpec import org.knora.webapi.exceptions.BadRequestException +import org.knora.webapi.messages.admin.responder.listsmessages.ListNodeCreatePayloadADM.ListChildNodeCreatePayloadADM import org.knora.webapi.messages.admin.responder.listsmessages.ListsErrorMessagesADM._ -import org.knora.webapi.messages.admin.responder.listsmessages.NodeCreatePayloadADM.ChildNodeCreatePayloadADM -import org.knora.webapi.messages.admin.responder.valueObjects.{Comments, Labels, ListIRI, ProjectIRI, Position} +import org.knora.webapi.messages.admin.responder.valueObjects._ import org.knora.webapi.messages.store.triplestoremessages.{StringLiteralSequenceV2, StringLiteralV2} import org.knora.webapi.sharedtestdata.{SharedListsTestDataADM, SharedTestDataADM} import spray.json._ +import java.util.UUID + object ListsMessagesADMSpec { val config = ConfigFactory.parseString(""" akka.loglevel = "DEBUG" @@ -27,11 +28,9 @@ object ListsMessagesADMSpec { * This spec is used to test 'ListAdminMessages'. */ class ListsMessagesADMSpec extends CoreSpec(ListsMessagesADMSpec.config) with ListADMJsonProtocol { - val exampleListIri = "http://rdfh.ch/lists/00FF/abcd" "Conversion from case class to JSON and back" should { - "work for a 'ListRootNodeInfoADM'" in { val listInfo = ListRootNodeInfoADM( @@ -59,7 +58,6 @@ class ListsMessagesADMSpec extends CoreSpec(ListsMessagesADMSpec.config) with Li } "work for a 'ListChildNodeInfoADM'" in { - val listNodeInfo = ListChildNodeInfoADM( id = "http://rdfh.ch/lists/00FF/526f26ed04", name = Some("sommer"), @@ -79,7 +77,6 @@ class ListsMessagesADMSpec extends CoreSpec(ListsMessagesADMSpec.config) with Li } "work for a 'ListChildNodeADM'" in { - val listNode: ListNodeADM = ListChildNodeADM( id = "http://rdfh.ch/lists/00FF/526f26ed04", name = Some("sommer"), @@ -100,7 +97,6 @@ class ListsMessagesADMSpec extends CoreSpec(ListsMessagesADMSpec.config) with Li } "work for a 'ListADM'" in { - val listInfo = SharedListsTestDataADM.treeListInfo val children = SharedListsTestDataADM.treeListChildNodes @@ -116,7 +112,6 @@ class ListsMessagesADMSpec extends CoreSpec(ListsMessagesADMSpec.config) with Li } "work for a 'NodeADM'" in { - val nodeInfo = SharedListsTestDataADM.summerNodeInfo val children = Seq.empty[ListChildNodeADM] @@ -129,53 +124,20 @@ class ListsMessagesADMSpec extends CoreSpec(ListsMessagesADMSpec.config) with Li converted.children should be(children) } - "throw 'BadRequestException' for `CreateNodeApiRequestADM` when value of a label is missing" in { - - val payload = - s""" - |{ - | "projectIri": "${SharedTestDataADM.IMAGES_PROJECT_IRI}", - | "labels": [{ "value": "Neuer List Node", "language": "de"}, { "value": "", "language": "en"}], - | "comments": [] - |} - """.stripMargin - - val thrown = the[BadRequestException] thrownBy payload.parseJson.convertTo[CreateNodeApiRequestADM] - - thrown.getMessage should equal("String value is missing.") - } - - "throw 'BadRequestException' for `CreateNodeApiRequestADM` when value of a comment is missing" in { - - val payload = - s""" - |{ - | "parentNodeIri": "$exampleListIri", - | "projectIri": "${SharedTestDataADM.IMAGES_PROJECT_IRI}", - | "labels": [{ "value": "Neuer List Node", "language": "de"}], - | "comments": [{ "value": "", "language": "de"}] - |} - """.stripMargin - - val thrown = the[BadRequestException] thrownBy payload.parseJson.convertTo[CreateNodeApiRequestADM] - - thrown.getMessage should equal("String value is missing.") - } - "throw 'BadRequestException' if invalid position given in payload of `createChildNodeRequest`" in { val caught = intercept[BadRequestException]( ListChildNodeCreateRequestADM( - createChildNodeRequest = ChildNodeCreatePayloadADM( - parentNodeIri = Some(ListIRI.create(exampleListIri).fold(e => throw e, v => v)), - projectIri = ProjectIRI.create(SharedTestDataADM.IMAGES_PROJECT_IRI).fold(e => throw e, v => v), - position = Some(Position.create(-3).fold(e => throw e, v => v)), + createChildNodeRequest = ListChildNodeCreatePayloadADM( + parentNodeIri = ListIRI.make(exampleListIri).fold(e => throw e.head, v => v), + projectIri = ProjectIRI.make(SharedTestDataADM.IMAGES_PROJECT_IRI).fold(e => throw e.head, v => v), + position = Some(Position.make(-3).fold(e => throw e.head, v => v)), labels = Labels - .create(Seq(StringLiteralV2(value = "New child node", language = Some("en")))) - .fold(e => throw e, v => v), + .make(Seq(StringLiteralV2(value = "New child node", language = Some("en")))) + .fold(e => throw e.head, v => v), comments = Some( Comments - .create(Seq(StringLiteralV2(value = "New child comment", language = Some("en")))) - .fold(e => throw e, v => v) + .make(Seq(StringLiteralV2(value = "New child comment", language = Some("en")))) + .fold(e => throw e.head, v => v) ) ), featureFactoryConfig = defaultFeatureFactoryConfig, @@ -187,7 +149,6 @@ class ListsMessagesADMSpec extends CoreSpec(ListsMessagesADMSpec.config) with Li } "throw 'BadRequestException' for `ChangeNodePositionApiRequestADM` when no parent node iri is given" in { - val payload = s""" |{ @@ -199,7 +160,6 @@ class ListsMessagesADMSpec extends CoreSpec(ListsMessagesADMSpec.config) with Li val thrown = the[BadRequestException] thrownBy payload.parseJson.convertTo[ChangeNodePositionApiRequestADM] thrown.getMessage should equal("IRI of parent node is missing.") - } "throw 'BadRequestException' for `ChangeNodePositionApiRequestADM` when parent node IRI is invalid" in { @@ -215,11 +175,9 @@ class ListsMessagesADMSpec extends CoreSpec(ListsMessagesADMSpec.config) with Li val thrown = the[BadRequestException] thrownBy payload.parseJson.convertTo[ChangeNodePositionApiRequestADM] thrown.getMessage should equal(s"Invalid IRI is given: $invalid_parentIri.") - } "throw 'BadRequestException' for `ChangeNodePositionApiRequestADM` when position is invalid" in { - val payload = s""" |{ @@ -231,7 +189,6 @@ class ListsMessagesADMSpec extends CoreSpec(ListsMessagesADMSpec.config) with Li val thrown = the[BadRequestException] thrownBy payload.parseJson.convertTo[ChangeNodePositionApiRequestADM] thrown.getMessage should equal(INVALID_POSITION) - } } } diff --git a/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/valueObjects/BUILD.bazel b/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/valueObjects/BUILD.bazel index da363f069d..e366db4100 100644 --- a/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/valueObjects/BUILD.bazel +++ b/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/valueObjects/BUILD.bazel @@ -7,6 +7,8 @@ scala_test( name = "ValueObjectsADMSpec", size = "small", # 60s srcs = [ + "GroupsValueObjectsADMSpec.scala", + "IriValueObjectsADMSpec.scala", "ListsValueObjectsADMSpec.scala", "ValueObjectsADMSpec.scala", ], diff --git a/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/valueObjects/GroupsValueObjectsADMSpec.scala b/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/valueObjects/GroupsValueObjectsADMSpec.scala new file mode 100644 index 0000000000..2ed1bebce6 --- /dev/null +++ b/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/valueObjects/GroupsValueObjectsADMSpec.scala @@ -0,0 +1,112 @@ +/* + * Copyright © 2021 Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.knora.webapi.messages.admin.responder.valueObjects + +import org.knora.webapi.UnitSpec +import org.knora.webapi.exceptions.BadRequestException +import org.knora.webapi.messages.admin.responder.groupsmessages.GroupsErrorMessagesADM._ +import org.knora.webapi.messages.store.triplestoremessages.StringLiteralV2 +import zio.prelude.Validation + +/** + * This spec is used to test the [[GroupsValueObjectsADM]] value objects creation. + */ +class GroupsValueObjectsADMSpec extends UnitSpec(ValueObjectsADMSpec.config) { + "GroupIRI value object" when { + val validGroupIri = "http://rdfh.ch/groups/0803/qBCJAdzZSCqC_2snW5Q7Nw" + + "created using empty value" should { + "throw BadRequestException" in { + GroupIRI.make("") should equal(Validation.fail(BadRequestException(GROUP_IRI_MISSING_ERROR))) + } + } + "created using invalid value" should { + "throw BadRequestException" in { + GroupIRI.make("not a group IRI") should equal(Validation.fail(BadRequestException(GROUP_IRI_INVALID_ERROR))) + } + } + "created using valid value" should { + "not throw BadRequestException" in { + GroupIRI.make(validGroupIri) should not equal Validation.fail(BadRequestException(GROUP_IRI_INVALID_ERROR)) + } + "return value passed to value object" in { + GroupIRI.make(validGroupIri).toOption.get.value should equal(validGroupIri) + } + } + } + + "GroupName value object" when { + val validGroupName = "Valid group name" + + "created using empty value" should { + "throw BadRequestException" in { + GroupName.make("") should equal(Validation.fail(BadRequestException(GROUP_NAME_MISSING_ERROR))) + } + } + "created using invalid value" should { + "throw BadRequestException" in { + GroupName.make("Invalid group name\r") should equal( + Validation.fail(BadRequestException(GROUP_NAME_INVALID_ERROR)) + ) + } + } + "created using valid value" should { + "not throw BadRequestExceptions" in { + GroupName.make(validGroupName) should not equal Validation.fail(BadRequestException(GROUP_NAME_INVALID_ERROR)) + } + "return value passed to value object" in { + GroupName.make(validGroupName).toOption.get.value should equal(validGroupName) + } + } + } + + "GroupDescriptions value object" when { + val validDescription = Seq(StringLiteralV2(value = "Valid description", language = Some("en"))) + val invalidDescription = Seq(StringLiteralV2(value = "Invalid description \r", language = Some("en"))) + + "created using empty value" should { + "throw BadRequestException" in { + GroupDescriptions.make(Seq.empty) should equal( + Validation.fail(BadRequestException(GROUP_DESCRIPTION_MISSING_ERROR)) + ) + } + } + "created using invalid value" should { + "throw BadRequestException" in { + GroupDescriptions.make(invalidDescription) should equal( + Validation.fail(BadRequestException(GROUP_DESCRIPTION_INVALID_ERROR)) + ) + } + } + "created using valid value" should { + "not throw BadRequestExceptions" in { + GroupDescriptions.make(validDescription).toOption.get.value should not equal + BadRequestException(GROUP_DESCRIPTION_INVALID_ERROR) + } + "return value passed to value object" in { + GroupDescriptions.make(validDescription).toOption.get.value should equal(validDescription) + } + } + } + + "GroupStatus value object" when { + "created using valid value" should { + "return value passed to value object" in { + GroupStatus.make(true).toOption.get.value should equal(true) + GroupStatus.make(false).toOption.get.value should equal(false) + } + } + } + + "GroupSelfJoin value object" when { + "created using valid value" should { + "return value passed to value object" in { + GroupSelfJoin.make(false).toOption.get.value should equal(false) + GroupSelfJoin.make(true).toOption.get.value should equal(true) + } + } + } +} diff --git a/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/valueObjects/IriValueObjectsADMSpec.scala b/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/valueObjects/IriValueObjectsADMSpec.scala new file mode 100644 index 0000000000..1e55be100d --- /dev/null +++ b/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/valueObjects/IriValueObjectsADMSpec.scala @@ -0,0 +1,44 @@ +/* + * Copyright © 2021 Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.knora.webapi.messages.admin.responder.valueObjects + +import org.knora.webapi.UnitSpec +import org.knora.webapi.exceptions.BadRequestException +import org.knora.webapi.messages.admin.responder.listsmessages.ListsErrorMessagesADM._ +import zio.prelude.Validation + +/** + * This spec is used to test the [[IriValueObjectsADM]] value objects creation. + */ +class IriValueObjectsADMSpec extends UnitSpec(ValueObjectsADMSpec.config) { + "ProjectIRI value object" when { +// TODO: check string formatter project iri validation because passing just "http://rdfh.ch/projects/@@@@@@" works + val validProjectIri = "http://rdfh.ch/projects/0001" + + "created using empty value" should { + "throw BadRequestException" in { + ProjectIRI.make("") should equal(Validation.fail(BadRequestException(PROJECT_IRI_MISSING_ERROR))) + } + } + "created using invalid value" should { + "throw BadRequestException" in { + ProjectIRI.make("not a project IRI") should equal( + Validation.fail(BadRequestException(PROJECT_IRI_INVALID_ERROR)) + ) + } + } + "created using valid value" should { + "not throw BadRequestExceptions" in { + ProjectIRI.make(validProjectIri) should not equal Validation.fail( + BadRequestException(PROJECT_IRI_INVALID_ERROR) + ) + } + "return value passed to value object" in { + ProjectIRI.make(validProjectIri).toOption.get.value should equal(validProjectIri) + } + } + } +} diff --git a/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/valueObjects/ListsValueObjectsADMSpec.scala b/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/valueObjects/ListsValueObjectsADMSpec.scala index 3b314ec0f4..6b3fdb9fa0 100644 --- a/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/valueObjects/ListsValueObjectsADMSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/valueObjects/ListsValueObjectsADMSpec.scala @@ -1,104 +1,62 @@ /* - * Copyright © 2015-2021 Data and Service Center for the Humanities (DaSCH) - * - * This file is part of Knora. - * - * Knora is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Knora is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public - * License along with Knora. If not, see . + * Copyright © 2021 Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. + * SPDX-License-Identifier: Apache-2.0 */ package org.knora.webapi.messages.admin.responder.valueObjects +import org.knora.webapi.UnitSpec import org.knora.webapi.exceptions.BadRequestException -import org.knora.webapi.messages.admin.responder.listsmessages.ListsErrorMessagesADM.{ - COMMENT_INVALID_ERROR, - COMMENT_MISSING_ERROR, - INVALID_POSITION, - LABEL_INVALID_ERROR, - LABEL_MISSING_ERROR, - LIST_NAME_INVALID_ERROR, - LIST_NAME_MISSING_ERROR, - LIST_NODE_IRI_INVALID_ERROR, - LIST_NODE_IRI_MISSING_ERROR, - PROJECT_IRI_INVALID_ERROR, - PROJECT_IRI_MISSING_ERROR -} +import org.knora.webapi.messages.admin.responder.listsmessages.ListsErrorMessagesADM._ import org.knora.webapi.messages.store.triplestoremessages.StringLiteralV2 -import org.knora.webapi.UnitSpec +import zio.prelude.Validation /** - * This spec is used to test the creation of value objects of the [[ListsValueObjectsADM]]. + * This spec is used to test the [[ListsValueObjectsADM]] value objects creation. */ class ListsValueObjectsADMSpec extends UnitSpec(ValueObjectsADMSpec.config) { - "ListIRI value object" when { val validListIri = "http://rdfh.ch/lists/0803/qBCJAdzZSCqC_2snW5Q7Nw" "created using empty value" should { "throw BadRequestException" in { - ListIRI.create("") should equal(Left(BadRequestException(LIST_NODE_IRI_MISSING_ERROR))) + ListIRI.make("") should equal(Validation.fail(BadRequestException(LIST_NODE_IRI_MISSING_ERROR))) } } "created using invalid value" should { "throw BadRequestException" in { - ListIRI.create("not a list IRI") should equal(Left(BadRequestException(LIST_NODE_IRI_INVALID_ERROR))) + ListIRI.make("not a list IRI") should equal(Validation.fail(BadRequestException(LIST_NODE_IRI_INVALID_ERROR))) } } "created using valid value" should { "return value object that value equals to the value used to its creation" in { - ListIRI.create(validListIri) should not equal Left(BadRequestException(LIST_NODE_IRI_INVALID_ERROR)) - } - } - } - - "ProjectIRI value object" when { -// TODO: check string formatter project iri validation because passing just "http://rdfh.ch/projects/@@@@@@" works - val validProjectIri = "http://rdfh.ch/projects/0001" - - "created using empty value" should { - "throw BadRequestException" in { - ProjectIRI.create("") should equal(Left(BadRequestException(PROJECT_IRI_MISSING_ERROR))) + ListIRI.make(validListIri) should not equal Validation.fail(BadRequestException(LIST_NODE_IRI_INVALID_ERROR)) } - } - "created using invalid value" should { - "throw BadRequestException" in { - ProjectIRI.create("not a project IRI") should equal(Left(BadRequestException(PROJECT_IRI_INVALID_ERROR))) - } - } - "created using valid value" should { - "not throw BadRequestExceptions" in { - ProjectIRI.create(validProjectIri) should not equal Left(BadRequestException(PROJECT_IRI_INVALID_ERROR)) + "return value passed to value object" in { + ListIRI.make(validListIri).toOption.get.value should equal(validListIri) } } } "ListName value object" when { - val validListName = "It's valid list name example" + val validListName = "Valid list name" "created using empty value" should { "throw BadRequestException" in { - ListName.create("") should equal(Left(BadRequestException(LIST_NAME_MISSING_ERROR))) + ListName.make("") should equal(Validation.fail(BadRequestException(LIST_NAME_MISSING_ERROR))) } } "created using invalid value" should { "throw BadRequestException" in { -// TODO: should this: "\"It's invalid list name example\"" pass? Same for comments and labels - ListName.create("\r") should equal(Left(BadRequestException(LIST_NAME_INVALID_ERROR))) + ListName.make("\r") should equal(Validation.fail(BadRequestException(LIST_NAME_INVALID_ERROR))) } } "created using valid value" should { "not throw BadRequestExceptions" in { - ListName.create(validListName) should not equal Left(BadRequestException(LIST_NAME_INVALID_ERROR)) + ListName.make(validListName) should not equal Validation.fail(BadRequestException(LIST_NAME_INVALID_ERROR)) + } + "return value passed to value object" in { + ListName.make(validListName).toOption.get.value should equal(validListName) } } } @@ -108,12 +66,15 @@ class ListsValueObjectsADMSpec extends UnitSpec(ValueObjectsADMSpec.config) { "created using invalid value" should { "throw BadRequestException" in { - Position.create(-2) should equal(Left(BadRequestException(INVALID_POSITION))) + Position.make(-2) should equal(Validation.fail(BadRequestException(INVALID_POSITION))) } } "created using valid value" should { "not throw BadRequestExceptions" in { - Position.create(validPosition) should not equal Left(BadRequestException(INVALID_POSITION)) + Position.make(validPosition) should not equal Validation.fail(BadRequestException(INVALID_POSITION)) + } + "return value passed to value object" in { + Position.make(validPosition).toOption.get.value should equal(validPosition) } } } @@ -124,38 +85,44 @@ class ListsValueObjectsADMSpec extends UnitSpec(ValueObjectsADMSpec.config) { "created using empty value" should { "throw BadRequestException" in { - Labels.create(Seq.empty) should equal(Left(BadRequestException(LABEL_MISSING_ERROR))) + Labels.make(Seq.empty) should equal(Validation.fail(BadRequestException(LABEL_MISSING_ERROR))) } } "created using invalid value" should { "throw BadRequestException" in { - Labels.create(invalidLabels) should equal(Left(BadRequestException(LABEL_INVALID_ERROR))) + Labels.make(invalidLabels) should equal(Validation.fail(BadRequestException(LABEL_INVALID_ERROR))) } } "created using valid value" should { "not throw BadRequestExceptions" in { - Labels.create(validLabels) should not equal Left(BadRequestException(LABEL_INVALID_ERROR)) + Labels.make(validLabels) should not equal Validation.fail(BadRequestException(LABEL_INVALID_ERROR)) + } + "return value passed to value object" in { + Labels.make(validLabels).toOption.get.value should equal(validLabels) } } } "Comments value object" when { - val validComments = Seq(StringLiteralV2(value = "New Comment", language = Some("en"))) - val invalidComments = Seq(StringLiteralV2(value = "\r", language = Some("en"))) + val validComments = Seq(StringLiteralV2(value = "Valid comment", language = Some("en"))) + val invalidComments = Seq(StringLiteralV2(value = "Invalid comment \r", language = Some("en"))) "created using empty value" should { "throw BadRequestException" in { - Comments.create(Seq.empty) should equal(Left(BadRequestException(COMMENT_MISSING_ERROR))) + Comments.make(Seq.empty) should equal(Validation.fail(BadRequestException(COMMENT_MISSING_ERROR))) } } "created using invalid value" should { "throw BadRequestException" in { - Comments.create(invalidComments) should equal(Left(BadRequestException(COMMENT_INVALID_ERROR))) + Comments.make(invalidComments) should equal(Validation.fail(BadRequestException(COMMENT_INVALID_ERROR))) } } "created using valid value" should { "not throw BadRequestExceptions" in { - Comments.create(validComments) should not equal Left(BadRequestException(COMMENT_INVALID_ERROR)) + Comments.make(validComments) should not equal Validation.fail(BadRequestException(COMMENT_INVALID_ERROR)) + } + "return value passed to value object" in { + Comments.make(validComments).toOption.get.value should equal(validComments) } } } diff --git a/webapi/src/test/scala/org/knora/webapi/responders/admin/GroupsResponderADMSpec.scala b/webapi/src/test/scala/org/knora/webapi/responders/admin/GroupsResponderADMSpec.scala index 3a0bf22f04..89f04045dc 100644 --- a/webapi/src/test/scala/org/knora/webapi/responders/admin/GroupsResponderADMSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/responders/admin/GroupsResponderADMSpec.scala @@ -9,7 +9,6 @@ */ package org.knora.webapi.responders.admin -import java.util.UUID import akka.actor.Status.Failure import akka.testkit.ImplicitSender import com.typesafe.config.{Config, ConfigFactory} @@ -17,11 +16,12 @@ import org.knora.webapi._ import org.knora.webapi.exceptions.{BadRequestException, DuplicateValueException, NotFoundException} import org.knora.webapi.messages.admin.responder.groupsmessages._ import org.knora.webapi.messages.admin.responder.usersmessages.UserInformationTypeADM -import org.knora.webapi.messages.admin.responder.valueObjects.{Description, Name, Selfjoin, Status} +import org.knora.webapi.messages.admin.responder.valueObjects._ import org.knora.webapi.messages.store.triplestoremessages.StringLiteralV2 import org.knora.webapi.sharedtestdata.SharedTestDataADM import org.knora.webapi.util.MutableTestIri +import java.util.UUID import scala.concurrent.duration._ object GroupsResponderADMSpec { @@ -36,16 +36,12 @@ object GroupsResponderADMSpec { * This spec is used to test the messages received by the [[org.knora.webapi.responders.admin.UsersResponderADM]] actor. */ class GroupsResponderADMSpec extends CoreSpec(GroupsResponderADMSpec.config) with ImplicitSender { - private val timeout = 5.seconds - private val imagesProject = SharedTestDataADM.imagesProject private val imagesReviewerGroup = SharedTestDataADM.imagesReviewerGroup - private val rootUser = SharedTestDataADM.rootUser "The GroupsResponder " when { - "asked about all groups" should { "return a list" in { responderManager ! GroupsGetRequestADM( @@ -69,6 +65,7 @@ class GroupsResponderADMSpec extends CoreSpec(GroupsResponderADMSpec.config) wit expectMsg(GroupGetResponseADM(imagesReviewerGroup)) } + "return 'NotFoundException' when the group is unknown " in { responderManager ! GroupGetRequestADM( groupIri = "http://rdfh.ch/groups/notexisting", @@ -83,24 +80,23 @@ class GroupsResponderADMSpec extends CoreSpec(GroupsResponderADMSpec.config) wit } "used to modify group information" should { - val newGroupIri = new MutableTestIri "CREATE the group and return the group's info if the supplied group name is unique" in { responderManager ! GroupCreateRequestADM( - createRequest = GroupCreatePayloadADM.create( + createRequest = GroupCreatePayloadADM( id = None, - name = Name.create("NewGroup").fold(e => throw e, v => v), - descriptions = Description + name = GroupName.make("NewGroup").fold(e => throw e.head, v => v), + descriptions = GroupDescriptions .make( Seq( StringLiteralV2(value = """NewGroupDescription with "quotes" and """, language = Some("en")) ) ) .fold(e => throw e.head, v => v), - project = SharedTestDataADM.IMAGES_PROJECT_IRI, - status = Status.make(true).fold(e => throw e.head, v => v), - selfjoin = Selfjoin.make(false).fold(e => throw e.head, v => v) + project = ProjectIRI.make(SharedTestDataADM.IMAGES_PROJECT_IRI).fold(e => throw e.head, v => v), + status = GroupStatus.make(true).fold(e => throw e.head, v => v), + selfjoin = GroupSelfJoin.make(false).fold(e => throw e.head, v => v) ), featureFactoryConfig = defaultFeatureFactoryConfig, requestingUser = SharedTestDataADM.imagesUser01, @@ -124,15 +120,17 @@ class GroupsResponderADMSpec extends CoreSpec(GroupsResponderADMSpec.config) wit "return a 'DuplicateValueException' if the supplied group name is not unique" in { responderManager ! GroupCreateRequestADM( - createRequest = GroupCreatePayloadADM.create( - id = Some(imagesReviewerGroup.id), - name = Name.create("NewGroup").fold(e => throw e, v => v), - descriptions = Description + createRequest = GroupCreatePayloadADM( + id = Some( + GroupIRI.make(imagesReviewerGroup.id).fold(e => throw e.head, v => v) + ), + name = GroupName.make("NewGroup").fold(e => throw e.head, v => v), + descriptions = GroupDescriptions .make(Seq(StringLiteralV2(value = "NewGroupDescription", language = Some("en")))) .fold(e => throw e.head, v => v), - project = SharedTestDataADM.IMAGES_PROJECT_IRI, - status = Status.make(true).fold(e => throw e.head, v => v), - selfjoin = Selfjoin.make(false).fold(e => throw e.head, v => v) + project = ProjectIRI.make(SharedTestDataADM.IMAGES_PROJECT_IRI).fold(e => throw e.head, v => v), + status = GroupStatus.make(true).fold(e => throw e.head, v => v), + selfjoin = GroupSelfJoin.make(false).fold(e => throw e.head, v => v) ), featureFactoryConfig = defaultFeatureFactoryConfig, requestingUser = SharedTestDataADM.imagesUser01, @@ -144,32 +142,17 @@ class GroupsResponderADMSpec extends CoreSpec(GroupsResponderADMSpec.config) wit } } - "return 'BadRequestException' if project IRI are missing" in { - responderManager ! GroupCreateRequestADM( - createRequest = GroupCreatePayloadADM.create( - id = Some(""), - name = Name.create("OtherNewGroup").fold(e => throw e, v => v), - descriptions = Description - .make(Seq(StringLiteralV2(value = "OtherNewGroupDescription", language = Some("en")))) - .fold(e => throw e.head, v => v), - project = "", - status = Status.make(true).fold(e => throw e.head, v => v), - selfjoin = Selfjoin.make(false).fold(e => throw e.head, v => v) - ), - featureFactoryConfig = defaultFeatureFactoryConfig, - requestingUser = SharedTestDataADM.imagesUser01, - apiRequestID = UUID.randomUUID - ) - expectMsg(Failure(BadRequestException("Project IRI cannot be empty"))) - } - "UPDATE a group" in { responderManager ! GroupChangeRequestADM( groupIri = newGroupIri.get, - changeGroupRequest = ChangeGroupApiRequestADM( - Some("UpdatedGroupName"), + changeGroupRequest = GroupUpdatePayloadADM( + Some(GroupName.make("UpdatedGroupName").fold(e => throw e.head, v => v)), Some( - Seq(StringLiteralV2(value = """UpdatedDescription with "quotes" and """, Some("en"))) + GroupDescriptions + .make( + Seq(StringLiteralV2(value = """UpdatedDescription with "quotes" and """, Some("en"))) + ) + .fold(e => throw e.head, v => v) ) ), featureFactoryConfig = defaultFeatureFactoryConfig, @@ -192,9 +175,13 @@ class GroupsResponderADMSpec extends CoreSpec(GroupsResponderADMSpec.config) wit "return 'NotFound' if a not-existing group IRI is submitted during update" in { responderManager ! GroupChangeRequestADM( groupIri = "http://rdfh.ch/groups/notexisting", - ChangeGroupApiRequestADM( - Some("UpdatedGroupName"), - Some(Seq(StringLiteralV2(value = "UpdatedDescription", language = Some("en")))) + GroupUpdatePayloadADM( + Some(GroupName.make("UpdatedGroupName").fold(e => throw e.head, v => v)), + Some( + GroupDescriptions + .make(Seq(StringLiteralV2(value = "UpdatedDescription", language = Some("en")))) + .fold(e => throw e.head, v => v) + ) ), featureFactoryConfig = defaultFeatureFactoryConfig, requestingUser = SharedTestDataADM.imagesUser01, @@ -209,9 +196,13 @@ class GroupsResponderADMSpec extends CoreSpec(GroupsResponderADMSpec.config) wit "return 'BadRequest' if the new group name already exists inside the project" in { responderManager ! GroupChangeRequestADM( groupIri = newGroupIri.get, - changeGroupRequest = ChangeGroupApiRequestADM( - Some("Image reviewer"), - Some(Seq(StringLiteralV2(value = "UpdatedDescription", language = Some("en")))) + changeGroupRequest = GroupUpdatePayloadADM( + Some(GroupName.make("Image reviewer").fold(e => throw e.head, v => v)), + Some( + GroupDescriptions + .make(Seq(StringLiteralV2(value = "UpdatedDescription", language = Some("en")))) + .fold(e => throw e.head, v => v) + ) ), featureFactoryConfig = defaultFeatureFactoryConfig, requestingUser = SharedTestDataADM.imagesUser01, @@ -229,7 +220,6 @@ class GroupsResponderADMSpec extends CoreSpec(GroupsResponderADMSpec.config) wit } "used to query members" should { - "return all members of a group identified by IRI" in { responderManager ! GroupMembersGetRequestADM( groupIri = SharedTestDataADM.imagesReviewerGroup.id, diff --git a/webapi/src/test/scala/org/knora/webapi/responders/admin/ListsResponderADMSpec.scala b/webapi/src/test/scala/org/knora/webapi/responders/admin/ListsResponderADMSpec.scala index ba25405eed..17c3b8e51a 100644 --- a/webapi/src/test/scala/org/knora/webapi/responders/admin/ListsResponderADMSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/responders/admin/ListsResponderADMSpec.scala @@ -5,31 +5,24 @@ package org.knora.webapi.responders.admin -import java.util.UUID import akka.actor.Status.Failure import akka.testkit._ import com.typesafe.config.{Config, ConfigFactory} import org.knora.webapi._ import org.knora.webapi.exceptions.{BadRequestException, DuplicateValueException, UpdateNotPerformedException} import org.knora.webapi.messages.StringFormatter -import org.knora.webapi.messages.admin.responder.listsmessages.NodeCreatePayloadADM.{ - ChildNodeCreatePayloadADM, - ListCreatePayloadADM +import org.knora.webapi.messages.admin.responder.listsmessages.ListNodeCreatePayloadADM.{ + ListChildNodeCreatePayloadADM, + ListRootNodeCreatePayloadADM } import org.knora.webapi.messages.admin.responder.listsmessages._ +import org.knora.webapi.messages.admin.responder.valueObjects._ import org.knora.webapi.messages.store.triplestoremessages.{RdfDataObject, StringLiteralV2} import org.knora.webapi.sharedtestdata.SharedTestDataV1._ import org.knora.webapi.sharedtestdata.{SharedListsTestDataADM, SharedTestDataADM} import org.knora.webapi.util.MutableTestIri -import org.knora.webapi.messages.admin.responder.valueObjects.{ - Comments, - Labels, - ListIRI, - ListName, - Position, - ProjectIRI -} +import java.util.UUID import scala.concurrent.duration._ /** @@ -65,9 +58,7 @@ class ListsResponderADMSpec extends CoreSpec(ListsResponderADMSpec.config) with private val treeListChildNodes: Seq[ListNodeADM] = SharedListsTestDataADM.treeListChildNodes "The Lists Responder" when { - "used to query information about lists" should { - "return all lists" in { responderManager ! ListsGetRequestADM( featureFactoryConfig = defaultFeatureFactoryConfig, @@ -172,16 +163,16 @@ class ListsResponderADMSpec extends CoreSpec(ListsResponderADMSpec.config) with "used to modify lists" should { "create a list" in { - responderManager ! ListCreateRequestADM( - createRootNode = ListCreatePayloadADM( - projectIri = ProjectIRI.create(IMAGES_PROJECT_IRI).fold(e => throw e, v => v), - name = Some(ListName.create("neuelistename").fold(e => throw e, v => v)), + responderManager ! ListRootNodeCreateRequestADM( + createRootNode = ListRootNodeCreatePayloadADM( + projectIri = ProjectIRI.make(IMAGES_PROJECT_IRI).fold(e => throw e.head, v => v), + name = Some(ListName.make("neuelistename").fold(e => throw e.head, v => v)), labels = Labels - .create(Seq(StringLiteralV2(value = "Neue Liste", language = Some("de")))) - .fold(e => throw e, v => v), + .make(Seq(StringLiteralV2(value = "Neue Liste", language = Some("de")))) + .fold(e => throw e.head, v => v), comments = Comments - .create(Seq(StringLiteralV2(value = "Neuer Kommentar", language = Some("de")))) - .fold(e => throw e, v => v) + .make(Seq(StringLiteralV2(value = "Neuer Kommentar", language = Some("de")))) + .fold(e => throw e.head, v => v) ), featureFactoryConfig = defaultFeatureFactoryConfig, requestingUser = SharedTestDataADM.imagesUser01, @@ -213,16 +204,16 @@ class ListsResponderADMSpec extends CoreSpec(ListsResponderADMSpec.config) with val labelWithSpecialCharacter = "Neue \\\"Liste\\\"" val commentWithSpecialCharacter = "Neue \\\"Kommentar\\\"" val nameWithSpecialCharacter = "a new \\\"name\\\"" - responderManager ! ListCreateRequestADM( - createRootNode = ListCreatePayloadADM( - projectIri = ProjectIRI.create(IMAGES_PROJECT_IRI).fold(e => throw e, v => v), - name = Some(ListName.create(nameWithSpecialCharacter).fold(e => throw e, v => v)), + responderManager ! ListRootNodeCreateRequestADM( + createRootNode = ListRootNodeCreatePayloadADM( + projectIri = ProjectIRI.make(IMAGES_PROJECT_IRI).fold(e => throw e.head, v => v), + name = Some(ListName.make(nameWithSpecialCharacter).fold(e => throw e.head, v => v)), labels = Labels - .create(Seq(StringLiteralV2(value = labelWithSpecialCharacter, language = Some("de")))) - .fold(e => throw e, v => v), + .make(Seq(StringLiteralV2(value = labelWithSpecialCharacter, language = Some("de")))) + .fold(e => throw e.head, v => v), comments = Comments - .create(Seq(StringLiteralV2(value = commentWithSpecialCharacter, language = Some("de")))) - .fold(e => throw e, v => v) + .make(Seq(StringLiteralV2(value = commentWithSpecialCharacter, language = Some("de")))) + .fold(e => throw e.head, v => v) ), featureFactoryConfig = defaultFeatureFactoryConfig, requestingUser = SharedTestDataADM.imagesUser01, @@ -254,29 +245,29 @@ class ListsResponderADMSpec extends CoreSpec(ListsResponderADMSpec.config) with "update basic list information" in { val changeNodeInfoRequest = NodeInfoChangeRequestADM( listIri = newListIri.get, - changeNodeRequest = NodeInfoChangePayloadADM( - listIri = ListIRI.create(newListIri.get).fold(e => throw e, v => v), - projectIri = ProjectIRI.create(IMAGES_PROJECT_IRI).fold(e => throw e, v => v), - name = Some(ListName.create("updated name").fold(e => throw e, v => v)), + changeNodeRequest = ListNodeChangePayloadADM( + listIri = ListIRI.make(newListIri.get).fold(e => throw e.head, v => v), + projectIri = ProjectIRI.make(IMAGES_PROJECT_IRI).fold(e => throw e.head, v => v), + name = Some(ListName.make("updated name").fold(e => throw e.head, v => v)), labels = Some( Labels - .create( + .make( Seq( StringLiteralV2(value = "Neue geänderte Liste", language = Some("de")), StringLiteralV2(value = "Changed List", language = Some("en")) ) ) - .fold(e => throw e, v => v) + .fold(e => throw e.head, v => v) ), comments = Some( Comments - .create( + .make( Seq( StringLiteralV2(value = "Neuer Kommentar", language = Some("de")), StringLiteralV2(value = "New Comment", language = Some("en")) ) ) - .fold(e => throw e, v => v) + .fold(e => throw e.head, v => v) ) ), featureFactoryConfig = defaultFeatureFactoryConfig, @@ -310,12 +301,12 @@ class ListsResponderADMSpec extends CoreSpec(ListsResponderADMSpec.config) with } "not update basic list information if name is duplicate" in { - val name = Some(ListName.create("sommer").fold(e => throw e, v => v)) - val projectIRI = ProjectIRI.create(IMAGES_PROJECT_IRI).fold(e => throw e, v => v) + val name = Some(ListName.make("sommer").fold(e => throw e.head, v => v)) + val projectIRI = ProjectIRI.make(IMAGES_PROJECT_IRI).fold(e => throw e.head, v => v) responderManager ! NodeInfoChangeRequestADM( listIri = newListIri.get, - changeNodeRequest = NodeInfoChangePayloadADM( - listIri = ListIRI.create(newListIri.get).fold(e => throw e, v => v), + changeNodeRequest = ListNodeChangePayloadADM( + listIri = ListIRI.make(newListIri.get).fold(e => throw e.head, v => v), projectIri = projectIRI, name = name ), @@ -334,17 +325,17 @@ class ListsResponderADMSpec extends CoreSpec(ListsResponderADMSpec.config) with "add child to list - to the root node" in { responderManager ! ListChildNodeCreateRequestADM( - createChildNodeRequest = ChildNodeCreatePayloadADM( - parentNodeIri = Some(ListIRI.create(newListIri.get).fold(e => throw e, v => v)), - projectIri = ProjectIRI.create(IMAGES_PROJECT_IRI).fold(e => throw e, v => v), - name = Some(ListName.create("first").fold(e => throw e, v => v)), + createChildNodeRequest = ListChildNodeCreatePayloadADM( + parentNodeIri = ListIRI.make(newListIri.get).fold(e => throw e.head, v => v), + projectIri = ProjectIRI.make(IMAGES_PROJECT_IRI).fold(e => throw e.head, v => v), + name = Some(ListName.make("first").fold(e => throw e.head, v => v)), labels = Labels - .create(Seq(StringLiteralV2(value = "New First Child List Node Value", language = Some("en")))) - .fold(e => throw e, v => v), + .make(Seq(StringLiteralV2(value = "New First Child List Node Value", language = Some("en")))) + .fold(e => throw e.head, v => v), comments = Some( Comments - .create(Seq(StringLiteralV2(value = "New First Child List Node Comment", language = Some("en")))) - .fold(e => throw e, v => v) + .make(Seq(StringLiteralV2(value = "New First Child List Node Comment", language = Some("en")))) + .fold(e => throw e.head, v => v) ) ), featureFactoryConfig = defaultFeatureFactoryConfig, @@ -386,18 +377,18 @@ class ListsResponderADMSpec extends CoreSpec(ListsResponderADMSpec.config) with "add second child to list in first position - to the root node" in { responderManager ! ListChildNodeCreateRequestADM( - createChildNodeRequest = ChildNodeCreatePayloadADM( - parentNodeIri = Some(ListIRI.create(newListIri.get).fold(e => throw e, v => v)), - projectIri = ProjectIRI.create(IMAGES_PROJECT_IRI).fold(e => throw e, v => v), - name = Some(ListName.create("second").fold(e => throw e, v => v)), - position = Some(Position.create(0).fold(e => throw e, v => v)), + createChildNodeRequest = ListChildNodeCreatePayloadADM( + parentNodeIri = ListIRI.make(newListIri.get).fold(e => throw e.head, v => v), + projectIri = ProjectIRI.make(IMAGES_PROJECT_IRI).fold(e => throw e.head, v => v), + name = Some(ListName.make("second").fold(e => throw e.head, v => v)), + position = Some(Position.make(0).fold(e => throw e.head, v => v)), labels = Labels - .create(Seq(StringLiteralV2(value = "New Second Child List Node Value", language = Some("en")))) - .fold(e => throw e, v => v), + .make(Seq(StringLiteralV2(value = "New Second Child List Node Value", language = Some("en")))) + .fold(e => throw e.head, v => v), comments = Some( Comments - .create(Seq(StringLiteralV2(value = "New Second Child List Node Comment", language = Some("en")))) - .fold(e => throw e, v => v) + .make(Seq(StringLiteralV2(value = "New Second Child List Node Comment", language = Some("en")))) + .fold(e => throw e.head, v => v) ) ), featureFactoryConfig = defaultFeatureFactoryConfig, @@ -439,17 +430,17 @@ class ListsResponderADMSpec extends CoreSpec(ListsResponderADMSpec.config) with "add child to second child node" in { responderManager ! ListChildNodeCreateRequestADM( - createChildNodeRequest = ChildNodeCreatePayloadADM( - parentNodeIri = Some(ListIRI.create(secondChildIri.get).fold(e => throw e, v => v)), - projectIri = ProjectIRI.create(IMAGES_PROJECT_IRI).fold(e => throw e, v => v), - name = Some(ListName.create("third").fold(e => throw e, v => v)), + createChildNodeRequest = ListChildNodeCreatePayloadADM( + parentNodeIri = ListIRI.make(secondChildIri.get).fold(e => throw e.head, v => v), + projectIri = ProjectIRI.make(IMAGES_PROJECT_IRI).fold(e => throw e.head, v => v), + name = Some(ListName.make("third").fold(e => throw e.head, v => v)), labels = Labels - .create(Seq(StringLiteralV2(value = "New Third Child List Node Value", language = Some("en")))) - .fold(e => throw e, v => v), + .make(Seq(StringLiteralV2(value = "New Third Child List Node Value", language = Some("en")))) + .fold(e => throw e.head, v => v), comments = Some( Comments - .create(Seq(StringLiteralV2(value = "New Third Child List Node Comment", language = Some("en")))) - .fold(e => throw e, v => v) + .make(Seq(StringLiteralV2(value = "New Third Child List Node Comment", language = Some("en")))) + .fold(e => throw e.head, v => v) ) ), featureFactoryConfig = defaultFeatureFactoryConfig, @@ -490,20 +481,20 @@ class ListsResponderADMSpec extends CoreSpec(ListsResponderADMSpec.config) with } "not create a node if given new position is out of range" in { - val givenPosition = Some(Position.create(20).fold(e => throw e, v => v)) + val givenPosition = Some(Position.make(20).fold(e => throw e.head, v => v)) responderManager ! ListChildNodeCreateRequestADM( - createChildNodeRequest = ChildNodeCreatePayloadADM( - parentNodeIri = Some(ListIRI.create(newListIri.get).fold(e => throw e, v => v)), - projectIri = ProjectIRI.create(IMAGES_PROJECT_IRI).fold(e => throw e, v => v), - name = Some(ListName.create("fourth").fold(e => throw e, v => v)), + createChildNodeRequest = ListChildNodeCreatePayloadADM( + parentNodeIri = ListIRI.make(newListIri.get).fold(e => throw e.head, v => v), + projectIri = ProjectIRI.make(IMAGES_PROJECT_IRI).fold(e => throw e.head, v => v), + name = Some(ListName.make("fourth").fold(e => throw e.head, v => v)), position = givenPosition, labels = Labels - .create(Seq(StringLiteralV2(value = "New Fourth Child List Node Value", language = Some("en")))) - .fold(e => throw e, v => v), + .make(Seq(StringLiteralV2(value = "New Fourth Child List Node Value", language = Some("en")))) + .fold(e => throw e.head, v => v), comments = Some( Comments - .create(Seq(StringLiteralV2(value = "New Fourth Child List Node Comment", language = Some("en")))) - .fold(e => throw e, v => v) + .make(Seq(StringLiteralV2(value = "New Fourth Child List Node Comment", language = Some("en")))) + .fold(e => throw e.head, v => v) ) ), featureFactoryConfig = defaultFeatureFactoryConfig,