Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(admin): add list child node deletion route (DEV-729) #2064

Merged
merged 12 commits into from May 24, 2022
30 changes: 30 additions & 0 deletions docs/03-apis/api-admin/lists.md
Expand Up @@ -15,6 +15,8 @@ If IRI of the child node is given, return the node with its immediate children
- `GET: /admin/lists/infos/<listIri>` : return list information (without children)
- `GET: /admin/lists/nodes/<nodeIri>` : return list node information (without children)
- `GET: /admin/lists/<listIri>/info` : return list basic information (without children)
- `GET: /admin/lists/candelete/<listItemIri>` : check if list or its node is unused and can be deleted


- `POST: /admin/lists` : create new list
- `POST: /admin/lists/<parentNodeIri>` : create new child node under the supplied parent node IRI
Expand All @@ -27,6 +29,7 @@ If IRI of the child node is given, return the node with its immediate children
parent node

- `DELETE: /admin/lists/<listItemIri>` : delete a list (i.e. root node) or a child node and all its children, if not used
- `DELETE: /admin/lists/comments/<nodeIri>` : delete comments of a node (child only)

## List Item Operations

Expand Down Expand Up @@ -61,6 +64,21 @@ and all its children
- Return list (or node) basic information, `listinfo` (or `nodeinfo`), without its children
- GET: `/admin/lists/<listIri>/info`

### Check if list node is unused and can be deleted

- Required permission: none
- GET: `/admin/lists/candelete/<listItemIri>`
- Return simple JSON that confirms if the list node can be deleted

```json
{
"canDeleteList": true,
"listIri": "http://rdfh.ch/lists/0801/xxx"
}
```

List (root node or child node with all its children) can be deleted only if it (or one of its children) is not used.

### Create new list

- Required permission: SystemAdmin / ProjectAdmin
Expand Down Expand Up @@ -338,3 +356,15 @@ remaining child nodes with respect to the position of the deleted node.
- If the IRI of a child node is given, the updated parent node is returned.

- Delete `/admin/lists/<listItemIri>`

### Delete child node comments

Performing a DELETE request to route `/admin/lists/comments/<nodeIri>` deletes the comments of that node.
As a response sipmle JSON is returned:

```json
{
"commentsDeleted": true,
"nodeIri": "http://rdfh.ch/lists/0801/xxx"
}
```
36 changes: 35 additions & 1 deletion test_data/all_data/anything-data.ttl
Expand Up @@ -2196,4 +2196,38 @@
knora-base:hasRootNode <http://rdfh.ch/lists/0001/notUsedList> ;
knora-base:listNodePosition 0 ;
rdfs:label "child of node 3"@en .


<http://rdfh.ch/lists/0001/testList>
a knora-base:ListNode ;
knora-base:isRootNode true ;
knora-base:listNodeName "Test list root for comments" ;
rdfs:label "Test list root label"@en ;
rdfs:comment "Test list root comment"@en ;
knora-base:attachedToProject <http://rdfh.ch/projects/0001> ;
knora-base:hasSubListNode <http://rdfh.ch/lists/0001/testList01>,
<http://rdfh.ch/lists/0001/testList02>,
<http://rdfh.ch/lists/0001/testList03> .

<http://rdfh.ch/lists/0001/testList01>
a knora-base:ListNode ;
knora-base:listNodeName "Test list node 01 with one comment" ;
knora-base:hasRootNode <http://rdfh.ch/lists/0001/testList> ;
knora-base:listNodePosition 0 ;
rdfs:label "Test list node 01"@en ;
rdfs:comment "Test list child node comment"@en .

<http://rdfh.ch/lists/0001/testList02>
a knora-base:ListNode ;
knora-base:listNodeName "Test list node 02 with two comments" ;
knora-base:hasRootNode <http://rdfh.ch/lists/0001/testList> ;
knora-base:listNodePosition 0 ;
rdfs:label "Test list node 02"@en ;
rdfs:comment "Test list child node comment 01"@en ;
rdfs:comment "Test list child node comment 02"@en .

<http://rdfh.ch/lists/0001/testList03>
a knora-base:ListNode ;
knora-base:listNodeName "Test list node 03 w/o comments" ;
knora-base:hasRootNode <http://rdfh.ch/lists/0001/testList> ;
knora-base:listNodePosition 0 ;
rdfs:label "Test list node 03"@en .
Expand Up @@ -22,9 +22,9 @@ import org.knora.webapi.messages.store.triplestoremessages.TriplestoreJsonProtoc
import spray.json._

import java.util.UUID
import org.knora.webapi.messages.admin.responder.valueObjects.Comments

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// API requests
/////////////// API requests

