diff --git a/docs/src/paradox/03-apis/api-admin/lists.md b/docs/src/paradox/03-apis/api-admin/lists.md index 80bcbc9717..2e7d137750 100644 --- a/docs/src/paradox/03-apis/api-admin/lists.md +++ b/docs/src/paradox/03-apis/api-admin/lists.md @@ -23,19 +23,30 @@ License along with Knora. If not, see . **List Operations:** -- `GET: /admin/lists[?projectIri=]` : return all lists optionally filtered by project -- `GET: /admin/lists/` : return complete list with children +- `GET: /admin/lists[?projectIRI=]` : return all lists optionally filtered by project +- `GET: /admin/lists/` : return complete list with children - `POST: /admin/lists` : create new list -- `POST: /admin/lists/` : create new child node under the supplied parent node IRI -- NOT IMPLEMENTED: `DELETE: /admin/lists/` : delete list including children if not used -- `GET: /admin/lists/infos/` : return list information (without children) -- `PUT: /admin/lists/infos/` : update list information +- NOT IMPLEMENTED: `PUT: /admin/lists/` : create new list with given IRI +- NOT IMPLEMENTED: `DELETE: /admin/lists/` : delete list including children if not used +- `GET: /admin/lists//Info` : return list information (without children) +- `PUT: /admin/lists//ListInfoName` : update list name information +- `PUT: /admin/lists//ListInfoLabel` : update list label information +- `PUT: /admin/lists//ListInfoComment` : update list comment information + + **List Node operations** -- `GET: /admin/lists/nodes/` : return list node information (without children) -- NOT IMPLEMENTED: `POST: /admin/lists/nodes/` : update list node information -- NOT IMPLEMENTED: `DELETE: /admin/lists/nodes/` : delete list node including children if not used +- NOT IMPLEMENTED: `GET: /admin/lists/nodes/` : return list node information (with children) +- `GET: /admin/lists/nodes//Info` : return list node information (without children) +- `POST: /admin/lists/nodes` : create new child node under the supplied parent node IRI +- NOT IMPLEMENTED: `PUT: /admin/lists/nodes/` : create child node with given IRI und the supplied parent node IRI +- `PUT: /admin/lists/nodes//NodeInfoName` : update list node name information +- `PUT: /admin/lists/nodes//NodeInfoLabel` : update list node label information +- `PUT: /admin/lists/nodes//NodeInfoComment` : update list node comment information +- NOT IMPLEMENTED: `PUT: /admin/lists/nodes//NodeInfoPosition` : update list node position information +- NOT IMPLEMENTED: `PUT: /admin/lists/nodes//NodeInfoParent` : update list node parent information +- NOT IMPLEMENTED: `DELETE: /admin/lists/nodes/` : delete list node including children if not used ## List Operations @@ -43,18 +54,18 @@ License along with Knora. If not, see . - Required permission: none - Return all lists optionally filtered by project - - GET: `/admin/lists[?projectIri=]` + - GET: `/admin/lists[?projectIRI=]` ### Get list - Required permission: none - Return complete list with children - - GET: `/admin/lists/` + - GET: `/admin/lists/` ### Create new list - - Required permission: SystemAdmin / ProjectAdmin + - Required permission: SystemAdmin or ProjectAdmin - POST: `/admin/lists` - BODY: ``` @@ -65,13 +76,88 @@ License along with Knora. If not, see . } ``` +### Get list's information + + - Required permission: none + - Return list information (without children) + - GET: `/admin/lists//Info` + +### Update lists's name information + + - Required permission: SystemAdmin or ProjectAdmin + - Update list name information + - PUT: `/admin/lists//ListInfoName` + - BODY + ``` + { + "listIri": "listIri", + "projectIri": "projectIri", + "name": "name" + } + ``` + - Submit empty parameter (`"name": ""`) to delete name + - `name` must be unique inside project + +### Update list's label information + + - Required permission: SystemAdmin or ProjectAdmin + - Update list label information + - PUT: `/admin/lists//ListInfoLabel` + - BODY + ``` + { + "listIri": "listIri", + "projectIri": "projectIri", + "labels": [{"value": "New label", "language": "en"}] + } + ``` + - At least one label must be submitted + +### Update list's comment information + + - Required permission: SystemAdmin or ProjectAdmin + - Update list comment information + - PUT: `/admin/lists//ListInfoName` + - BODY + ``` + { + "listIri": "listIri", + "projectIri": "projectIri", + "comment": [{"value": "New Comment", "language": "en"}] + } + ``` + - Submit empty parameter (`"comment": []`) to delete comments + + + +### Delete list + + - Required permission: SystemAdmin or ProjectAdmin + - Delete List including children if not used + - DELETE: `/admin/lists/` + + +## List Node Operations + +### Get List Node + + - Required permission: none + - Return list node with children + - GET: `/admin/lists/nodes/` + +### Get node's information + + - Required permission: none + - Return node information (without children) + - GET: `/admin/lists/node//Info` + ### Create new child node - - Required permission: SystemAdmin / ProjectAdmin + - Required permission: SystemAdmin or 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. Children are currently only appended. - - POST: `/admin/lists/` + - POST: `/admin/lists/nodes` - BODY: ``` { @@ -82,32 +168,56 @@ License along with Knora. If not, see . "comments": [{ "value": "New First Child List Node Comment", "language": "en"}] } ``` + +### Delete List node -### Get list's information - - - Required permission: none - - Return list information (without children) - - GET: `/admin/lists/infos/` + - Required permission: SystemAdmin or ProjectAdmin + - Delete node including children if not used + - DELETE: `/admin/lists/nodes/` -### Update list's information +### Update node's name information - - Required permission: none - - Update list information - - PUT: `/admin/lists/infos/` - - BODY: + - Required permission: SystemAdmin or ProjectAdmin + - Update list node name information + - PUT: `/admin/lists/nodes//NodeInfoName` + - BODY ``` { - "listIri": "listIri", - "projectIri": "someprojectiri", - "labels": [{ "value": "Neue geönderte Liste", "language": "de"}, { "value": "Changed list", "language": "en"}], - "comments": [{ "value": "Neuer Kommentar", "language": "de"}, { "value": "New comment", "language": "en"}] + "nodeIri": "nodeIri", + "projectIri": "projectIri", + "name": "name" } ``` + - Submit empty parameter (`"name": ""`) to delete name + - `name` must be unique inside project + +### Update node's label information -## List Node Operations - -### Get List Node Information + - Required permission: SystemAdmin or ProjectAdmin + - Update list node label information + - PUT: `/admin/lists/nodes//NodeInfoLabel` + - BODY + ``` + { + "nodeIri": "nodeIri", + "projectIri": "projectIri", + "labels": [{"value": "New label", "language": "en"}] + } + ``` + - At least one label must be submitted + +### Update node's comment information - - Required permission: none - - Return list node information (without children) - - GET: `/admin/lists/nodes/` + - Required permission: SystemAdmin or ProjectAdmin + - Update list node comment information + - PUT: `/admin/lists/nodes//NodeInfoName` + - BODY + ``` + { + "nodeIri": "nodeIri", + "projectIri": "projectIri", + "comment": [{"value": "New Comment", "language": "en"}] + } + ``` + - Submit empty parameter (`"comment": []`) to delete comments + \ No newline at end of file diff --git a/webapi/src/main/scala/org/knora/webapi/SharedTestDataADM.scala b/webapi/src/main/scala/org/knora/webapi/SharedTestDataADM.scala index 3a8c1431a6..7cf2db297e 100644 --- a/webapi/src/main/scala/org/knora/webapi/SharedTestDataADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/SharedTestDataADM.scala @@ -29,6 +29,7 @@ import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectADM import org.knora.webapi.messages.admin.responder.usersmessages.UserADM import org.knora.webapi.messages.store.triplestoremessages.StringLiteralV2 import org.knora.webapi.util.StringFormatter +import spray.json.{JsArray, JsObject, JsString} /** * This object holds the same user which are loaded with '_test_data/all_data/admin-data.ttl'. Using this object @@ -632,19 +633,125 @@ object SharedTestDataADM { val createListRequest: String = s"""{ | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", + | "name": "newList", | "labels": [{ "value": "Neue Liste", "language": "de"}], | "comments": [] |}""".stripMargin - def updateListInfoRequest(listIri: IRI): String = { + def updateListNameRequest(listIri: IRI, projectIri: IRI, name: String): String = { s"""{ | "listIri": "$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"}] + | "projectIri": "$projectIri", + | "name": "$name" + |}""".stripMargin + } + + def updateListLabelsRequest(listIri: IRI, projectIri: IRI, labels: Vector[Map[String, String]]): String = { + JsObject( + Map( + "listIri" -> JsString(listIri), + "projectIri" -> JsString(projectIri), + "labels" -> JsArray( + labels.map { + labelMap => + JsObject( + labelMap.map { + case (key, value) => key -> JsString(value) + } + ) + } + ) + ) + ).prettyPrint + } + + def updateListCommentsRequest(listIri: IRI, projectIri: IRI, comments: Vector[Map[String, String]]): String = { + JsObject( + Map( + "listIri" -> JsString(listIri), + "projectIri" -> JsString(projectIri), + "comments" -> JsArray( + comments.map { + labelMap => + JsObject( + labelMap.map { + case (key, value) => key -> JsString(value) + } + ) + } + ) + ) + ).prettyPrint + } + + def updateListNodeNameRequest(nodeIri: IRI, projectIri: IRI, name: String): String = { + s"""{ + | "nodeIri": "$nodeIri", + | "projectIri": "$projectIri", + | "name": "$name" |}""".stripMargin } + def updateListNodeLabelsRequest(nodeIri: IRI, projectIri: IRI, labels: Vector[Map[String, String]]): String = { + JsObject( + Map( + "nodeIri" -> JsString(nodeIri), + "projectIri" -> JsString(projectIri), + "labels" -> JsArray( + labels.map { + labelMap => + JsObject( + labelMap.map { + case (key, value) => key -> JsString(value) + } + ) + } + ) + ) + ).prettyPrint + } + + def updateListNodeCommentsRequest(nodeIri: IRI, projectIri: IRI, comments: Vector[Map[String, String]]): String = { + JsObject( + Map( + "nodeIri" -> JsString(nodeIri), + "projectIri" -> JsString(projectIri), + "comments" -> JsArray( + comments.map { + labelMap => + JsObject( + labelMap.map { + case (key, value) => key -> JsString(value) + } + ) + } + ) + ) + ).prettyPrint + } + + val updatedLabels: Vector[Map[String, String]] = Vector( + Map( + "value" -> "Geändertes Etikett", + "language" -> "de" + ), + Map( + "value" -> "Changed label", + "language" -> "en" + ) + ) + + val updatedComments: Vector[Map[String, String]] = Vector( + Map( + "value" -> "Geänderter Kommentar", + "language" -> "de" + ), + Map( + "value" -> "Changed comment", + "language" -> "en" + ) + ) + def addChildListNodeRequest(parentNodeIri: IRI, name: String, label: String, 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 13c912ba91..16e1f31658 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 @@ -69,14 +69,16 @@ case class CreateListApiRequestADM(projectIri: IRI, } /** - * Represents an API request payload that asks the Knora API server to create a new child list node which will be - * attached to the list node identified by the supplied listNodeIri, where the list node to which a child list node - * is added can be either a root list node or a child list node. At least one label needs to be supplied. If other - * child nodes exist, the newly created list node will be appended to the end. + * Represents an API request payload that asks the Knora API server to create + * a new child list node which will be attached to the list node identified by + * the supplied listNodeIri, where the list node to which a child list node + * is added can be either a root list node or a child list node. At least one + * label needs to be supplied. If other child nodes exist, the newly created + * list node will be appended to the end. * - * @param parentNodeIri - * @param labels - * @param comments + * @param parentNodeIri the IRI of the parent node. + * @param labels the labels. + * @param comments the comments. */ case class CreateChildNodeApiRequestADM(parentNodeIri: IRI, projectIri: IRI, @@ -115,17 +117,20 @@ case class CreateChildNodeApiRequestADM(parentNodeIri: IRI, } /** - * Represents an API request payload that asks the Knora API server to update an existing list's basic information. + * Represents an API request payload that asks the Knora API server to update + * an existing list's basic information. * * @param listIri the IRI of the list to change. * @param projectIri the IRI of the project the list belongs to. + * @param name the name. * @param labels the labels. * @param comments the comments. */ case class ChangeListInfoApiRequestADM(listIri: IRI, projectIri: IRI, - labels: Seq[StringLiteralV2], - comments: Seq[StringLiteralV2]) extends ListADMJsonProtocol { + name: Option[String] = None, + labels: Option[Seq[StringLiteralV2]] = None, + comments: Option[Seq[StringLiteralV2]] = None) extends ListADMJsonProtocol { private val stringFormatter = StringFormatter.getInstanceForConstantOntologies @@ -145,11 +150,126 @@ case class ChangeListInfoApiRequestADM(listIri: IRI, throw BadRequestException(PROJECT_IRI_INVALID_ERROR) } - if (labels.isEmpty && comments.isEmpty) { - throw BadRequestException(REQUEST_NOT_CHANGING_DATA_ERROR) + def toJsValue: JsValue = changeListInfoApiRequestADMFormat.write(this) +} + +/** + * Represents a payload that asks the Knora API server to update + * an existing list's basic information. + * + * @param listIri the IRI of the list to change. + * @param projectIri the IRI of the project the list belongs to. + * @param name the name. + * @param labels the labels. + * @param comments the comments. + */ +case class ChangeListInfoPayloadADM(listIri: IRI, + projectIri: IRI, + name: Option[Option[String]] = None, + labels: Option[Seq[StringLiteralV2]] = None, + comments: Option[Seq[StringLiteralV2]]= None) extends ListADMJsonProtocol { + + private val stringFormatter = StringFormatter.getInstanceForConstantOntologies + + if (listIri.isEmpty) { + throw BadRequestException(LIST_IRI_MISSING_ERROR) } - def toJsValue: JsValue = changeListInfoApiRequestADMFormat.write(this) + if (!stringFormatter.isKnoraListIriStr(listIri)) { + throw BadRequestException(LIST_IRI_INVALID_ERROR) + } + + if (projectIri.isEmpty) { + throw BadRequestException(PROJECT_IRI_MISSING_ERROR) + } + + if (!stringFormatter.isKnoraProjectIriStr(projectIri)) { + throw BadRequestException(PROJECT_IRI_INVALID_ERROR) + } + + if (labels.isDefined && labels.get.isEmpty) { + throw BadRequestException("Lists need at least one label.") + } + + def toJsValue: JsValue = changeListInfoPayloadADMFormat.write(this) +} + +/** + * Represents an API request payload that asks the Knora API server to update + * an existing list nodes's basic information. + * + * @param nodeIri the IRI of the list node to change. + * @param projectIri the IRI of the project the list belongs to. + * @param name the name. + * @param labels the labels. + * @param comments the comments. + */ +case class ChangeListNodeInfoApiRequestADM(nodeIri: IRI, + projectIri: IRI, + name: Option[String] = None, + labels: Option[Seq[StringLiteralV2]] = None, + comments: Option[Seq[StringLiteralV2]] = None) extends ListADMJsonProtocol { + + private val stringFormatter = StringFormatter.getInstanceForConstantOntologies + + if (nodeIri.isEmpty) { + throw BadRequestException(LIST_NODE_IRI_MISSING_ERROR) + } + + if (!stringFormatter.isKnoraListIriStr(nodeIri)) { + throw BadRequestException(LIST_NODE_IRI_INVALID_ERROR) + } + + if (projectIri.isEmpty) { + throw BadRequestException(PROJECT_IRI_MISSING_ERROR) + } + + if (!stringFormatter.isKnoraProjectIriStr(projectIri)) { + throw BadRequestException(PROJECT_IRI_INVALID_ERROR) + } + + def toJsValue: JsValue = changeListNodeInfoApiRequestADMFormat.write(this) +} + +/** + * Represents a payload that asks the Knora API server to update + * an existing list nodes's basic information. + * + * @param nodeIri the IRI of the list node to change. + * @param projectIri the IRI of the project the list belongs to. + * @param name the name. + * @param labels the labels. + * @param comments the comments. + */ +case class ChangeListNodeInfoPayloadADM(nodeIri: IRI, + projectIri: IRI, + name: Option[Option[String]] = None, + labels: Option[Seq[StringLiteralV2]] = None, + comments: Option[Seq[StringLiteralV2]] = None) extends ListADMJsonProtocol { + + private val stringFormatter = StringFormatter.getInstanceForConstantOntologies + + if (nodeIri.isEmpty) { + throw BadRequestException(LIST_NODE_IRI_MISSING_ERROR) + } + + if (!stringFormatter.isKnoraListIriStr(nodeIri)) { + throw BadRequestException(LIST_NODE_IRI_INVALID_ERROR) + } + + if (projectIri.isEmpty) { + throw BadRequestException(PROJECT_IRI_MISSING_ERROR) + } + + if (!stringFormatter.isKnoraProjectIriStr(projectIri)) { + throw BadRequestException(PROJECT_IRI_INVALID_ERROR) + } + + if (labels.isDefined && labels.get.isEmpty) { + throw BadRequestException("Listnodes need at least one label.") + } + + def toJsValue: JsValue = changeListNodeInfoPayloadADMFormat.write(this) } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -230,10 +350,23 @@ case class ListCreateRequestADM(createListRequest: CreateListApiRequestADM, * @param apiRequestID the ID of the API request. */ case class ListInfoChangeRequestADM(listIri: IRI, - changeListRequest: ChangeListInfoApiRequestADM, + changeListRequest: ChangeListInfoPayloadADM, requestingUser: UserADM, apiRequestID: UUID) extends ListsResponderRequestADM +/** + * Request updating basic information of an existing node. + * + * @param nodeIri the IRI of the node to be updated. + * @param changeNodeRequest the data which needs to be update. + * @param requestingUser the user initiating the request. + * @param apiRequestID the ID of the API request. + */ +case class ListNodeInfoChangeRequestADM(nodeIri: IRI, + changeNodeRequest: ChangeListNodeInfoPayloadADM, + requestingUser: UserADM, + apiRequestID: UUID) extends ListsResponderRequestADM + /** * Request the creation of a new list (child) node. * @@ -295,9 +428,9 @@ case class ListNodeInfoGetResponseADM(nodeinfo: ListNodeInfoADM) extends KnoraRe /** * Responds to a [[NodePathGetRequestADM]] by providing the path to a particular hierarchical list node. * - * @param elements a list of the nodes composing the path from the list's root node up to and including the specified node. + * @param nodeList a list of the nodes composing the path from the list's root node up to and including the specified node. */ -case class NodePathGetResponseADM(elements: Seq[NodePathElementADM]) extends KnoraResponseADM with ListADMJsonProtocol { +case class NodePathGetResponseADM(nodeList: Seq[NodePathElementADM]) extends KnoraResponseADM with ListADMJsonProtocol { def toJsValue = nodePathGetResponseADMFormat.write(this) } @@ -398,6 +531,16 @@ case class ListRootNodeInfoADM(id: IRI, projectIri: IRI, name: Option[String], l } + +/** + * + * @param id the IRI of the list. + * @param name the name of the list node. + * @param labels the labels of the node in all available languages. + * @param comments the comments attached to the node in all available languages. + * @param position the position of the node among its siblings (optional). + * @param hasRootNode the Iri of the root node, if this is not the root node. + */ case class ListChildNodeInfoADM(id: IRI, name: Option[String], labels: StringLiteralSequenceV2, comments: StringLiteralSequenceV2, position: Int, hasRootNode: IRI) extends ListNodeInfoADM(id, name, labels, comments) { /** @@ -486,7 +629,7 @@ abstract class ListNodeADM(id: IRI, name: Option[String], labels: StringLiteralS * @param comments the comment(s) attached to the list in a specific language (if language tags are used) . * @param children the list node's child nodes. */ -case class ListRootNodeADM(id: IRI, projectIri: IRI, name: Option[String], labels: StringLiteralSequenceV2, comments: StringLiteralSequenceV2, children: Seq[ListChildNodeADM]) extends ListNodeADM(id, name, labels, comments, children) { +case class ListRootNodeADM(id: IRI, projectIri: IRI, name: Option[String], labels: StringLiteralSequenceV2, comments: StringLiteralSequenceV2, children: Seq[ListChildNodeADM]) extends ListNodeADM(id, name, labels, comments, children) { /** * Sorts the whole hierarchy. @@ -873,13 +1016,13 @@ trait ListADMJsonProtocol extends SprayJsonSupport with DefaultJsonProtocol with * @param element a [[NodePathElementADM]]. * @return a [[JsValue]]. */ - def write(element: NodePathElementADM): JsValue = { + def write(nodeInfo: NodePathElementADM): JsValue = { JsObject( - "id" -> element.id.toJson, - "name" -> element.name.toJson, - "labels" -> JsArray(element.labels.stringLiterals.map(_.toJson)), - "comments" -> JsArray(element.comments.stringLiterals.map(_.toJson)) + "id" -> nodeInfo.id.toJson, + "name" -> nodeInfo.name.toJson, + "labels" -> JsArray(nodeInfo.labels.stringLiterals.map(_.toJson)), + "comments" -> JsArray(nodeInfo.comments.stringLiterals.map(_.toJson)) ) } @@ -958,7 +1101,10 @@ trait ListADMJsonProtocol extends SprayJsonSupport with DefaultJsonProtocol with implicit val createListApiRequestADMFormat: RootJsonFormat[CreateListApiRequestADM] = jsonFormat(CreateListApiRequestADM, "projectIri", "name", "labels", "comments") implicit val createListNodeApiRequestADMFormat: RootJsonFormat[CreateChildNodeApiRequestADM] = jsonFormat(CreateChildNodeApiRequestADM, "parentNodeIri", "projectIri", "name", "labels", "comments") - implicit val changeListInfoApiRequestADMFormat: RootJsonFormat[ChangeListInfoApiRequestADM] = jsonFormat(ChangeListInfoApiRequestADM, "listIri", "projectIri", "labels", "comments") + implicit val changeListInfoApiRequestADMFormat: RootJsonFormat[ChangeListInfoApiRequestADM] = jsonFormat(ChangeListInfoApiRequestADM, "listIri", "projectIri", "name", "labels", "comments") + implicit val changeListInfoPayloadADMFormat: RootJsonFormat[ChangeListInfoPayloadADM] = jsonFormat(ChangeListInfoPayloadADM, "listIri", "projectIri", "name", "labels", "comments") + implicit val changeListNodeInfoApiRequestADMFormat: RootJsonFormat[ChangeListNodeInfoApiRequestADM] = jsonFormat(ChangeListNodeInfoApiRequestADM, "nodeIri", "projectIri", "name", "labels", "comments") + implicit val changeListNodeInfoPayloadADMFormat: RootJsonFormat[ChangeListNodeInfoPayloadADM] = jsonFormat(ChangeListNodeInfoPayloadADM, "nodeIri", "projectIri", "name", "labels", "comments") implicit val nodePathGetResponseADMFormat: RootJsonFormat[NodePathGetResponseADM] = jsonFormat(NodePathGetResponseADM, "elements") implicit val listsGetResponseADMFormat: RootJsonFormat[ListsGetResponseADM] = jsonFormat(ListsGetResponseADM, "lists") implicit val listGetResponseADMFormat: RootJsonFormat[ListGetResponseADM] = jsonFormat(ListGetResponseADM, "list") diff --git a/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/ontologymessages/KnoraAdminToApiV2ComplexTransformationRules.scala b/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/ontologymessages/KnoraAdminToApiV2ComplexTransformationRules.scala index 41d53f966f..3415bead01 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/ontologymessages/KnoraAdminToApiV2ComplexTransformationRules.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/ontologymessages/KnoraAdminToApiV2ComplexTransformationRules.scala @@ -361,30 +361,6 @@ object KnoraAdminToApiV2ComplexTransformationRules extends OntologyTransformatio ) ) - private val UpdateListInfoRequest = makeClass( - classIri = OntologyConstants.KnoraAdminV2.UpdateListInfoRequest, - predicates = Seq( - makePredicate( - predicateIri = OntologyConstants.Rdfs.Label, - objectsWithLang = Map( - LanguageCodes.EN -> "update list info request" - ) - ), - makePredicate( - predicateIri = OntologyConstants.Rdfs.Comment, - objectsWithLang = Map( - LanguageCodes.EN -> "A request to update information about a list." - ) - ) - ), - directCardinalities = Map( - OntologyConstants.KnoraAdminV2.ListIri -> Cardinality.MustHaveOne, - OntologyConstants.KnoraAdminV2.ProjectIri -> Cardinality.MayHaveOne, - OntologyConstants.KnoraAdminV2.Labels -> Cardinality.MustHaveSome, - OntologyConstants.KnoraAdminV2.Comments -> Cardinality.MayHaveMany - ) - ) - private val CreateListRequest = makeClass( classIri = OntologyConstants.KnoraAdminV2.CreateListRequest, predicates = Seq( @@ -1721,7 +1697,6 @@ object KnoraAdminToApiV2ComplexTransformationRules extends OntologyTransformatio ListClass, CreateListRequest, CreateChildNodeRequest, - UpdateListInfoRequest, ListInfoResponse, ListNodeInfoResponse, CreateGroupRequest, 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 2f9e560d57..0130ce2ddd 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 @@ -36,7 +36,7 @@ import org.knora.webapi.util.SmartIri import scala.annotation.tailrec import scala.collection.breakOut -import scala.concurrent.Future +import scala.concurrent.{Await, Future} object ListsResponderADM { @@ -72,6 +72,7 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde case NodePathGetRequestADM(iri, requestingUser) => nodePathGetAdminRequest(iri, requestingUser) case ListCreateRequestADM(createListRequest, requestingUser, apiRequestID) => listCreateRequestADM(createListRequest, requestingUser, apiRequestID) case ListInfoChangeRequestADM(listIri, changeListRequest, requestingUser, apiRequestID) => listInfoChangeRequest(listIri, changeListRequest, requestingUser, apiRequestID) + case ListNodeInfoChangeRequestADM(nodeIri, changeNodeRequest, requestingUser, apiRequestID) => listNodeInfoChangeRequest(nodeIri, changeNodeRequest, requestingUser, apiRequestID) case ListChildNodeCreateRequestADM(parentNodeIri, createListNodeRequest, requestingUser, apiRequestID) => listChildNodeCreateRequestADM(parentNodeIri, createListNodeRequest, requestingUser, apiRequestID) case other => handleUnexpectedMessage(other, log, this.getClass.getName) } @@ -586,7 +587,7 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde case None => acc } } - } yield NodePathGetResponseADM(elements = makePath(queryNodeIri, nodeMap, parentMap, Nil)) + } yield NodePathGetResponseADM(nodeList = makePath(queryNodeIri, nodeMap, parentMap, Nil)) } @@ -679,14 +680,14 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde * @throws BadRequestException in the case when the project IRI is missing or invalid. * @throws UpdateNotPerformedException in the case something else went wrong, and the change could not be performed. */ - private def listInfoChangeRequest(listIri: IRI, changeListRequest: ChangeListInfoApiRequestADM, requestingUser: UserADM, apiRequestID: UUID): Future[ListInfoGetResponseADM] = { + private def listInfoChangeRequest(listIri: IRI, changeListRequest: ChangeListInfoPayloadADM, requestingUser: UserADM, apiRequestID: UUID): Future[ListInfoGetResponseADM] = { /** * The actual task run with an IRI lock. */ - def listInfoChangeTask(listIri: IRI, changeListRequest: ChangeListInfoApiRequestADM, requestingUser: UserADM, apiRequestID: UUID): Future[ListInfoGetResponseADM] = for { + def listInfoChangeTask(listIri: IRI, changeListRequest: ChangeListInfoPayloadADM, requestingUser: UserADM, apiRequestID: UUID): Future[ListInfoGetResponseADM] = for { // check if required information is supplied - _ <- Future(if (changeListRequest.labels.isEmpty && changeListRequest.comments.isEmpty) throw BadRequestException(REQUEST_NOT_CHANGING_DATA_ERROR)) + _ <- Future(if (changeListRequest.name.isEmpty && changeListRequest.labels.isEmpty && changeListRequest.comments.isEmpty) throw BadRequestException(REQUEST_NOT_CHANGING_DATA_ERROR)) _ = if (!listIri.equals(changeListRequest.listIri)) throw BadRequestException("List IRI in path and payload don't match.") // check if the requesting user is allowed to perform operation @@ -712,6 +713,12 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde case None => throw BadRequestException(s"Project '${list.listinfo.projectIri}' not found.") } + /* verify that the list node name is unique for the project */ + nodeNameUnique: Boolean <- listNodeNameIsProjectUnique(changeListRequest.projectIri, changeListRequest.name.flatten) + _ = if (!nodeNameUnique) { + throw DuplicateValueException(s"The node name ${changeListRequest.name.get} is already used by a list inside the project ${changeListRequest.projectIri}.") + } + // get the data graph of the project. dataNamedGraph = stringFormatter.projectDataNamedGraphV2(project) @@ -722,35 +729,43 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde listIri = listIri, projectIri = project.id, listClassIri = OntologyConstants.KnoraBase.ListNode, + maybeName = changeListRequest.name, maybeLabels = changeListRequest.labels, maybeComments = changeListRequest.comments ).toString - // _ = log.debug("listCreateRequestADM - createNewListSparqlString: {}", createNewListSparqlString) - changeResourceResponse <- (storeManager ? SparqlUpdateRequest(changeListInfoSparqlString)).mapTo[SparqlUpdateResponse] + _ <- (storeManager ? SparqlUpdateRequest(changeListInfoSparqlString)).mapTo[SparqlUpdateResponse] /* Verify that the list was updated */ maybeListADM <- listGetADM(listIri, KnoraSystemInstances.Users.SystemUser) updatedList = maybeListADM.getOrElse(throw UpdateNotPerformedException(s"List $listIri was not updated. Please report this as a possible bug.")) - - + /* verify name update */ + _ = if (changeListRequest.name.nonEmpty) { + if (changeListRequest.name.get.nonEmpty) { + if (updatedList.listinfo.name.nonEmpty) { + if (updatedList.listinfo.name.get != changeListRequest.name.get.get) throw UpdateNotPerformedException("Lists's 'name' was not updated. Please report this as a possible bug.") + } + } else { + if (updatedList.listinfo.name.nonEmpty) { + throw UpdateNotPerformedException("Lists's 'name' was not updated. Please report this as a possible bug.") + } + } + } + /* verify label update */ _ = if (changeListRequest.labels.nonEmpty) { - if (updatedList.listinfo.labels.stringLiterals.sorted != changeListRequest.labels.sorted) throw UpdateNotPerformedException("Lists's 'labels' where not updated. Please report this as a possible bug.") + if (updatedList.listinfo.labels.stringLiterals.sorted != changeListRequest.labels.get.sorted) throw UpdateNotPerformedException("Lists's 'labels' was not updated. Please report this as a possible bug.") } - + /* verify comment update */ _ = if (changeListRequest.comments.nonEmpty) { - if (updatedList.listinfo.comments.stringLiterals.sorted != changeListRequest.comments.sorted) throw UpdateNotPerformedException("List's 'comments' was not updated. Please report this as a possible bug.") + if (updatedList.listinfo.comments.stringLiterals.sorted != changeListRequest.comments.get.sorted) throw UpdateNotPerformedException("List's 'comments' was not updated. Please report this as a possible bug.") } - - // _ = log.debug(s"listInfoChangeRequest - updatedList: {}", updatedList) - } yield ListInfoGetResponseADM(listinfo = updatedList.listinfo) for { // run list info update with an local IRI lock taskResult <- IriLocker.runWithIriLock( apiRequestID, - listIri, + LISTS_GLOBAL_LOCK_IRI, () => listInfoChangeTask(listIri, changeListRequest, requestingUser, apiRequestID) ) } yield taskResult @@ -862,6 +877,114 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde } + /** + * Changes basic list node information stored in the list node. + * + * @param nodeIri the node's IRI. + * @param changeNodeRequest the new list information. + * @param requestingUser the user that is making the request. + * @param apiRequestID the unique api request ID. + * @return a [[ListNodeInfoGetResponseADM]] + * @throws ForbiddenException in the case that the user is not allowed to perform the operation. + * @throws BadRequestException in the case when the project IRI is missing or invalid. + * @throws UpdateNotPerformedException in the case something else went wrong, and the change could not be performed. + */ + private def listNodeInfoChangeRequest(nodeIri: IRI, changeNodeRequest: ChangeListNodeInfoPayloadADM, requestingUser: UserADM, apiRequestID: UUID): Future[ListNodeInfoGetResponseADM] = { + + /** + * The actual task run with an IRI lock. + */ + def listNodeInfoChangeTask(nodeIri: IRI, changeNodeRequest: ChangeListNodeInfoPayloadADM, requestingUser: UserADM, apiRequestID: UUID) = for { + // check if the requesting user is allowed to perform operation + _ <- Future( + if (!requestingUser.permissions.isProjectAdmin(changeNodeRequest.projectIri) && !requestingUser.permissions.isSystemAdmin) { + // not project or a system admin + throw ForbiddenException(LIST_CHANGE_PERMISSION_ERROR) + } + ) + // check if required information is supplied + _ = if (!nodeIri.equals(changeNodeRequest.nodeIri)) throw BadRequestException("Node IRI in path and payload don't match.") + + /* Verify that the node exists. */ + maybeListNode <- listNodeGetADM(nodeIri = nodeIri, shallow = false, requestingUser = KnoraSystemInstances.Users.SystemUser) + rootNodeIri = maybeListNode match { + case Some(root: ListRootNodeADM) => root.asInstanceOf[ListRootNodeADM].id + case Some(child: ListChildNodeADM) => child.asInstanceOf[ListChildNodeADM].hasRootNode + case _ => throw BadRequestException(s"Node '$nodeIri' not found.") + } + + /* Get the project information */ + maybeProject <- (responderManager ? ProjectGetADM(ProjectIdentifierADM(maybeIri = Some(changeNodeRequest.projectIri)), KnoraSystemInstances.Users.SystemUser)).mapTo[Option[ProjectADM]] + project: ProjectADM = maybeProject match { + case Some(project: ProjectADM) => project + case _ => throw BadRequestException(s"Project '${changeNodeRequest.projectIri}' not found.") + } + + /* verify that the list node name is unique for the project */ + _ = if (changeNodeRequest.name.nonEmpty) { + val result = listNodeNameIsProjectUnique(changeNodeRequest.projectIri, changeNodeRequest.name.get) + if (!Await.result(result, timeout.duration)) throw DuplicateValueException(s"The node name ${changeNodeRequest.name.get} is already used by a list inside the project ${changeNodeRequest.projectIri}.") + } + + nodeNameUnique: Boolean <- listNodeNameIsProjectUnique(changeNodeRequest.projectIri, changeNodeRequest.name.flatten) + _ = if (!nodeNameUnique) { + throw DuplicateValueException(s"The node name ${changeNodeRequest.name.get} is already used by a list inside the project ${changeNodeRequest.projectIri}.") + } + + // get the data graph of the project. + dataNamedGraph = stringFormatter.projectDataNamedGraphV2(project) + + // Update the list + changeListNodeInfoSparqlString = queries.sparql.admin.txt.updateListNodeInfo( + dataNamedGraph = dataNamedGraph, + triplestore = settings.triplestoreType, + nodeIri = nodeIri, + nodeClassIri = OntologyConstants.KnoraBase.ListNode, + maybeName = changeNodeRequest.name, + maybeLabels = changeNodeRequest.labels, + maybeComments = changeNodeRequest.comments + ).toString + _ <- (storeManager ? SparqlUpdateRequest(changeListNodeInfoSparqlString)).mapTo[SparqlUpdateResponse] + + /* Verify that the list was updated */ + maybeListNodeInfoADM <- listNodeInfoGetADM(nodeIri = nodeIri, requestingUser = KnoraSystemInstances.Users.SystemUser) + (updatedNode, name, labels, comments) = maybeListNodeInfoADM match { + case Some(root: ListRootNodeInfoADM) => (root.asInstanceOf[ListRootNodeInfoADM], root.name, root.labels, root.comments) + case Some(child: ListChildNodeInfoADM) => (child.asInstanceOf[ListChildNodeInfoADM], child.name, child.labels, child.comments) + case _ => throw UpdateNotPerformedException(s"Node $nodeIri was not updated. Please report this as a possible bug.") + } + /* verify name update */ + _ = if (changeNodeRequest.name.nonEmpty) { + if (changeNodeRequest.name.get.nonEmpty) { + if (name.nonEmpty) { + if (name.get != changeNodeRequest.name.get.get) throw UpdateNotPerformedException(s"Node $nodeIri's 'name' was not updated. Please report this as a possible bug.") + } + } else { + if (name.nonEmpty) { + throw UpdateNotPerformedException(s"Node $nodeIri's 'name' was not updated. Please report this as a possible bug.") + } + } + } + /* verify label update */ + _ = if (changeNodeRequest.labels.nonEmpty) { + if (labels.stringLiterals.sorted != changeNodeRequest.labels.get.sorted) throw UpdateNotPerformedException(s"Node $nodeIri's 'labels' was not updated. Please report this as a possible bug.") + } + /* verify comment update */ + _ = if (changeNodeRequest.comments.nonEmpty) { + if (comments.stringLiterals.sorted != changeNodeRequest.comments.get.sorted) throw UpdateNotPerformedException(s"Node $nodeIri's 'comments' was not updated. Please report this as a possible bug.") + } + } yield ListNodeInfoGetResponseADM(nodeinfo = updatedNode) + + for { + // run list info update with an local IRI lock + taskResult <- IriLocker.runWithIriLock( + apiRequestID, + LISTS_GLOBAL_LOCK_IRI, + () => listNodeInfoChangeTask(nodeIri, changeNodeRequest, requestingUser, apiRequestID) + ) + } yield taskResult + } + //////////////////// // Helper Methods // //////////////////// 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 3eee0bb0dc..6b96e531fc 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 @@ -81,26 +81,35 @@ class ListsRouteADM(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit private val ListResponse = classRef(OntologyConstants.KnoraAdminV2.ListResponse.toSmartIri) private val ListInfoResponse = classRef(OntologyConstants.KnoraAdminV2.ListInfoResponse.toSmartIri) private val ListNodeInfoResponse = classRef(OntologyConstants.KnoraAdminV2.ListNodeInfoResponse.toSmartIri) - private val UpdateListInfoRequest = classRef(OntologyConstants.KnoraAdminV2.UpdateListInfoRequest.toSmartIri) private val CreateChildNodeRequest = classRef(OntologyConstants.KnoraAdminV2.CreateChildNodeRequest.toSmartIri) + private val StringLiteral = classRef(OntologyConstants.KnoraAdminV2.StringLiteral.toSmartIri) private val anythingList = URLEncoder.encode("http://rdfh.ch/lists/0001/treeList", "UTF-8") private val anythingListNode = URLEncoder.encode("http://rdfh.ch/lists/0001/treeList01", "UTF-8") /** * Returns the route. */ - override def knoraApiPath: Route = getLists ~ postList ~ getList ~ putList ~ postListChildNode ~ deleteListNode ~ - getListInfo ~ updateListInfo ~ getListNodeInfo + /* concatenate paths in the CORRECT order and return */ + override def knoraApiPath: Route = getLists ~ postList ~ getList ~ putListWithIRI ~ deleteList ~ getListInfo ~ + putListInfo ~ postListChildNode ~ getListNode ~ putNodeWithIRI ~ deleteListNode ~ getListNodeInfo ~ putNodeInfo + + // ------------------------------------- + // --------------- LISTS --------------- + // ------------------------------------- /* return all lists optionally filtered by project */ - @ApiOperation(value = "Get lists", nickname = "getlists", httpMethod = "GET", response = classOf[ListsGetResponseADM]) + @ApiOperation( + value = "Get all lists optionally filtered by project", + nickname = "getlists", + httpMethod = "GET", + response = classOf[ListsGetResponseADM] + ) @ApiResponses(Array( new ApiResponse(code = 500, message = "Internal server error") )) /* return all lists optionally filtered by project */ def getLists: Route = path(ListsBasePath) { get { - /* return all lists */ parameters("projectIri".?) { maybeProjectIri: Option[IRI] => requestContext => val projectIri = stringFormatter.toOptionalIri(maybeProjectIri, throw BadRequestException(s"Invalid param project IRI: $maybeProjectIri")) @@ -148,7 +157,12 @@ class ListsRouteADM(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit } /* create a new list (root node) */ - @ApiOperation(value = "Add new list", nickname = "addList", httpMethod = "POST", response = classOf[ListGetResponseADM]) + @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[CreateListApiRequestADM], paramType = "body") @@ -158,7 +172,6 @@ class ListsRouteADM(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit )) def postList: Route = path(ListsBasePath) { post { - /* create a list */ entity(as[CreateListApiRequestADM]) { apiRequest => requestContext => val requestMessage: Future[ListCreateRequestADM] = for { @@ -199,15 +212,19 @@ class ListsRouteADM(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit ) } - /* get a list */ + /** get a list with all list nodes */ @Path("/{IRI}") - @ApiOperation(value = "Get a list", nickname = "getlist", httpMethod = "GET", response = classOf[ListGetResponseADM]) + @ApiOperation( + value = "Get a list with all list nodes", + nickname = "getList", + httpMethod = "GET", + response = classOf[ListGetResponseADM] + ) @ApiResponses(Array( new ApiResponse(code = 500, message = "Internal server error") )) def getList: Route = path(ListsBasePath / Segment) { iri => get { - /* return a list (a graph with all list nodes) */ requestContext => val listIri = stringFormatter.validateAndEscapeIri(iri, throw BadRequestException(s"Invalid param list IRI: $iri")) @@ -243,11 +260,78 @@ class ListsRouteADM(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit ) } - /** - * update list - */ - @Path("/{IRI}") - @ApiOperation(value = "Update basic list information", nickname = "putList", httpMethod = "PUT", response = classOf[ListInfoGetResponseADM]) + /** create new list (root node) with given IRI */ + def putListWithIRI: Route = path(ListsBasePath / Segment) { iri => + put { + throw NotImplementedException("Method not implemented.") + ??? + } + } + + /** delete list/node which should also delete all children */ + def deleteList: Route = path(ListsBasePath / Segment) { iri => + delete { + throw NotImplementedException("Method not implemented.") + ??? + } + } + + @Path("/{IRI}/Info") + @ApiOperation( + value = "Get basic list information (without children)", + nickname = "getListInfo", + httpMethod = "GET", + response = classOf[ListInfoGetResponseADM] + ) + @ApiResponses(Array( + new ApiResponse(code = 500, message = "Internal server error") + )) + /** return basic information about a list (without children) */ + def getListInfo: Route = path(ListsBasePath / Segment / "Info") { iri => + get { + requestContext => + val listIri = stringFormatter.validateAndEscapeIri(iri, throw BadRequestException(s"Invalid param list IRI: $iri")) + + val requestMessage: Future[ListInfoGetRequestADM] = for { + requestingUser <- getUserADM(requestContext) + } yield ListInfoGetRequestADM(listIri, requestingUser) + + RouteUtilADM.runJsonRoute( + requestMessage, + requestContext, + settings, + responderManager, + log + ) + } + } + + private val getListInfoFunction: ClientFunction = + "getListInfo" description "Returns information about a list." params ( + "iri" description "The IRI of the list." paramType UriDatatype + ) doThis { + httpGet( + path = arg("iri") / str("Info") + ) + } returns ListInfoResponse + + private def getListInfoTestResponse: Future[SourceCodeFileContent] = { + for { + responseStr <- doTestDataRequest(Get(s"$baseApiUrl$ListsBasePathString/$anythingList/Info") ~> addCredentials(BasicHttpCredentials(SharedTestDataADM.rootUser.email, SharedTestDataADM.testPass))) + } yield SourceCodeFileContent( + filePath = SourceCodeFilePath.makeJsonPath("get-list-info-response"), + text = responseStr + ) + } + + /** update existing list info */ + @Path("/{IRI}/Info") + @ApiOperation( + value = "Update basic list information", + nickname = "putListInfo", + httpMethod = "PUT", + response = classOf[ListInfoGetResponseADM] + ) @ApiImplicitParams(Array( new ApiImplicitParam(name = "body", value = "\"list\" to update", required = true, dataTypeClass = classOf[ChangeListInfoApiRequestADM], paramType = "body") @@ -255,18 +339,45 @@ class ListsRouteADM(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit @ApiResponses(Array( new ApiResponse(code = 500, message = "Internal server error") )) - def putList: Route = path(ListsBasePath / Segment) { iri => + def putListInfo: Route = path(ListsBasePath / Segment / Segment) { (iri, attribute) => put { - /* update existing list node (either root or child) */ entity(as[ChangeListInfoApiRequestADM]) { apiRequest => requestContext => val listIri = stringFormatter.validateAndEscapeIri(iri, throw BadRequestException(s"Invalid param list IRI: $iri")) + val requestPayload = attribute match { + case "ListInfoName" => + ChangeListInfoPayloadADM( + listIri = apiRequest.listIri, + projectIri = apiRequest.projectIri, + name = apiRequest.name.getOrElse(throw BadRequestException("Missing parameter for name")) match { + case "" => Some(None) + case _ => Some(apiRequest.name) + } + ) + + case "ListInfoLabel" => + ChangeListInfoPayloadADM( + listIri = apiRequest.listIri, + projectIri = apiRequest.projectIri, + labels = apiRequest.labels + ) + + case "ListInfoComment" => + ChangeListInfoPayloadADM( + listIri = apiRequest.listIri, + projectIri = apiRequest.projectIri, + comments = apiRequest.comments + ) + + case _ => throw BadRequestException(s"Invalid attribute: $attribute") + } + val requestMessage: Future[ListInfoChangeRequestADM] = for { requestingUser <- getUserADM(requestContext) } yield ListInfoChangeRequestADM( listIri = listIri, - changeListRequest = apiRequest, + changeListRequest = requestPayload, requestingUser = requestingUser, apiRequestID = UUID.randomUUID() ) @@ -282,30 +393,106 @@ class ListsRouteADM(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit } } - private val updateListInfoFunction: ClientFunction = - "updateListInfo" description "Updates information about a list." params ( - "listInfo" description "Information about the list to be created." paramType UpdateListInfoRequest - ) doThis { + private val updateListNameFunction: ClientFunction = + "updateListName" description "Updates the name of a list." params( + "listIri" description "The IRI of the list." paramType UriDatatype, + "projectIri" description "The IRI of the project that the list belongs to." paramType UriDatatype, + "name" description "The new name of the list." paramType StringDatatype + ) doThis { httpPut( - path = argMember("listInfo", "listIri"), - body = Some(arg("listInfo")) + path = arg("listIri") / str("ListInfoName"), + body = Some(json( + "listIri" -> arg("listIri"), + "projectIri" -> arg("projectIri"), + "name" -> arg("name") + )) ) } returns ListInfoResponse - private def updateListInfoTestRequest: Future[SourceCodeFileContent] = { + private def updateListNameTestRequest: Future[SourceCodeFileContent] = { FastFuture.successful( SourceCodeFileContent( - filePath = SourceCodeFilePath.makeJsonPath("update-list-info-request"), - text = SharedTestDataADM.updateListInfoRequest("http://rdfh.ch/lists/0001/treeList01") + filePath = SourceCodeFilePath.makeJsonPath("update-list-name-request"), + text = SharedTestDataADM.updateListNameRequest( + listIri = "http://rdfh.ch/lists/0001/treeList", + projectIri = SharedTestDataADM.ANYTHING_PROJECT_IRI, + name = "newTestName" + ) ) ) } - /** - * create a new child node - */ + private val updateListLabelsFunction: ClientFunction = + "updateListLabels" description "Updates the labels of a list." params( + "listIri" description "The IRI of the list." paramType UriDatatype, + "projectIri" description "The IRI of the project that the list belongs to." paramType UriDatatype, + "labels" description "The new labels of the list." paramType ArrayType(StringLiteral) + ) doThis { + httpPut( + path = arg("listIri") / str("ListInfoLabel"), + body = Some(json( + "listIri" -> arg("listIri"), + "projectIri" -> arg("projectIri"), + "labels" -> arg("labels") + )) + ) + } returns ListInfoResponse + + private def updateListLabelsTestRequest: Future[SourceCodeFileContent] = { + FastFuture.successful( + SourceCodeFileContent( + filePath = SourceCodeFilePath.makeJsonPath("update-list-labels-request"), + text = SharedTestDataADM.updateListLabelsRequest( + listIri = "http://rdfh.ch/lists/0001/treeList", + projectIri = SharedTestDataADM.ANYTHING_PROJECT_IRI, + labels = SharedTestDataADM.updatedLabels + ) + ) + ) + } + + private val updateListCommentsFunction: ClientFunction = + "updateListComments" description "Updates the comments of a list." params( + "listIri" description "The IRI of the list." paramType UriDatatype, + "projectIri" description "The IRI of the project that the list belongs to." paramType UriDatatype, + "comments" description "The new comments of the list." paramType ArrayType(StringLiteral) + ) doThis { + httpPut( + path = arg("listIri") / str("ListInfoComment"), + body = Some(json( + "listIri" -> arg("listIri"), + "projectIri" -> arg("projectIri"), + "comments" -> arg("comments") + )) + ) + } returns ListInfoResponse + + private def updateListCommentsTestRequest: Future[SourceCodeFileContent] = { + FastFuture.successful( + SourceCodeFileContent( + filePath = SourceCodeFilePath.makeJsonPath("update-list-comments-request"), + text = SharedTestDataADM.updateListCommentsRequest( + listIri = "http://rdfh.ch/lists/0001/treeList", + projectIri = SharedTestDataADM.ANYTHING_PROJECT_IRI, + comments = SharedTestDataADM.updatedComments + ) + ) + ) + } + + // ------------------------------------- + // --------------- NODES --------------- + // ------------------------------------- + + + /** create a new child node */ @Path("/{IRI}") - @ApiOperation(value = "Add new child node", nickname = "addListChildNode", httpMethod = "POST", response = classOf[ListNodeInfoGetResponseADM]) + @ApiOperation( + value = "Add new child node", + nickname = "addListChildNode", + httpMethod = "POST", + response = classOf[ListNodeInfoGetResponseADM] + ) @ApiImplicitParams(Array( new ApiImplicitParam(name = "body", value = "\"node\" to create", required = true, dataTypeClass = classOf[CreateChildNodeApiRequestADM], paramType = "body") @@ -313,11 +500,12 @@ class ListsRouteADM(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit @ApiResponses(Array( new ApiResponse(code = 500, message = "Internal server error") )) - def postListChildNode: Route = path(ListsBasePath / Segment) { iri => + def postListChildNode: Route = path(ListsBasePath / "nodes") { post { /* add node to existing list node. the existing list node can be either the root or a child */ entity(as[CreateChildNodeApiRequestADM]) { apiRequest => requestContext => + val iri = apiRequest.parentNodeIri val parentNodeIri = stringFormatter.validateAndEscapeIri(iri, throw BadRequestException(s"Invalid param list IRI: $iri")) val requestMessage: Future[ListChildNodeCreateRequestADM] = for { @@ -345,7 +533,7 @@ class ListsRouteADM(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit "node" description "The node to be created." paramType CreateChildNodeRequest ) doThis { httpPost( - path = argMember("node", "parentNodeIri"), + path = str("nodes"), body = Some(arg("node")) ) } returns ListNodeInfoResponse @@ -364,24 +552,39 @@ class ListsRouteADM(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit ) } - /* delete list node which should also delete its children */ - def deleteListNode: Route = path(ListsBasePath / Segment) { iri => + /** return node with children */ + def getListNode: Route = path(ListsBasePath / "nodes" / Segment) { iri => + get { + throw NotImplementedException("Method not implemented.") + ??? + } + } + + /** create new child node with given IRI */ + def putNodeWithIRI: Route = path(ListsBasePath / "nodes" / Segment) { iri => + put { + throw NotImplementedException("Method not implemented.") + ??? + } + } + + /** delete list node with children if not used */ + def deleteListNode: Route = path(ListsBasePath / "nodes" / Segment) { iri => delete { - /* delete (deactivate) list */ throw NotImplementedException("Method not implemented.") ??? } } - def getListInfo: Route = path(ListsBasePath / "infos" / Segment) { iri => + /** return information about a single node (without children) */ + def getListNodeInfo: Route = path(ListsBasePath / "nodes" / Segment / "Info") { iri => get { - /* return information about a list (without children) */ requestContext => - val listIri = stringFormatter.validateAndEscapeIri(iri, throw BadRequestException(s"Invalid param list IRI: $iri")) + val nodeIri = stringFormatter.validateAndEscapeIri(iri, throw BadRequestException(s"Invalid param list node IRI: $iri")) - val requestMessage: Future[ListInfoGetRequestADM] = for { + val requestMessage: Future[ListNodeInfoGetRequestADM] = for { requestingUser <- getUserADM(requestContext) - } yield ListInfoGetRequestADM(listIri, requestingUser) + } yield ListNodeInfoGetRequestADM(nodeIri, requestingUser) RouteUtilADM.runJsonRoute( requestMessage, @@ -393,36 +596,64 @@ class ListsRouteADM(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit } } - private val getListInfoFunction: ClientFunction = - "getListInfo" description "Returns information about a list." params ( - "iri" description "The IRI of the list." paramType UriDatatype + private val getListNodeInfoFunction: ClientFunction = + "getListNodeInfo" description "Returns information about a list node." params ( + "iri" description "The IRI of the node." paramType UriDatatype ) doThis { httpGet( - path = str("infos") / arg("iri") + path = str("nodes") / arg("iri") / str("Info") ) - } returns ListInfoResponse + } returns ListNodeInfoResponse - private def getListInfoTestResponse: Future[SourceCodeFileContent] = { + private def getListNodeInfoTestResponse: Future[SourceCodeFileContent] = { for { - responseStr <- doTestDataRequest(Get(s"$baseApiUrl$ListsBasePathString/infos/$anythingList") ~> addCredentials(BasicHttpCredentials(SharedTestDataADM.rootUser.email, SharedTestDataADM.testPass))) + responseStr <- doTestDataRequest(Get(s"$baseApiUrl$ListsBasePathString/nodes/$anythingListNode/Info") ~> addCredentials(BasicHttpCredentials(SharedTestDataADM.rootUser.email, SharedTestDataADM.testPass))) } yield SourceCodeFileContent( - filePath = SourceCodeFilePath.makeJsonPath("get-list-info-response"), + filePath = SourceCodeFilePath.makeJsonPath("get-list-node-info-response"), text = responseStr ) } - def updateListInfo: Route = path(ListsBasePath / "infos" / Segment) { iri => + /** update list node */ + def putNodeInfo: Route = path(ListsBasePath / "nodes" / Segment / Segment) { (iri, attribute) => put { - /* update list info */ - entity(as[ChangeListInfoApiRequestADM]) { apiRequest => + entity(as[ChangeListNodeInfoApiRequestADM]) { apiRequest => requestContext => - val listIri = stringFormatter.validateAndEscapeIri(iri, throw BadRequestException(s"Invalid param list IRI: $iri")) - - val requestMessage: Future[ListInfoChangeRequestADM] = for { + val nodeIri = stringFormatter.validateAndEscapeIri(iri, throw BadRequestException(s"Invalid param node IRI: $iri")) + + val requestPayload = attribute match { + case "NodeInfoName" => + ChangeListNodeInfoPayloadADM( + nodeIri = apiRequest.nodeIri, + projectIri = apiRequest.projectIri, + name = apiRequest.name.getOrElse(throw BadRequestException("Missing parameter for name")) match { + case "" => Some(None) + case _ => Some(apiRequest.name) + } + ) + + case "NodeInfoLabel" => ChangeListNodeInfoPayloadADM( + nodeIri = apiRequest.nodeIri, + projectIri = apiRequest.projectIri, + labels = apiRequest.labels + ) + case "NodeInfoComment" => + ChangeListNodeInfoPayloadADM( + nodeIri = apiRequest.nodeIri, + projectIri = apiRequest.projectIri, + comments = apiRequest.comments + ) + + case "NodeInfoPosition" => throw NotImplementedException("Move listnode to new position is not implemented.") + case "NodeInfoParent" => throw NotImplementedException("Move listnode to new parent is not implemented.") + case _ => throw BadRequestException(s"Invalid attribute: $attribute") + } + + val requestMessage: Future[ListNodeInfoChangeRequestADM] = for { requestingUser <- getUserADM(requestContext) - } yield ListInfoChangeRequestADM( - listIri = listIri, - changeListRequest = apiRequest, + } yield ListNodeInfoChangeRequestADM( + nodeIri = nodeIri, + changeNodeRequest = requestPayload, requestingUser = requestingUser, apiRequestID = UUID.randomUUID() ) @@ -438,51 +669,90 @@ class ListsRouteADM(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit } } - def getListNodeInfo: 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")) + private val updateListNodeNameFunction: ClientFunction = + "updateListNodeName" description "Updates the name of a list node." params( + "nodeIri" description "The IRI of the list node." paramType UriDatatype, + "projectIri" description "The IRI of the project that the list belongs to." paramType UriDatatype, + "name" description "The new name of the list node." paramType StringDatatype + ) doThis { + httpPut( + path = str("nodes") / arg("nodeIri") / str("NodeInfoName"), + body = Some(json( + "nodeIri" -> arg("nodeIri"), + "projectIri" -> arg("projectIri"), + "name" -> arg("name") + )) + ) + } returns ListInfoResponse - val requestMessage: Future[ListNodeInfoGetRequestADM] = for { - requestingUser <- getUserADM(requestContext) - } yield ListNodeInfoGetRequestADM(listIri, requestingUser) + private def updateListNodeNameTestRequest: Future[SourceCodeFileContent] = { + FastFuture.successful( + SourceCodeFileContent( + filePath = SourceCodeFilePath.makeJsonPath("update-list-node-name-request"), + text = SharedTestDataADM.updateListNodeNameRequest( + nodeIri = "http://rdfh.ch/lists/0001/treeList01", + projectIri = SharedTestDataADM.ANYTHING_PROJECT_IRI, + name = "newTestName" + ) + ) + ) + } - RouteUtilADM.runJsonRoute( - requestMessage, - requestContext, - settings, - responderManager, - log + private val updateListNodeLabelsFunction: ClientFunction = + "updateListNodeLabels" description "Updates the labels of a list node." params( + "nodeIri" description "The IRI of the list node." paramType UriDatatype, + "projectIri" description "The IRI of the project that the list belongs to." paramType UriDatatype, + "labels" description "The new labels of the list node." paramType ArrayType(StringLiteral) + ) doThis { + httpPut( + path = str("nodes") / arg("nodeIri") / str("NodeInfoLabel"), + body = Some(json( + "nodeIri" -> arg("nodeIri"), + "projectIri" -> arg("projectIri"), + "labels" -> arg("labels") + )) + ) + } returns ListInfoResponse + + private def updateListNodeLabelsTestRequest: Future[SourceCodeFileContent] = { + FastFuture.successful( + SourceCodeFileContent( + filePath = SourceCodeFilePath.makeJsonPath("update-list-node-labels-request"), + text = SharedTestDataADM.updateListNodeLabelsRequest( + nodeIri = "http://rdfh.ch/lists/0001/treeList01", + projectIri = SharedTestDataADM.ANYTHING_PROJECT_IRI, + labels = SharedTestDataADM.updatedLabels ) - } ~ - put { - /* update list node */ - throw NotImplementedException("Method not implemented.") - ??? - } ~ - delete { - /* delete list node */ - throw NotImplementedException("Method not implemented.") - ??? - } + ) + ) } - private val getListNodeInfoFunction: ClientFunction = - "getListNodeInfo" description "Returns information about a list node." params ( - "iri" description "The IRI of the node." paramType UriDatatype - ) doThis { - httpGet( - path = str("nodes") / arg("iri") + private val updateListNodeCommentsFunction: ClientFunction = + "updateListNodeComments" description "Updates the comments of a list node." params( + "nodeIri" description "The IRI of the list node." paramType UriDatatype, + "projectIri" description "The IRI of the project that the list belongs to." paramType UriDatatype, + "comments" description "The new comments of the list node." paramType ArrayType(StringLiteral) + ) doThis { + httpPut( + path = str("nodes") / arg("nodeIri") / str("NodeInfoComment"), + body = Some(json( + "nodeIri" -> arg("nodeIri"), + "projectIri" -> arg("projectIri"), + "comments" -> arg("comments") + )) ) - } returns ListNodeInfoResponse + } returns ListInfoResponse - private def getListNodeInfoTestResponse: Future[SourceCodeFileContent] = { - for { - responseStr <- doTestDataRequest(Get(s"$baseApiUrl$ListsBasePathString/nodes/$anythingListNode") ~> addCredentials(BasicHttpCredentials(SharedTestDataADM.rootUser.email, SharedTestDataADM.testPass))) - } yield SourceCodeFileContent( - filePath = SourceCodeFilePath.makeJsonPath("get-list-node-info-response"), - text = responseStr + private def updateListNodeCommentsTestRequest: Future[SourceCodeFileContent] = { + FastFuture.successful( + SourceCodeFileContent( + filePath = SourceCodeFilePath.makeJsonPath("update-list-node-comments-request"), + text = SharedTestDataADM.updateListNodeCommentsRequest( + nodeIri = "http://rdfh.ch/lists/0001/treeList01", + projectIri = SharedTestDataADM.ANYTHING_PROJECT_IRI, + comments = SharedTestDataADM.updatedComments + ) + ) ) } @@ -494,10 +764,15 @@ class ListsRouteADM(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit getListsInProjectFunction, createListFunction, getListFunction, - updateListInfoFunction, + updateListNameFunction, + updateListLabelsFunction, + updateListCommentsFunction, createChildNodeFunction, getListInfoFunction, - getListNodeInfoFunction + getListNodeInfoFunction, + updateListNodeNameFunction, + updateListNodeLabelsFunction, + updateListNodeCommentsFunction ) /** @@ -511,10 +786,15 @@ class ListsRouteADM(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit getListsTestResponse, createListTestRequest, getListTestResponse, - updateListInfoTestRequest, + updateListNameTestRequest, + updateListLabelsTestRequest, + updateListCommentsTestRequest, createChildNodeTestRequest, getListInfoTestResponse, - getListNodeInfoTestResponse + getListNodeInfoTestResponse, + updateListNodeNameTestRequest, + updateListNodeLabelsTestRequest, + updateListNodeCommentsTestRequest ) } } diff --git a/webapi/src/main/scala/org/knora/webapi/util/clientapi/ClientApi.scala b/webapi/src/main/scala/org/knora/webapi/util/clientapi/ClientApi.scala index afbbcadfb8..9d7cab5af3 100644 --- a/webapi/src/main/scala/org/knora/webapi/util/clientapi/ClientApi.scala +++ b/webapi/src/main/scala/org/knora/webapi/util/clientapi/ClientApi.scala @@ -213,8 +213,8 @@ trait ClientEndpoint { val paramClasses: Set[SmartIri] = function.params.map { param => param.objectType }.collect { - case classRef: ClassRef => classRef.classIri - }.toSet + case typeWithClassIri: TypeWithClassIri => typeWithClassIri.getClassIri + }.flatten.toSet paramClasses ++ maybeReturnedClass }.toSet @@ -337,7 +337,7 @@ object EndpointFunctionDSL { * @param name the name of the argument. * @return an [[ArgValue]]. */ - def arg(name: String) = ArgValue(name) + def arg(name: String): ArgValue = ArgValue(name) /** * Constructs an [[ArgValue]] referring to a member of a function argument. @@ -346,7 +346,7 @@ object EndpointFunctionDSL { * @param member the name of the member of the argument. * @return an [[ArgValue]]. */ - def argMember(name: String, member: String) = ArgValue(name = name, memberVariableName = Some(member)) + def argMember(name: String, member: String): ArgValue = ArgValue(name = name, memberVariableName = Some(member)) /** * A URL path representing the base path of an endpoint. diff --git a/webapi/src/main/scala/org/knora/webapi/util/clientapi/GeneratorFrontEnd.scala b/webapi/src/main/scala/org/knora/webapi/util/clientapi/GeneratorFrontEnd.scala index a5e6fc7061..bb102bc1f3 100644 --- a/webapi/src/main/scala/org/knora/webapi/util/clientapi/GeneratorFrontEnd.scala +++ b/webapi/src/main/scala/org/knora/webapi/util/clientapi/GeneratorFrontEnd.scala @@ -70,7 +70,7 @@ class GeneratorFrontEnd(routeData: KnoraRouteData, requestingUser: UserADM) { def getClassDefsRec(classIri: SmartIri, definitionAcc: ClientDefsWithOntologies): Future[ClientDefsWithOntologies] = { val className = classIri.getEntityName - // Is this the IRI of a derived class that hasn't been generated yet? + // Is this is the IRI of a derived class that hasn't been generated yet? if (ClassRef.isGeneratedDerivedClassName(className)) { // Yes. Do we already have the definition of the base class? @@ -84,7 +84,7 @@ class GeneratorFrontEnd(routeData: KnoraRouteData, requestingUser: UserADM) { getClassDefsRec(baseClassIri, definitionAcc) } } else { - // Get the IRI of the ontology containing the class. + // No, this is the IRI of a class defined in an ontology. Get the IRI of the ontology containing the class. val classOntologyIri: SmartIri = classIri.getOntologyFromEntity for { diff --git a/webapi/src/main/twirl/queries/sparql/admin/updateListInfo.scala.txt b/webapi/src/main/twirl/queries/sparql/admin/updateListInfo.scala.txt index b3ec1ee0c2..61c7539a33 100644 --- a/webapi/src/main/twirl/queries/sparql/admin/updateListInfo.scala.txt +++ b/webapi/src/main/twirl/queries/sparql/admin/updateListInfo.scala.txt @@ -29,6 +29,7 @@ * @param listIri the IRI of the list we want to update. * @param projectIri the IRI of the list's project. * @param listClassIri the IRI of the OWL class that the list should belong to. + * @param maybeName the new optional name value * @param maybelabels the new optional label values. * @param maybeComments the new optional comment values. *@ @@ -37,8 +38,9 @@ listIri: IRI, projectIri: IRI, listClassIri: IRI, - maybeLabels: Seq[StringLiteralV2], - maybeComments: Seq[StringLiteralV2]) + maybeName: Option[Option[String]], + maybeLabels: Option[Seq[StringLiteralV2]], + maybeComments: Option[Seq[StringLiteralV2]]) prefix rdf: prefix rdfs: @@ -50,8 +52,12 @@ DELETE { @* Delete current values, for which we have a new one. *@ + @if(maybeName.nonEmpty) { + ?listIri knora-base:listNodeName ?currentListNodeName . + } + @if(maybeLabels.nonEmpty) { - ?listIri rdfs:label ?currentLabels . + ?listIri rdfs:label ?currentLabels . } @if(maybeComments.nonEmpty) { @@ -62,8 +68,14 @@ DELETE { @* Add the new values. *@ + @if(maybeName.nonEmpty) { + @if(maybeName.get.nonEmpty) { + ?listIri knora-base:listNodeName "@maybeName.get.get"^^xsd:string . + } + } + @if(maybeLabels.nonEmpty) { - @for(label <- maybeLabels) { + @for(label <- maybeLabels.get) { @if(label.language.nonEmpty) { ?listIri rdfs:label """@label.value"""@@@{label.language.get} . } else { @@ -73,7 +85,7 @@ DELETE { } @if(maybeComments.nonEmpty) { - @for(comment <- maybeComments) { + @for(comment <- maybeComments.get) { @if(comment.language.nonEmpty) { ?listIri rdfs:comment """@comment.value"""@@@{comment.language.get} . } else { @@ -109,6 +121,8 @@ WHERE { ?listIri knora-base:attachedToProject ?projectIri . + optional {?listIri knora-base:listNodeName ?currentListNodeName .} + optional {?listIri rdfs:label ?currentLabels .} optional {?listIri rdfs:comment ?currentComments .} diff --git a/webapi/src/main/twirl/queries/sparql/admin/updateListNodeInfo.scala.txt b/webapi/src/main/twirl/queries/sparql/admin/updateListNodeInfo.scala.txt new file mode 100644 index 0000000000..bd70871f10 --- /dev/null +++ b/webapi/src/main/twirl/queries/sparql/admin/updateListNodeInfo.scala.txt @@ -0,0 +1,125 @@ +@* + * Copyright © 2015-2019 the contributors (see Contributors.md). + * + * 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 . + *@ + +@import org.knora.webapi.IRI +@import org.knora.webapi.messages.store.triplestoremessages.StringLiteralV2 + +@** + * Updates an existing listnode with the provided values. + * + * @param dataNamedGraph the named graph to update. + * @param triplestore the name of the triplestore being used. The template uses this value to exclude inferred + results from the WHERE clause of the update. + * @param nodeIri the IRI of the listnode we want to update. + * @param nodeClassIri the IRI of the OWL class that the node should belong to. + * @param maybeName the new optional name value + * @param maybelabels the new optional label values. + * @param maybeComments the new optional comment values. + *@ +@(dataNamedGraph: IRI, + triplestore: String, + nodeIri: IRI, + nodeClassIri: IRI, + maybeName: Option[Option[String]], + maybeLabels: Option[Seq[StringLiteralV2]], + maybeComments: Option[Seq[StringLiteralV2]]) + +prefix rdf: +prefix rdfs: +prefix xsd: +prefix knora-base: + +WITH <@dataNamedGraph> +DELETE { + + @* Delete current values, for which we have a new one. *@ + + @if(maybeName.nonEmpty) { + ?nodeIri knora-base:listNodeName ?currentListNodeName . + } + + @if(maybeLabels.nonEmpty) { + ?nodeIri rdfs:label ?currentLabels . + } + + @if(maybeComments.nonEmpty) { + ?nodeIri rdfs:comment ?currentComments . + } + +} INSERT { + + @* Add the new values. *@ + + @if(maybeName.nonEmpty) { + @if(maybeName.get.nonEmpty) { + ?nodeIri knora-base:listNodeName "@maybeName.get.get"^^xsd:string . + } + } + + @if(maybeLabels.nonEmpty) { + @for(label <- maybeLabels.get) { + @if(label.language.nonEmpty) { + ?nodeIri rdfs:label """@label.value"""@@@{label.language.get} . + } else { + ?nodeIri rdfs:label """@label.value"""^^xsd:string . + } + } + } + + @if(maybeComments.nonEmpty) { + @for(comment <- maybeComments.get) { + @if(comment.language.nonEmpty) { + ?nodeIri rdfs:comment """@comment.value"""@@@{comment.language.get} . + } else { + ?nodeIri rdfs:comment """@comment.value"""^^xsd:string . + } + } + } +} + +@* + +GraphDB's consistency checking requires reasoning, but reasoning interferes with certain things +in the WHERE clauses of our SPARQL updates, so we set a GraphDB-specific flag to return only +explicit statements in the WHERE clause here. + +*@ + +@triplestore match { + case "graphdb" | "graphdb-free" => { + USING + } + + case other => {} +} + +WHERE { + BIND(IRI("@nodeIri") AS ?nodeIri) + + @* Get all current defined values. *@ + + ?nodeIri rdf:type knora-base:ListNode . + + optional {?nodeIri knora-base:listNodeName ?currentListNodeName .} + + optional {?nodeIri rdfs:label ?currentLabels .} + + optional {?nodeIri rdfs:comment ?currentComments .} + +} diff --git a/webapi/src/test/scala/org/knora/webapi/e2e/admin/ListsADME2ESpec.scala b/webapi/src/test/scala/org/knora/webapi/e2e/admin/ListsADME2ESpec.scala index 3c50dd7b10..bf46bedc39 100644 --- a/webapi/src/test/scala/org/knora/webapi/e2e/admin/ListsADME2ESpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/e2e/admin/ListsADME2ESpec.scala @@ -30,7 +30,7 @@ import org.knora.webapi.messages.v1.responder.sessionmessages.SessionJsonProtoco import org.knora.webapi.messages.v1.routing.authenticationmessages.CredentialsADM import org.knora.webapi.testing.tags.E2ETest import org.knora.webapi.util.{AkkaHttpUtils, MutableTestIri} -import org.knora.webapi.{E2ESpec, SharedListsTestDataADM, SharedTestDataADM, SharedTestDataV1} +import org.knora.webapi.{E2ESpec, SharedListsTestDataADM, SharedTestDataADM} import scala.concurrent.duration._ @@ -78,7 +78,12 @@ class ListsADME2ESpec extends E2ESpec(ListsADME2ESpec.config) with SessionJsonPr private val treeListInfo: ListRootNodeInfoADM = SharedListsTestDataADM.treeListInfo private val treeListNodes: Seq[ListChildNodeADM] = SharedListsTestDataADM.treeListChildNodes - "The Lists Route (/admin/lists)" when { + val newListIri = new MutableTestIri + val firstChildIri = new MutableTestIri + val secondChildIri = new MutableTestIri + val thirdChildIri = new MutableTestIri + + "The Lists Route ('/admin/lists')" when { "used to query information about lists" should { @@ -126,7 +131,7 @@ class ListsADME2ESpec extends E2ESpec(ListsADME2ESpec.config) with SessionJsonPr } "return basic list information" in { - val request = Get(baseApiUrl + s"/admin/lists/infos/http%3A%2F%2Frdfh.ch%2Flists%2F0001%2FtreeList") ~> addCredentials(rootCreds.basicHttpCredentials) + 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}") @@ -154,14 +159,10 @@ class ListsADME2ESpec extends E2ESpec(ListsADME2ESpec.config) with SessionJsonPr "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 request = Post(baseApiUrl + s"/admin/lists", HttpEntity(ContentTypes.`application/json`, SharedTestDataADM.createListRequest)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val params = SharedTestDataADM.createListRequest + val request = Post(baseApiUrl + s"/admin/lists", HttpEntity(ContentTypes.`application/json`, params)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) val response: HttpResponse = singleAwaitingRequest(request) // log.debug(s"response: ${response.toString}") response.status should be(StatusCodes.OK) @@ -171,6 +172,9 @@ class ListsADME2ESpec extends E2ESpec(ListsADME2ESpec.config) with SessionJsonPr val listInfo = receivedList.listinfo listInfo.projectIri should be (SharedTestDataADM.ANYTHING_PROJECT_IRI) + val name = listInfo.name + name should be (Some("newList")) + val labels: Seq[StringLiteralV2] = listInfo.labels.stringLiterals labels.size should be (1) labels.head should be (StringLiteralV2(value = "Neue Liste", language = Some("de"))) @@ -252,13 +256,66 @@ class ListsADME2ESpec extends E2ESpec(ListsADME2ESpec.config) with SessionJsonPr } - "update basic list information" in { - val params = SharedTestDataADM.updateListInfoRequest(newListIri.get) - val encodedListUrl = java.net.URLEncoder.encode(newListIri.get, "utf-8") + "return a BadRequestException during list change when the provided name is already in use" in { + val params = + s""" + |{ + | "listIri": "${newListIri.get}", + | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", + | "name": "newList" + |} + """.stripMargin + + val encodedListIri = java.net.URLEncoder.encode(newListIri.get, "utf-8") - val request = Put(baseApiUrl + s"/admin/lists/infos/" + encodedListUrl, HttpEntity(ContentTypes.`application/json`, params)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val request = Put(baseApiUrl + s"/admin/lists/" + encodedListIri + "/ListInfoName", HttpEntity(ContentTypes.`application/json`, params)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val response: HttpResponse = singleAwaitingRequest(request) + response.status should be(StatusCodes.BadRequest) + } + + "update basic list information: name" in { + val paramsUpdate = SharedTestDataADM.updateListNameRequest( + listIri = newListIri.get, + projectIri = SharedTestDataADM.ANYTHING_PROJECT_IRI, + name = "newTestName" + ) + + val encodedListIri = java.net.URLEncoder.encode(newListIri.get, "utf-8") + + val requestUpdate = Put(baseApiUrl + s"/admin/lists/" + encodedListIri + "/ListInfoName", HttpEntity(ContentTypes.`application/json`, paramsUpdate)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val responseUpdate: HttpResponse = singleAwaitingRequest(requestUpdate) + responseUpdate.status should be(StatusCodes.OK) + + val receivedListInfoUpdate: ListRootNodeInfoADM = AkkaHttpUtils.httpResponseToJson(responseUpdate).fields("listinfo").convertTo[ListRootNodeInfoADM] + receivedListInfoUpdate.projectIri should be (SharedTestDataADM.ANYTHING_PROJECT_IRI) + receivedListInfoUpdate.name should be (Some("newTestName")) + + val paramsDelete = SharedTestDataADM.updateListNameRequest( + listIri = newListIri.get, + projectIri = SharedTestDataADM.ANYTHING_PROJECT_IRI, + name = "" + ) + + val requestDelete = Put(baseApiUrl + s"/admin/lists/" + encodedListIri + "/ListInfoName", HttpEntity(ContentTypes.`application/json`, paramsDelete)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val responseDelete: HttpResponse = singleAwaitingRequest(requestDelete) + responseDelete.status should be(StatusCodes.OK) + + val receivedListInfoDelete: ListRootNodeInfoADM = AkkaHttpUtils.httpResponseToJson(responseDelete).fields("listinfo").convertTo[ListRootNodeInfoADM] + receivedListInfoDelete.projectIri should be (SharedTestDataADM.ANYTHING_PROJECT_IRI) + receivedListInfoDelete.name.isEmpty should be (true) + } + + "update basic list information: label" in { + val params = SharedTestDataADM.updateListLabelsRequest( + listIri = newListIri.get, + projectIri = SharedTestDataADM.ANYTHING_PROJECT_IRI, + labels = SharedTestDataADM.updatedLabels + ) + + val encodedListIri = java.net.URLEncoder.encode(newListIri.get, "utf-8") + + val request = Put(baseApiUrl + s"/admin/lists/" + encodedListIri + "/ListInfoLabel", HttpEntity(ContentTypes.`application/json`, params)) ~> 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] @@ -267,9 +324,42 @@ class ListsADME2ESpec extends E2ESpec(ListsADME2ESpec.config) with SessionJsonPr val labels: Seq[StringLiteralV2] = receivedListInfo.labels.stringLiterals labels.size should be (2) + } + + "update basic list information: comment" in { + val paramsUpdate = SharedTestDataADM.updateListCommentsRequest( + listIri = newListIri.get, + projectIri = SharedTestDataADM.ANYTHING_PROJECT_IRI, + comments = SharedTestDataADM.updatedComments + ) + + val encodedListIri = java.net.URLEncoder.encode(newListIri.get, "utf-8") - val comments = receivedListInfo.comments.stringLiterals - comments.size should be (2) + val requestUpdate = Put(baseApiUrl + s"/admin/lists/" + encodedListIri + "/ListInfoComment", HttpEntity(ContentTypes.`application/json`, paramsUpdate)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val responseUpdate: HttpResponse = singleAwaitingRequest(requestUpdate) + responseUpdate.status should be(StatusCodes.OK) + + val receivedListInfoComment: ListRootNodeInfoADM = AkkaHttpUtils.httpResponseToJson(responseUpdate).fields("listinfo").convertTo[ListRootNodeInfoADM] + receivedListInfoComment.projectIri should be (SharedTestDataADM.ANYTHING_PROJECT_IRI) + + val commentsUpdate = receivedListInfoComment.comments.stringLiterals + commentsUpdate.size should be (2) + + val paramsDelete = SharedTestDataADM.updateListCommentsRequest( + listIri = newListIri.get, + projectIri = SharedTestDataADM.ANYTHING_PROJECT_IRI, + comments = Vector.empty + ) + + val requestDelete = Put(baseApiUrl + s"/admin/lists/" + encodedListIri + "/ListInfoComment", HttpEntity(ContentTypes.`application/json`, paramsDelete)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val responseDelete: HttpResponse = singleAwaitingRequest(requestDelete) + responseDelete.status should be(StatusCodes.OK) + + val receivedListInfoDelete: ListRootNodeInfoADM = AkkaHttpUtils.httpResponseToJson(responseDelete).fields("listinfo").convertTo[ListRootNodeInfoADM] + receivedListInfoDelete.projectIri should be (SharedTestDataADM.ANYTHING_PROJECT_IRI) + + val commentsDelete = receivedListInfoDelete.comments.stringLiterals + commentsDelete.size should be (0) } "return a ForbiddenException if the user updating the list is not project or system admin" in { @@ -278,22 +368,33 @@ class ListsADME2ESpec extends E2ESpec(ListsADME2ESpec.config) with SessionJsonPr |{ | "listIri": "${newListIri.get}", | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", + | "name": "newTestName", | "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 encodedListIri = java.net.URLEncoder.encode(newListIri.get, "utf-8") - val request = Put(baseApiUrl + s"/admin/lists/infos/" + encodedListUrl, HttpEntity(ContentTypes.`application/json`, params)) ~> addCredentials(anythingUserCreds.basicHttpCredentials) - val response: HttpResponse = singleAwaitingRequest(request) - // log.debug(s"response: ${response.toString}") - response.status should be(StatusCodes.Forbidden) + val requestName = Put(baseApiUrl + s"/admin/lists/" + encodedListIri + "/ListInfoName", HttpEntity(ContentTypes.`application/json`, params)) ~> addCredentials(anythingUserCreds.basicHttpCredentials) + val responseName: HttpResponse = singleAwaitingRequest(requestName) + + responseName.status should be(StatusCodes.Forbidden) + + val requestLabel = Put(baseApiUrl + s"/admin/lists/" + encodedListIri + "/ListInfoLabel", HttpEntity(ContentTypes.`application/json`, params)) ~> addCredentials(anythingUserCreds.basicHttpCredentials) + val responseLabel: HttpResponse = singleAwaitingRequest(requestLabel) + + responseLabel.status should be(StatusCodes.Forbidden) + + val requestComment = Put(baseApiUrl + s"/admin/lists/" + encodedListIri + "/ListInfoComment", HttpEntity(ContentTypes.`application/json`, params)) ~> addCredentials(anythingUserCreds.basicHttpCredentials) + val responseComment: HttpResponse = singleAwaitingRequest(requestComment) + + responseComment.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") + val encodedListIri = java.net.URLEncoder.encode(newListIri.get, "utf-8") // empty list IRI val params01 = @@ -301,15 +402,21 @@ class ListsADME2ESpec extends E2ESpec(ListsADME2ESpec.config) with SessionJsonPr |{ | "listIri": "", | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", + | "name": "newTestName", | "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/infos/" + encodedListUrl, HttpEntity(ContentTypes.`application/json`, params01)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) - val response01: HttpResponse = singleAwaitingRequest(request01) - // log.debug(s"response: ${response.toString}") - response01.status should be(StatusCodes.BadRequest) + val request01Name = Put(baseApiUrl + s"/admin/lists/" + encodedListIri + "/ListInfoName", HttpEntity(ContentTypes.`application/json`, params01)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val response01Name: HttpResponse = singleAwaitingRequest(request01Name) + response01Name.status should be(StatusCodes.BadRequest) + val request01Label = Put(baseApiUrl + s"/admin/lists/" + encodedListIri + "/ListInfoLabel", HttpEntity(ContentTypes.`application/json`, params01)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val response01Label: HttpResponse = singleAwaitingRequest(request01Label) + response01Label.status should be(StatusCodes.BadRequest) + val request01Comment = Put(baseApiUrl + s"/admin/lists/" + encodedListIri + "/ListInfoComment", HttpEntity(ContentTypes.`application/json`, params01)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val response01Comment: HttpResponse = singleAwaitingRequest(request01Comment) + response01Comment.status should be(StatusCodes.BadRequest) // empty project val params02 = @@ -317,52 +424,77 @@ class ListsADME2ESpec extends E2ESpec(ListsADME2ESpec.config) with SessionJsonPr |{ | "listIri": "${newListIri.get}", | "projectIri": "", + | "name": "newTestName", | "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/infos/" + encodedListUrl, HttpEntity(ContentTypes.`application/json`, params02)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) - val response02: HttpResponse = singleAwaitingRequest(request02) - // log.debug(s"response: ${response.toString}") - response02.status should be(StatusCodes.BadRequest) - - // empty parameters + val request02Name = Put(baseApiUrl + s"/admin/lists/" + encodedListIri + "/ListInfoName", HttpEntity(ContentTypes.`application/json`, params02)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val response02Name: HttpResponse = singleAwaitingRequest(request02Name) + response02Name.status should be(StatusCodes.BadRequest) + val request02Label = Put(baseApiUrl + s"/admin/lists/" + encodedListIri + "/ListInfoLabel", HttpEntity(ContentTypes.`application/json`, params02)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val response02Label: HttpResponse = singleAwaitingRequest(request02Label) + response02Label.status should be(StatusCodes.BadRequest) + val request02Comment = Put(baseApiUrl + s"/admin/lists/" + encodedListIri + "/ListInfoComment", HttpEntity(ContentTypes.`application/json`, params02)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val response02Comment: HttpResponse = singleAwaitingRequest(request02Comment) + response02Comment.status should be(StatusCodes.BadRequest) + + // empty parameters (at least one label must be supplied) val params03 = s""" |{ | "listIri": "${newListIri.get}", | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", + | "name": "newTestName", | "labels": [], | "comments": [] |} """.stripMargin - val request03 = Put(baseApiUrl + s"/admin/lists/infos/" + encodedListUrl, HttpEntity(ContentTypes.`application/json`, params03)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) - val response03: HttpResponse = singleAwaitingRequest(request03) - // log.debug(s"response: ${response.toString}") - response03.status should be(StatusCodes.BadRequest) + val request03Label = Put(baseApiUrl + s"/admin/lists/" + encodedListIri + "/ListInfoLabel", HttpEntity(ContentTypes.`application/json`, params03)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val response03Label: HttpResponse = singleAwaitingRequest(request03Label) + response03Label.status should be(StatusCodes.BadRequest) + } + } + } + + "The Nodes Route ('/admin/lists/nodes')" when { + + "used to query information about nodes" should { + + "return a list" ignore { } - "add child to list - to the root node" in { + "return a sublist" ignore { - val encodedListUrl = java.net.URLEncoder.encode(newListIri.get, "utf-8") + } - val name = "first" - val label = "New First Child List Node Value" - val comment = "New First Child List Node Comment" + "return basic node information" ignore { - val params = SharedTestDataADM.addChildListNodeRequest( - parentNodeIri = newListIri.get, - name = name, - label = label, - comment = comment - ) + } + + } + + "used to modify node information" should { + + "add child to list - to the root node" in { - val request = Post(baseApiUrl + s"/admin/lists/" + encodedListUrl, HttpEntity(ContentTypes.`application/json`, params)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val params = + s""" + |{ + | "parentNodeIri": "${newListIri.get}", + | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", + | "name": "first", + | "labels": [{ "value": "New First Child List Node Value", "language": "en"}], + | "comments": [{ "value": "New First Child List Node Comment", "language": "en"}] + |} + """.stripMargin + + val request = Post(baseApiUrl + s"/admin/lists/nodes", HttpEntity(ContentTypes.`application/json`, params)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) val response: HttpResponse = singleAwaitingRequest(request) - // println(s"response: ${response.toString}") + //println(s"response: ${response.toString}") response.status should be(StatusCodes.OK) val received: ListNodeInfoADM = AkkaHttpUtils.httpResponseToJson(response).fields("nodeinfo").convertTo[ListNodeInfoADM] @@ -376,12 +508,12 @@ class ListsADME2ESpec extends E2ESpec(ListsADME2ESpec.config) with SessionJsonPr // 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")))) + labels.sorted should be (Seq(StringLiteralV2(value = "New First Child List Node Value", 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")))) + comments.sorted should be (Seq(StringLiteralV2(value = "New First Child List Node Comment", language = Some("en")))) // check position val position = childNodeInfo.position @@ -396,20 +528,18 @@ class ListsADME2ESpec extends E2ESpec(ListsADME2ESpec.config) with SessionJsonPr "add second child to list - to the root node" in { - val encodedListUrl = java.net.URLEncoder.encode(newListIri.get, "utf-8") - - val name = "second" - val label = "New Second Child List Node Value" - val comment = "New Second Child List Node Comment" - - val params = SharedTestDataADM.addChildListNodeRequest( - parentNodeIri = newListIri.get, - name = name, - label = label, - comment = comment - ) + val params = + s""" + |{ + | "parentNodeIri": "${newListIri.get}", + | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", + | "name": "second", + | "labels": [{ "value": "New Second Child List Node Value", "language": "en"}], + | "comments": [{ "value": "New Second Child List Node Comment", "language": "en"}] + |} + """.stripMargin - val request = Post(baseApiUrl + s"/admin/lists/" + encodedListUrl, HttpEntity(ContentTypes.`application/json`, params)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val request = Post(baseApiUrl + s"/admin/lists/nodes", HttpEntity(ContentTypes.`application/json`, params)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) val response: HttpResponse = singleAwaitingRequest(request) // println(s"response: ${response.toString}") response.status should be(StatusCodes.OK) @@ -425,12 +555,12 @@ class ListsADME2ESpec extends E2ESpec(ListsADME2ESpec.config) with SessionJsonPr // 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")))) + labels.sorted should be (Seq(StringLiteralV2(value = "New Second Child List Node Value", 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")))) + comments.sorted should be (Seq(StringLiteralV2(value = "New Second Child List Node Comment", language = Some("en")))) // check position val position = childNodeInfo.position @@ -446,20 +576,18 @@ class ListsADME2ESpec extends E2ESpec(ListsADME2ESpec.config) with SessionJsonPr "add child to second child node" in { - val encodedListUrl = java.net.URLEncoder.encode(secondChildIri.get, "utf-8") - - val name = "third" - val label = "New Third Child List Node Value" - val comment = "New Third Child List Node Comment" - - val params = SharedTestDataADM.addChildListNodeRequest( - parentNodeIri = secondChildIri.get, - name = name, - label = label, - comment = comment - ) + val params = + s""" + |{ + | "parentNodeIri": "${secondChildIri.get}", + | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", + | "name": "third", + | "labels": [{ "value": "New Child List Node Value", "language": "en"}], + | "comments": [{ "value": "New Child List Node Comment", "language": "en"}] + |} + """.stripMargin - val request = Post(baseApiUrl + s"/admin/lists/" + encodedListUrl, HttpEntity(ContentTypes.`application/json`, params)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val request = Post(baseApiUrl + s"/admin/lists/nodes", HttpEntity(ContentTypes.`application/json`, params)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) val response: HttpResponse = singleAwaitingRequest(request) // println(s"response: ${response.toString}") response.status should be(StatusCodes.OK) @@ -475,12 +603,12 @@ class ListsADME2ESpec extends E2ESpec(ListsADME2ESpec.config) with SessionJsonPr // 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")))) + labels.sorted should be (Seq(StringLiteralV2(value = "New Child List Node Value", 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")))) + comments.sorted should be (Seq(StringLiteralV2(value = "New Child List Node Comment", language = Some("en")))) // check position val position = childNodeInfo.position @@ -493,6 +621,300 @@ class ListsADME2ESpec extends E2ESpec(ListsADME2ESpec.config) with SessionJsonPr thirdChildIri.set(childNodeInfo.id) } + "return a 'ForbiddenException' if the user creating the node is not project or system admin" in { + val params = + s""" + |{ + | "parentNodeIri": "${secondChildIri.get}", + | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", + | "name": "first", + | "labels": [{ "value": "New Child List Node Value", "language": "en"}], + | "comments": [] + |} + """.stripMargin + + val request = Post(baseApiUrl + s"/admin/lists/nodes", HttpEntity(ContentTypes.`application/json`, params)) ~> addCredentials(anythingUserCreds.basicHttpCredentials) + val response: HttpResponse = singleAwaitingRequest(request) + response.status should be(StatusCodes.Forbidden) + } + + "return a BadRequestException during node creation when payload is not correct" in { + // no parentNode IRI + val params00 = + s""" + |{ + | "parentNodeIri": "", + | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", + | "name": "first", + | "labels": [{ "value": "New Child List Node Value", "language": "en"}], + | "comments": [] + |} + """.stripMargin + + val request00 = Post(baseApiUrl + s"/admin/lists/nodes", HttpEntity(ContentTypes.`application/json`, params00)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val response00: HttpResponse = singleAwaitingRequest(request00) + response00.status should be(StatusCodes.BadRequest) + + + // invalid parentNode IRI + val params01 = + s""" + |{ + | "parentNodeIri": "invalidIRI", + | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", + | "name": "first", + | "labels": [{ "value": "New Child List Node Value", "language": "en"}], + | "comments": [] + |} + """.stripMargin + + val request01 = Post(baseApiUrl + s"/admin/lists/nodes", HttpEntity(ContentTypes.`application/json`, params01)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val response01: HttpResponse = singleAwaitingRequest(request01) + response01.status should be(StatusCodes.BadRequest) + + + // no project IRI + val params02 = + s""" + |{ + | "parentNodeIri": "${secondChildIri.get}", + | "projectIri": "", + | "name": "first", + | "labels": [{ "value": "New Child List Node Value", "language": "en"}], + | "comments": [] + |} + """.stripMargin + + val request02 = Post(baseApiUrl + s"/admin/lists/nodes", HttpEntity(ContentTypes.`application/json`, params02)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val response02: HttpResponse = singleAwaitingRequest(request02) + response02.status should be(StatusCodes.BadRequest) + + + // invalid project IRI + val params03 = + s""" + |{ + | "parentNodeIri": "${secondChildIri.get}", + | "projectIri": "invalidIRI", + | "name": "first", + | "labels": [{ "value": "New Child List Node Value", "language": "en"}], + | "comments": [] + |} + """.stripMargin + + val request03 = Post(baseApiUrl + s"/admin/lists/nodes", HttpEntity(ContentTypes.`application/json`, params03)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val response03: HttpResponse = singleAwaitingRequest(request03) + response03.status should be(StatusCodes.BadRequest) + + + // missing label + val params05 = + s""" + |{ + | "parentNodeIri": "${secondChildIri.get}", + | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", + | "name": "first", + | "labels": [], + | "comments": [] + |} + """.stripMargin + + val request05 = Post(baseApiUrl + s"/admin/lists/nodes", HttpEntity(ContentTypes.`application/json`, params05)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val response05: HttpResponse = singleAwaitingRequest(request05) + // println(s"response: ${response03.toString}") + response05.status should be(StatusCodes.BadRequest) + + } + + "return a BadRequestException during list node change when the provided name is already in use" in { + val params = + s""" + |{ + | "nodeIri": "${firstChildIri.get}", + | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", + | "name": "first" + |} + """.stripMargin + + val encodedListIri = java.net.URLEncoder.encode(firstChildIri.get, "utf-8") + + val request = Put(baseApiUrl + s"/admin/lists/nodes/" + encodedListIri + "/NodeInfoName", HttpEntity(ContentTypes.`application/json`, params)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val response: HttpResponse = singleAwaitingRequest(request) + response.status should be(StatusCodes.BadRequest) + } + + "update basic list node information: name" in { + val paramsUpdate = SharedTestDataADM.updateListNodeNameRequest( + nodeIri = firstChildIri.get, + projectIri = SharedTestDataADM.ANYTHING_PROJECT_IRI, + name = "newTestName" + ) + + val encodedListNodeUrl = java.net.URLEncoder.encode(firstChildIri.get, "utf-8") + + val requestUpdate = Put(baseApiUrl + s"/admin/lists/nodes/" + encodedListNodeUrl + "/NodeInfoName", HttpEntity(ContentTypes.`application/json`, paramsUpdate)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val responseUpdate: HttpResponse = singleAwaitingRequest(requestUpdate) + responseUpdate.status should be(StatusCodes.OK) + + val receivedListNodeInfoUpdate: ListChildNodeInfoADM = AkkaHttpUtils.httpResponseToJson(responseUpdate).fields("nodeinfo").convertTo[ListChildNodeInfoADM] + receivedListNodeInfoUpdate.name should be (Some("newTestName")) + + val paramsDelete = SharedTestDataADM.updateListNodeNameRequest( + nodeIri = firstChildIri.get, + projectIri = SharedTestDataADM.ANYTHING_PROJECT_IRI, + name = "" + ) + + val requestDelete = Put(baseApiUrl + s"/admin/lists/nodes/" + encodedListNodeUrl + "/NodeInfoName", HttpEntity(ContentTypes.`application/json`, paramsDelete)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val responseDelete: HttpResponse = singleAwaitingRequest(requestDelete) + responseDelete.status should be(StatusCodes.OK) + + val receivedListNodeInfoDelete: ListChildNodeInfoADM = AkkaHttpUtils.httpResponseToJson(responseDelete).fields("nodeinfo").convertTo[ListChildNodeInfoADM] + receivedListNodeInfoDelete.name.isEmpty should be (true) + } + + "update basic list node information: label" in { + val params = SharedTestDataADM.updateListNodeLabelsRequest( + nodeIri = firstChildIri.get, + projectIri = SharedTestDataADM.ANYTHING_PROJECT_IRI, + labels = SharedTestDataADM.updatedLabels + ) + + val encodedListNodeUrl = java.net.URLEncoder.encode(firstChildIri.get, "utf-8") + + val request = Put(baseApiUrl + s"/admin/lists/nodes/" + encodedListNodeUrl + "/NodeInfoLabel", HttpEntity(ContentTypes.`application/json`, params)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val response: HttpResponse = singleAwaitingRequest(request) + response.status should be(StatusCodes.OK) + + val receivedListNodeInfo: ListChildNodeInfoADM = AkkaHttpUtils.httpResponseToJson(response).fields("nodeinfo").convertTo[ListChildNodeInfoADM] + + val labels: Seq[StringLiteralV2] = receivedListNodeInfo.labels.stringLiterals + labels.size should be (2) + } + + "update basic list node information: comment" in { + val paramsUpdate = SharedTestDataADM.updateListNodeCommentsRequest( + nodeIri = firstChildIri.get, + projectIri = SharedTestDataADM.ANYTHING_PROJECT_IRI, + comments = SharedTestDataADM.updatedComments + ) + + val encodedListNodeUrl = java.net.URLEncoder.encode(firstChildIri.get, "utf-8") + + val requestUpdate = Put(baseApiUrl + s"/admin/lists/nodes/" + encodedListNodeUrl + "/NodeInfoComment", HttpEntity(ContentTypes.`application/json`, paramsUpdate)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val responseUpdate: HttpResponse = singleAwaitingRequest(requestUpdate) + responseUpdate.status should be(StatusCodes.OK) + + val receivedListInfoComment: ListChildNodeInfoADM = AkkaHttpUtils.httpResponseToJson(responseUpdate).fields("nodeinfo").convertTo[ListChildNodeInfoADM] + val commentsUpdate = receivedListInfoComment.comments.stringLiterals + commentsUpdate.size should be (2) + + val paramsDelete = SharedTestDataADM.updateListNodeCommentsRequest( + nodeIri = firstChildIri.get, + projectIri = SharedTestDataADM.ANYTHING_PROJECT_IRI, + comments = Vector.empty + ) + + val requestDelete = Put(baseApiUrl + s"/admin/lists/nodes/" + encodedListNodeUrl + "/NodeInfoComment", HttpEntity(ContentTypes.`application/json`, paramsDelete)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val responseDelete: HttpResponse = singleAwaitingRequest(requestDelete) + responseDelete.status should be(StatusCodes.OK) + + val receivedListInfoDelete: ListChildNodeInfoADM = AkkaHttpUtils.httpResponseToJson(responseDelete).fields("nodeinfo").convertTo[ListChildNodeInfoADM] + val commentsDelete = receivedListInfoDelete.comments.stringLiterals + commentsDelete.size should be (0) + } + + "return a 'ForbiddenException' if the user updating the list node is not project or system admin" in { + val params = + s""" + |{ + | "nodeIri": "${firstChildIri.get}", + | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", + | "name": "newTestName", + | "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 encodedListNodeUrl = java.net.URLEncoder.encode(firstChildIri.get, "utf-8") + + val requestName = Put(baseApiUrl + s"/admin/lists/nodes/" + encodedListNodeUrl + "/NodeInfoName", HttpEntity(ContentTypes.`application/json`, params)) ~> addCredentials(anythingUserCreds.basicHttpCredentials) + val responseName: HttpResponse = singleAwaitingRequest(requestName) + responseName.status should be(StatusCodes.Forbidden) + + val requestLabel = Put(baseApiUrl + s"/admin/lists/nodes/" + encodedListNodeUrl + "/NodeInfoLabel", HttpEntity(ContentTypes.`application/json`, params)) ~> addCredentials(anythingUserCreds.basicHttpCredentials) + val responseLabel: HttpResponse = singleAwaitingRequest(requestLabel) + responseLabel.status should be(StatusCodes.Forbidden) + + val requestComment = Put(baseApiUrl + s"/admin/lists/nodes/" + encodedListNodeUrl + "/NodeInfoComment", HttpEntity(ContentTypes.`application/json`, params)) ~> addCredentials(anythingUserCreds.basicHttpCredentials) + val responseComment: HttpResponse = singleAwaitingRequest(requestComment) + responseComment.status should be(StatusCodes.Forbidden) + } + + "return a BadRequestException during list node change when payload is not correct" in { + val encodedListNodeUrl = java.net.URLEncoder.encode(firstChildIri.get, "utf-8") + + // empty node IRI + val params01 = + s""" + |{ + | "nodeIri": "", + | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", + | "name": "newTestName", + | "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 request01Name = Put(baseApiUrl + s"/admin/lists/nodes/" + encodedListNodeUrl + "/NodeInfoName", HttpEntity(ContentTypes.`application/json`, params01)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val response01Name: HttpResponse = singleAwaitingRequest(request01Name) + response01Name.status should be(StatusCodes.BadRequest) + val request01Label = Put(baseApiUrl + s"/admin/lists/nodes/" + encodedListNodeUrl + "/NodeInfoLabel", HttpEntity(ContentTypes.`application/json`, params01)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val response01Label: HttpResponse = singleAwaitingRequest(request01Label) + response01Label.status should be(StatusCodes.BadRequest) + val request01Comment = Put(baseApiUrl + s"/admin/lists/nodes/" + encodedListNodeUrl + "/NodeInfoComment", HttpEntity(ContentTypes.`application/json`, params01)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val response01Comment: HttpResponse = singleAwaitingRequest(request01Comment) + response01Comment.status should be(StatusCodes.BadRequest) + + // empty project + val params02 = + s""" + |{ + | "nodeIri": "${firstChildIri.get}", + | "projectIri": "", + | "name": "newTestName", + | "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 request02Name = Put(baseApiUrl + s"/admin/lists/nodes/" + encodedListNodeUrl + "/NodeInfoName", HttpEntity(ContentTypes.`application/json`, params02)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val response02Name: HttpResponse = singleAwaitingRequest(request02Name) + response02Name.status should be(StatusCodes.BadRequest) + val request02Label = Put(baseApiUrl + s"/admin/lists/nodes/" + encodedListNodeUrl + "/NodeInfoLabel", HttpEntity(ContentTypes.`application/json`, params02)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val response02Label: HttpResponse = singleAwaitingRequest(request02Label) + response02Label.status should be(StatusCodes.BadRequest) + val request02Comment = Put(baseApiUrl + s"/admin/lists/nodes/" + encodedListNodeUrl + "/NodeInfoComment", HttpEntity(ContentTypes.`application/json`, params02)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val response02Comment: HttpResponse = singleAwaitingRequest(request02Comment) + response02Comment.status should be(StatusCodes.BadRequest) + + // empty parameters (at least one label must be supplied) + val params03 = + s""" + |{ + | "nodeIri": "${firstChildIri.get}", + | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", + | "name": "", + | "labels": [], + | "comments": [] + |} + """.stripMargin + + val request03Label = Put(baseApiUrl + s"/admin/lists/nodes/" + encodedListNodeUrl + "/NodeInfoLabel", HttpEntity(ContentTypes.`application/json`, params03)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val response03Label: HttpResponse = singleAwaitingRequest(request03Label) + response03Label.status should be(StatusCodes.BadRequest) + } + "add flat nodes" ignore { } @@ -508,7 +930,6 @@ class ListsADME2ESpec extends E2ESpec(ListsADME2ESpec.config) with SessionJsonPr "delete node if not in use" ignore { } - } } } 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 dc8abc4361..864e9cb4ba 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 @@ -166,6 +166,7 @@ class ListsMessagesADMSpec extends WordSpecLike with Matchers with ListADMJsonPr |{ | "listIri": "", | "projectIri": "${SharedTestDataADM.IMAGES_PROJECT_IRI}", + | "name": "newTestName", | "labels": [{ "value": "Neue geönderte Liste", "language": "de"}, { "value": "Changed list", "language": "en"}], | "comments": [{ "value": "Neuer Kommentar", "language": "de"}, { "value": "New comment", "language": "en"}] |} @@ -183,6 +184,7 @@ class ListsMessagesADMSpec extends WordSpecLike with Matchers with ListADMJsonPr |{ | "listIri": "notvalidIRI", | "projectIri": "${SharedTestDataADM.IMAGES_PROJECT_IRI}", + | "name": "newTestName", | "labels": [{ "value": "Neue geönderte Liste", "language": "de"}, { "value": "Changed list", "language": "en"}], | "comments": [{ "value": "Neuer Kommentar", "language": "de"}, { "value": "New comment", "language": "en"}] |} @@ -200,6 +202,7 @@ class ListsMessagesADMSpec extends WordSpecLike with Matchers with ListADMJsonPr |{ | "listIri": "$exampleListIri", | "projectIri": "", + | "name": "newTestName", | "labels": [{ "value": "Neue geönderte Liste", "language": "de"}, { "value": "Changed list", "language": "en"}], | "comments": [{ "value": "Neuer Kommentar", "language": "de"}, { "value": "New comment", "language": "en"}] |} @@ -217,6 +220,7 @@ class ListsMessagesADMSpec extends WordSpecLike with Matchers with ListADMJsonPr |{ | "listIri": "$exampleListIri", | "projectIri": "notvalidIRI", + | "name": "newTestName", | "labels": [{ "value": "Neue geönderte Liste", "language": "de"}, { "value": "Changed list", "language": "en"}], | "comments": [{ "value": "Neuer Kommentar", "language": "de"}, { "value": "New comment", "language": "en"}] |} @@ -227,24 +231,6 @@ class ListsMessagesADMSpec extends WordSpecLike with Matchers with ListADMJsonPr thrown.getMessage should equal (PROJECT_IRI_INVALID_ERROR) } - "throw 'BadRequestException' for `ChangeListInfoApiRequestADM` when labels and comments are empty" in { - - val payload = - s""" - |{ - | "listIri": "$exampleListIri", - | "projectIri": "${SharedTestDataADM.IMAGES_PROJECT_IRI}", - | "labels": [], - | "comments": [] - |} - """.stripMargin - - val thrown = the [BadRequestException] thrownBy payload.parseJson.convertTo[ChangeListInfoApiRequestADM] - - thrown.getMessage should equal (REQUEST_NOT_CHANGING_DATA_ERROR) - - } - "throw 'BadRequestException' for `CreateChildNodeApiRequestADM` when list node iri is empty" in { val payload = @@ -335,5 +321,76 @@ class ListsMessagesADMSpec extends WordSpecLike with Matchers with ListADMJsonPr } + "throw 'BadRequestException' for `ChangeListNodeInfoApiRequestADM` when node IRI is empty" in { + + val payload = + s""" + |{ + | "nodeIri": "", + | "projectIri": "${SharedTestDataADM.IMAGES_PROJECT_IRI}", + | "name": "newTestName", + | "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 thrown = the [BadRequestException] thrownBy payload.parseJson.convertTo[ChangeListNodeInfoApiRequestADM] + + thrown.getMessage should equal (LIST_NODE_IRI_MISSING_ERROR) + } + + "throw 'BadRequestException' for `ChangeListNodeInfoApiRequestADM` when node IRI is invalid" in { + + val payload = + s""" + |{ + | "nodeIri": "notvalidIRI", + | "projectIri": "${SharedTestDataADM.IMAGES_PROJECT_IRI}", + | "name": "newTestName", + | "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 thrown = the [BadRequestException] thrownBy payload.parseJson.convertTo[ChangeListNodeInfoApiRequestADM] + + thrown.getMessage should equal (LIST_NODE_IRI_INVALID_ERROR) + } + + "throw 'BadRequestException' for `ChangeListNodeInfoApiRequestADM` when project IRI is empty" in { + + val payload = + s""" + |{ + | "nodeIri": "$exampleListIri", + | "projectIri": "", + | "name": "newTestName", + | "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 thrown = the [BadRequestException] thrownBy payload.parseJson.convertTo[ChangeListNodeInfoApiRequestADM] + + thrown.getMessage should equal (PROJECT_IRI_MISSING_ERROR) + } + + "throw 'BadRequestException' for `ChangeListNodeInfoApiRequestADM` when project IRI is invalid" in { + + val payload = + s""" + |{ + | "nodeIri": "$exampleListIri", + | "projectIri": "notvalidIRI", + | "name": "newTestName", + | "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 thrown = the [BadRequestException] thrownBy payload.parseJson.convertTo[ChangeListNodeInfoApiRequestADM] + + thrown.getMessage should equal (PROJECT_IRI_INVALID_ERROR) + } } } \ No newline at end of file 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 7912163579..6495a15bcc 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 @@ -217,17 +217,18 @@ class ListsResponderADMSpec extends CoreSpec(ListsResponderADMSpec.config) with "update basic list information" in { responderManager ! ListInfoChangeRequestADM( listIri = newListIri.get, - changeListRequest = ChangeListInfoApiRequestADM( + changeListRequest = ChangeListInfoPayloadADM( listIri = newListIri.get, projectIri = IMAGES_PROJECT_IRI, - labels = Seq( + name = Some(Some("new test name")), + labels = Some(Seq( StringLiteralV2(value = "Neue geänderte Liste", language = Some("de")), StringLiteralV2(value = "Changed list", language = Some("en")) - ), - comments = Seq( + )), + comments = Some(Seq( StringLiteralV2(value = "Neuer Kommentar", language = Some("de")), StringLiteralV2(value = "New comment", language = Some("en")) - ) + )) ), requestingUser = SharedTestDataADM.imagesUser01, apiRequestID = UUID.randomUUID @@ -238,6 +239,9 @@ class ListsResponderADMSpec extends CoreSpec(ListsResponderADMSpec.config) with val listInfo = received.listinfo listInfo.projectIri should be (IMAGES_PROJECT_IRI) + val name: String = listInfo.name.get + name should be ("new test name") + val labels: Seq[StringLiteralV2] = listInfo.labels.stringLiterals labels.size should be (2) labels.sorted should be (Seq( @@ -257,17 +261,18 @@ class ListsResponderADMSpec extends CoreSpec(ListsResponderADMSpec.config) with "return a 'ForbiddenException' if the user changing the list is not project or system admin" in { responderManager ! ListInfoChangeRequestADM( listIri = newListIri.get, - changeListRequest = ChangeListInfoApiRequestADM( + changeListRequest = ChangeListInfoPayloadADM( listIri = newListIri.get, projectIri = IMAGES_PROJECT_IRI, - labels = Seq( + name = Some(Some("newTestName")), + labels = Some(Seq( StringLiteralV2(value = "Neue geänderte Liste", language = Some("de")), StringLiteralV2(value = "Changed list", language = Some("en")) - ), - comments = Seq( + )), + comments = Some(Seq( StringLiteralV2(value = "Neuer Kommentar", language = Some("de")), StringLiteralV2(value = "New comment", language = Some("en")) - ) + )) ), requestingUser = SharedTestDataADM.imagesUser02, apiRequestID = UUID.randomUUID @@ -410,6 +415,73 @@ class ListsResponderADMSpec extends CoreSpec(ListsResponderADMSpec.config) with thirdChildIri.set(childNodeInfo.id) } + "update basic list node information" in { + responderManager ! ListNodeInfoChangeRequestADM( + nodeIri = firstChildIri.get, + changeNodeRequest = ChangeListNodeInfoPayloadADM( + nodeIri = firstChildIri.get, + projectIri = IMAGES_PROJECT_IRI, + name = Some(Some("new TestName")), + labels = Some(Seq(StringLiteralV2(value = "New Test List Node Value", language = Some("en")))), + comments = Some(Seq(StringLiteralV2(value = "New Test List Node Comment", language = Some("en")))) + ), + requestingUser = SharedTestDataADM.imagesUser01, + apiRequestID = UUID.randomUUID + ) + + val received: ListNodeInfoGetResponseADM = expectMsgType[ListNodeInfoGetResponseADM](timeout) + val nodeInfo = received.nodeinfo + + // check correct node info + val childNodeInfo = nodeInfo match { + case info: ListChildNodeInfoADM => info + case something => fail(s"expecting ListChildNodeInfoADM but got ${something.getClass.toString} instead.") + } + + // check name + val name = childNodeInfo.name + name should be (Some("new TestName")) + + // check labels + val labels: Seq[StringLiteralV2] = childNodeInfo.labels.stringLiterals + labels.size should be (1) + labels.sorted should be (Seq(StringLiteralV2(value = "New Test List Node Value", language = Some("en")))) + + // check comments + val comments = childNodeInfo.comments.stringLiterals + comments.size should be (1) + comments.sorted should be (Seq(StringLiteralV2(value = "New Test List Node 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) + } + + "return a 'ForbiddenException' if the user changing the list node is not project or system admin" in { + responderManager ! ListNodeInfoChangeRequestADM( + nodeIri = firstChildIri.get, + changeNodeRequest = ChangeListNodeInfoPayloadADM( + nodeIri = firstChildIri.get, + projectIri = IMAGES_PROJECT_IRI, + name = Some(Some("newTestName")), + labels = Some(Seq( + StringLiteralV2(value = "Neue geänderte Liste", language = Some("de")), + StringLiteralV2(value = "Changed list", language = Some("en")) + )), + comments = Some(Seq( + StringLiteralV2(value = "Neuer Kommentar", language = Some("de")), + StringLiteralV2(value = "New comment", language = Some("en")) + )) + ), + requestingUser = SharedTestDataADM.imagesUser02, + apiRequestID = UUID.randomUUID + ) + expectMsg(Failure(ForbiddenException(LIST_CHANGE_PERMISSION_ERROR))) + } "change node order" ignore {