From 7752a364e2e361f354c060b428f9e565edd15741 Mon Sep 17 00:00:00 2001 From: Marcin Procyk Date: Fri, 29 Oct 2021 21:42:15 +0200 Subject: [PATCH] feat: add value objects to list routes - old and new (DEV-65) (#1917) * refactor: fix filename * feat: add create list payload & value objects * feat: add update list payload & value objects + update routes & responder * test: update test data * many things happened, mainly add ChangeNodeInfoPayloadADM * update NodeCreatePayloadADM class * refactor list payloads * WIP - fix tests * fix tests * separate root and child nodes creation * WIP: fix tests * bring back ListChildNodeInfoADM non-optional comments * fix tests * update tests * refactor list value objects + add tests * remove redundant fields from RootNodeCreatePayloadADM * refactor list naming + remove redundand request type * fix test * add ProjectIRI value object * add CustomID value object * add ListIRI value object * add RootNodeIRI value object * fix tests * update value objects + add unit tests * update value objects * remove RootNodeIRI value object * add more checks to value objects + update tests * remove redundand tests * fix optional comments creating child node * clenup * review fixes * minor improvements --- ...yloadsADM.scala => GroupPayloadsADM.scala} | 0 .../listsmessages/ListPayloadsADM.scala | 68 ++++ ...lADM.scala => ListsErrorMessagesADM.scala} | 7 +- .../listsmessages/ListsMessagesADM.scala | 214 ++-------- .../valueObjects/ListsValueObjectsADM.scala | 155 ++++++++ .../valueObjects/ValueObjectsADM.scala | 28 +- .../listsmessages/ListsMessagesV2.scala | 13 +- .../responders/admin/ListsResponderADM.scala | 206 ++++++---- .../webapi/routing/admin/GroupsRouteADM.scala | 4 +- .../admin/lists/NewListsRouteADMFeature.scala | 134 ++++++- .../admin/lists/OldListsRouteADMFeature.scala | 154 +++++++- .../admin/lists/UpdateListItemsRouteADM.scala | 23 +- .../sparql/admin/createNewListNode.scala.txt | 18 +- .../NewListsRoutesADMFeatureE2ESpec.scala | 20 +- .../OldListsRouteADMFeatureE2ESpec.scala | 22 +- .../UpdateListItemsRouteADME2ESpec.scala | 44 ++- .../listsmessages/ListsMessagesADMSpec.scala | 370 +----------------- .../admin/responder/valueObjects/BUILD.bazel | 1 + .../ListsValueObjectsADMSpec.scala | 162 ++++++++ .../admin/ListsResponderADMSpec.scala | 177 ++++++--- .../SharedListsTestDataADM.scala | 14 +- 21 files changed, 1086 insertions(+), 748 deletions(-) rename webapi/src/main/scala/org/knora/webapi/messages/admin/responder/groupsmessages/{GroupsPayloadsADM.scala => GroupPayloadsADM.scala} (100%) create mode 100644 webapi/src/main/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListPayloadsADM.scala rename webapi/src/main/scala/org/knora/webapi/messages/admin/responder/listsmessages/{ListsMessagesUtilADM.scala => ListsErrorMessagesADM.scala} (78%) create mode 100644 webapi/src/main/scala/org/knora/webapi/messages/admin/responder/valueObjects/ListsValueObjectsADM.scala create mode 100644 webapi/src/test/scala/org/knora/webapi/messages/admin/responder/valueObjects/ListsValueObjectsADMSpec.scala diff --git a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupsPayloadsADM.scala b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupPayloadsADM.scala similarity index 100% rename from webapi/src/main/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupsPayloadsADM.scala rename to webapi/src/main/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupPayloadsADM.scala diff --git a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListPayloadsADM.scala b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListPayloadsADM.scala new file mode 100644 index 0000000000..e917c7ccce --- /dev/null +++ b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListPayloadsADM.scala @@ -0,0 +1,68 @@ +package org.knora.webapi.messages.admin.responder.listsmessages + +import org.knora.webapi.messages.admin.responder.valueObjects.{ + Comments, + Labels, + ListIRI, + ListName, + Position, + ProjectIRI +} + +/** + * List (parent node, former root node) and Node (former child node) creation payloads + */ +sealed trait NodeCreatePayloadADM +object NodeCreatePayloadADM { + final case class ListCreatePayloadADM( + id: Option[ListIRI] = None, + projectIri: ProjectIRI, + name: Option[ListName] = None, + labels: Labels, + comments: Comments + ) extends NodeCreatePayloadADM + final case class ChildNodeCreatePayloadADM( + id: Option[ListIRI] = None, + parentNodeIri: Option[ListIRI] = None, + projectIri: ProjectIRI, + name: Option[ListName] = None, + position: Option[Position] = None, + labels: Labels, + comments: Option[Comments] = None + ) extends NodeCreatePayloadADM +} + +/** + * Node Info update payload + */ +final case class NodeInfoChangePayloadADM( + listIri: ListIRI, + projectIri: ProjectIRI, + hasRootNode: Option[ListIRI] = None, + position: Option[Position] = None, + name: Option[ListName] = None, + labels: Option[Labels] = None, + comments: Option[Comments] = None +) + +/** + * Node Name update payload + */ +final case class NodeNameChangePayloadADM( + name: ListName +) + +/** + * Node Labels update payload + */ +final case class NodeLabelsChangePayloadADM( + labels: Labels +) + +/** + * Node Comments update payload + */ +final case class NodeCommentsChangePayloadADM( +// TODO: remove Option here + comments: Option[Comments] = None +) diff --git a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListsMessagesUtilADM.scala b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListsErrorMessagesADM.scala similarity index 78% rename from webapi/src/main/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListsMessagesUtilADM.scala rename to webapi/src/main/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListsErrorMessagesADM.scala index 7b3790b398..81e5ec16dd 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListsMessagesUtilADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListsErrorMessagesADM.scala @@ -5,14 +5,19 @@ package org.knora.webapi.messages.admin.responder.listsmessages -object ListsMessagesUtilADM { +object ListsErrorMessagesADM { val LIST_IRI_MISSING_ERROR = "List IRI cannot be empty." val LIST_IRI_INVALID_ERROR = "List IRI cannot be empty." val LIST_NODE_IRI_MISSING_ERROR = "List node IRI cannot be empty." val LIST_NODE_IRI_INVALID_ERROR = "List node IRI is invalid." val PROJECT_IRI_MISSING_ERROR = "Project IRI cannot be empty." val PROJECT_IRI_INVALID_ERROR = "Project IRI is invalid." + val LIST_NAME_MISSING_ERROR = "List name cannot be empty." + val LIST_NAME_INVALID_ERROR = "List name is invalid." val LABEL_MISSING_ERROR = "At least one label needs to be supplied." + val LABEL_INVALID_ERROR = "Invalid label." + val COMMENT_MISSING_ERROR = "At least one comment needs to be supplied." + val COMMENT_INVALID_ERROR = "Invalid comment." val LIST_CREATE_PERMISSION_ERROR = "A list can only be created by the project or system administrator." val LIST_NODE_CREATE_PERMISSION_ERROR = "A list node can only be created by the project or system administrator." val LIST_CHANGE_PERMISSION_ERROR = "A list can only be changed by the project or system administrator." 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 e0d90c6103..747db919b6 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 @@ -6,13 +6,13 @@ package org.knora.webapi.messages.admin.responder.listsmessages import java.util.UUID - import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport import org.knora.webapi._ -import org.knora.webapi.exceptions.{BadRequestException, ForbiddenException} +import org.knora.webapi.exceptions.BadRequestException import org.knora.webapi.feature.FeatureFactoryConfig import org.knora.webapi.messages.StringFormatter -import org.knora.webapi.messages.admin.responder.listsmessages.ListsMessagesUtilADM._ +import org.knora.webapi.messages.admin.responder.listsmessages.ListsErrorMessagesADM._ +import org.knora.webapi.messages.admin.responder.listsmessages.NodeCreatePayloadADM.ChildNodeCreatePayloadADM import org.knora.webapi.messages.admin.responder.usersmessages._ import org.knora.webapi.messages.admin.responder.{KnoraRequestADM, KnoraResponseADM} import org.knora.webapi.messages.store.triplestoremessages.{ @@ -26,44 +26,7 @@ import spray.json._ // API requests /** - * Represents an API request payload that asks the Knora API server to create a new list. At least one - * label needs to be supplied. - * - * @param id the optional custom list IRI. - * @param projectIri the IRI of the project the list belongs to. - * @param name the optional name of the list. - * @param labels the list's labels. - * @param comments the list's comments. - */ -case class CreateListApiRequestADM( - id: Option[IRI] = None, - projectIri: IRI, - name: Option[String] = None, - labels: Seq[StringLiteralV2], - comments: Seq[StringLiteralV2] -) extends ListADMJsonProtocol { - - private val stringFormatter = StringFormatter.getInstanceForConstantOntologies - - stringFormatter.validateOptionalListIri(id, throw BadRequestException(s"Invalid list IRI")) - - if (projectIri.isEmpty) { - throw BadRequestException(PROJECT_IRI_MISSING_ERROR) - } - - if (!stringFormatter.isKnoraProjectIriStr(projectIri)) { - throw BadRequestException(PROJECT_IRI_INVALID_ERROR) - } - - if (labels.isEmpty) { - throw BadRequestException(LABEL_MISSING_ERROR) - } - - def toJsValue: JsValue = createListApiRequestADMFormat.write(this) -} - -/** - * Represents an API request payload that asks the Knora API server to create a new node. + * Represents an API request payload that asks the Knora API server to create a new list or child node. * If the IRI of the parent node is given, the new node is attached to the parent node as a sublist node. * If a specific position is given, insert the child node there. Otherwise, the newly created list node will be appended * to the end of the list of children. @@ -87,56 +50,7 @@ case class CreateNodeApiRequestADM( labels: Seq[StringLiteralV2], comments: Seq[StringLiteralV2] ) extends ListADMJsonProtocol { - - private val stringFormatter = StringFormatter.getInstanceForConstantOntologies - stringFormatter.validateOptionalListIri(id, throw BadRequestException(s"Invalid list node IRI")) - - if (parentNodeIri.nonEmpty && !stringFormatter.isKnoraListIriStr(parentNodeIri.get)) { - 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.isEmpty) { - throw BadRequestException(LABEL_MISSING_ERROR) - } - - if (position.exists(_ < -1)) { - throw BadRequestException(INVALID_POSITION) - } - def toJsValue: JsValue = createListNodeApiRequestADMFormat.write(this) - - /** - * Escapes special characters within strings - */ - def escape: CreateNodeApiRequestADM = { - val escapedLabels: Seq[StringLiteralV2] = labels.map { label => - val escapedLabel = - stringFormatter.toSparqlEncodedString(label.value, throw BadRequestException(s"Invalid label: ${label.value}")) - StringLiteralV2(value = escapedLabel, language = label.language) - } - val escapedComments = comments.map { comment => - val escapedComment = - stringFormatter.toSparqlEncodedString( - comment.value, - throw BadRequestException(s"Invalid comment: ${comment.value}") - ) - StringLiteralV2(value = escapedComment, language = comment.language) - } - val escapedName: Option[String] = name match { - case None => None - case Some(value: String) => - Some(stringFormatter.toSparqlEncodedString(value, throw BadRequestException(s"Invalid string: $value"))) - } - copy(labels = escapedLabels, comments = escapedComments, name = escapedName) - } } /** @@ -159,39 +73,6 @@ case class ChangeNodeInfoApiRequestADM( labels: Option[Seq[StringLiteralV2]] = None, comments: Option[Seq[StringLiteralV2]] = None ) extends ListADMJsonProtocol { - - private val stringFormatter = StringFormatter.getInstanceForConstantOntologies - - if (listIri.isEmpty) { - throw BadRequestException(s"IRI of list item is missing.") - } - - if (!stringFormatter.isKnoraListIriStr(listIri)) { - throw BadRequestException(s"Invalid IRI is given: $listIri.") - } - - // Check that project Iri is given - if (projectIri.isEmpty) { - throw BadRequestException(PROJECT_IRI_MISSING_ERROR) - } - - // Verify the project IRI - if (!stringFormatter.isKnoraProjectIriStr(projectIri)) { - throw BadRequestException(PROJECT_IRI_INVALID_ERROR) - } - - if (hasRootNode.isDefined && !stringFormatter.isKnoraListIriStr(hasRootNode.get)) { - throw BadRequestException(s"Invalid root node IRI is given.") - } - // If payload contains labels, they should not be empty - if (labels.exists(_.isEmpty)) { - throw BadRequestException(UPDATE_REQUEST_EMPTY_LABEL_ERROR) - } - - if (position.exists(_ < -1)) { - throw BadRequestException(INVALID_POSITION) - } - def toJsValue: JsValue = changeListInfoApiRequestADMFormat.write(this) } @@ -302,25 +183,17 @@ case class NodePathGetRequestADM(iri: IRI, featureFactoryConfig: FeatureFactoryC /** * Requests the creation of a new list. * - * @param createRootNode the [[CreateNodeApiRequestADM]] information used for creating the root node of the list. + * @param createRootNode the [[NodeCreatePayloadADM]] information used for creating the root node of the list. * @param featureFactoryConfig the feature factory configuration. * @param requestingUser the user creating the new list. * @param apiRequestID the ID of the API request. */ case class ListCreateRequestADM( - createRootNode: CreateNodeApiRequestADM, + createRootNode: NodeCreatePayloadADM, featureFactoryConfig: FeatureFactoryConfig, requestingUser: UserADM, apiRequestID: UUID -) extends ListsResponderRequestADM { - // check if the requesting user is allowed to perform operation - if ( - !requestingUser.permissions.isProjectAdmin(createRootNode.projectIri) && !requestingUser.permissions.isSystemAdmin - ) { - // not project or a system admin - throw ForbiddenException(LIST_CREATE_PERMISSION_ERROR) - } -} +) extends ListsResponderRequestADM /** * Request updating basic information of an existing node. @@ -333,22 +206,11 @@ case class ListCreateRequestADM( */ case class NodeInfoChangeRequestADM( listIri: IRI, - changeNodeRequest: ChangeNodeInfoApiRequestADM, + changeNodeRequest: NodeInfoChangePayloadADM, featureFactoryConfig: FeatureFactoryConfig, requestingUser: UserADM, apiRequestID: UUID -) extends ListsResponderRequestADM { - // check if the requesting user is allowed to perform operation - if ( - !requestingUser.permissions.isProjectAdmin( - changeNodeRequest.projectIri - ) && !requestingUser.permissions.isSystemAdmin - ) { - // not project or a system admin - throw ForbiddenException(LIST_CHANGE_PERMISSION_ERROR) - } - -} +) extends ListsResponderRequestADM /** * Request the creation of a new list node, root or child. @@ -359,22 +221,11 @@ case class NodeInfoChangeRequestADM( * @param apiRequestID the ID of the API request. */ case class ListChildNodeCreateRequestADM( - createChildNodeRequest: CreateNodeApiRequestADM, + createChildNodeRequest: ChildNodeCreatePayloadADM, featureFactoryConfig: FeatureFactoryConfig, requestingUser: UserADM, apiRequestID: UUID -) extends ListsResponderRequestADM { - // check if the requesting user is allowed to perform operation - if ( - !requestingUser.permissions.isProjectAdmin( - createChildNodeRequest.projectIri - ) && !requestingUser.permissions.isSystemAdmin - ) { - // not project or a system admin - throw ForbiddenException(LIST_NODE_CREATE_PERMISSION_ERROR) - } - -} +) extends ListsResponderRequestADM /** * Request updating the name of an existing node. @@ -387,7 +238,7 @@ case class ListChildNodeCreateRequestADM( */ case class NodeNameChangeRequestADM( nodeIri: IRI, - changeNodeNameRequest: ChangeNodeNameApiRequestADM, + changeNodeNameRequest: NodeNameChangePayloadADM, featureFactoryConfig: FeatureFactoryConfig, requestingUser: UserADM, apiRequestID: UUID @@ -404,7 +255,7 @@ case class NodeNameChangeRequestADM( */ case class NodeLabelsChangeRequestADM( nodeIri: IRI, - changeNodeLabelsRequest: ChangeNodeLabelsApiRequestADM, + changeNodeLabelsRequest: NodeLabelsChangePayloadADM, featureFactoryConfig: FeatureFactoryConfig, requestingUser: UserADM, apiRequestID: UUID @@ -421,7 +272,7 @@ case class NodeLabelsChangeRequestADM( */ case class NodeCommentsChangeRequestADM( nodeIri: IRI, - changeNodeCommentsRequest: ChangeNodeCommentsApiRequestADM, + changeNodeCommentsRequest: NodeCommentsChangePayloadADM, featureFactoryConfig: FeatureFactoryConfig, requestingUser: UserADM, apiRequestID: UUID @@ -725,7 +576,7 @@ case class ListChildNodeInfoADM( id = id, name = name, labels = labels.sortByStringValue, - comments = comments.sortByStringValue, + comments = comments, position = position, hasRootNode = hasRootNode ) @@ -909,11 +760,17 @@ case class ListChildNodeADM( id: IRI, name: Option[String], labels: StringLiteralSequenceV2, - comments: StringLiteralSequenceV2, + comments: Option[StringLiteralSequenceV2], position: Int, hasRootNode: IRI, children: Seq[ListChildNodeADM] -) extends ListNodeADM(id, name, labels, comments, children) { +) extends ListNodeADM( + id, + name, + labels, + comments = comments.getOrElse(StringLiteralSequenceV2(Vector.empty[StringLiteralV2])), + children + ) { /** * Sorts the whole hierarchy. @@ -925,7 +782,7 @@ case class ListChildNodeADM( id = id, name = name, labels = labels.sortByStringValue, - comments = comments.sortByStringValue, + comments = comments, position = position, hasRootNode = hasRootNode, children = children.sortBy(_.position).map(_.sorted) @@ -938,11 +795,14 @@ case class ListChildNodeADM( val stringFormatter: StringFormatter = StringFormatter.getGeneralInstance val unescapedLabels = stringFormatter.unescapeStringLiteralSeq(labels) - val unescapedComments = stringFormatter.unescapeStringLiteralSeq(comments) + val unescapedComments = comments match { + case Some(value) => Some(stringFormatter.unescapeStringLiteralSeq(value)) + case None => None + } val unescapedName: Option[String] = name match { - case None => None case Some(value) => Some(stringFormatter.fromSparqlEncodedString(value)) + case None => None } copy(name = unescapedName, labels = unescapedLabels, comments = unescapedComments) @@ -966,7 +826,10 @@ case class ListChildNodeADM( * @return the comment in the preferred language. */ def getCommentInPreferredLanguage(userLang: String, fallbackLang: String): Option[String] = - comments.getPreferredLanguage(userLang, fallbackLang) + comments match { + case Some(value) => value.getPreferredLanguage(userLang, fallbackLang) + case None => None + } } /** @@ -1168,7 +1031,12 @@ trait ListADMJsonProtocol extends SprayJsonSupport with DefaultJsonProtocol with "id" -> child.id.toJson, "name" -> child.name.toJson, "labels" -> JsArray(child.labels.stringLiterals.map(_.toJson)), - "comments" -> JsArray(child.comments.stringLiterals.map(_.toJson)), + "comments" -> JsArray( + child.comments + .getOrElse(StringLiteralSequenceV2(Vector.empty[StringLiteralV2])) + .stringLiterals + .map(_.toJson) + ), "position" -> child.position.toJson, "hasRootNode" -> child.hasRootNode.toJson, "children" -> JsArray(child.children.map(write).toVector) @@ -1233,7 +1101,7 @@ trait ListADMJsonProtocol extends SprayJsonSupport with DefaultJsonProtocol with id = id, name = name, labels = StringLiteralSequenceV2(labels.toVector), - comments = StringLiteralSequenceV2(comments.toVector), + comments = Some(StringLiteralSequenceV2(comments.toVector)), position = maybePosition.getOrElse(throw DeserializationException("The position is not defined.")), hasRootNode = maybeHasRootNode.getOrElse(throw DeserializationException("The root node is not defined.")), children = children @@ -1373,8 +1241,6 @@ trait ListADMJsonProtocol extends SprayJsonSupport with DefaultJsonProtocol with } } - implicit val createListApiRequestADMFormat: RootJsonFormat[CreateListApiRequestADM] = - jsonFormat(CreateListApiRequestADM, "id", "projectIri", "name", "labels", "comments") implicit val createListNodeApiRequestADMFormat: RootJsonFormat[CreateNodeApiRequestADM] = jsonFormat(CreateNodeApiRequestADM, "id", "parentNodeIri", "projectIri", "name", "position", "labels", "comments") implicit val changeListInfoApiRequestADMFormat: RootJsonFormat[ChangeNodeInfoApiRequestADM] = jsonFormat( diff --git a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/valueObjects/ListsValueObjectsADM.scala b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/valueObjects/ListsValueObjectsADM.scala new file mode 100644 index 0000000000..c8508597a2 --- /dev/null +++ b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/valueObjects/ListsValueObjectsADM.scala @@ -0,0 +1,155 @@ +package org.knora.webapi.messages.admin.responder.valueObjects + +import org.knora.webapi.exceptions.BadRequestException +import org.knora.webapi.messages.StringFormatter +import org.knora.webapi.messages.admin.responder.listsmessages.ListsErrorMessagesADM.{ + COMMENT_INVALID_ERROR, + COMMENT_MISSING_ERROR, + INVALID_POSITION, + LABEL_INVALID_ERROR, + LABEL_MISSING_ERROR, + LIST_NAME_INVALID_ERROR, + LIST_NAME_MISSING_ERROR, + LIST_NODE_IRI_INVALID_ERROR, + LIST_NODE_IRI_MISSING_ERROR, + PROJECT_IRI_INVALID_ERROR, + PROJECT_IRI_MISSING_ERROR +} +import org.knora.webapi.messages.store.triplestoremessages.StringLiteralV2 + +import scala.util.{Failure, Success, Try} + +/** + * List ListIRI value object. + */ +sealed abstract case class ListIRI private (value: String) +object ListIRI { + val stringFormatter = StringFormatter.getGeneralInstance + + def create(value: String): Either[Throwable, ListIRI] = + if (value.isEmpty) { + Left(BadRequestException(LIST_NODE_IRI_MISSING_ERROR)) + } else { + if (value.nonEmpty && !stringFormatter.isKnoraListIriStr(value)) { + Left(BadRequestException(LIST_NODE_IRI_INVALID_ERROR)) + } else { + val validatedValue = Try( + stringFormatter.validateAndEscapeIri(value, throw BadRequestException(LIST_NODE_IRI_INVALID_ERROR)) + ) + + validatedValue match { + case Success(iri) => Right(new ListIRI(iri) {}) + case Failure(_) => Left(BadRequestException(LIST_NODE_IRI_INVALID_ERROR)) + } + } + } +} + +/** + * List ProjectIRI value object. + */ +sealed abstract case class ProjectIRI private (value: String) +object ProjectIRI { + val stringFormatter = StringFormatter.getGeneralInstance + + def create(value: String): Either[Throwable, ProjectIRI] = + if (value.isEmpty) { + Left(BadRequestException(PROJECT_IRI_MISSING_ERROR)) + } else { + if (value.nonEmpty && !stringFormatter.isKnoraProjectIriStr(value)) { + Left(BadRequestException(PROJECT_IRI_INVALID_ERROR)) + } else { + val validatedValue = Try( + stringFormatter.validateAndEscapeProjectIri(value, throw BadRequestException(PROJECT_IRI_INVALID_ERROR)) + ) + + validatedValue match { + case Success(iri) => Right(new ProjectIRI(iri) {}) + case Failure(_) => Left(BadRequestException(PROJECT_IRI_INVALID_ERROR)) + } + } + } +} + +/** + * List ListName value object. + */ +sealed abstract case class ListName private (value: String) +object ListName { + val stringFormatter = StringFormatter.getGeneralInstance + + def create(value: String): Either[Throwable, ListName] = + if (value.isEmpty) { + Left(BadRequestException(LIST_NAME_MISSING_ERROR)) + } else { + val validatedValue = Try( + stringFormatter.toSparqlEncodedString(value, throw BadRequestException(LIST_NAME_INVALID_ERROR)) + ) + + validatedValue match { + case Success(name) => Right(new ListName(name) {}) + case Failure(_) => Left(BadRequestException(LIST_NAME_INVALID_ERROR)) + } + } +} + +/** + * List Position value object. + */ +sealed abstract case class Position private (value: Int) +object Position { + def create(value: Int): Either[Throwable, Position] = + if (value < -1) { + Left(BadRequestException(INVALID_POSITION)) + } else { + Right(new Position(value) {}) + } +} + +/** + * List Labels value object. + */ +sealed abstract case class Labels private (value: Seq[StringLiteralV2]) +object Labels { + val stringFormatter = StringFormatter.getGeneralInstance + + def create(value: Seq[StringLiteralV2]): Either[Throwable, Labels] = + if (value.isEmpty) { + Left(BadRequestException(LABEL_MISSING_ERROR)) + } else { + val validatedLabels = Try(value.map { label => + val validatedLabel = + stringFormatter.toSparqlEncodedString(label.value, throw BadRequestException(LABEL_INVALID_ERROR)) + StringLiteralV2(value = validatedLabel, language = label.language) + }) + + validatedLabels match { + case Success(valid) => Right(new Labels(valid) {}) + case Failure(_) => Left(BadRequestException(LABEL_INVALID_ERROR)) + } + } +} + +/** + * List Comments value object. + */ +sealed abstract case class Comments private (value: Seq[StringLiteralV2]) +object Comments { + val stringFormatter = StringFormatter.getGeneralInstance + + def create(value: Seq[StringLiteralV2]): Either[Throwable, Comments] = + if (value.isEmpty) { + Left(BadRequestException(COMMENT_MISSING_ERROR)) + } else { + val validatedComments = Try(value.map { comment => + val validatedComment = + stringFormatter.toSparqlEncodedString(comment.value, throw BadRequestException(COMMENT_INVALID_ERROR)) + StringLiteralV2(value = validatedComment, language = comment.language) + }) + + validatedComments match { + case Success(valid) => Right(new Comments(valid) {}) + case Failure(_) => Left(BadRequestException(COMMENT_INVALID_ERROR)) + } + } +} diff --git a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/valueObjects/ValueObjectsADM.scala b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/valueObjects/ValueObjectsADM.scala index 99554cf585..fd422202b4 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/valueObjects/ValueObjectsADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/valueObjects/ValueObjectsADM.scala @@ -215,21 +215,6 @@ object Logo { self => } } -/** Groups value objects */ - -/** - * Group Name value object. - */ -sealed abstract case class Name private (value: String) -object Name { - def create(value: String): Either[Throwable, Name] = - if (value.isEmpty) { - Left(BadRequestException("Missing Name")) - } else { - Right(new Name(value) {}) - } -} - /** Shared value objects */ /** @@ -262,3 +247,16 @@ object Description { Validation.succeed(new Description(value) {}) } } + +/** + * Name value object. + */ +sealed abstract case class Name private (value: String) +object Name { + def create(value: String): Either[Throwable, Name] = + if (value.isEmpty) { + Left(BadRequestException("Missing Name")) + } else { + Right(new Name(value) {}) + } +} diff --git a/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/listsmessages/ListsMessagesV2.scala b/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/listsmessages/ListsMessagesV2.scala index 2745ae2d3e..dcda2c4457 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/listsmessages/ListsMessagesV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/listsmessages/ListsMessagesV2.scala @@ -10,7 +10,7 @@ import org.knora.webapi.feature.FeatureFactoryConfig import org.knora.webapi.messages.IriConversions._ import org.knora.webapi.messages.admin.responder.listsmessages._ import org.knora.webapi.messages.admin.responder.usersmessages.UserADM -import org.knora.webapi.messages.store.triplestoremessages.StringLiteralSequenceV2 +import org.knora.webapi.messages.store.triplestoremessages.{StringLiteralSequenceV2, StringLiteralV2} import org.knora.webapi.messages.util.rdf import org.knora.webapi.messages.util.rdf._ import org.knora.webapi.messages.v2.responder.{KnoraJsonLDResponseV2, KnoraRequestV2} @@ -86,11 +86,16 @@ case class ListGetResponseV2(list: ListADM, userLang: String, fallbackLang: Stri * @return a [[JsonLDObject]] representing the node. */ def makeNode(node: ListChildNodeADM): JsonLDObject = { - val label: Map[IRI, JsonLDString] = makeMapIriToJSONLDString(OntologyConstants.Rdfs.Label, node.labels, userLang, fallbackLang) - val comment: Map[IRI, JsonLDString] = - makeMapIriToJSONLDString(OntologyConstants.Rdfs.Comment, node.comments, userLang, fallbackLang) + val comment: Map[IRI, JsonLDString] = { + makeMapIriToJSONLDString( + OntologyConstants.Rdfs.Comment, + node.comments.getOrElse(StringLiteralSequenceV2(Vector.empty[StringLiteralV2])), + userLang, + fallbackLang + ) + } val position: Map[IRI, JsonLDInt] = Map( OntologyConstants.KnoraBase.ListNodePosition.toSmartIri.toOntologySchema(ApiV2Complex).toString -> JsonLDInt( 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 788b2fef88..9be2342052 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 @@ -6,14 +6,14 @@ package org.knora.webapi.responders.admin import java.util.UUID - import akka.http.scaladsl.util.FastFuture import akka.pattern._ import org.knora.webapi._ import org.knora.webapi.exceptions._ import org.knora.webapi.feature.FeatureFactoryConfig import org.knora.webapi.messages.IriConversions._ -import org.knora.webapi.messages.admin.responder.listsmessages.ListsMessagesUtilADM._ +import org.knora.webapi.messages.admin.responder.listsmessages.ListsErrorMessagesADM._ +import org.knora.webapi.messages.admin.responder.listsmessages.NodeCreatePayloadADM.ChildNodeCreatePayloadADM import org.knora.webapi.messages.admin.responder.listsmessages._ import org.knora.webapi.messages.admin.responder.projectsmessages.{ProjectADM, ProjectGetADM, ProjectIdentifierADM} import org.knora.webapi.messages.admin.responder.usersmessages._ @@ -23,6 +23,7 @@ import org.knora.webapi.messages.util.{KnoraSystemInstances, ResponderData} import org.knora.webapi.messages.{OntologyConstants, SmartIri} import org.knora.webapi.responders.Responder.handleUnexpectedMessage import org.knora.webapi.responders.{IriLocker, Responder} +import org.knora.webapi.messages.admin.responder.valueObjects.{ListIRI, ListName, ProjectIRI} import scala.annotation.tailrec import scala.concurrent.Future @@ -585,7 +586,7 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde .get(OntologyConstants.KnoraBase.ListNodeName.toSmartIri) .map(_.head.asInstanceOf[StringLiteralV2].value), labels = StringLiteralSequenceV2(labels.toVector.sortBy(_.language)), - comments = StringLiteralSequenceV2(comments.toVector.sortBy(_.language)), + comments = Some(StringLiteralSequenceV2(comments.toVector.sortBy(_.language))), position = positionOption.getOrElse( throw InconsistentRepositoryDataException( s"Required position property missing for list node $nodeIri." @@ -684,7 +685,7 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde id = nodeIri, name = nameOption, labels = StringLiteralSequenceV2(labels.toVector.sortBy(_.language)), - comments = StringLiteralSequenceV2(comments.toVector.sortBy(_.language)), + comments = Some(StringLiteralSequenceV2(comments.toVector.sortBy(_.language))), children = children.map(_.sorted), position = position, hasRootNode = hasRootNode @@ -833,24 +834,33 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde * @return a [newListNodeIri] */ private def createNode( - createNodeRequest: CreateNodeApiRequestADM, + createNodeRequest: NodeCreatePayloadADM, featureFactoryConfig: FeatureFactoryConfig ): Future[IRI] = { +// println("ZZZZZ-createNode", createNodeRequest) + + val (id, parentNodeIri, projectIri, name, position) = createNodeRequest match { + case parent: NodeCreatePayloadADM.ListCreatePayloadADM => + (parent.id, None, parent.projectIri, parent.name, None) + case node: NodeCreatePayloadADM.ChildNodeCreatePayloadADM => + (node.id, node.parentNodeIri, node.projectIri, node.name, node.position) + } + def getPositionOfNewChild(children: Seq[ListChildNodeADM]): Int = { - if (createNodeRequest.position.exists(_ > children.size)) { - val givenPosition = createNodeRequest.position.get + if (position.exists(_.value > children.size)) { + val givenPosition = position.map(_.value) throw BadRequestException( s"Invalid position given $givenPosition, maximum allowed position is = ${children.size}." ) } - val position = if (createNodeRequest.position.isEmpty || createNodeRequest.position.exists(_.equals(-1))) { + val newPosition = if (position.isEmpty || position.exists(_.value.equals(-1))) { children.size } else { - createNodeRequest.position.get + position.get.value } - position + newPosition } def getRootNodeIri(parentListNode: ListNodeADM): IRI = @@ -910,23 +920,26 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde for { /* Verify that the project exists by retrieving it. We need the project information so that we can calculate the data graph and IRI for the new node. */ maybeProject <- (responderManager ? ProjectGetADM( - identifier = ProjectIdentifierADM(maybeIri = Some(createNodeRequest.projectIri)), + identifier = ProjectIdentifierADM(maybeIri = Some(projectIri.value)), featureFactoryConfig = featureFactoryConfig, KnoraSystemInstances.Users.SystemUser )).mapTo[Option[ProjectADM]] project: ProjectADM = maybeProject match { case Some(project: ProjectADM) => project - case None => throw BadRequestException(s"Project '${createNodeRequest.projectIri}' not found.") + case None => throw BadRequestException(s"Project '${projectIri}' not found.") } /* verify that the list node name is unique for the project */ - projectUniqueNodeName <- listNodeNameIsProjectUnique(createNodeRequest.projectIri, createNodeRequest.name) + projectUniqueNodeName <- listNodeNameIsProjectUnique( + projectIri.value, + name + ) _ = if (!projectUniqueNodeName) { - val escapedName = createNodeRequest.name.get + val escapedName = name.get.value val unescapedName = stringFormatter.fromSparqlEncodedString(escapedName) throw BadRequestException( - s"The node name ${unescapedName} is already used by a list inside the project ${createNodeRequest.projectIri}." + s"The node name ${unescapedName} is already used by a list inside the project ${projectIri.value}." ) } @@ -934,10 +947,10 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde dataNamedGraph: IRI = stringFormatter.projectDataNamedGraphV2(project) // if parent node is known, find the root node of the list and the position of the new child node - (position: Option[Int], rootNodeIri: Option[IRI]) <- - if (createNodeRequest.parentNodeIri.nonEmpty) { + (newPosition: Option[Int], rootNodeIri: Option[IRI]) <- + if (parentNodeIri.nonEmpty) { getRootNodeAndPositionOfNewChild( - parentNodeIri = createNodeRequest.parentNodeIri.get, + parentNodeIri = parentNodeIri.get.value, dataNamedGraph = dataNamedGraph, featureFactoryConfig = featureFactoryConfig ) @@ -946,26 +959,61 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde } // check the custom IRI; if not given, create an unused IRI - customListIri: Option[SmartIri] = createNodeRequest.id.map(iri => iri.toSmartIri) + customListIri: Option[SmartIri] = id.map(_.value).map(_.toSmartIri) maybeShortcode: String = project.shortcode newListNodeIri: IRI <- checkOrCreateEntityIri(customListIri, stringFormatter.makeRandomListIri(maybeShortcode)) - // Create the new list node - createNewListSparqlString = org.knora.webapi.messages.twirl.queries.sparql.admin.txt - .createNewListNode( - dataNamedGraph = dataNamedGraph, - triplestore = settings.triplestoreType, - listClassIri = OntologyConstants.KnoraBase.ListNode, - projectIri = createNodeRequest.projectIri, - nodeIri = newListNodeIri, - parentNodeIri = createNodeRequest.parentNodeIri, - rootNodeIri = rootNodeIri, - position = position, - maybeName = createNodeRequest.name, - maybeLabels = createNodeRequest.labels, - maybeComments = createNodeRequest.comments - ) - .toString + // Create the new list node depending on type + createNewListSparqlString: String = createNodeRequest match { + case NodeCreatePayloadADM.ListCreatePayloadADM( + _, + projectIri, + name, + labels, + comments + ) => { + org.knora.webapi.messages.twirl.queries.sparql.admin.txt + .createNewListNode( + dataNamedGraph = dataNamedGraph, + triplestore = settings.triplestoreType, + listClassIri = OntologyConstants.KnoraBase.ListNode, + projectIri = projectIri.value, + nodeIri = newListNodeIri, + parentNodeIri = None, + rootNodeIri = rootNodeIri, + position = None, + maybeName = name.map(_.value), + maybeLabels = labels.value, + maybeComments = Some(comments.value) + ) + .toString + } + case NodeCreatePayloadADM.ChildNodeCreatePayloadADM( + _, + parentNodeIri, + projectIri, + name, + position, + labels, + comments + ) => { + org.knora.webapi.messages.twirl.queries.sparql.admin.txt + .createNewListNode( + dataNamedGraph = dataNamedGraph, + triplestore = settings.triplestoreType, + listClassIri = OntologyConstants.KnoraBase.ListNode, + projectIri = projectIri.value, + nodeIri = newListNodeIri, + parentNodeIri = parentNodeIri.map(_.value), + rootNodeIri = rootNodeIri, + position = newPosition, + maybeName = name.map(_.value), + maybeLabels = labels.value, + maybeComments = comments.map(_.value) + ) + .toString + } + } _ <- (storeManager ? SparqlUpdateRequest(createNewListSparqlString)).mapTo[SparqlUpdateResponse] } yield newListNodeIri @@ -980,16 +1028,18 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde * @return a [[RootNodeInfoGetResponseADM]] */ private def listCreateRequestADM( - createRootRequest: CreateNodeApiRequestADM, + createRootRequest: NodeCreatePayloadADM, featureFactoryConfig: FeatureFactoryConfig, apiRequestID: UUID ): Future[ListGetResponseADM] = { +// println("XXXXX-listCreateRequestADM") + /** * The actual task run with an IRI lock. */ def listCreateTask( - createRootRequest: CreateNodeApiRequestADM, + createRootRequest: NodeCreatePayloadADM, featureFactoryConfig: FeatureFactoryConfig, apiRequestID: UUID ): Future[ListGetResponseADM] = @@ -1036,7 +1086,7 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde */ private def nodeInfoChangeRequest( nodeIri: IRI, - changeNodeRequest: ChangeNodeInfoApiRequestADM, + changeNodeRequest: NodeInfoChangePayloadADM, featureFactoryConfig: FeatureFactoryConfig, apiRequestID: UUID ): Future[NodeInfoGetResponseADM] = { @@ -1044,17 +1094,17 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde def verifyUpdatedNode(updatedNode: ListNodeInfoADM): Unit = { if (changeNodeRequest.labels.nonEmpty) { - if (updatedNode.getLabels.stringLiterals.diff(changeNodeRequest.labels.get).nonEmpty) + if (updatedNode.getLabels.stringLiterals.diff(changeNodeRequest.labels.get.value).nonEmpty) throw UpdateNotPerformedException("Lists's 'labels' where not updated. Please report this as a possible bug.") } if (changeNodeRequest.comments.nonEmpty) { - if (updatedNode.getComments.stringLiterals.diff(changeNodeRequest.comments.get).nonEmpty) + if (updatedNode.getComments.stringLiterals.diff(changeNodeRequest.comments.get.value).nonEmpty) throw UpdateNotPerformedException("List's 'comments' was not updated. Please report this as a possible bug.") } if (changeNodeRequest.name.nonEmpty) { - if (updatedNode.getName.nonEmpty && updatedNode.getName.get != changeNodeRequest.name.get) + if (updatedNode.getName.nonEmpty && updatedNode.getName.get != changeNodeRequest.name.get.value) throw UpdateNotPerformedException("List's 'name' was not updated. Please report this as a possible bug.") } } @@ -1064,15 +1114,17 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde */ def nodeInfoChangeTask( nodeIri: IRI, - changeNodeRequest: ChangeNodeInfoApiRequestADM, + changeNodeRequest: NodeInfoChangePayloadADM, featureFactoryConfig: FeatureFactoryConfig, apiRequestID: UUID ): Future[NodeInfoGetResponseADM] = for { +// _ <- Future(println("77777", nodeIri, changeNodeRequest.listIri)) + // check if nodeIRI in path and payload match _ <- Future( - if (!nodeIri.equals(changeNodeRequest.listIri)) + if (!nodeIri.equals(changeNodeRequest.listIri.value)) throw BadRequestException("IRI in path and payload don't match.") ) @@ -1121,7 +1173,7 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde * @return a [[ChildNodeInfoGetResponseADM]] */ private def listChildNodeCreateRequestADM( - createChildNodeRequest: CreateNodeApiRequestADM, + createChildNodeRequest: ChildNodeCreatePayloadADM, featureFactoryConfig: FeatureFactoryConfig, apiRequestID: UUID ): Future[ChildNodeInfoGetResponseADM] = { @@ -1130,7 +1182,7 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde * The actual task run with an IRI lock. */ def listChildNodeCreateTask( - createChildNodeRequest: CreateNodeApiRequestADM, + createChildNodeRequest: ChildNodeCreatePayloadADM, featureFactoryConfig: FeatureFactoryConfig, apiRequestID: UUID ): Future[ChildNodeInfoGetResponseADM] = @@ -1180,14 +1232,14 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde */ private def nodeNameChangeRequest( nodeIri: IRI, - changeNodeNameRequest: ChangeNodeNameApiRequestADM, + changeNodeNameRequest: NodeNameChangePayloadADM, featureFactoryConfig: FeatureFactoryConfig, requestingUser: UserADM, apiRequestID: UUID ): Future[NodeInfoGetResponseADM] = { def verifyUpdatedNode(updatedNode: ListNodeInfoADM): Unit = - if (updatedNode.getName.nonEmpty && updatedNode.getName.get != changeNodeNameRequest.name) + if (updatedNode.getName.nonEmpty && updatedNode.getName.get != changeNodeNameRequest.name.value) throw UpdateNotPerformedException("Node's 'name' was not updated. Please report this as a possible bug.") /** @@ -1195,7 +1247,7 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde */ def nodeNameChangeTask( nodeIri: IRI, - changeNodeNameRequest: ChangeNodeNameApiRequestADM, + changeNodeNameRequest: NodeNameChangePayloadADM, featureFactoryConfig: FeatureFactoryConfig, requestingUser: UserADM, apiRequestID: UUID @@ -1210,9 +1262,9 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde } changeNodeNameSparqlString <- getUpdateNodeInfoSparqlStatement( - changeNodeInfoRequest = ChangeNodeInfoApiRequestADM( - listIri = nodeIri, - projectIri = projectIri, + changeNodeInfoRequest = NodeInfoChangePayloadADM( + listIri = ListIRI.create(nodeIri).fold(e => throw e, v => v), + projectIri = ProjectIRI.create(projectIri).fold(e => throw e, v => v), name = Some(changeNodeNameRequest.name) ), featureFactoryConfig = featureFactoryConfig @@ -1266,14 +1318,14 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde */ private def nodeLabelsChangeRequest( nodeIri: IRI, - changeNodeLabelsRequest: ChangeNodeLabelsApiRequestADM, + changeNodeLabelsRequest: NodeLabelsChangePayloadADM, featureFactoryConfig: FeatureFactoryConfig, requestingUser: UserADM, apiRequestID: UUID ): Future[NodeInfoGetResponseADM] = { def verifyUpdatedNode(updatedNode: ListNodeInfoADM): Unit = - if (updatedNode.getLabels.stringLiterals.diff(changeNodeLabelsRequest.labels).nonEmpty) + if (updatedNode.getLabels.stringLiterals.diff(changeNodeLabelsRequest.labels.value).nonEmpty) throw UpdateNotPerformedException("Node's 'labels' were not updated. Please report this as a possible bug.") /** @@ -1281,7 +1333,7 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde */ def nodeLabelsChangeTask( nodeIri: IRI, - changeNodeLabelsRequest: ChangeNodeLabelsApiRequestADM, + changeNodeLabelsRequest: NodeLabelsChangePayloadADM, featureFactoryConfig: FeatureFactoryConfig, requestingUser: UserADM, apiRequestID: UUID @@ -1296,9 +1348,9 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde throw ForbiddenException(LIST_CHANGE_PERMISSION_ERROR) } changeNodeLabelsSparqlString <- getUpdateNodeInfoSparqlStatement( - changeNodeInfoRequest = ChangeNodeInfoApiRequestADM( - listIri = nodeIri, - projectIri = projectIri, + changeNodeInfoRequest = NodeInfoChangePayloadADM( + listIri = ListIRI.create(nodeIri).fold(e => throw e, v => v), + projectIri = ProjectIRI.create(projectIri).fold(e => throw e, v => v), labels = Some(changeNodeLabelsRequest.labels) ), featureFactoryConfig = featureFactoryConfig @@ -1351,13 +1403,13 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde */ private def nodeCommentsChangeRequest( nodeIri: IRI, - changeNodeCommentsRequest: ChangeNodeCommentsApiRequestADM, + changeNodeCommentsRequest: NodeCommentsChangePayloadADM, featureFactoryConfig: FeatureFactoryConfig, requestingUser: UserADM, apiRequestID: UUID ): Future[NodeInfoGetResponseADM] = { def verifyUpdatedNode(updatedNode: ListNodeInfoADM): Unit = - if (updatedNode.getComments.stringLiterals.diff(changeNodeCommentsRequest.comments).nonEmpty) + if (updatedNode.getComments.stringLiterals == changeNodeCommentsRequest.comments.map(_.value)) throw UpdateNotPerformedException("Node's 'comments' were not updated. Please report this as a possible bug.") /** @@ -1365,7 +1417,7 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde */ def nodeCommentsChangeTask( nodeIri: IRI, - changeNodeCommentsRequest: ChangeNodeCommentsApiRequestADM, + changeNodeCommentsRequest: NodeCommentsChangePayloadADM, featureFactoryConfig: FeatureFactoryConfig, requestingUser: UserADM, apiRequestID: UUID @@ -1381,10 +1433,10 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde } changeNodeCommentsSparqlString <- getUpdateNodeInfoSparqlStatement( - changeNodeInfoRequest = ChangeNodeInfoApiRequestADM( - listIri = nodeIri, - projectIri = projectIri, - comments = Some(changeNodeCommentsRequest.comments) + changeNodeInfoRequest = NodeInfoChangePayloadADM( + listIri = ListIRI.create(nodeIri).fold(e => throw e, v => v), + projectIri = ProjectIRI.create(projectIri).fold(e => throw e, v => v), + comments = changeNodeCommentsRequest.comments ), featureFactoryConfig = featureFactoryConfig ) @@ -2065,15 +2117,15 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde * @param listNodeName the list node name. * @return a [[Boolean]]. */ - private def listNodeNameIsProjectUnique(projectIri: IRI, listNodeName: Option[String]): Future[Boolean] = + private def listNodeNameIsProjectUnique(projectIri: IRI, listNodeName: Option[ListName]): Future[Boolean] = listNodeName match { case Some(name) => for { askString <- Future( org.knora.webapi.messages.twirl.queries.sparql.admin.txt .checkListNodeNameIsProjectUnique( - projectIri = projectIri, - listNodeName = name + projectIri, + listNodeName = name.value ) .toString ) @@ -2095,27 +2147,27 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde * @return a [[String]]. */ private def getUpdateNodeInfoSparqlStatement( - changeNodeInfoRequest: ChangeNodeInfoApiRequestADM, + changeNodeInfoRequest: NodeInfoChangePayloadADM, featureFactoryConfig: FeatureFactoryConfig ): Future[String] = for { // get the data graph of the project. - dataNamedGraph <- getDataNamedGraph(changeNodeInfoRequest.projectIri, featureFactoryConfig) + dataNamedGraph <- getDataNamedGraph(changeNodeInfoRequest.projectIri.value, featureFactoryConfig) /* verify that the list name is unique for the project */ nodeNameUnique: Boolean <- listNodeNameIsProjectUnique( - changeNodeInfoRequest.projectIri, + changeNodeInfoRequest.projectIri.value, changeNodeInfoRequest.name ) _ = if (!nodeNameUnique) { throw DuplicateValueException( - s"The name ${changeNodeInfoRequest.name.get} is already used by a list inside the project ${changeNodeInfoRequest.projectIri}." + s"The name ${changeNodeInfoRequest.name.get} is already used by a list inside the project ${changeNodeInfoRequest.projectIri.value}." ) } /* Verify that the node with Iri exists. */ maybeNode <- listNodeGetADM( - nodeIri = changeNodeInfoRequest.listIri, + nodeIri = changeNodeInfoRequest.listIri.value, shallow = true, featureFactoryConfig = featureFactoryConfig, requestingUser = KnoraSystemInstances.Users.SystemUser @@ -2138,14 +2190,14 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde .updateListInfo( dataNamedGraph = dataNamedGraph, triplestore = settings.triplestoreType, - nodeIri = changeNodeInfoRequest.listIri, + nodeIri = changeNodeInfoRequest.listIri.value, hasOldName = hasOldName, isRootNode = isRootNode, - maybeName = changeNodeInfoRequest.name, - projectIri = changeNodeInfoRequest.projectIri, + maybeName = changeNodeInfoRequest.name.map(_.value), + projectIri = changeNodeInfoRequest.projectIri.value, listClassIri = OntologyConstants.KnoraBase.ListNode, - maybeLabels = changeNodeInfoRequest.labels, - maybeComments = changeNodeInfoRequest.comments + maybeLabels = changeNodeInfoRequest.labels.map(_.value), + maybeComments = changeNodeInfoRequest.comments.map(_.value) ) .toString } yield changeNodeInfoSparqlString diff --git a/webapi/src/main/scala/org/knora/webapi/routing/admin/GroupsRouteADM.scala b/webapi/src/main/scala/org/knora/webapi/routing/admin/GroupsRouteADM.scala index d4100dbc29..f35a8778eb 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/admin/GroupsRouteADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/admin/GroupsRouteADM.scala @@ -80,7 +80,7 @@ class GroupsRouteADM(routeData: KnoraRouteData) entity(as[CreateGroupApiRequestADM]) { apiRequest => requestContext => val groupCreatePayloadADM: GroupCreatePayloadADM = GroupCreatePayloadADM.create( id = stringFormatter - .validateAndEscapeOptionalIri(apiRequest.id, throw BadRequestException(s"Invalid group IRI")), + .validateAndEscapeOptionalIri(apiRequest.id, throw BadRequestException(s"Invalid custom group IRI")), name = Name.create(apiRequest.name).fold(e => throw e, v => v), descriptions = Description.make(apiRequest.descriptions).fold(e => throw e.head, v => v), project = stringFormatter @@ -122,7 +122,7 @@ class GroupsRouteADM(routeData: KnoraRouteData) /* returns a single group identified through iri */ requestContext => val checkedGroupIri = - stringFormatter.validateAndEscapeIri(value, throw BadRequestException(s"Invalid group IRI $value")) + stringFormatter.validateAndEscapeIri(value, throw BadRequestException(s"Invalid custom group IRI $value")) val requestMessage = for { requestingUser <- getUserADM( diff --git a/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/NewListsRouteADMFeature.scala b/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/NewListsRouteADMFeature.scala index 7ae10dd7a0..67a9b79e43 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/NewListsRouteADMFeature.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/NewListsRouteADMFeature.scala @@ -6,15 +6,28 @@ package org.knora.webapi.routing.admin.lists import java.util.UUID - import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.{PathMatcher, Route} import io.swagger.annotations._ + import javax.ws.rs.Path import org.knora.webapi.IRI -import org.knora.webapi.exceptions.BadRequestException +import org.knora.webapi.exceptions.{BadRequestException, ForbiddenException} import org.knora.webapi.feature.{Feature, FeatureFactoryConfig} +import org.knora.webapi.messages.admin.responder.listsmessages.ListsErrorMessagesADM.LIST_CREATE_PERMISSION_ERROR +import org.knora.webapi.messages.admin.responder.listsmessages.NodeCreatePayloadADM.{ + ChildNodeCreatePayloadADM, + ListCreatePayloadADM +} import org.knora.webapi.messages.admin.responder.listsmessages._ +import org.knora.webapi.messages.admin.responder.valueObjects.{ + Comments, + Labels, + ListIRI, + ListName, + Position, + ProjectIRI +} import org.knora.webapi.routing.{Authenticator, KnoraRoute, KnoraRouteData, RouteUtilADM} import scala.concurrent.Future @@ -120,7 +133,7 @@ class NewListsRouteADMFeature(routeData: KnoraRouteData) name = "body", value = "\"list\" item to create", required = true, - dataTypeClass = classOf[CreateListApiRequestADM], + dataTypeClass = classOf[CreateNodeApiRequestADM], paramType = "body" ), new ApiImplicitParam( @@ -141,22 +154,82 @@ class NewListsRouteADMFeature(routeData: KnoraRouteData) post { /* create a list item (root or child node) */ entity(as[CreateNodeApiRequestADM]) { apiRequest => requestContext => + val maybeId: Option[ListIRI] = apiRequest.id match { + case Some(value) => Some(ListIRI.create(value).fold(e => throw e, v => v)) + case None => None + } + + val maybeParentNodeIri: Option[ListIRI] = apiRequest.parentNodeIri match { + case Some(value) => Some(ListIRI.create(value).fold(e => throw e, v => v)) + case None => None + } + + val maybeName: Option[ListName] = apiRequest.name match { + case Some(value) => Some(ListName.create(value).fold(e => throw e, v => v)) + case None => None + } + + val maybePosition: Option[Position] = apiRequest.position match { + case Some(value) => Some(Position.create(value).fold(e => throw e, v => v)) + case None => None + } + + val labels = Labels.create(apiRequest.labels).fold(e => throw e, v => v) + val projectIri = ProjectIRI.create(apiRequest.projectIri).fold(e => throw e, v => v) + val requestMessage = for { requestingUser <- getUserADM(requestContext, featureFactoryConfig) + + _ = if ( + !requestingUser.permissions.isProjectAdmin(projectIri.value) && !requestingUser.permissions.isSystemAdmin + ) { + // not project or a system admin + throw ForbiddenException(LIST_CREATE_PERMISSION_ERROR) + } + // Is parent node IRI given in the payload? createRequest = if (apiRequest.parentNodeIri.isEmpty) { // No, create a new list with given information of its root node. + + val comments = Comments.create(apiRequest.comments).fold(e => throw e, v => v) + + val createRootNodePayloadADM: ListCreatePayloadADM = ListCreatePayloadADM( + id = maybeId, + projectIri, + name = maybeName, + labels, + comments + ) + ListCreateRequestADM( - createRootNode = apiRequest.escape, + createRootNode = createRootNodePayloadADM, featureFactoryConfig = featureFactoryConfig, requestingUser = requestingUser, apiRequestID = UUID.randomUUID() ) } else { // Yes, create a new child and attach it to the parent node. + + // allows to omit comments / send empty comments creating child node + val maybeComments = if (apiRequest.comments.isEmpty) { + None + } else { + Some(Comments.create(apiRequest.comments).fold(e => throw e, v => v)) + } + + val createChildNodePayloadADM: ChildNodeCreatePayloadADM = ChildNodeCreatePayloadADM( + id = maybeId, + parentNodeIri = maybeParentNodeIri, + projectIri, + name = maybeName, + position = maybePosition, + labels, + comments = maybeComments + ) + ListChildNodeCreateRequestADM( - createChildNodeRequest = apiRequest.escape, + createChildNodeRequest = createChildNodePayloadADM, featureFactoryConfig = featureFactoryConfig, requestingUser = requestingUser, apiRequestID = UUID.randomUUID() @@ -263,14 +336,57 @@ class NewListsRouteADMFeature(routeData: KnoraRouteData) put { /* update existing list node (either root or child) */ entity(as[ChangeNodeInfoApiRequestADM]) { apiRequest => requestContext => - val listIri = - stringFormatter.validateAndEscapeIri(iri, throw BadRequestException(s"Invalid param list IRI: $iri")) + val listIri = ListIRI.create(apiRequest.listIri).fold(e => throw e, v => v) + val projectIri = ProjectIRI.create(apiRequest.projectIri).fold(e => throw e, v => v) + + val maybeHasRootNode: Option[ListIRI] = apiRequest.hasRootNode match { + case Some(value) => Some(ListIRI.create(value).fold(e => throw e, v => v)) + case None => None + } + + val maybeName: Option[ListName] = apiRequest.name match { + case Some(value) => Some(ListName.create(value).fold(e => throw e, v => v)) + case None => None + } + + val maybePosition: Option[Position] = apiRequest.position match { + case Some(value) => Some(Position.create(value).fold(e => throw e, v => v)) + case None => None + } + + val maybeLabels: Option[Labels] = apiRequest.labels match { + case Some(value) => Some(Labels.create(value).fold(e => throw e, v => v)) + case None => None + } + + val maybeComments: Option[Comments] = apiRequest.comments match { + case Some(value) => Some(Comments.create(value).fold(e => throw e, v => v)) + case None => None + } + + val changeNodeInfoPayloadADM: NodeInfoChangePayloadADM = NodeInfoChangePayloadADM( + listIri, + projectIri, + hasRootNode = maybeHasRootNode, + position = maybePosition, + name = maybeName, + labels = maybeLabels, + comments = maybeComments + ) val requestMessage: Future[NodeInfoChangeRequestADM] = for { requestingUser <- getUserADM(requestContext, featureFactoryConfig) + // check if the requesting user is allowed to perform operation + _ = if ( + !requestingUser.permissions.isProjectAdmin(projectIri.value) && !requestingUser.permissions.isSystemAdmin + ) { + // not project or a system admin + throw ForbiddenException(LIST_CREATE_PERMISSION_ERROR) + } } yield NodeInfoChangeRequestADM( - listIri = listIri, - changeNodeRequest = apiRequest, + //TODO: why "listIri" property is doubled - here and inside "changeNodeRequest" + listIri = listIri.value, + changeNodeRequest = changeNodeInfoPayloadADM, featureFactoryConfig = featureFactoryConfig, requestingUser = requestingUser, apiRequestID = UUID.randomUUID() diff --git a/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/OldListsRouteADMFeature.scala b/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/OldListsRouteADMFeature.scala index 6b1aedd34a..dece508ad4 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/OldListsRouteADMFeature.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/OldListsRouteADMFeature.scala @@ -6,15 +6,31 @@ package org.knora.webapi.routing.admin.lists import java.util.UUID - import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.{PathMatcher, Route} import io.swagger.annotations._ + import javax.ws.rs.Path import org.knora.webapi.IRI -import org.knora.webapi.exceptions.BadRequestException +import org.knora.webapi.exceptions.{BadRequestException, ForbiddenException} import org.knora.webapi.feature.{Feature, FeatureFactoryConfig} +import org.knora.webapi.messages.admin.responder.listsmessages.ListsErrorMessagesADM.{ + LIST_CREATE_PERMISSION_ERROR, + LIST_NODE_CREATE_PERMISSION_ERROR +} +import org.knora.webapi.messages.admin.responder.listsmessages.NodeCreatePayloadADM.{ + ChildNodeCreatePayloadADM, + ListCreatePayloadADM +} import org.knora.webapi.messages.admin.responder.listsmessages._ +import org.knora.webapi.messages.admin.responder.valueObjects.{ + Comments, + Labels, + ListIRI, + ListName, + Position, + ProjectIRI +} import org.knora.webapi.routing.{Authenticator, KnoraRoute, KnoraRouteData, RouteUtilADM} import scala.concurrent.Future @@ -101,7 +117,7 @@ class OldListsRouteADMFeature(routeData: KnoraRouteData) name = "body", value = "\"list\" to create", required = true, - dataTypeClass = classOf[CreateListApiRequestADM], + dataTypeClass = classOf[CreateNodeApiRequestADM], paramType = "body" ) ) @@ -115,13 +131,43 @@ class OldListsRouteADMFeature(routeData: KnoraRouteData) post { /* create a list */ entity(as[CreateNodeApiRequestADM]) { apiRequest => requestContext => + val maybeId: Option[ListIRI] = apiRequest.id match { + case Some(value) => Some(ListIRI.create(value).fold(e => throw e, v => v)) + case None => None + } + + val maybeName: Option[ListName] = apiRequest.name match { + case Some(value) => Some(ListName.create(value).fold(e => throw e, v => v)) + case None => None + } + + val projectIri = ProjectIRI.create(apiRequest.projectIri).fold(e => throw e, v => v) + + val createRootNodePayloadADM: ListCreatePayloadADM = ListCreatePayloadADM( + id = maybeId, + projectIri, + name = maybeName, + labels = Labels.create(apiRequest.labels).fold(e => throw e, v => v), + comments = Comments.create(apiRequest.comments).fold(e => throw e, v => v) + ) + +// println("AAA-createList", createRootNodePayloadADM) + val requestMessage: Future[ListCreateRequestADM] = for { requestingUser <- getUserADM( requestContext = requestContext, featureFactoryConfig = featureFactoryConfig ) + + // check if the requesting user is allowed to perform operation + _ = if ( + !requestingUser.permissions.isProjectAdmin(projectIri.value) && !requestingUser.permissions.isSystemAdmin + ) { + // not project or a system admin + throw ForbiddenException(LIST_CREATE_PERMISSION_ERROR) + } } yield ListCreateRequestADM( - createRootNode = apiRequest.escape, + createRootNode = createRootNodePayloadADM, featureFactoryConfig = featureFactoryConfig, requestingUser = requestingUser, apiRequestID = UUID.randomUUID() @@ -206,13 +252,56 @@ class OldListsRouteADMFeature(routeData: KnoraRouteData) put { /* update existing list node (either root or child) */ entity(as[ChangeNodeInfoApiRequestADM]) { apiRequest => requestContext => - val listIri = - stringFormatter.validateAndEscapeIri(iri, throw BadRequestException(s"Invalid param list IRI: $iri")) + val listIri = ListIRI.create(apiRequest.listIri).fold(e => throw e, v => v) + val projectIri = ProjectIRI.create(apiRequest.projectIri).fold(e => throw e, v => v) + + val maybeHasRootNode: Option[ListIRI] = apiRequest.hasRootNode match { + case Some(value) => Some(ListIRI.create(value).fold(e => throw e, v => v)) + case None => None + } + + val maybeName: Option[ListName] = apiRequest.name match { + case Some(value) => Some(ListName.create(value).fold(e => throw e, v => v)) + case None => None + } + + val maybePosition: Option[Position] = apiRequest.position match { + case Some(value) => Some(Position.create(value).fold(e => throw e, v => v)) + case None => None + } + + val maybeLabels: Option[Labels] = apiRequest.labels match { + case Some(value) => Some(Labels.create(value).fold(e => throw e, v => v)) + case None => None + } + + val maybeComments: Option[Comments] = apiRequest.comments match { + case Some(value) => Some(Comments.create(value).fold(e => throw e, v => v)) + case None => None + } + + val changeNodeInfoPayloadADM: NodeInfoChangePayloadADM = NodeInfoChangePayloadADM( + listIri, + projectIri, + hasRootNode = maybeHasRootNode, + position = maybePosition, + name = maybeName, + labels = maybeLabels, + comments = maybeComments + ) + val requestMessage: Future[NodeInfoChangeRequestADM] = for { requestingUser <- getUserADM(requestContext, featureFactoryConfig) + // check if the requesting user is allowed to perform operation + _ = if ( + !requestingUser.permissions.isProjectAdmin(projectIri.value) && !requestingUser.permissions.isSystemAdmin + ) { + // not project or a system admin + throw ForbiddenException(LIST_NODE_CREATE_PERMISSION_ERROR) + } } yield NodeInfoChangeRequestADM( - listIri = listIri, - changeNodeRequest = apiRequest, + listIri = listIri.value, + changeNodeRequest = changeNodeInfoPayloadADM, featureFactoryConfig = featureFactoryConfig, requestingUser = requestingUser, apiRequestID = UUID.randomUUID() @@ -261,15 +350,60 @@ class OldListsRouteADMFeature(routeData: KnoraRouteData) post { /* add node to existing list node. the existing list node can be either the root or a child */ entity(as[CreateNodeApiRequestADM]) { apiRequest => requestContext => - val _ = stringFormatter.validateAndEscapeIri(iri, throw BadRequestException(s"Invalid param list IRI: $iri")) + val maybeId: Option[ListIRI] = apiRequest.id match { + case Some(value) => Some(ListIRI.create(value).fold(e => throw e, v => v)) + case None => None + } + + val maybeParentNodeIri: Option[ListIRI] = apiRequest.parentNodeIri match { + case Some(value) => Some(ListIRI.create(value).fold(e => throw e, v => v)) + case None => None + } + + val projectIri = ProjectIRI.create(apiRequest.projectIri).fold(e => throw e, v => v) + + val maybeName: Option[ListName] = apiRequest.name match { + case Some(value) => Some(ListName.create(value).fold(e => throw e, v => v)) + case None => None + } + + val maybePosition: Option[Position] = apiRequest.position match { + case Some(value) => Some(Position.create(value).fold(e => throw e, v => v)) + case None => None + } + + // allows to omit comments / send empty comments creating child node + val maybeComments = if (apiRequest.comments.isEmpty) { + None + } else { + Some(Comments.create(apiRequest.comments).fold(e => throw e, v => v)) + } + + val createChildNodeRequest: ChildNodeCreatePayloadADM = ChildNodeCreatePayloadADM( + id = maybeId, + parentNodeIri = maybeParentNodeIri, + projectIri, + name = maybeName, + position = maybePosition, + labels = Labels.create(apiRequest.labels).fold(e => throw e, v => v), + comments = maybeComments + ) val requestMessage: Future[ListChildNodeCreateRequestADM] = for { requestingUser <- getUserADM( requestContext = requestContext, featureFactoryConfig = featureFactoryConfig ) + + // check if the requesting user is allowed to perform operation + _ = if ( + !requestingUser.permissions.isProjectAdmin(projectIri.value) && !requestingUser.permissions.isSystemAdmin + ) { + // not project or a system admin + throw ForbiddenException(LIST_CREATE_PERMISSION_ERROR) + } } yield ListChildNodeCreateRequestADM( - createChildNodeRequest = apiRequest.escape, + createChildNodeRequest = createChildNodeRequest, featureFactoryConfig = featureFactoryConfig, requestingUser = requestingUser, apiRequestID = UUID.randomUUID() diff --git a/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/UpdateListItemsRouteADM.scala b/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/UpdateListItemsRouteADM.scala index cf9f2b3464..399af2f67f 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/UpdateListItemsRouteADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/UpdateListItemsRouteADM.scala @@ -6,14 +6,15 @@ package org.knora.webapi.routing.admin.lists import java.util.UUID - import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.{PathMatcher, Route} import io.swagger.annotations._ + import javax.ws.rs.Path import org.knora.webapi.exceptions.BadRequestException import org.knora.webapi.feature.{Feature, FeatureFactoryConfig} import org.knora.webapi.messages.admin.responder.listsmessages._ +import org.knora.webapi.messages.admin.responder.valueObjects.{Comments, Labels, ListName} import org.knora.webapi.routing.{Authenticator, KnoraRoute, KnoraRouteData, RouteUtilADM} import scala.concurrent.Future @@ -75,11 +76,14 @@ class UpdateListItemsRouteADM(routeData: KnoraRouteData) val nodeIri = stringFormatter.validateAndEscapeIri(iri, throw BadRequestException(s"Invalid param node IRI: $iri")) + val namePayload: NodeNameChangePayloadADM = + NodeNameChangePayloadADM(ListName.create(apiRequest.name).fold(e => throw e, v => v)) + val requestMessage: Future[NodeNameChangeRequestADM] = for { requestingUser <- getUserADM(requestContext, featureFactoryConfig) } yield NodeNameChangeRequestADM( nodeIri = nodeIri, - changeNodeNameRequest = apiRequest, + changeNodeNameRequest = namePayload, featureFactoryConfig = featureFactoryConfig, requestingUser = requestingUser, apiRequestID = UUID.randomUUID() @@ -131,11 +135,14 @@ class UpdateListItemsRouteADM(routeData: KnoraRouteData) val nodeIri = stringFormatter.validateAndEscapeIri(iri, throw BadRequestException(s"Invalid param node IRI: $iri")) + val labelsPayload: NodeLabelsChangePayloadADM = + NodeLabelsChangePayloadADM(Labels.create(apiRequest.labels).fold(e => throw e, v => v)) + val requestMessage: Future[NodeLabelsChangeRequestADM] = for { requestingUser <- getUserADM(requestContext, featureFactoryConfig) } yield NodeLabelsChangeRequestADM( nodeIri = nodeIri, - changeNodeLabelsRequest = apiRequest, + changeNodeLabelsRequest = labelsPayload, featureFactoryConfig = featureFactoryConfig, requestingUser = requestingUser, apiRequestID = UUID.randomUUID() @@ -182,16 +189,22 @@ class UpdateListItemsRouteADM(routeData: KnoraRouteData) private def updateNodeComments(featureFactoryConfig: FeatureFactoryConfig): Route = path(ListsBasePath / Segment / "comments") { iri => put { - /* update labels of an existing list node (either root or child) */ + /* update comments of an existing list node (either root or child) */ entity(as[ChangeNodeCommentsApiRequestADM]) { apiRequest => requestContext => val nodeIri = stringFormatter.validateAndEscapeIri(iri, throw BadRequestException(s"Invalid param node IRI: $iri")) + val commentsPayload: NodeCommentsChangePayloadADM = if (apiRequest.comments.isEmpty) { + NodeCommentsChangePayloadADM(None) + } else { + NodeCommentsChangePayloadADM(Some(Comments.create(apiRequest.comments).fold(e => throw e, v => v))) + } + val requestMessage: Future[NodeCommentsChangeRequestADM] = for { requestingUser <- getUserADM(requestContext, featureFactoryConfig) } yield NodeCommentsChangeRequestADM( nodeIri = nodeIri, - changeNodeCommentsRequest = apiRequest, + changeNodeCommentsRequest = commentsPayload, featureFactoryConfig = featureFactoryConfig, requestingUser = requestingUser, apiRequestID = UUID.randomUUID() diff --git a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/admin/createNewListNode.scala.txt b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/admin/createNewListNode.scala.txt index e5ea5c1fc1..b1393164be 100644 --- a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/admin/createNewListNode.scala.txt +++ b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/admin/createNewListNode.scala.txt @@ -7,7 +7,7 @@ @import org.knora.webapi.messages.store.triplestoremessages.StringLiteralV2 @* - * Creates a new list node. + * Creates a new root list node. * * @param dataNamedGraph the name of the graph into which the new list node will be created. * @param triplestore the name of the triplestore being used. The template uses this value to exclude inferred @@ -17,7 +17,7 @@ * @param nodeIri the IRI of the new list node. * @param parentNodeIri the IRI of the parent node if creating a child node. * @param rootNodeIri the IRI of the root node if creating a child node. - * @param position the position of the of the new node if creating a child node. + * @param position the position of the new node if creating a child node. * @param maybeName the optional name of the node. * @param maybeLabels the new node's labels. * @param maybeComments the new node's comments. @@ -33,7 +33,7 @@ position: Option[Int], maybeName: Option[String], maybeLabels: Seq[StringLiteralV2], - maybeComments: Seq[StringLiteralV2] + maybeComments: Option[Seq[StringLiteralV2]] ) PREFIX xsd: @@ -75,11 +75,13 @@ INSERT { } @if(maybeComments.nonEmpty) { - @for(comment <- maybeComments) { - @if(comment.language.nonEmpty) { - ?nodeIri rdfs:comment """@comment.value"""@@@{comment.language.get} . - } else { - ?nodeIri rdfs:comment """@comment.value"""^^xsd:string . + @if(maybeComments.get.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 . + } } } } diff --git a/webapi/src/test/scala/org/knora/webapi/e2e/admin/lists/NewListsRoutesADMFeatureE2ESpec.scala b/webapi/src/test/scala/org/knora/webapi/e2e/admin/lists/NewListsRoutesADMFeatureE2ESpec.scala index 12e10d1fac..14dce19bb2 100644 --- a/webapi/src/test/scala/org/knora/webapi/e2e/admin/lists/NewListsRoutesADMFeatureE2ESpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/e2e/admin/lists/NewListsRoutesADMFeatureE2ESpec.scala @@ -295,7 +295,7 @@ class NewListsRouteADMFeatureE2ESpec | "id": "${SharedTestDataADM.customListIRI}", | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", | "labels": [{ "value": "New list with a custom IRI", "language": "en"}], - | "comments": [] + | "comments": [{ "value": "XXXXX", "language": "en"}] |}""".stripMargin clientTestDataCollector.addFile( @@ -348,7 +348,7 @@ class NewListsRouteADMFeatureE2ESpec | "id": "${SharedTestDataADM.customListIRI}", | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", | "labels": [{ "value": "New List", "language": "en"}], - | "comments": [] + | "comments": [{ "value": "XXXXX", "language": "en"}] |} """.stripMargin @@ -374,7 +374,7 @@ class NewListsRouteADMFeatureE2ESpec | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", | "name": "node with a custom IRI", | "labels": [{ "value": "New List Node", "language": "en"}], - | "comments": [] + | "comments": [{ "value": "XXXXX", "language": "en"}] |}""".stripMargin clientTestDataCollector.addFile( @@ -434,7 +434,7 @@ class NewListsRouteADMFeatureE2ESpec s"""{ | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", | "labels": [{ "value": "Neue Liste", "language": "de"}], - | "comments": [] + | "comments": [{ "value": "XXXXX", "language": "en"}] |}""".stripMargin clientTestDataCollector.addFile( @@ -465,7 +465,7 @@ class NewListsRouteADMFeatureE2ESpec labels.head should be(StringLiteralV2(value = "Neue Liste", language = Some("de"))) val comments = receivedList.listinfo.comments.stringLiterals - comments.isEmpty should be(true) + comments.isEmpty should be(false) val children = receivedList.children children.size should be(0) @@ -490,7 +490,7 @@ class NewListsRouteADMFeatureE2ESpec |{ | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", | "labels": [{ "value": "Neue Liste", "language": "de"}], - | "comments": [] + | "comments": [{ "value": "XXXXX", "language": "en"}] |} """.stripMargin @@ -511,7 +511,7 @@ class NewListsRouteADMFeatureE2ESpec |{ | "projectIri": "", | "labels": [{ "value": "Neue Liste", "language": "de"}], - | "comments": [] + | "comments": [{ "value": "XXXXX", "language": "en"}] |} """.stripMargin @@ -527,7 +527,7 @@ class NewListsRouteADMFeatureE2ESpec |{ | "projectIri": "notvalidIRI", | "labels": [{ "value": "Neue Liste", "language": "de"}], - | "comments": [] + | "comments": [{ "value": "XXXXX", "language": "en"}] |} """.stripMargin @@ -543,7 +543,7 @@ class NewListsRouteADMFeatureE2ESpec |{ | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", | "labels": [], - | "comments": [] + | "comments": [{ "value": "XXXXX", "language": "en"}] |} """.stripMargin @@ -800,7 +800,7 @@ class NewListsRouteADMFeatureE2ESpec | "listIri": "${newListIri.get}", | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", | "labels": [], - | "comments": [] + | "comments": [{ "value": "XXXXX", "language": "en"}] |} """.stripMargin diff --git a/webapi/src/test/scala/org/knora/webapi/e2e/admin/lists/OldListsRouteADMFeatureE2ESpec.scala b/webapi/src/test/scala/org/knora/webapi/e2e/admin/lists/OldListsRouteADMFeatureE2ESpec.scala index 2803e792ac..06e51cd1fc 100644 --- a/webapi/src/test/scala/org/knora/webapi/e2e/admin/lists/OldListsRouteADMFeatureE2ESpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/e2e/admin/lists/OldListsRouteADMFeatureE2ESpec.scala @@ -93,8 +93,6 @@ class OldListsRouteADMFeatureE2ESpec Get(baseApiUrl + s"/admin/lists") ~> addCredentials(BasicHttpCredentials(rootCreds.email, rootCreds.password)) val response: HttpResponse = singleAwaitingRequest(request) - // println(s"response: ${response.toString}") - response.status should be(StatusCodes.OK) val lists: Seq[ListNodeInfoADM] = @@ -285,7 +283,7 @@ class OldListsRouteADMFeatureE2ESpec | "id": "${SharedTestDataADM.customListIRI}", | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", | "labels": [{ "value": "New list with a custom IRI", "language": "en"}], - | "comments": [] + | "comments": [{ "value": "XXXXX", "language": "en"}] |}""".stripMargin clientTestDataCollector.addFile( @@ -335,7 +333,7 @@ class OldListsRouteADMFeatureE2ESpec | "id": "${SharedTestDataADM.customListIRI}", | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", | "labels": [{ "value": "New List", "language": "en"}], - | "comments": [] + | "comments": [{ "value": "XXXXX", "language": "en"}] |} """.stripMargin @@ -361,7 +359,7 @@ class OldListsRouteADMFeatureE2ESpec | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", | "name": "node with a custom IRI", | "labels": [{ "value": "New List Node", "language": "en"}], - | "comments": [] + | "comments": [{ "value": "XXXXX", "language": "en"}] |}""".stripMargin clientTestDataCollector.addFile( @@ -420,7 +418,7 @@ class OldListsRouteADMFeatureE2ESpec s"""{ | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", | "labels": [{ "value": "Neue Liste", "language": "de"}], - | "comments": [] + | "comments": [{ "value": "XXXXX", "language": "en"}] |}""".stripMargin clientTestDataCollector.addFile( @@ -451,7 +449,7 @@ class OldListsRouteADMFeatureE2ESpec labels.head should be(StringLiteralV2(value = "Neue Liste", language = Some("de"))) val comments = receivedList.listinfo.comments.stringLiterals - comments.isEmpty should be(true) + comments.isEmpty should be(false) val children = receivedList.children children.size should be(0) @@ -476,7 +474,7 @@ class OldListsRouteADMFeatureE2ESpec |{ | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", | "labels": [{ "value": "Neue Liste", "language": "de"}], - | "comments": [] + | "comments": [{ "value": "XXXXX", "language": "en"}] |} """.stripMargin @@ -497,7 +495,7 @@ class OldListsRouteADMFeatureE2ESpec |{ | "projectIri": "", | "labels": [{ "value": "Neue Liste", "language": "de"}], - | "comments": [] + | "comments": [{ "value": "XXXXX", "language": "en"}] |} """.stripMargin @@ -512,7 +510,7 @@ class OldListsRouteADMFeatureE2ESpec |{ | "projectIri": "notvalidIRI", | "labels": [{ "value": "Neue Liste", "language": "de"}], - | "comments": [] + | "comments": [{ "value": "XXXXX", "language": "en"}] |} """.stripMargin @@ -527,7 +525,7 @@ class OldListsRouteADMFeatureE2ESpec |{ | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", | "labels": [], - | "comments": [] + | "comments": [{ "value": "XXXXX", "language": "en"}] |} """.stripMargin @@ -770,7 +768,7 @@ class OldListsRouteADMFeatureE2ESpec | "listIri": "${newListIri.get}", | "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}", | "labels": [], - | "comments": [] + | "comments": [{ "value": "XXXXX", "language": "en"}] |} """.stripMargin diff --git a/webapi/src/test/scala/org/knora/webapi/e2e/admin/lists/UpdateListItemsRouteADME2ESpec.scala b/webapi/src/test/scala/org/knora/webapi/e2e/admin/lists/UpdateListItemsRouteADME2ESpec.scala index 91ac1fcbf5..59e802d7ba 100644 --- a/webapi/src/test/scala/org/knora/webapi/e2e/admin/lists/UpdateListItemsRouteADME2ESpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/e2e/admin/lists/UpdateListItemsRouteADME2ESpec.scala @@ -207,8 +207,8 @@ class UpdateListItemsRouteADME2ESpec ) ) } - "delete node comments" in { - val deleteCommentsLabels = + "not delete root node comments" in { + val deleteComments = s"""{ | "comments": [] |}""".stripMargin @@ -216,10 +216,10 @@ class UpdateListItemsRouteADME2ESpec val request = Put( baseApiUrl + s"/admin/lists/" + encodedListUrl + "/comments", - HttpEntity(ContentTypes.`application/json`, deleteCommentsLabels) + HttpEntity(ContentTypes.`application/json`, deleteComments) ) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) val response: HttpResponse = singleAwaitingRequest(request) - // log.debug(s"response: ${response.toString}") + log.debug(s"response: ${response.toString}") response.status should be(StatusCodes.OK) val receivedListInfo: ListRootNodeInfoADM = AkkaHttpUtils.httpResponseToJson(response).fields("listinfo").convertTo[ListRootNodeInfoADM] @@ -227,7 +227,8 @@ class UpdateListItemsRouteADME2ESpec receivedListInfo.projectIri should be(SharedTestDataADM.ANYTHING_PROJECT_IRI) val comments: Seq[StringLiteralV2] = receivedListInfo.comments.stringLiterals - comments.size should be(0) + comments.size should be(1) + comments should contain(StringLiteralV2(value = "nya kommentarer", language = Some("se"))) } } @@ -366,6 +367,39 @@ class UpdateListItemsRouteADME2ESpec ) } + "not delete child node comments by sending empty array" in { + val deleteNodeComments = + s"""{ + | "comments": [] + |}""".stripMargin + + clientTestDataCollector.addFile( + TestDataFileContent( + filePath = TestDataFilePath( + directoryPath = clientTestDataPath, + filename = "update-childNode-comments-request", + fileExtension = "json" + ), + text = deleteNodeComments + ) + ) + + val encodedListUrl = java.net.URLEncoder.encode(treeChildNode.id, "utf-8") + + val request = Put( + baseApiUrl + s"/admin/lists/" + encodedListUrl + "/comments", + HttpEntity(ContentTypes.`application/json`, deleteNodeComments) + ) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials) + val response: HttpResponse = singleAwaitingRequest(request) + + response.status should be(StatusCodes.OK) + + val receivedNodeInfo: ListChildNodeInfoADM = + AkkaHttpUtils.httpResponseToJson(response).fields("nodeinfo").convertTo[ListChildNodeInfoADM] + val comments: Seq[StringLiteralV2] = receivedNodeInfo.comments.stringLiterals + comments.size should be(1) + } + "not update the position of a node if given IRI is invalid" in { val parentIri = "http://rdfh.ch/lists/0001/notUsedList01" val newPosition = 1 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 9b79c0c94e..fdd2827ccc 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 @@ -6,13 +6,13 @@ package org.knora.webapi.messages.admin.responder.listsmessages import java.util.UUID - import com.typesafe.config.ConfigFactory import org.knora.webapi.CoreSpec -import org.knora.webapi.exceptions.{BadRequestException, ForbiddenException} -import org.knora.webapi.messages.admin.responder.listsmessages.ListsMessagesUtilADM._ +import org.knora.webapi.exceptions.BadRequestException +import org.knora.webapi.messages.admin.responder.listsmessages.ListsErrorMessagesADM._ +import org.knora.webapi.messages.admin.responder.listsmessages.NodeCreatePayloadADM.ChildNodeCreatePayloadADM +import org.knora.webapi.messages.admin.responder.valueObjects.{Comments, Labels, ListIRI, ProjectIRI, Position} import org.knora.webapi.messages.store.triplestoremessages.{StringLiteralSequenceV2, StringLiteralV2} -import org.knora.webapi.sharedtestdata.SharedTestDataV1.IMAGES_PROJECT_IRI import org.knora.webapi.sharedtestdata.{SharedListsTestDataADM, SharedTestDataADM} import spray.json._ @@ -84,7 +84,7 @@ class ListsMessagesADMSpec extends CoreSpec(ListsMessagesADMSpec.config) with Li id = "http://rdfh.ch/lists/00FF/526f26ed04", name = Some("sommer"), labels = StringLiteralSequenceV2(Vector(StringLiteralV2("Sommer"))), - comments = StringLiteralSequenceV2(Vector.empty[StringLiteralV2]), + comments = Some(StringLiteralSequenceV2(Vector.empty[StringLiteralV2])), children = Seq.empty[ListChildNodeADM], position = 0, hasRootNode = "http://rdfh.ch/lists/00FF/d19af9ab" @@ -129,90 +129,7 @@ class ListsMessagesADMSpec extends CoreSpec(ListsMessagesADMSpec.config) with Li converted.children should be(children) } - "throw 'ForbiddenException' if user requesting `ListCreateApiRequestADM` is not system or project admin" in { - val caught = intercept[ForbiddenException]( - ListCreateRequestADM( - createRootNode = CreateNodeApiRequestADM( - projectIri = SharedTestDataADM.IMAGES_PROJECT_IRI, - labels = Seq(StringLiteralV2(value = "Neue Liste", language = Some("de"))), - comments = Seq.empty[StringLiteralV2] - ), - featureFactoryConfig = defaultFeatureFactoryConfig, - requestingUser = SharedTestDataADM.imagesUser02, - apiRequestID = UUID.randomUUID() - ) - ) - assert(caught.getMessage === LIST_CREATE_PERMISSION_ERROR) - } - - "throw 'BadRequestException' for `CreateListApiRequestADM` when project IRI is empty" in { - - val payload = - s""" - |{ - | "projectIri": "", - | "labels": [{ "value": "Neue Liste", "language": "de"}], - | "comments": [] - |} - """.stripMargin - - val thrown = the[BadRequestException] thrownBy payload.parseJson.convertTo[CreateListApiRequestADM] - - thrown.getMessage should equal(PROJECT_IRI_MISSING_ERROR) - - } - - "throw 'BadRequestException' for `CreateListApiRequestADM` when project IRI is invalid" in { - - val payload = - s""" - |{ - | "projectIri": "not an IRI", - | "labels": [{ "value": "Neue Liste", "language": "de"}], - | "comments": [] - |} - """.stripMargin - - val thrown = the[BadRequestException] thrownBy payload.parseJson.convertTo[CreateListApiRequestADM] - - thrown.getMessage should equal(PROJECT_IRI_INVALID_ERROR) - } - - "throw 'BadRequestException' for `CreateListApiRequestADM` when labels is empty" in { - - val payload = - s""" - |{ - | "projectIri": "${SharedTestDataADM.IMAGES_PROJECT_IRI}", - | "labels": [], - | "comments": [] - |} - """.stripMargin - - val thrown = the[BadRequestException] thrownBy payload.parseJson.convertTo[CreateListApiRequestADM] - - thrown.getMessage should equal(LABEL_MISSING_ERROR) - } - - "throw a 'BadRequestException' for `CreateListApiRequestADM` when an invalid list IRI is given" in { - - // invalid list IRI - val payload = - s""" - |{ - | "id": "invalid-list-IRI", - | "projectIri": "${SharedTestDataADM.IMAGES_PROJECT_IRI}", - | "labels": [{ "value": "New List", "language": "en"}], - | "comments": [] - |} - """.stripMargin - - val thrown = the[BadRequestException] thrownBy payload.parseJson.convertTo[CreateListApiRequestADM] - - thrown.getMessage should equal("Invalid list IRI") - } - - "throw 'BadRequestException' for `CreateListApiRequestADM` when value of a label is missing" in { + "throw 'BadRequestException' for `CreateNodeApiRequestADM` when value of a label is missing" in { val payload = s""" @@ -223,12 +140,12 @@ class ListsMessagesADMSpec extends CoreSpec(ListsMessagesADMSpec.config) with Li |} """.stripMargin - val thrown = the[BadRequestException] thrownBy payload.parseJson.convertTo[CreateListApiRequestADM] + val thrown = the[BadRequestException] thrownBy payload.parseJson.convertTo[CreateNodeApiRequestADM] thrown.getMessage should equal("String value is missing.") } - "throw 'BadRequestException' for `CreateListApiRequestADM` when value of a comment is missing" in { + "throw 'BadRequestException' for `CreateNodeApiRequestADM` when value of a comment is missing" in { val payload = s""" @@ -240,135 +157,26 @@ class ListsMessagesADMSpec extends CoreSpec(ListsMessagesADMSpec.config) with Li |} """.stripMargin - val thrown = the[BadRequestException] thrownBy payload.parseJson.convertTo[CreateListApiRequestADM] + val thrown = the[BadRequestException] thrownBy payload.parseJson.convertTo[CreateNodeApiRequestADM] thrown.getMessage should equal("String value is missing.") } - "throw 'BadRequestException' for `ChangeNodeInfoApiRequestADM` when list IRI is empty" in { - - val payload = - s""" - |{ - | "listIri": "", - | "projectIri": "${SharedTestDataADM.IMAGES_PROJECT_IRI}", - | "labels": [{ "value": "Neue geönderte Liste", "language": "de"}, { "value": "Changed list", "language": "en"}], - | "comments": [{ "value": "Neuer Kommentar", "language": "de"}, { "value": "New comment", "language": "en"}] - |} - """.stripMargin - - val thrown = the[BadRequestException] thrownBy payload.parseJson.convertTo[ChangeNodeInfoApiRequestADM] - - thrown.getMessage should equal("IRI of list item is missing.") - } - - "throw 'BadRequestException' for `ChangeNodeInfoApiRequestADM` when list IRI is invalid" in { - val invalidIri = "notvalidIRI" - val payload = - s""" - |{ - | "listIri": "$invalidIri", - | "projectIri": "${SharedTestDataADM.IMAGES_PROJECT_IRI}", - | "labels": [{ "value": "Neue geönderte Liste", "language": "de"}, { "value": "Changed list", "language": "en"}], - | "comments": [{ "value": "Neuer Kommentar", "language": "de"}, { "value": "New comment", "language": "en"}] - |} - """.stripMargin - - val thrown = the[BadRequestException] thrownBy payload.parseJson.convertTo[ChangeNodeInfoApiRequestADM] - - thrown.getMessage should equal(s"Invalid IRI is given: $invalidIri.") - } - - "throw 'BadRequestException' for `ChangeNodeInfoApiRequestADM` when project IRI is empty" in { - - val payload = - s""" - |{ - | "listIri": "$exampleListIri", - | "projectIri": "", - | "labels": [{ "value": "Neue geönderte Liste", "language": "de"}, { "value": "Changed list", "language": "en"}] - |} - """.stripMargin - - val thrown = the[BadRequestException] thrownBy payload.parseJson.convertTo[ChangeNodeInfoApiRequestADM] - - thrown.getMessage should equal(PROJECT_IRI_MISSING_ERROR) - } - - "throw 'BadRequestException' for `ChangeNodeInfoApiRequestADM` when project IRI is invalid" in { - - val payload = - s""" - |{ - | "listIri": "$exampleListIri", - | "projectIri": "notvalidIRI", - | "name": "a new name" - |} - """.stripMargin - - val thrown = the[BadRequestException] thrownBy payload.parseJson.convertTo[ChangeNodeInfoApiRequestADM] - - thrown.getMessage should equal(PROJECT_IRI_INVALID_ERROR) - } - - "throw 'BadRequestException' for `ChangeNodeInfoApiRequestADM` when labels are empty" in { - - val payload = - s""" - |{ - | "listIri": "$exampleListIri", - | "projectIri": "${SharedTestDataADM.IMAGES_PROJECT_IRI}", - | "labels": [], - | "comments": [] - |} - """.stripMargin - - val thrown = the[BadRequestException] thrownBy payload.parseJson.convertTo[ChangeNodeInfoApiRequestADM] - - thrown.getMessage should equal(UPDATE_REQUEST_EMPTY_LABEL_ERROR) - } - - "throw 'BadRequestException' for `ChangeNodeInfoApiRequestADM` when position is invalid" in { - - val payload = - s""" - |{ - | "listIri": "$exampleListIri", - | "projectIri": "${SharedTestDataADM.IMAGES_PROJECT_IRI}", - | "position": -2 - |} - """.stripMargin - - val thrown = the[BadRequestException] thrownBy payload.parseJson.convertTo[ChangeNodeInfoApiRequestADM] - - thrown.getMessage should equal(INVALID_POSITION) - } - "throw 'ForbiddenException' if user requesting `createChildNodeRequest` is not system or project admin" in { - val caught = intercept[ForbiddenException]( - ListChildNodeCreateRequestADM( - createChildNodeRequest = CreateNodeApiRequestADM( - parentNodeIri = Some(exampleListIri), - projectIri = SharedTestDataADM.IMAGES_PROJECT_IRI, - labels = Seq(StringLiteralV2(value = "New child node", language = Some("en"))), - comments = Seq.empty[StringLiteralV2] - ), - featureFactoryConfig = defaultFeatureFactoryConfig, - requestingUser = SharedTestDataADM.imagesUser02, - apiRequestID = UUID.randomUUID() - ) - ) - assert(caught.getMessage === LIST_NODE_CREATE_PERMISSION_ERROR) - } - "throw 'BadRequestException' if invalid position given in payload of `createChildNodeRequest`" in { val caught = intercept[BadRequestException]( ListChildNodeCreateRequestADM( - createChildNodeRequest = CreateNodeApiRequestADM( - parentNodeIri = Some(exampleListIri), - projectIri = SharedTestDataADM.IMAGES_PROJECT_IRI, - labels = Seq(StringLiteralV2(value = "New child node", language = Some("en"))), - position = Some(-3), - comments = Seq.empty[StringLiteralV2] + createChildNodeRequest = ChildNodeCreatePayloadADM( + parentNodeIri = Some(ListIRI.create(exampleListIri).fold(e => throw e, v => v)), + projectIri = ProjectIRI.create(SharedTestDataADM.IMAGES_PROJECT_IRI).fold(e => throw e, v => v), + position = Some(Position.create(-3).fold(e => throw e, v => v)), + labels = Labels + .create(Seq(StringLiteralV2(value = "New child node", language = Some("en")))) + .fold(e => throw e, v => v), + comments = Some( + Comments + .create(Seq(StringLiteralV2(value = "New child comment", language = Some("en")))) + .fold(e => throw e, v => v) + ) ), featureFactoryConfig = defaultFeatureFactoryConfig, requestingUser = SharedTestDataADM.imagesUser01, @@ -378,142 +186,6 @@ class ListsMessagesADMSpec extends CoreSpec(ListsMessagesADMSpec.config) with Li assert(caught.getMessage === INVALID_POSITION) } - "return a 'ForbiddenException' if the user changing the node info is not project or system admin" in { - val caught = intercept[ForbiddenException]( - NodeInfoChangeRequestADM( - listIri = exampleListIri, - changeNodeRequest = ChangeNodeInfoApiRequestADM( - listIri = exampleListIri, - projectIri = IMAGES_PROJECT_IRI, - 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")) - ) - ) - ), - featureFactoryConfig = defaultFeatureFactoryConfig, - requestingUser = SharedTestDataADM.imagesUser02, - apiRequestID = UUID.randomUUID - ) - ) - assert(caught.getMessage === LIST_CHANGE_PERMISSION_ERROR) - } - - "throw 'BadRequestException' for `createChildNodeRequest` when no parent node iri is given" in { - - val payload = - s""" - |{ - | "parentNodeIri": "", - | "projectIri": "${SharedTestDataADM.IMAGES_PROJECT_IRI}", - | "labels": [{ "value": "Neuer List Node", "language": "de"}], - | "comments": [] - |} - """.stripMargin - - val thrown = the[BadRequestException] thrownBy payload.parseJson.convertTo[CreateNodeApiRequestADM] - - thrown.getMessage should equal(LIST_NODE_IRI_INVALID_ERROR) - - } - - "throw 'BadRequestException' for `createChildNodeRequest` when parent node iri is invalid" in { - - val payload = - s""" - |{ - | "parentNodeIri": "notvalidIRI", - | "projectIri": "${SharedTestDataADM.IMAGES_PROJECT_IRI}", - | "labels": [{ "value": "Neuer List Node", "language": "de"}], - | "comments": [] - |} - """.stripMargin - - val thrown = the[BadRequestException] thrownBy payload.parseJson.convertTo[CreateNodeApiRequestADM] - - thrown.getMessage should equal(LIST_NODE_IRI_INVALID_ERROR) - - } - - "throw 'BadRequestException' for `createChildNodeRequest` when project iri is empty" in { - - val payload = - s""" - |{ - | "parentNodeIri": "$exampleListIri", - | "projectIri": "", - | "labels": [{ "value": "Neuer List Node", "language": "de"}], - | "comments": [] - |} - """.stripMargin - - val thrown = the[BadRequestException] thrownBy payload.parseJson.convertTo[CreateNodeApiRequestADM] - - thrown.getMessage should equal(PROJECT_IRI_MISSING_ERROR) - - } - - "throw 'BadRequestException' for `createChildNodeRequest` when project iri is invalid" in { - - val payload = - s""" - |{ - | "parentNodeIri": "$exampleListIri", - | "projectIri": "notvalidIRI", - | "labels": [{ "value": "Neuer List Node", "language": "de"}], - | "comments": [] - |} - """.stripMargin - - val thrown = the[BadRequestException] thrownBy payload.parseJson.convertTo[CreateNodeApiRequestADM] - - thrown.getMessage should equal(PROJECT_IRI_INVALID_ERROR) - - } - - "throw 'BadRequestException' for `createChildNodeRequest` when labels are empty" in { - - val payload = - s""" - |{ - | "parentNodeIri": "$exampleListIri", - | "projectIri": "${SharedTestDataADM.IMAGES_PROJECT_IRI}", - | "labels": [], - | "comments": [] - |} - """.stripMargin - - val thrown = the[BadRequestException] thrownBy payload.parseJson.convertTo[CreateNodeApiRequestADM] - - thrown.getMessage should equal(LABEL_MISSING_ERROR) - - } - - "throw 'BadRequestException' for `createChildNodeRequest` when custom iri of the child node is invalid" in { - - val payload = - s""" - |{ "id": "invalid-list-node-IRI", - | "parentNodeIri": "$exampleListIri", - | "projectIri": "${SharedTestDataADM.IMAGES_PROJECT_IRI}", - | "labels": [{ "value": "Neuer List Node", "language": "de"}], - | "comments": [] - |} - """.stripMargin - - val thrown = the[BadRequestException] thrownBy payload.parseJson.convertTo[CreateNodeApiRequestADM] - - thrown.getMessage should equal("Invalid list node IRI") - - } - "throw 'BadRequestException' for `ChangeNodePositionApiRequestADM` when no parent node iri is given" in { val payload = diff --git a/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/valueObjects/BUILD.bazel b/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/valueObjects/BUILD.bazel index af90934728..da363f069d 100644 --- a/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/valueObjects/BUILD.bazel +++ b/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/valueObjects/BUILD.bazel @@ -7,6 +7,7 @@ scala_test( name = "ValueObjectsADMSpec", size = "small", # 60s srcs = [ + "ListsValueObjectsADMSpec.scala", "ValueObjectsADMSpec.scala", ], data = [ diff --git a/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/valueObjects/ListsValueObjectsADMSpec.scala b/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/valueObjects/ListsValueObjectsADMSpec.scala new file mode 100644 index 0000000000..3b314ec0f4 --- /dev/null +++ b/webapi/src/test/scala/org/knora/webapi/messages/admin/responder/valueObjects/ListsValueObjectsADMSpec.scala @@ -0,0 +1,162 @@ +/* + * Copyright © 2015-2021 Data and Service Center for the Humanities (DaSCH) + * + * This file is part of Knora. + * + * Knora is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Knora is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with Knora. If not, see . + */ + +package org.knora.webapi.messages.admin.responder.valueObjects + +import org.knora.webapi.exceptions.BadRequestException +import org.knora.webapi.messages.admin.responder.listsmessages.ListsErrorMessagesADM.{ + COMMENT_INVALID_ERROR, + COMMENT_MISSING_ERROR, + INVALID_POSITION, + LABEL_INVALID_ERROR, + LABEL_MISSING_ERROR, + LIST_NAME_INVALID_ERROR, + LIST_NAME_MISSING_ERROR, + LIST_NODE_IRI_INVALID_ERROR, + LIST_NODE_IRI_MISSING_ERROR, + PROJECT_IRI_INVALID_ERROR, + PROJECT_IRI_MISSING_ERROR +} +import org.knora.webapi.messages.store.triplestoremessages.StringLiteralV2 +import org.knora.webapi.UnitSpec + +/** + * This spec is used to test the creation of value objects of the [[ListsValueObjectsADM]]. + */ +class ListsValueObjectsADMSpec extends UnitSpec(ValueObjectsADMSpec.config) { + + "ListIRI value object" when { + val validListIri = "http://rdfh.ch/lists/0803/qBCJAdzZSCqC_2snW5Q7Nw" + + "created using empty value" should { + "throw BadRequestException" in { + ListIRI.create("") should equal(Left(BadRequestException(LIST_NODE_IRI_MISSING_ERROR))) + } + } + "created using invalid value" should { + "throw BadRequestException" in { + ListIRI.create("not a list IRI") should equal(Left(BadRequestException(LIST_NODE_IRI_INVALID_ERROR))) + } + } + "created using valid value" should { + "return value object that value equals to the value used to its creation" in { + ListIRI.create(validListIri) should not equal Left(BadRequestException(LIST_NODE_IRI_INVALID_ERROR)) + } + } + } + + "ProjectIRI value object" when { +// TODO: check string formatter project iri validation because passing just "http://rdfh.ch/projects/@@@@@@" works + val validProjectIri = "http://rdfh.ch/projects/0001" + + "created using empty value" should { + "throw BadRequestException" in { + ProjectIRI.create("") should equal(Left(BadRequestException(PROJECT_IRI_MISSING_ERROR))) + } + } + "created using invalid value" should { + "throw BadRequestException" in { + ProjectIRI.create("not a project IRI") should equal(Left(BadRequestException(PROJECT_IRI_INVALID_ERROR))) + } + } + "created using valid value" should { + "not throw BadRequestExceptions" in { + ProjectIRI.create(validProjectIri) should not equal Left(BadRequestException(PROJECT_IRI_INVALID_ERROR)) + } + } + } + + "ListName value object" when { + val validListName = "It's valid list name example" + + "created using empty value" should { + "throw BadRequestException" in { + ListName.create("") should equal(Left(BadRequestException(LIST_NAME_MISSING_ERROR))) + } + } + "created using invalid value" should { + "throw BadRequestException" in { +// TODO: should this: "\"It's invalid list name example\"" pass? Same for comments and labels + ListName.create("\r") should equal(Left(BadRequestException(LIST_NAME_INVALID_ERROR))) + } + } + "created using valid value" should { + "not throw BadRequestExceptions" in { + ListName.create(validListName) should not equal Left(BadRequestException(LIST_NAME_INVALID_ERROR)) + } + } + } + + "Position value object" when { + val validPosition = 0 + + "created using invalid value" should { + "throw BadRequestException" in { + Position.create(-2) should equal(Left(BadRequestException(INVALID_POSITION))) + } + } + "created using valid value" should { + "not throw BadRequestExceptions" in { + Position.create(validPosition) should not equal Left(BadRequestException(INVALID_POSITION)) + } + } + } + + "Labels value object" when { + val validLabels = Seq(StringLiteralV2(value = "New Label", language = Some("en"))) + val invalidLabels = Seq(StringLiteralV2(value = "\r", language = Some("en"))) + + "created using empty value" should { + "throw BadRequestException" in { + Labels.create(Seq.empty) should equal(Left(BadRequestException(LABEL_MISSING_ERROR))) + } + } + "created using invalid value" should { + "throw BadRequestException" in { + Labels.create(invalidLabels) should equal(Left(BadRequestException(LABEL_INVALID_ERROR))) + } + } + "created using valid value" should { + "not throw BadRequestExceptions" in { + Labels.create(validLabels) should not equal Left(BadRequestException(LABEL_INVALID_ERROR)) + } + } + } + + "Comments value object" when { + val validComments = Seq(StringLiteralV2(value = "New Comment", language = Some("en"))) + val invalidComments = Seq(StringLiteralV2(value = "\r", language = Some("en"))) + + "created using empty value" should { + "throw BadRequestException" in { + Comments.create(Seq.empty) should equal(Left(BadRequestException(COMMENT_MISSING_ERROR))) + } + } + "created using invalid value" should { + "throw BadRequestException" in { + Comments.create(invalidComments) should equal(Left(BadRequestException(COMMENT_INVALID_ERROR))) + } + } + "created using valid value" should { + "not throw BadRequestExceptions" in { + Comments.create(validComments) should not equal Left(BadRequestException(COMMENT_INVALID_ERROR)) + } + } + } +} 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 46ada9b838..ba25405eed 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 @@ -6,18 +6,29 @@ package org.knora.webapi.responders.admin import java.util.UUID - import akka.actor.Status.Failure import akka.testkit._ import com.typesafe.config.{Config, ConfigFactory} import org.knora.webapi._ import org.knora.webapi.exceptions.{BadRequestException, DuplicateValueException, UpdateNotPerformedException} import org.knora.webapi.messages.StringFormatter +import org.knora.webapi.messages.admin.responder.listsmessages.NodeCreatePayloadADM.{ + ChildNodeCreatePayloadADM, + ListCreatePayloadADM +} import org.knora.webapi.messages.admin.responder.listsmessages._ import org.knora.webapi.messages.store.triplestoremessages.{RdfDataObject, StringLiteralV2} import org.knora.webapi.sharedtestdata.SharedTestDataV1._ import org.knora.webapi.sharedtestdata.{SharedListsTestDataADM, SharedTestDataADM} import org.knora.webapi.util.MutableTestIri +import org.knora.webapi.messages.admin.responder.valueObjects.{ + Comments, + Labels, + ListIRI, + ListName, + Position, + ProjectIRI +} import scala.concurrent.duration._ @@ -162,12 +173,16 @@ class ListsResponderADMSpec extends CoreSpec(ListsResponderADMSpec.config) with "used to modify lists" should { "create a list" in { responderManager ! ListCreateRequestADM( - createRootNode = CreateNodeApiRequestADM( - projectIri = IMAGES_PROJECT_IRI, - name = Some("neuelistename"), - labels = Seq(StringLiteralV2(value = "Neue Liste", language = Some("de"))), - comments = Seq.empty[StringLiteralV2] - ).escape, + createRootNode = ListCreatePayloadADM( + projectIri = ProjectIRI.create(IMAGES_PROJECT_IRI).fold(e => throw e, v => v), + name = Some(ListName.create("neuelistename").fold(e => throw e, v => v)), + labels = Labels + .create(Seq(StringLiteralV2(value = "Neue Liste", language = Some("de")))) + .fold(e => throw e, v => v), + comments = Comments + .create(Seq(StringLiteralV2(value = "Neuer Kommentar", language = Some("de")))) + .fold(e => throw e, v => v) + ), featureFactoryConfig = defaultFeatureFactoryConfig, requestingUser = SharedTestDataADM.imagesUser01, apiRequestID = UUID.randomUUID @@ -184,8 +199,8 @@ class ListsResponderADMSpec extends CoreSpec(ListsResponderADMSpec.config) with labels.size should be(1) labels.head should be(StringLiteralV2(value = "Neue Liste", language = Some("de"))) - val comments = received.list.listinfo.comments.stringLiterals - comments.isEmpty should be(true) + val comments: Seq[StringLiteralV2] = listInfo.comments.stringLiterals + comments.isEmpty should be(false) val children = received.list.children children.size should be(0) @@ -199,12 +214,16 @@ class ListsResponderADMSpec extends CoreSpec(ListsResponderADMSpec.config) with val commentWithSpecialCharacter = "Neue \\\"Kommentar\\\"" val nameWithSpecialCharacter = "a new \\\"name\\\"" responderManager ! ListCreateRequestADM( - createRootNode = CreateNodeApiRequestADM( - projectIri = IMAGES_PROJECT_IRI, - name = Some(nameWithSpecialCharacter), - labels = Seq(StringLiteralV2(value = labelWithSpecialCharacter, language = Some("de"))), - comments = Seq(StringLiteralV2(value = commentWithSpecialCharacter, language = Some("de"))) - ).escape, + createRootNode = ListCreatePayloadADM( + projectIri = ProjectIRI.create(IMAGES_PROJECT_IRI).fold(e => throw e, v => v), + name = Some(ListName.create(nameWithSpecialCharacter).fold(e => throw e, v => v)), + labels = Labels + .create(Seq(StringLiteralV2(value = labelWithSpecialCharacter, language = Some("de")))) + .fold(e => throw e, v => v), + comments = Comments + .create(Seq(StringLiteralV2(value = commentWithSpecialCharacter, language = Some("de")))) + .fold(e => throw e, v => v) + ), featureFactoryConfig = defaultFeatureFactoryConfig, requestingUser = SharedTestDataADM.imagesUser01, apiRequestID = UUID.randomUUID @@ -235,21 +254,29 @@ class ListsResponderADMSpec extends CoreSpec(ListsResponderADMSpec.config) with "update basic list information" in { val changeNodeInfoRequest = NodeInfoChangeRequestADM( listIri = newListIri.get, - changeNodeRequest = ChangeNodeInfoApiRequestADM( - listIri = newListIri.get, - projectIri = IMAGES_PROJECT_IRI, - name = Some("updated name"), + changeNodeRequest = NodeInfoChangePayloadADM( + listIri = ListIRI.create(newListIri.get).fold(e => throw e, v => v), + projectIri = ProjectIRI.create(IMAGES_PROJECT_IRI).fold(e => throw e, v => v), + name = Some(ListName.create("updated name").fold(e => throw e, v => v)), labels = Some( - Seq( - StringLiteralV2(value = "Neue geänderte Liste", language = Some("de")), - StringLiteralV2(value = "Changed List", language = Some("en")) - ) + Labels + .create( + Seq( + StringLiteralV2(value = "Neue geänderte Liste", language = Some("de")), + StringLiteralV2(value = "Changed List", language = Some("en")) + ) + ) + .fold(e => throw e, v => v) ), comments = Some( - Seq( - StringLiteralV2(value = "Neuer Kommentar", language = Some("de")), - StringLiteralV2(value = "New Comment", language = Some("en")) - ) + Comments + .create( + Seq( + StringLiteralV2(value = "Neuer Kommentar", language = Some("de")), + StringLiteralV2(value = "New Comment", language = Some("en")) + ) + ) + .fold(e => throw e, v => v) ) ), featureFactoryConfig = defaultFeatureFactoryConfig, @@ -283,12 +310,14 @@ class ListsResponderADMSpec extends CoreSpec(ListsResponderADMSpec.config) with } "not update basic list information if name is duplicate" in { + val name = Some(ListName.create("sommer").fold(e => throw e, v => v)) + val projectIRI = ProjectIRI.create(IMAGES_PROJECT_IRI).fold(e => throw e, v => v) responderManager ! NodeInfoChangeRequestADM( listIri = newListIri.get, - changeNodeRequest = ChangeNodeInfoApiRequestADM( - listIri = newListIri.get, - projectIri = IMAGES_PROJECT_IRI, - name = Some("sommer") + changeNodeRequest = NodeInfoChangePayloadADM( + listIri = ListIRI.create(newListIri.get).fold(e => throw e, v => v), + projectIri = projectIRI, + name = name ), featureFactoryConfig = defaultFeatureFactoryConfig, requestingUser = SharedTestDataADM.imagesUser01, @@ -297,7 +326,7 @@ class ListsResponderADMSpec extends CoreSpec(ListsResponderADMSpec.config) with expectMsg( Failure( DuplicateValueException( - "The name sommer is already used by a list inside the project http://rdfh.ch/projects/00FF." + s"The name ${name.value} is already used by a list inside the project ${projectIRI.value}." ) ) ) @@ -305,12 +334,18 @@ class ListsResponderADMSpec extends CoreSpec(ListsResponderADMSpec.config) with "add child to list - to the root node" in { responderManager ! ListChildNodeCreateRequestADM( - createChildNodeRequest = CreateNodeApiRequestADM( - parentNodeIri = Some(newListIri.get), - projectIri = IMAGES_PROJECT_IRI, - name = Some("first"), - labels = Seq(StringLiteralV2(value = "New First Child List Node Value", language = Some("en"))), - comments = Seq(StringLiteralV2(value = "New First Child List Node Comment", language = Some("en"))) + createChildNodeRequest = ChildNodeCreatePayloadADM( + parentNodeIri = Some(ListIRI.create(newListIri.get).fold(e => throw e, v => v)), + projectIri = ProjectIRI.create(IMAGES_PROJECT_IRI).fold(e => throw e, v => v), + name = Some(ListName.create("first").fold(e => throw e, v => v)), + labels = Labels + .create(Seq(StringLiteralV2(value = "New First Child List Node Value", language = Some("en")))) + .fold(e => throw e, v => v), + comments = Some( + Comments + .create(Seq(StringLiteralV2(value = "New First Child List Node Comment", language = Some("en")))) + .fold(e => throw e, v => v) + ) ), featureFactoryConfig = defaultFeatureFactoryConfig, requestingUser = SharedTestDataADM.imagesUser01, @@ -351,13 +386,19 @@ class ListsResponderADMSpec extends CoreSpec(ListsResponderADMSpec.config) with "add second child to list in first position - to the root node" in { responderManager ! ListChildNodeCreateRequestADM( - createChildNodeRequest = CreateNodeApiRequestADM( - parentNodeIri = Some(newListIri.get), - projectIri = IMAGES_PROJECT_IRI, - name = Some("second"), - position = Some(0), - labels = Seq(StringLiteralV2(value = "New Second Child List Node Value", language = Some("en"))), - comments = Seq(StringLiteralV2(value = "New Second Child List Node Comment", language = Some("en"))) + createChildNodeRequest = ChildNodeCreatePayloadADM( + parentNodeIri = Some(ListIRI.create(newListIri.get).fold(e => throw e, v => v)), + projectIri = ProjectIRI.create(IMAGES_PROJECT_IRI).fold(e => throw e, v => v), + name = Some(ListName.create("second").fold(e => throw e, v => v)), + position = Some(Position.create(0).fold(e => throw e, v => v)), + labels = Labels + .create(Seq(StringLiteralV2(value = "New Second Child List Node Value", language = Some("en")))) + .fold(e => throw e, v => v), + comments = Some( + Comments + .create(Seq(StringLiteralV2(value = "New Second Child List Node Comment", language = Some("en")))) + .fold(e => throw e, v => v) + ) ), featureFactoryConfig = defaultFeatureFactoryConfig, requestingUser = SharedTestDataADM.imagesUser01, @@ -398,12 +439,18 @@ class ListsResponderADMSpec extends CoreSpec(ListsResponderADMSpec.config) with "add child to second child node" in { responderManager ! ListChildNodeCreateRequestADM( - createChildNodeRequest = CreateNodeApiRequestADM( - parentNodeIri = Some(secondChildIri.get), - projectIri = IMAGES_PROJECT_IRI, - name = Some("third"), - labels = Seq(StringLiteralV2(value = "New Third Child List Node Value", language = Some("en"))), - comments = Seq(StringLiteralV2(value = "New Third Child List Node Comment", language = Some("en"))) + createChildNodeRequest = ChildNodeCreatePayloadADM( + parentNodeIri = Some(ListIRI.create(secondChildIri.get).fold(e => throw e, v => v)), + projectIri = ProjectIRI.create(IMAGES_PROJECT_IRI).fold(e => throw e, v => v), + name = Some(ListName.create("third").fold(e => throw e, v => v)), + labels = Labels + .create(Seq(StringLiteralV2(value = "New Third Child List Node Value", language = Some("en")))) + .fold(e => throw e, v => v), + comments = Some( + Comments + .create(Seq(StringLiteralV2(value = "New Third Child List Node Comment", language = Some("en")))) + .fold(e => throw e, v => v) + ) ), featureFactoryConfig = defaultFeatureFactoryConfig, requestingUser = SharedTestDataADM.imagesUser01, @@ -443,22 +490,32 @@ class ListsResponderADMSpec extends CoreSpec(ListsResponderADMSpec.config) with } "not create a node if given new position is out of range" in { - val givenPosition = 20 + val givenPosition = Some(Position.create(20).fold(e => throw e, v => v)) responderManager ! ListChildNodeCreateRequestADM( - createChildNodeRequest = CreateNodeApiRequestADM( - parentNodeIri = Some(newListIri.get), - projectIri = IMAGES_PROJECT_IRI, - name = Some("fourth"), - position = Some(givenPosition), - labels = Seq(StringLiteralV2(value = "New Fourth Child List Node Value", language = Some("en"))), - comments = Seq(StringLiteralV2(value = "New Fourth Child List Node Comment", language = Some("en"))) + createChildNodeRequest = ChildNodeCreatePayloadADM( + parentNodeIri = Some(ListIRI.create(newListIri.get).fold(e => throw e, v => v)), + projectIri = ProjectIRI.create(IMAGES_PROJECT_IRI).fold(e => throw e, v => v), + name = Some(ListName.create("fourth").fold(e => throw e, v => v)), + position = givenPosition, + labels = Labels + .create(Seq(StringLiteralV2(value = "New Fourth Child List Node Value", language = Some("en")))) + .fold(e => throw e, v => v), + comments = Some( + Comments + .create(Seq(StringLiteralV2(value = "New Fourth Child List Node Comment", language = Some("en")))) + .fold(e => throw e, v => v) + ) ), featureFactoryConfig = defaultFeatureFactoryConfig, requestingUser = SharedTestDataADM.imagesUser01, apiRequestID = UUID.randomUUID ) expectMsg( - Failure(BadRequestException(s"Invalid position given ${givenPosition}, maximum allowed position is = 2.")) + Failure( + BadRequestException( + s"Invalid position given ${givenPosition.map(_.value)}, maximum allowed position is = 2." + ) + ) ) } } diff --git a/webapi/src/test/scala/org/knora/webapi/sharedtestdata/SharedListsTestDataADM.scala b/webapi/src/test/scala/org/knora/webapi/sharedtestdata/SharedListsTestDataADM.scala index 93284800ca..6433bab674 100644 --- a/webapi/src/test/scala/org/knora/webapi/sharedtestdata/SharedListsTestDataADM.scala +++ b/webapi/src/test/scala/org/knora/webapi/sharedtestdata/SharedListsTestDataADM.scala @@ -32,7 +32,7 @@ object SharedListsTestDataADM { id = "http://rdfh.ch/lists/00FF/526f26ed04", name = Some("sommer"), labels = StringLiteralSequenceV2(Vector(StringLiteralV2("Sommer"))), - comments = StringLiteralSequenceV2(Vector.empty[StringLiteralV2]), + comments = Some(StringLiteralSequenceV2(Vector.empty[StringLiteralV2])), children = Seq.empty[ListChildNodeADM], position = 0, hasRootNode = "http://rdfh.ch/lists/00FF/d19af9ab" @@ -41,7 +41,7 @@ object SharedListsTestDataADM { id = "http://rdfh.ch/lists/00FF/eda2792605", name = Some("winter"), labels = StringLiteralSequenceV2(Vector(StringLiteralV2("Winter"))), - comments = StringLiteralSequenceV2(Vector.empty[StringLiteralV2]), + comments = Some(StringLiteralSequenceV2(Vector.empty[StringLiteralV2])), children = Seq.empty[ListChildNodeADM], position = 1, hasRootNode = "http://rdfh.ch/lists/00FF/d19af9ab" @@ -95,7 +95,7 @@ object SharedListsTestDataADM { id = "http://rdfh.ch/lists/0001/treeList01", name = Some("Tree list node 01"), labels = StringLiteralSequenceV2(Vector(StringLiteralV2(value = "Tree list node 01", language = Some("en")))), - comments = StringLiteralSequenceV2(Vector.empty[StringLiteralV2]), + comments = Some(StringLiteralSequenceV2(Vector.empty[StringLiteralV2])), children = Seq.empty[ListChildNodeADM], position = 0, hasRootNode = "http://rdfh.ch/lists/0001/treeList" @@ -109,7 +109,7 @@ object SharedListsTestDataADM { StringLiteralV2(value = "Tree list node 02", language = Some("en")) ) ), - comments = StringLiteralSequenceV2(Vector.empty[StringLiteralV2]), + comments = Some(StringLiteralSequenceV2(Vector.empty[StringLiteralV2])), children = Seq.empty[ListChildNodeADM], position = 1, hasRootNode = "http://rdfh.ch/lists/0001/treeList" @@ -118,13 +118,13 @@ object SharedListsTestDataADM { id = "http://rdfh.ch/lists/0001/treeList03", name = Some("Tree list node 03"), labels = StringLiteralSequenceV2(Vector(StringLiteralV2(value = "Tree list node 03", language = Some("en")))), - comments = StringLiteralSequenceV2(Vector.empty[StringLiteralV2]), + comments = Some(StringLiteralSequenceV2(Vector.empty[StringLiteralV2])), children = Seq( ListChildNodeADM( id = "http://rdfh.ch/lists/0001/treeList10", name = Some("Tree list node 10"), labels = StringLiteralSequenceV2(Vector(StringLiteralV2(value = "Tree list node 10", language = Some("en")))), - comments = StringLiteralSequenceV2(Vector.empty[StringLiteralV2]), + comments = Some(StringLiteralSequenceV2(Vector.empty[StringLiteralV2])), children = Seq.empty[ListChildNodeADM], position = 0, hasRootNode = "http://rdfh.ch/lists/0001/treeList" @@ -133,7 +133,7 @@ object SharedListsTestDataADM { id = "http://rdfh.ch/lists/0001/treeList11", name = Some("Tree list node 11"), labels = StringLiteralSequenceV2(Vector(StringLiteralV2(value = "Tree list node 11", language = Some("en")))), - comments = StringLiteralSequenceV2(Vector.empty[StringLiteralV2]), + comments = Some(StringLiteralSequenceV2(Vector.empty[StringLiteralV2])), children = Seq.empty[ListChildNodeADM], position = 1, hasRootNode = "http://rdfh.ch/lists/0001/treeList"