/**
* Represents an API request payload that asks the Knora API server to create a new list root node.
Expand Down Expand Up @@ -330,7 +330,7 @@ case class ListItemDeleteRequestADM(
) extends ListsResponderRequestADM

/**
* Request checks if a list is unused and can be deleted. A successful response will be a [[CanDeleteListResponseADM]]
* Requests checks if a list is unused and can be deleted. A successful response will be a [[CanDeleteListResponseADM]]
*
* @param iri the IRI of the list node (root or child).
* @param featureFactoryConfig the feature factory configuration.
Expand All @@ -339,8 +339,45 @@ case class ListItemDeleteRequestADM(
case class CanDeleteListRequestADM(iri: IRI, featureFactoryConfig: FeatureFactoryConfig, requestingUser: UserADM)
extends ListsResponderRequestADM

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Responses
/**
* Requests deletion of all list node comments. A successful response will be a [[ListNodeCommentsDeleteADM]]
*
* @param iri the IRI of the list node (root or child).
* @param featureFactoryConfig the feature factory configuration.
* @param requestingUser the user making the request.
*/
case class ListNodeCommentsDeleteRequestADM(
iri: IRI,
featureFactoryConfig: FeatureFactoryConfig,
requestingUser: UserADM
) extends ListsResponderRequestADM

///////////////////////// Responses

/**
* Responds to deletion of list node's comments by returning a success message.
*
* @param nodeIri the IRI of the list that comments are deleted.
* @param commentsDeleted contains a boolean value if comments were deleted.
*/
case class ListNodeCommentsDeleteResponseADM(nodeIri: IRI, commentsDeleted: Boolean)
extends KnoraResponseADM
with ListADMJsonProtocol {
def toJsValue: JsValue = ListNodeCommentsDeleteResponseADMFormat.write(this)
}

/**
* Returns an information if node can be deleted (none of its nodes is used in data).
*
* @param iri the IRI of the list that is checked.
* @param canDeleteList contains a boolean value if list node can be deleted.
*/
case class CanDeleteListResponseADM(listIri: IRI, canDeleteList: Boolean)
extends KnoraResponseADM
with ListADMJsonProtocol {

def toJsValue: JsValue = canDeleteListResponseADMFormat.write(this)
}

/**
* Represents a sequence of list info nodes.
Expand Down Expand Up @@ -433,16 +470,6 @@ case class ChildNodeDeleteResponseADM(node: ListNodeADM) extends ListItemDeleteR
def toJsValue: JsValue = listNodeDeleteResponseADMFormat.write(this)
}

/**
* Checks if a list can be deleted (none of its nodes is used in data).
*
* @param iri the IRI of the list that is checked.
*/
case class CanDeleteListResponseADM(listIri: IRI, canDeleteList: Boolean) extends ListItemDeleteResponseADM {

def toJsValue: JsValue = canDeleteListResponseADMFormat.write(this)
}

/**
* Responds to change of a child node's position by returning its parent node together with list of its children.
*
Expand Down Expand Up @@ -1339,4 +1366,6 @@ trait ListADMJsonProtocol extends SprayJsonSupport with DefaultJsonProtocol with
jsonFormat(ListDeleteResponseADM, "iri", "deleted")
implicit val canDeleteListResponseADMFormat: RootJsonFormat[CanDeleteListResponseADM] =
jsonFormat(CanDeleteListResponseADM, "listIri", "canDeleteList")
implicit val ListNodeCommentsDeleteResponseADMFormat: RootJsonFormat[ListNodeCommentsDeleteResponseADM] =
jsonFormat(ListNodeCommentsDeleteResponseADM, "nodeIri", "commentsDeleted")
}
Expand Up @@ -92,6 +92,8 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde
deleteListItemRequestADM(nodeIri, featureFactoryConfig, requestingUser, apiRequestID)
case CanDeleteListRequestADM(iri, featureFactoryConfig, requestingUser) =>
canDeleteListRequestADM(iri)
case ListNodeCommentsDeleteRequestADM(iri, featureFactoryConfig, requestingUser) =>
deleteListNodeCommentsADM(iri, featureFactoryConfig, requestingUser)
case other => handleUnexpectedMessage(other, log, this.getClass.getName)
}

Expand Down Expand Up @@ -1830,6 +1832,57 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde

} yield CanDeleteListResponseADM(iri, canDelete)

/**
* Deletes all comments from requested list node (only child).
*/
private def deleteListNodeCommentsADM(
iri: IRI,
featureFactoryConfig: FeatureFactoryConfig,
requestingUser: UserADM
): Future[ListNodeCommentsDeleteResponseADM] =
for {
node <- listNodeInfoGetADM(
nodeIri = iri,
featureFactoryConfig = featureFactoryConfig,
requestingUser = KnoraSystemInstances.Users.SystemUser
)

doesNodeHaveComments = node.get.getComments.stringLiterals.length > 0

_ = if (!doesNodeHaveComments) {
throw BadRequestException(s"Nothing to delete. Node $iri does not have comments.")
}

isRootNode =
node match {
case Some(_: ListRootNodeInfoADM) => true
case Some(_: ListChildNodeInfoADM) => false
case _ => throw InconsistentRepositoryDataException("Bad data. List node expected.")
}

_ = if (isRootNode) {
throw BadRequestException("Root node comments cannot be deleted.")
}

projectIri <- getProjectIriFromNode(iri, featureFactoryConfig)
namedGraph <- getDataNamedGraph(projectIri, featureFactoryConfig)

sparqlQuery <-
Future(
org.knora.webapi.messages.twirl.queries.sparql.admin.txt
.deleteListNodeComments(
namedGraph = namedGraph,
nodeIri = iri,
isRootNode = isRootNode
)
.toString()
)

_: SparqlUpdateResponse <- (storeManager ? SparqlUpdateRequest(sparqlQuery))
.mapTo[SparqlUpdateResponse]
mpro7 marked this conversation as resolved.
Show resolved Hide resolved

} yield ListNodeCommentsDeleteResponseADM(iri, !isRootNode)

/**
* Delete a node (root or child). If a root node is given, check for its usage in data and ontology. If not used,
* delete the list and return a confirmation message.
Expand Down
Expand Up @@ -39,7 +39,8 @@ class DeleteListItemsRouteADM(routeData: KnoraRouteData)

def makeRoute(featureFactoryConfig: FeatureFactoryConfig): Route =
deleteListItem(featureFactoryConfig) ~
canDeleteList(featureFactoryConfig)
canDeleteList(featureFactoryConfig) ~
deleteListNodeComments(featureFactoryConfig)

/* delete list (i.e. root node) or a child node which should also delete its children */
private def deleteListItem(featureFactoryConfig: FeatureFactoryConfig): Route = path(ListsBasePath / Segment) { iri =>
Expand Down Expand Up @@ -96,4 +97,32 @@ class DeleteListItemsRouteADM(routeData: KnoraRouteData)
)
}
}

/**
* Deletes all comments from requested list node (only child).
*/
private def deleteListNodeComments(featureFactoryConfig: FeatureFactoryConfig): Route =
path(ListsBasePath / "comments" / Segment) { iri =>
delete { requestContext =>
val listIri = stringFormatter.validateAndEscapeIri(iri, throw BadRequestException(s"Invalid list IRI: $iri"))

val requestMessage: Future[ListNodeCommentsDeleteRequestADM] =
for {
requestingUser <- getUserADM(requestContext, featureFactoryConfig)
} yield ListNodeCommentsDeleteRequestADM(
iri = listIri,
featureFactoryConfig = featureFactoryConfig,
requestingUser = requestingUser
)

RouteUtilADM.runJsonRoute(
requestMessageF = requestMessage,
requestContext = requestContext,
featureFactoryConfig = featureFactoryConfig,
settings = settings,
responderManager = responderManager,
log = log
)
}
}
}
@@ -0,0 +1,32 @@
@*
* Copyright © 2021 - 2022 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors.
* SPDX-License-Identifier: Apache-2.0
*@

@import org.knora.webapi.IRI

@**
* Deletes a list node comment.
*
* @param namedGraph the named graph to update.
* @param nodeIri the IRI of the list node to update.
* @param isRootNode flag to identify node type.
*@
@(namedGraph: IRI,
nodeIri: IRI,
isRootNode: Boolean)

PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX knora-base: <http://www.knora.org/ontology/knora-base#>

DELETE {
GRAPH <@namedGraph> {
<@nodeIri> rdfs:comment ?comments .
}
}

WHERE {
GRAPH <@namedGraph> {
<@nodeIri> rdfs:comment ?comments .
}
}
Expand Up @@ -106,7 +106,7 @@ class OldListsRouteADMFeatureE2ESpec

// log.debug("lists: {}", lists)
mpro7 marked this conversation as resolved.
Show resolved Hide resolved

lists.size should be(8)
lists.size should be(9)
clientTestDataCollector.addFile(
TestDataFileContent(
filePath = TestDataFilePath(
Expand Down Expand Up @@ -161,7 +161,7 @@ class OldListsRouteADMFeatureE2ESpec

// log.debug("lists: {}", lists)

lists.size should be(3)
lists.size should be(4)

clientTestDataCollector.addFile(
TestDataFileContent(
Expand Down