diff --git a/integration/src/test/scala/org/knora/webapi/core/LayersTest.scala b/integration/src/test/scala/org/knora/webapi/core/LayersTest.scala index 6c6e95e38d..57f05c80b1 100644 --- a/integration/src/test/scala/org/knora/webapi/core/LayersTest.scala +++ b/integration/src/test/scala/org/knora/webapi/core/LayersTest.scala @@ -106,7 +106,7 @@ object LayersTest { with IriConverter with IriService with KnoraProjectRepoLive - with ListsResponderADM + with ListsResponder with ListsResponderV2 with MessageRelay with OntologyCache @@ -174,7 +174,9 @@ object LayersTest { IriService.layer, KnoraProjectRepoLive.layer, KnoraResponseRenderer.layer, - ListsResponderADMLive.layer, + ListsEndpoints.layer, + ListsEndpointsHandlers.layer, + ListsResponder.layer, ListsResponderV2Live.layer, MaintenanceEndpoints.layer, MaintenanceEndpointsHandlers.layer, diff --git a/integration/src/test/scala/org/knora/webapi/responders/admin/ListsResponderADMSpec.scala b/integration/src/test/scala/org/knora/webapi/responders/admin/ListsResponderSpec.scala similarity index 97% rename from integration/src/test/scala/org/knora/webapi/responders/admin/ListsResponderADMSpec.scala rename to integration/src/test/scala/org/knora/webapi/responders/admin/ListsResponderSpec.scala index 85700388af..6573428fa4 100644 --- a/integration/src/test/scala/org/knora/webapi/responders/admin/ListsResponderADMSpec.scala +++ b/integration/src/test/scala/org/knora/webapi/responders/admin/ListsResponderSpec.scala @@ -23,6 +23,7 @@ import org.knora.webapi.messages.admin.responder.listsmessages.ListNodeCreatePay import org.knora.webapi.messages.admin.responder.listsmessages.* import org.knora.webapi.messages.store.triplestoremessages.RdfDataObject import org.knora.webapi.messages.store.triplestoremessages.StringLiteralV2 +import org.knora.webapi.routing.UnsafeZioRun import org.knora.webapi.sharedtestdata.SharedListsTestDataADM import org.knora.webapi.sharedtestdata.SharedTestDataADM import org.knora.webapi.sharedtestdata.SharedTestDataADM2.* @@ -32,7 +33,7 @@ import org.knora.webapi.util.MutableTestIri /** * Tests [[ListsResponderADM]]. */ -class ListsResponderADMSpec extends CoreSpec with ImplicitSender { +class ListsResponderSpec extends CoreSpec with ImplicitSender { override lazy val rdfDataObjects = List( RdfDataObject( @@ -56,39 +57,18 @@ class ListsResponderADMSpec extends CoreSpec with ImplicitSender { "The Lists Responder" when { "used to query information about lists" should { "return all lists" in { - appActor ! ListsGetRequestADM( - requestingUser = SharedTestDataADM.imagesUser01 - ) - - val received: ListsGetResponseADM = expectMsgType[ListsGetResponseADM](timeout) - - received.lists.size should be(9) + val actual = UnsafeZioRun.runOrThrow(ListsResponder.getLists(None)) + actual.lists.size should be(9) } "return all lists belonging to the images project" in { - appActor ! ListsGetRequestADM( - projectIri = Some(imagesProjectIri), - requestingUser = SharedTestDataADM.imagesUser01 - ) - - val received: ListsGetResponseADM = expectMsgType[ListsGetResponseADM](timeout) - - // log.debug("received: " + received) - - received.lists.size should be(4) + val actual = UnsafeZioRun.runOrThrow(ListsResponder.getLists(Some(ProjectIri.unsafeFrom(imagesProjectIri)))) + actual.lists.size should be(4) } "return all lists belonging to the anything project" in { - appActor ! ListsGetRequestADM( - projectIri = Some(anythingProjectIri), - requestingUser = SharedTestDataADM.imagesUser01 - ) - - val received: ListsGetResponseADM = expectMsgType[ListsGetResponseADM](timeout) - - // log.debug("received: " + received) - - received.lists.size should be(4) + val actual = UnsafeZioRun.runOrThrow(ListsResponder.getLists(Some(ProjectIri.unsafeFrom(anythingProjectIri)))) + actual.lists.size should be(4) } "return basic list information (anything list)" in { diff --git a/webapi/src/main/scala/org/knora/webapi/core/LayersLive.scala b/webapi/src/main/scala/org/knora/webapi/core/LayersLive.scala index 14211d0a9d..ba3cd70ca7 100644 --- a/webapi/src/main/scala/org/knora/webapi/core/LayersLive.scala +++ b/webapi/src/main/scala/org/knora/webapi/core/LayersLive.scala @@ -75,7 +75,7 @@ object LayersLive { AuthorizationRestService & CacheService & CacheServiceRequestMessageHandler & CardinalityHandler & CardinalityService & ConstructResponseUtilV2 & ConstructTransformer & GravsearchTypeInspectionRunner & GroupsResponderADM & HttpServer & IIIFRequestMessageHandler & InferenceOptimizationService & - InstrumentationServerConfig & IriConverter & IriService & JwtService & KnoraProjectRepo & ListsResponderADM & + InstrumentationServerConfig & IriConverter & IriService & JwtService & KnoraProjectRepo & ListsResponder & ListsResponderV2 & MessageRelay & OntologyCache & OntologyHelpers & OntologyInferencer & OntologyRepo & OntologyResponderV2 & PermissionsResponderADM & PermissionsRestService & PermissionUtilADM & PredicateObjectMapper & ProjectADMRestService & ProjectADMService & ProjectExportService & @@ -122,7 +122,9 @@ object LayersLive { JwtServiceLive.layer, KnoraProjectRepoLive.layer, KnoraResponseRenderer.layer, - ListsResponderADMLive.layer, + ListsEndpoints.layer, + ListsEndpointsHandlers.layer, + ListsResponder.layer, ListsResponderV2Live.layer, MaintenanceEndpoints.layer, MaintenanceEndpointsHandlers.layer, 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 6f4f901d69..547c0cb771 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 @@ -150,21 +150,10 @@ case class ChangeNodePositionApiRequestADM(position: Int, parentIri: IRI) extend // Messages /** - * An abstract trait for messages that can be sent to `HierarchicalListsResponderV2`. + * A trait for messages that can be sent to `HierarchicalListsResponderV2`. */ sealed trait ListsResponderRequestADM extends KnoraRequestADM with RelayedMessage -/** - * Requests a list of all lists or the lists inside a project. A successful response will be a [[ListsGetResponseADM]] - * - * @param projectIri the IRI of the project. - * @param requestingUser the user making the request. - */ -case class ListsGetRequestADM( - projectIri: Option[IRI] = None, - requestingUser: User -) extends ListsResponderRequestADM - /** * Requests a node (root or child). A successful response will be a [[ListItemGetResponseADM]] * @@ -474,18 +463,28 @@ case class NodeADM(nodeinfo: ListChildNodeInfoADM, children: Seq[ListChildNodeAD /** * Represents basic information about a list node, the information which is found in the list's root or child node. - * - * @param id the IRI of the list. - * @param name the name of the list node. - * @param labels the labels of the node in all available languages. - * @param comments the comments attached to the node in all available languages. */ -abstract class ListNodeInfoADM( - id: IRI, - name: Option[String], - labels: StringLiteralSequenceV2, - comments: StringLiteralSequenceV2 -) { +sealed trait ListNodeInfoADM { + + /** + * @return The IRI of the list node. + */ + def id: IRI + + /** + * @return The name of the list node. + */ + def name: Option[String] + + /** + * @return The labels of the node in all available languages. + */ + def labels: StringLiteralSequenceV2 + + /** + * @return The comments attached to the node in all available languages. + */ + def comments: StringLiteralSequenceV2 /** * Sorts the whole hierarchy. @@ -494,10 +493,6 @@ abstract class ListNodeInfoADM( */ def sorted: ListNodeInfoADM - def getName: Option[String] = name - - def getComments: StringLiteralSequenceV2 = comments - /** * Gets the label in the user's preferred language. * @@ -515,7 +510,6 @@ abstract class ListNodeInfoADM( * @return the comment in the preferred language. */ def getCommentInPreferredLanguage(userLang: String, fallbackLang: String): Option[String] - } case class ListRootNodeInfoADM( @@ -524,7 +518,7 @@ case class ListRootNodeInfoADM( name: Option[String] = None, labels: StringLiteralSequenceV2, comments: StringLiteralSequenceV2 -) extends ListNodeInfoADM(id, name, labels, comments) { +) extends ListNodeInfoADM { /** * Sorts the whole hierarchy. @@ -587,7 +581,7 @@ case class ListChildNodeInfoADM( comments: StringLiteralSequenceV2, position: Int, hasRootNode: IRI -) extends ListNodeInfoADM(id, name, labels, comments) { +) extends ListNodeInfoADM { /** * Sorts the whole hierarchy. diff --git a/webapi/src/main/scala/org/knora/webapi/responders/admin/ListsResponderADM.scala b/webapi/src/main/scala/org/knora/webapi/responders/admin/ListsResponder.scala similarity index 94% rename from webapi/src/main/scala/org/knora/webapi/responders/admin/ListsResponderADM.scala rename to webapi/src/main/scala/org/knora/webapi/responders/admin/ListsResponder.scala index aadfdcce84..9c8f399b57 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/admin/ListsResponderADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/admin/ListsResponder.scala @@ -23,7 +23,8 @@ import org.knora.webapi.config.AppConfig import org.knora.webapi.core.MessageHandler import org.knora.webapi.core.MessageRelay import org.knora.webapi.messages.IriConversions.* -import org.knora.webapi.messages.OntologyConstants +import org.knora.webapi.messages.OntologyConstants.KnoraBase +import org.knora.webapi.messages.OntologyConstants.Rdfs import org.knora.webapi.messages.ResponderRequest import org.knora.webapi.messages.SmartIri import org.knora.webapi.messages.StringFormatter @@ -33,14 +34,17 @@ import org.knora.webapi.messages.admin.responder.listsmessages.* import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectADM import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectGetADM import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM.* +import org.knora.webapi.messages.store.triplestoremessages.SparqlExtendedConstructResponse.ConstructPredicateObjects import org.knora.webapi.messages.store.triplestoremessages.* import org.knora.webapi.messages.twirl.queries.sparql import org.knora.webapi.responders.IriLocker import org.knora.webapi.responders.IriService import org.knora.webapi.responders.Responder +import org.knora.webapi.responders.admin.ListsResponder.Queries import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri import org.knora.webapi.slice.admin.domain.model.User import org.knora.webapi.slice.admin.domain.service.ProjectADMService +import org.knora.webapi.slice.common.repo.service.PredicateObjectMapper import org.knora.webapi.store.triplestore.api.TriplestoreService import org.knora.webapi.store.triplestore.api.TriplestoreService.Queries.Ask import org.knora.webapi.store.triplestore.api.TriplestoreService.Queries.Construct @@ -48,18 +52,14 @@ import org.knora.webapi.store.triplestore.api.TriplestoreService.Queries.Select import org.knora.webapi.store.triplestore.api.TriplestoreService.Queries.Update import org.knora.webapi.util.ZioHelper -/** - * A responder that returns information about lists. - */ -trait ListsResponderADM {} -final case class ListsResponderADMLive( +final case class ListsResponder( appConfig: AppConfig, iriService: IriService, messageRelay: MessageRelay, + mapper: PredicateObjectMapper, triplestore: TriplestoreService, implicit val stringFormatter: StringFormatter -) extends ListsResponderADM - with MessageHandler +) extends MessageHandler with LazyLogging { // The IRI used to lock user creation and update @@ -72,7 +72,6 @@ final case class ListsResponderADMLive( * Receives a message of type [[ListsResponderRequestADM]], and returns an appropriate response message. */ override def handle(msg: ResponderRequest): Task[Any] = msg match { - case ListsGetRequestADM(projectIri, _) => listsGetRequestADM(projectIri) case ListGetRequestADM(listIri, _) => listGetRequestADM(listIri) case ListNodeInfoGetRequestADM(listIri, _) => listNodeInfoGetRequestADM(listIri) case NodePathGetRequestADM(iri, requestingUser) => nodePathGetAdminRequest(iri, requestingUser) @@ -113,57 +112,28 @@ final case class ListsResponderADMLive( } /** - * Gets all lists and returns them as a [[ListsGetResponseADM]]. For performance reasons + * Gets all lists or list belonging to a project and returns them as a [[ListsGetResponseADM]]. For performance reasons * (as lists can be very large), we only return the head of the list, i.e. the root node without * any children. * - * @param projectIri the IRI of the project the list belongs to. + * @param projectIri [[Some(ProjectIri)]] if the project for which lists are to be queried. + * [[None]] if all lists are to be queried. * @return a [[ListsGetResponseADM]]. */ - private def listsGetRequestADM(projectIri: Option[IRI]) = + def getLists(projectIri: Option[ProjectIri]): Task[ListsGetResponseADM] = for { - statements <- triplestore - .query(Construct(sparql.admin.txt.getLists(projectIri))) - .flatMap(_.asExtended) - .map(_.statements.toList) - - lists: Seq[ListNodeInfoADM] = - statements.map { case (listIri: SubjectV2, propsMap: Map[SmartIri, Seq[LiteralV2]]) => - val name: Option[String] = propsMap - .get(OntologyConstants.KnoraBase.ListNodeName.toSmartIri) - .map(_.head.asInstanceOf[StringLiteralV2].value) - val labels: Seq[StringLiteralV2] = propsMap - .getOrElse( - OntologyConstants.Rdfs.Label.toSmartIri, - Seq.empty[StringLiteralV2] - ) - .map(_.asInstanceOf[StringLiteralV2]) - val comments: Seq[StringLiteralV2] = propsMap - .getOrElse( - OntologyConstants.Rdfs.Comment.toSmartIri, - Seq.empty[StringLiteralV2] - ) - .map(_.asInstanceOf[StringLiteralV2]) - - ListRootNodeInfoADM( - id = listIri.toString, - projectIri = propsMap - .getOrElse( - OntologyConstants.KnoraBase.AttachedToProject.toSmartIri, - throw InconsistentRepositoryDataException( - "The required property 'attachedToProject' not found." - ) - ) - .head - .asInstanceOf[IriLiteralV2] - .value, - name = name, - labels = StringLiteralSequenceV2(labels.toVector), - comments = StringLiteralSequenceV2(comments.toVector) - ).unescape + statements <- + triplestore.query(Construct(Queries.getListsQuery(projectIri))).flatMap(_.asExtended).map(_.statements) + lists <- + ZIO.foreach(statements.toList) { case (listIri: SubjectV2, objs: ConstructPredicateObjects) => + for { + name <- mapper.getSingleOption[StringLiteralV2](KnoraBase.ListNodeName, objs).map(_.map(_.value)) + labels <- mapper.getList[StringLiteralV2](Rdfs.Label, objs).map(_.toVector).map(StringLiteralSequenceV2) + comments <- mapper.getList[StringLiteralV2](Rdfs.Comment, objs).map(_.toVector).map(StringLiteralSequenceV2) + projectIri <- mapper.getSingleOrFail[IriLiteralV2](KnoraBase.AttachedToProject, objs).map(_.value) + } yield ListRootNodeInfoADM(listIri.toString, projectIri, name, labels, comments).unescape } - - } yield ListsGetResponseADM(lists = lists) + } yield ListsGetResponseADM(lists) /** * Retrieves a complete list (root and all children) from the triplestore and returns it as a optional [[ListADM]]. @@ -287,14 +257,14 @@ final case class ListsResponderADMLive( val nodeInfo: ListNodeInfoADM = statements.head match { case (nodeIri: SubjectV2, propsMap: Map[SmartIri, Seq[LiteralV2]]) => val labels: Seq[StringLiteralV2] = propsMap - .getOrElse(OntologyConstants.Rdfs.Label.toSmartIri, Seq.empty[StringLiteralV2]) + .getOrElse(Rdfs.Label.toSmartIri, Seq.empty[StringLiteralV2]) .map(_.asInstanceOf[StringLiteralV2]) val comments: Seq[StringLiteralV2] = propsMap - .getOrElse(OntologyConstants.Rdfs.Comment.toSmartIri, Seq.empty[StringLiteralV2]) + .getOrElse(Rdfs.Comment.toSmartIri, Seq.empty[StringLiteralV2]) .map(_.asInstanceOf[StringLiteralV2]) val attachedToProjectOption: Option[IRI] = - propsMap.get(OntologyConstants.KnoraBase.AttachedToProject.toSmartIri) match { + propsMap.get(KnoraBase.AttachedToProject.toSmartIri) match { case Some(iris: Seq[LiteralV2]) => iris.headOption match { case Some(iri: IriLiteralV2) => Some(iri.value) @@ -308,7 +278,7 @@ final case class ListsResponderADMLive( } val hasRootNodeOption: Option[IRI] = - propsMap.get(OntologyConstants.KnoraBase.HasRootNode.toSmartIri) match { + propsMap.get(KnoraBase.HasRootNode.toSmartIri) match { case Some(iris: Seq[LiteralV2]) => iris.headOption match { case Some(iri: IriLiteralV2) => Some(iri.value) @@ -321,7 +291,7 @@ final case class ListsResponderADMLive( case None => None } - val isRootNode: Boolean = propsMap.get(OntologyConstants.KnoraBase.IsRootNode.toSmartIri) match { + val isRootNode: Boolean = propsMap.get(KnoraBase.IsRootNode.toSmartIri) match { case Some(values: Seq[LiteralV2]) => values.headOption match { case Some(value: BooleanLiteralV2) => value.value @@ -336,7 +306,7 @@ final case class ListsResponderADMLive( } val positionOption: Option[Int] = propsMap - .get(OntologyConstants.KnoraBase.ListNodePosition.toSmartIri) + .get(KnoraBase.ListNodePosition.toSmartIri) .map(_.head.asInstanceOf[IntLiteralV2].value) if (isRootNode) { @@ -348,7 +318,7 @@ final case class ListsResponderADMLive( ) ), name = propsMap - .get(OntologyConstants.KnoraBase.ListNodeName.toSmartIri) + .get(KnoraBase.ListNodeName.toSmartIri) .map(_.head.asInstanceOf[StringLiteralV2].value), labels = StringLiteralSequenceV2(labels.toVector), comments = StringLiteralSequenceV2(comments.toVector) @@ -357,7 +327,7 @@ final case class ListsResponderADMLive( ListChildNodeInfoADM( id = nodeIri.toString, name = propsMap - .get(OntologyConstants.KnoraBase.ListNodeName.toSmartIri) + .get(KnoraBase.ListNodeName.toSmartIri) .map(_.head.asInstanceOf[StringLiteralV2].value), labels = StringLiteralSequenceV2(labels.toVector), comments = StringLiteralSequenceV2(comments.toVector), @@ -426,14 +396,14 @@ final case class ListsResponderADMLive( node: ListNodeADM = statements.head match { case (nodeIri: SubjectV2, propsMap: Map[SmartIri, Seq[LiteralV2]]) => val labels: Seq[StringLiteralV2] = propsMap - .getOrElse(OntologyConstants.Rdfs.Label.toSmartIri, Seq.empty[StringLiteralV2]) + .getOrElse(Rdfs.Label.toSmartIri, Seq.empty[StringLiteralV2]) .map(_.asInstanceOf[StringLiteralV2]) val comments: Seq[StringLiteralV2] = propsMap - .getOrElse(OntologyConstants.Rdfs.Comment.toSmartIri, Seq.empty[StringLiteralV2]) + .getOrElse(Rdfs.Comment.toSmartIri, Seq.empty[StringLiteralV2]) .map(_.asInstanceOf[StringLiteralV2]) val attachedToProjectOption: Option[IRI] = - propsMap.get(OntologyConstants.KnoraBase.AttachedToProject.toSmartIri) match { + propsMap.get(KnoraBase.AttachedToProject.toSmartIri) match { case Some(iris: Seq[LiteralV2]) => iris.headOption match { case Some(iri: IriLiteralV2) => Some(iri.value) @@ -447,7 +417,7 @@ final case class ListsResponderADMLive( } val hasRootNodeOption: Option[IRI] = - propsMap.get(OntologyConstants.KnoraBase.HasRootNode.toSmartIri) match { + propsMap.get(KnoraBase.HasRootNode.toSmartIri) match { case Some(iris: Seq[LiteralV2]) => iris.headOption match { case Some(iri: IriLiteralV2) => Some(iri.value) @@ -461,7 +431,7 @@ final case class ListsResponderADMLive( } val isRootNode: Boolean = - propsMap.get(OntologyConstants.KnoraBase.IsRootNode.toSmartIri) match { + propsMap.get(KnoraBase.IsRootNode.toSmartIri) match { case Some(values: Seq[LiteralV2]) => values.headOption match { case Some(value: BooleanLiteralV2) => value.value @@ -476,7 +446,7 @@ final case class ListsResponderADMLive( } val positionOption: Option[Int] = propsMap - .get(OntologyConstants.KnoraBase.ListNodePosition.toSmartIri) + .get(KnoraBase.ListNodePosition.toSmartIri) .map(_.head.asInstanceOf[IntLiteralV2].value) if (isRootNode) { @@ -488,7 +458,7 @@ final case class ListsResponderADMLive( ) ), name = propsMap - .get(OntologyConstants.KnoraBase.ListNodeName.toSmartIri) + .get(KnoraBase.ListNodeName.toSmartIri) .map(_.head.asInstanceOf[StringLiteralV2].value), labels = StringLiteralSequenceV2(labels.toVector), comments = StringLiteralSequenceV2(comments.toVector), @@ -498,7 +468,7 @@ final case class ListsResponderADMLive( ListChildNodeADM( id = nodeIri.toString, name = propsMap - .get(OntologyConstants.KnoraBase.ListNodeName.toSmartIri) + .get(KnoraBase.ListNodeName.toSmartIri) .map(_.head.asInstanceOf[StringLiteralV2].value), labels = StringLiteralSequenceV2(labels.toVector), comments = Some(StringLiteralSequenceV2(comments.toVector)), @@ -548,31 +518,31 @@ final case class ListsResponderADMLive( val hasRootNode: IRI = propsMap .getOrElse( - OntologyConstants.KnoraBase.HasRootNode.toSmartIri, + KnoraBase.HasRootNode.toSmartIri, throw InconsistentRepositoryDataException(s"Required hasRootNode property missing for list node $nodeIri.") ) .head .toString val nameOption = propsMap - .get(OntologyConstants.KnoraBase.ListNodeName.toSmartIri) + .get(KnoraBase.ListNodeName.toSmartIri) .map(_.head.asInstanceOf[StringLiteralV2].value) val labels: Seq[StringLiteralV2] = propsMap - .getOrElse(OntologyConstants.Rdfs.Label.toSmartIri, Seq.empty[StringLiteralV2]) + .getOrElse(Rdfs.Label.toSmartIri, Seq.empty[StringLiteralV2]) .map(_.asInstanceOf[StringLiteralV2]) val comments: Seq[StringLiteralV2] = propsMap - .getOrElse(OntologyConstants.Rdfs.Comment.toSmartIri, Seq.empty[StringLiteralV2]) + .getOrElse(Rdfs.Comment.toSmartIri, Seq.empty[StringLiteralV2]) .map(_.asInstanceOf[StringLiteralV2]) val positionOption: Option[Int] = propsMap - .get(OntologyConstants.KnoraBase.ListNodePosition.toSmartIri) + .get(KnoraBase.ListNodePosition.toSmartIri) .map(_.head.asInstanceOf[IntLiteralV2].value) val position = positionOption.getOrElse( throw InconsistentRepositoryDataException(s"Required position property missing for list node $nodeIri.") ) - val children: Seq[ListChildNodeADM] = propsMap.get(OntologyConstants.KnoraBase.HasSubListNode.toSmartIri) match { + val children: Seq[ListChildNodeADM] = propsMap.get(KnoraBase.HasSubListNode.toSmartIri) match { case Some(iris: Seq[LiteralV2]) => if (!shallow) { // if not shallow then get the children of this node @@ -606,7 +576,7 @@ final case class ListsResponderADMLive( startNodePropsMap = statements.filter(_._1 == IriSubjectV2(ofNodeIri)).head._2 - children = startNodePropsMap.get(OntologyConstants.KnoraBase.HasSubListNode.toSmartIri) match { + children = startNodePropsMap.get(KnoraBase.HasSubListNode.toSmartIri) match { case Some(iris: Seq[LiteralV2]) => iris.map { iri => createChildNode(iri.toString, statements) @@ -719,7 +689,7 @@ final case class ListsResponderADMLive( private def createNode( createNodeRequest: ListNodeCreatePayloadADM ): Task[IRI] = { -// TODO-mpro: it's quickfix, refactor + // TODO-mpro: it's quickfix, refactor val parentNode: Option[ListIri] = createNodeRequest match { case ListRootNodeCreatePayloadADM(_, _, _, _, _) => None case ListChildNodeCreatePayloadADM(_, parentNodeIri, _, _, _, _, _) => Some(parentNodeIri) @@ -855,7 +825,7 @@ final case class ListsResponderADMLive( sparql.admin.txt .createNewListNode( dataNamedGraph = dataNamedGraph, - listClassIri = OntologyConstants.KnoraBase.ListNode, + listClassIri = KnoraBase.ListNode, projectIri = projectIri.value, nodeIri = newListNodeIri, parentNodeIri = None, @@ -877,7 +847,7 @@ final case class ListsResponderADMLive( sparql.admin.txt .createNewListNode( dataNamedGraph = dataNamedGraph, - listClassIri = OntologyConstants.KnoraBase.ListNode, + listClassIri = KnoraBase.ListNode, projectIri = projectIri.value, nodeIri = newListNodeIri, parentNodeIri = Some(parentNodeIri.value), @@ -905,8 +875,6 @@ final case class ListsResponderADMLive( apiRequestID: UUID ): Task[ListGetResponseADM] = { -// println("XXXXX-listCreateRequestADM") - /** * The actual task run with an IRI lock. */ @@ -1548,7 +1516,7 @@ final case class ListsResponderADMLive( for { node <- listNodeInfoGetADM(nodeIri) - doesNodeHaveComments = node.get.getComments.stringLiterals.nonEmpty + doesNodeHaveComments = node.get.comments.stringLiterals.nonEmpty _ <- ZIO.when(!doesNodeHaveComments) { ZIO.fail(BadRequestException(s"Nothing to delete. Node $nodeIri does not have comments.")) @@ -1862,7 +1830,7 @@ final case class ListsResponderADMLive( isRootNode = maybeNode.exists(_.isInstanceOf[ListRootNodeADM]), maybeName = changeNodeInfoRequest.name.map(_.value), projectIri = changeNodeInfoRequest.projectIri.value, - listClassIri = OntologyConstants.KnoraBase.ListNode, + listClassIri = KnoraBase.ListNode, maybeLabels = changeNodeInfoRequest.labels.map(_.value), maybeComments = changeNodeInfoRequest.comments.map(_.value) ) @@ -2077,18 +2045,40 @@ final case class ListsResponderADMLive( } yield () } -object ListsResponderADMLive { +object ListsResponder { + + private object Queries { + def getListsQuery(projectIri: Option[ProjectIri]): String = + s""" + |PREFIX xsd: + |PREFIX rdf: + |PREFIX rdfs: + |PREFIX knora-base: + | + |CONSTRUCT { ?s ?p ?o . } + |WHERE { + | ?s rdf:type knora-base:ListNode . + | ?s knora-base:isRootNode "true"^^xsd:boolean . + | ${projectIri.map(_.value).map(iri => s"?s knora-base:attachedToProject <$iri> .").getOrElse("")} + | ?s ?p ?o . + |}""".stripMargin + } + + def getLists(projectIri: Option[ProjectIri]): ZIO[ListsResponder, Throwable, ListsGetResponseADM] = + ZIO.serviceWithZIO[ListsResponder](_.getLists(projectIri)) + val layer: URLayer[ - StringFormatter & TriplestoreService & MessageRelay & IriService & AppConfig, - ListsResponderADM + AppConfig & IriService & MessageRelay & PredicateObjectMapper & StringFormatter & TriplestoreService, + ListsResponder ] = ZLayer.fromZIO { for { config <- ZIO.service[AppConfig] iriS <- ZIO.service[IriService] mr <- ZIO.service[MessageRelay] + pom <- ZIO.service[PredicateObjectMapper] ts <- ZIO.service[TriplestoreService] sf <- ZIO.service[StringFormatter] - handler <- mr.subscribe(ListsResponderADMLive(config, iriS, mr, ts, sf)) + handler <- mr.subscribe(ListsResponder(config, iriS, mr, pom, ts, sf)) } yield handler } } diff --git a/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/GetListItemsRouteADM.scala b/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/GetListItemsRouteADM.scala index 3f305c6b90..21b16c006e 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/GetListItemsRouteADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/GetListItemsRouteADM.scala @@ -8,9 +8,6 @@ package org.knora.webapi.routing.admin.lists import org.apache.pekko import zio.* -import dsp.errors.BadRequestException -import dsp.valueobjects.Iri -import org.knora.webapi.IRI import org.knora.webapi.core.MessageRelay import org.knora.webapi.messages.StringFormatter import org.knora.webapi.messages.admin.responder.listsmessages.* @@ -37,32 +34,11 @@ final case class GetListItemsRouteADM( val listsBasePath: PathMatcher[Unit] = PathMatcher("admin" / "lists") def makeRoute: Route = - getLists ~ - getListNode ~ + getListNode ~ getListOrNodeInfo("infos") ~ getListOrNodeInfo("nodes") ~ getListInfo - /** - * Returns all lists optionally filtered by project. - */ - private def getLists: Route = path(listsBasePath) { - get { - parameters("projectIri".?) { (maybeProjectIri: Option[IRI]) => requestContext => - val task = for { - iri <- ZIO.foreach(maybeProjectIri)(iri => - Iri - .validateAndEscapeIri(iri) - .toZIO - .orElseFail(BadRequestException(s"Invalid param project IRI: $iri")) - ) - user <- Authenticator.getUserADM(requestContext) - } yield ListsGetRequestADM(iri, user) - runJsonRouteZ(task, requestContext) - } - } - } - /** * Returns a list node, root or child, with children (if exist). */ diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/AdminApiEndpoints.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/AdminApiEndpoints.scala index cb03f3504d..f3e72d0925 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/AdminApiEndpoints.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/AdminApiEndpoints.scala @@ -10,6 +10,7 @@ import zio.ZLayer final case class AdminApiEndpoints( groupsEndpoints: GroupsEndpoints, + listsEndpoints: ListsEndpoints, maintenanceEndpoints: MaintenanceEndpoints, permissionsEndpoints: PermissionsEndpoints, projectsEndpoints: ProjectsEndpoints, @@ -20,6 +21,7 @@ final case class AdminApiEndpoints( val endpoints: Seq[AnyEndpoint] = groupsEndpoints.endpoints ++ + listsEndpoints.endpoints ++ maintenanceEndpoints.endpoints ++ permissionsEndpoints.endpoints ++ projectsEndpoints.endpoints ++ diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/AdminApiRoutes.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/AdminApiRoutes.scala index 873e7a6c41..e78a053868 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/AdminApiRoutes.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/AdminApiRoutes.scala @@ -11,23 +11,25 @@ import zio.ZLayer import org.knora.webapi.slice.common.api.TapirToPekkoInterpreter final case class AdminApiRoutes( + filesEndpoints: FilesEndpointsHandler, groups: GroupsEndpointsHandler, + lists: ListsEndpointsHandlers, maintenance: MaintenanceEndpointsHandlers, permissions: PermissionsEndpointsHandlers, project: ProjectsEndpointsHandler, storeEndpoints: StoreEndpointsHandler, - filesEndpoints: FilesEndpointsHandler, users: UsersEndpointsHandler, tapirToPekko: TapirToPekkoInterpreter ) { private val handlers = filesEndpoints.allHandlers ++ - groups.handlers ++ - maintenance.handlers ++ + groups.allHandlers ++ + lists.allHandlers ++ + maintenance.allHandlers ++ permissions.allHanders ++ - storeEndpoints.allHandlers ++ project.allHanders ++ + storeEndpoints.allHandlers ++ users.allHanders val routes: Seq[Route] = handlers.map(tapirToPekko.toRoute(_)) diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/FilesEndpointsHandler.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/FilesEndpointsHandler.scala index 660b53cfe0..254ffa71b3 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/FilesEndpointsHandler.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/FilesEndpointsHandler.scala @@ -12,7 +12,7 @@ import org.knora.webapi.messages.admin.responder.sipimessages.PermissionCodeAndP import org.knora.webapi.responders.admin.AssetPermissionsResponder import org.knora.webapi.slice.admin.domain.model.User import org.knora.webapi.slice.common.api.HandlerMapper -import org.knora.webapi.slice.common.api.SecuredEndpointAndZioHandler +import org.knora.webapi.slice.common.api.SecuredEndpointHandler import org.knora.webapi.slice.common.domain.SparqlEncodedString final case class FilesEndpointsHandler( @@ -22,7 +22,7 @@ final case class FilesEndpointsHandler( ) { private val getAdminFilesShortcodeFileIri = - SecuredEndpointAndZioHandler[ + SecuredEndpointHandler[ (ShortcodeIdentifier, SparqlEncodedString), PermissionCodeAndProjectRestrictedViewSettings ]( @@ -32,7 +32,7 @@ final case class FilesEndpointsHandler( } ) - val allHandlers = List(getAdminFilesShortcodeFileIri).map(mapper.mapEndpointAndHandler(_)) + val allHandlers = List(getAdminFilesShortcodeFileIri).map(mapper.mapSecuredEndpointHandler(_)) } object FilesEndpointsHandler { diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/GroupsEndpointsHandler.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/GroupsEndpointsHandler.scala index ec951bd1c0..add6bb0cdc 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/GroupsEndpointsHandler.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/GroupsEndpointsHandler.scala @@ -9,9 +9,9 @@ import zio.ZLayer import org.knora.webapi.slice.admin.api.service.GroupsRestService import org.knora.webapi.slice.admin.domain.model.GroupIri -import org.knora.webapi.slice.common.api.EndpointAndZioHandler import org.knora.webapi.slice.common.api.HandlerMapper -import org.knora.webapi.slice.common.api.SecuredEndpointAndZioHandler +import org.knora.webapi.slice.common.api.PublicEndpointHandler +import org.knora.webapi.slice.common.api.SecuredEndpointHandler case class GroupsEndpointsHandler( endpoints: GroupsEndpoints, @@ -19,26 +19,26 @@ case class GroupsEndpointsHandler( mapper: HandlerMapper ) { private val getGroupsHandler = - EndpointAndZioHandler( + PublicEndpointHandler( endpoints.getGroups, (_: Unit) => restService.getGroups ) private val getGroupHandler = - EndpointAndZioHandler( + PublicEndpointHandler( endpoints.getGroup, (iri: GroupIri) => restService.getGroup(iri) ) private val getGroupMembersHandler = - SecuredEndpointAndZioHandler( + SecuredEndpointHandler( endpoints.getGroupMembers, user => iri => restService.getGroupMembers(iri, user) ) - private val securedHandlers = List(getGroupMembersHandler).map(mapper.mapEndpointAndHandler(_)) + private val securedHandlers = List(getGroupMembersHandler).map(mapper.mapSecuredEndpointHandler(_)) - val handlers = List(getGroupsHandler, getGroupHandler).map(mapper.mapEndpointAndHandler(_)) + val allHandlers = List(getGroupsHandler, getGroupHandler).map(mapper.mapPublicEndpointHandler(_)) ++ securedHandlers } diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/ListsEndpoints.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/ListsEndpoints.scala new file mode 100644 index 0000000000..fad2280e27 --- /dev/null +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/ListsEndpoints.scala @@ -0,0 +1,32 @@ +/* + * Copyright © 2021 - 2024 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.knora.webapi.slice.admin.api + +import sttp.tapir.* +import sttp.tapir.generic.auto.* +import sttp.tapir.json.spray.jsonBody as sprayJsonBody +import zio.ZLayer + +import org.knora.webapi.messages.admin.responder.listsmessages.ListADMJsonProtocol +import org.knora.webapi.messages.admin.responder.listsmessages.ListsGetResponseADM +import org.knora.webapi.slice.admin.api.model.AdminQueryVariables +import org.knora.webapi.slice.common.api.BaseEndpoints + +case class ListsEndpoints(baseEndpoints: BaseEndpoints) extends ListADMJsonProtocol { + private val base = "admin" / "lists" + + val getListsQueryByProjectIriOption = baseEndpoints.publicEndpoint.get + .in(base) + .in(AdminQueryVariables.projectIriOption) + .out(sprayJsonBody[ListsGetResponseADM].description("Contains the list of all root nodes of each found list.")) + .description("Get all lists or all lists belonging to a project.") + + val endpoints = List(getListsQueryByProjectIriOption) +} + +object ListsEndpoints { + val layer = ZLayer.derive[ListsEndpoints] +} diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/ListsEndpointsHandlers.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/ListsEndpointsHandlers.scala new file mode 100644 index 0000000000..4a6e5657fa --- /dev/null +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/ListsEndpointsHandlers.scala @@ -0,0 +1,33 @@ +/* + * Copyright © 2021 - 2024 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.knora.webapi.slice.admin.api + +import zio.ZLayer + +import org.knora.webapi.responders.admin.ListsResponder +import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri +import org.knora.webapi.slice.common.api.HandlerMapper +import org.knora.webapi.slice.common.api.PublicEndpointHandler + +final case class ListsEndpointsHandlers( + listsEndpoints: ListsEndpoints, + listsResponder: ListsResponder, + mapper: HandlerMapper +) { + + private val getListsQueryByProjectIriHandler = PublicEndpointHandler( + listsEndpoints.getListsQueryByProjectIriOption, + (iri: Option[ProjectIri]) => listsResponder.getLists(iri) + ) + + val allHandlers = List( + getListsQueryByProjectIriHandler + ).map(mapper.mapPublicEndpointHandler(_)) +} + +object ListsEndpointsHandlers { + val layer = ZLayer.derive[ListsEndpointsHandlers] +} diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/MaintenanceEndpointsHandlers.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/MaintenanceEndpointsHandlers.scala index 153f48af3f..f443f44fa7 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/MaintenanceEndpointsHandlers.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/MaintenanceEndpointsHandlers.scala @@ -11,7 +11,7 @@ import zio.json.ast.Json import org.knora.webapi.slice.admin.api.service.MaintenanceRestService import org.knora.webapi.slice.admin.domain.model.User import org.knora.webapi.slice.common.api.HandlerMapper -import org.knora.webapi.slice.common.api.SecuredEndpointAndZioHandler +import org.knora.webapi.slice.common.api.SecuredEndpointHandler final case class MaintenanceEndpointsHandlers( endpoints: MaintenanceEndpoints, @@ -20,14 +20,14 @@ final case class MaintenanceEndpointsHandlers( ) { private val postMaintenanceHandler = - SecuredEndpointAndZioHandler[(String, Option[Json]), Unit]( + SecuredEndpointHandler[(String, Option[Json]), Unit]( endpoints.postMaintenance, (user: User) => { case (action: String, jsonMaybe: Option[Json]) => restService.executeMaintenanceAction(user, action, jsonMaybe) } ) - val handlers = List(postMaintenanceHandler).map(mapper.mapEndpointAndHandler(_)) + val allHandlers = List(postMaintenanceHandler).map(mapper.mapSecuredEndpointHandler(_)) } object MaintenanceEndpointsHandlers { diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/PermissionsEndpointsHandlers.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/PermissionsEndpointsHandlers.scala index 8557b0f3d1..545199689c 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/PermissionsEndpointsHandlers.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/PermissionsEndpointsHandlers.scala @@ -26,7 +26,7 @@ import org.knora.webapi.slice.admin.api.service.PermissionsRestService import org.knora.webapi.slice.admin.domain.model.GroupIri import org.knora.webapi.slice.admin.domain.model.PermissionIri import org.knora.webapi.slice.common.api.HandlerMapper -import org.knora.webapi.slice.common.api.SecuredEndpointAndZioHandler +import org.knora.webapi.slice.common.api.SecuredEndpointHandler final case class PermissionsEndpointsHandlers( permissionsEndpoints: PermissionsEndpoints, @@ -35,7 +35,7 @@ final case class PermissionsEndpointsHandlers( ) { private val postPermissionsApHandler = - SecuredEndpointAndZioHandler[ + SecuredEndpointHandler[ CreateAdministrativePermissionAPIRequestADM, AdministrativePermissionCreateResponseADM ]( @@ -46,7 +46,7 @@ final case class PermissionsEndpointsHandlers( ) private val getPermissionsApByProjectIriHandler = - SecuredEndpointAndZioHandler[IriIdentifier, AdministrativePermissionsForProjectGetResponseADM]( + SecuredEndpointHandler[IriIdentifier, AdministrativePermissionsForProjectGetResponseADM]( permissionsEndpoints.getPermissionsApByProjectIri, user => { case (projectIri: IriIdentifier) => restService.getPermissionsApByProjectIri(projectIri.value, user) @@ -54,7 +54,7 @@ final case class PermissionsEndpointsHandlers( ) private val getPermissionsApByProjectAndGroupIriHandler = - SecuredEndpointAndZioHandler[(IriIdentifier, GroupIri), AdministrativePermissionGetResponseADM]( + SecuredEndpointHandler[(IriIdentifier, GroupIri), AdministrativePermissionGetResponseADM]( permissionsEndpoints.getPermissionsApByProjectAndGroupIri, user => { case (projectIri: IriIdentifier, groupIri: GroupIri) => restService.getPermissionsApByProjectAndGroupIri(projectIri.value, groupIri, user) @@ -62,7 +62,7 @@ final case class PermissionsEndpointsHandlers( ) private val getPermissionsDaopByProjectIriHandler = - SecuredEndpointAndZioHandler[IriIdentifier, DefaultObjectAccessPermissionsForProjectGetResponseADM]( + SecuredEndpointHandler[IriIdentifier, DefaultObjectAccessPermissionsForProjectGetResponseADM]( permissionsEndpoints.getPermissionsDoapByProjectIri, user => { case (projectIri: IriIdentifier) => restService.getPermissionsDaopByProjectIri(projectIri.value, user) @@ -70,7 +70,7 @@ final case class PermissionsEndpointsHandlers( ) private val getPermissionsByProjectIriHandler = - SecuredEndpointAndZioHandler[IriIdentifier, PermissionsForProjectGetResponseADM]( + SecuredEndpointHandler[IriIdentifier, PermissionsForProjectGetResponseADM]( permissionsEndpoints.getPermissionsByProjectIri, user => { case (projectIri: IriIdentifier) => restService.getPermissionsByProjectIri(projectIri.value, user) @@ -78,7 +78,7 @@ final case class PermissionsEndpointsHandlers( ) private val deletePermissionHandler = - SecuredEndpointAndZioHandler[PermissionIri, PermissionDeleteResponseADM]( + SecuredEndpointHandler[PermissionIri, PermissionDeleteResponseADM]( permissionsEndpoints.deletePermission, user => { case (permissionIri: PermissionIri) => restService.deletePermission(permissionIri, user) @@ -86,7 +86,7 @@ final case class PermissionsEndpointsHandlers( ) private val postPermissionsDoapHandler = - SecuredEndpointAndZioHandler[ + SecuredEndpointHandler[ CreateDefaultObjectAccessPermissionAPIRequestADM, DefaultObjectAccessPermissionCreateResponseADM ]( @@ -97,7 +97,7 @@ final case class PermissionsEndpointsHandlers( ) private val putPermissionsProjectIriGroupHandler = - SecuredEndpointAndZioHandler[ + SecuredEndpointHandler[ (PermissionIri, ChangePermissionGroupApiRequestADM), PermissionGetResponseADM ]( @@ -108,7 +108,7 @@ final case class PermissionsEndpointsHandlers( ) private val putPermissionsHasPermissionsHandler = - SecuredEndpointAndZioHandler[ + SecuredEndpointHandler[ (PermissionIri, ChangePermissionHasPermissionsApiRequestADM), PermissionGetResponseADM ]( @@ -119,7 +119,7 @@ final case class PermissionsEndpointsHandlers( ) private val putPermissionsResourceClass = - SecuredEndpointAndZioHandler[(PermissionIri, ChangePermissionResourceClassApiRequestADM), PermissionGetResponseADM]( + SecuredEndpointHandler[(PermissionIri, ChangePermissionResourceClassApiRequestADM), PermissionGetResponseADM]( permissionsEndpoints.putPermisssionsResourceClass, user => { case (permissionIri: PermissionIri, request: ChangePermissionResourceClassApiRequestADM) => restService.updatePermissionResourceClass(permissionIri, request, user) @@ -127,7 +127,7 @@ final case class PermissionsEndpointsHandlers( ) private val putPermissionsProperty = - SecuredEndpointAndZioHandler[(PermissionIri, ChangePermissionPropertyApiRequestADM), PermissionGetResponseADM]( + SecuredEndpointHandler[(PermissionIri, ChangePermissionPropertyApiRequestADM), PermissionGetResponseADM]( permissionsEndpoints.putPermissionsProperty, user => { case (permissionIri: PermissionIri, request: ChangePermissionPropertyApiRequestADM) => restService.updatePermissionProperty(permissionIri, request, user) @@ -147,7 +147,7 @@ final case class PermissionsEndpointsHandlers( putPermissionsResourceClass, deletePermissionHandler, postPermissionsDoapHandler - ).map(mapper.mapEndpointAndHandler(_)) + ).map(mapper.mapSecuredEndpointHandler(_)) val allHanders = securedHandlers } diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/ProjectsEndpointsHandler.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/ProjectsEndpointsHandler.scala index 0dab83e117..a91fb06b79 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/ProjectsEndpointsHandler.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/ProjectsEndpointsHandler.scala @@ -21,9 +21,9 @@ import org.knora.webapi.slice.admin.api.model.ProjectsEndpointsRequestsAndRespon import org.knora.webapi.slice.admin.api.model.ProjectsEndpointsRequestsAndResponses.SetRestrictedViewRequest import org.knora.webapi.slice.admin.api.service.ProjectADMRestService import org.knora.webapi.slice.admin.domain.model.User -import org.knora.webapi.slice.common.api.EndpointAndZioHandler import org.knora.webapi.slice.common.api.HandlerMapper -import org.knora.webapi.slice.common.api.SecuredEndpointAndZioHandler +import org.knora.webapi.slice.common.api.PublicEndpointHandler +import org.knora.webapi.slice.common.api.SecuredEndpointHandler final case class ProjectsEndpointsHandler( projectsEndpoints: ProjectsEndpoints, @@ -32,59 +32,59 @@ final case class ProjectsEndpointsHandler( ) { val getAdminProjectsHandler = - EndpointAndZioHandler(projectsEndpoints.Public.getAdminProjects, (_: Unit) => restService.listAllProjects()) + PublicEndpointHandler(projectsEndpoints.Public.getAdminProjects, (_: Unit) => restService.listAllProjects()) val getAdminProjectsKeywordsHandler = - EndpointAndZioHandler( + PublicEndpointHandler( projectsEndpoints.Public.getAdminProjectsKeywords, (_: Unit) => restService.listAllKeywords() ) val getAdminProjectsByProjectIriHandler = - EndpointAndZioHandler( + PublicEndpointHandler( projectsEndpoints.Public.getAdminProjectsByProjectIri, (id: IriIdentifier) => restService.findProject(id) ) val getAdminProjectsByProjectShortcodeHandler = - EndpointAndZioHandler( + PublicEndpointHandler( projectsEndpoints.Public.getAdminProjectsByProjectShortcode, (id: ShortcodeIdentifier) => restService.findProject(id) ) val getAdminProjectsByProjectShortnameHandler = - EndpointAndZioHandler( + PublicEndpointHandler( projectsEndpoints.Public.getAdminProjectsByProjectShortname, (id: ShortnameIdentifier) => restService.findProject(id) ) val getAdminProjectsKeywordsByProjectIriHandler = - EndpointAndZioHandler( + PublicEndpointHandler( projectsEndpoints.Public.getAdminProjectsKeywordsByProjectIri, (iri: IriIdentifier) => restService.getKeywordsByProjectIri(iri.value) ) val getAdminProjectByProjectIriRestrictedViewSettingsHandler = - EndpointAndZioHandler( + PublicEndpointHandler( projectsEndpoints.Public.getAdminProjectsByProjectIriRestrictedViewSettings, (id: IriIdentifier) => restService.getProjectRestrictedViewSettings(id) ) val getAdminProjectByProjectShortcodeRestrictedViewSettingsHandler = - EndpointAndZioHandler( + PublicEndpointHandler( projectsEndpoints.Public.getAdminProjectsByProjectShortcodeRestrictedViewSettings, (id: ShortcodeIdentifier) => restService.getProjectRestrictedViewSettings(id) ) val getAdminProjectByProjectShortnameRestrictedViewSettingsHandler = - EndpointAndZioHandler( + PublicEndpointHandler( projectsEndpoints.Public.getAdminProjectsByProjectShortnameRestrictedViewSettings, (id: ShortnameIdentifier) => restService.getProjectRestrictedViewSettings(id) ) // secured endpoints val postAdminProjectsByProjectIriRestrictedViewSettingsHandler = - SecuredEndpointAndZioHandler[ + SecuredEndpointHandler[ (IriIdentifier, SetRestrictedViewRequest), RestrictedViewResponse ]( @@ -93,7 +93,7 @@ final case class ProjectsEndpointsHandler( ) val postAdminProjectsByProjectShortcodeRestrictedViewSettingsHandler = - SecuredEndpointAndZioHandler[ + SecuredEndpointHandler[ (ShortcodeIdentifier, SetRestrictedViewRequest), RestrictedViewResponse ]( @@ -104,73 +104,73 @@ final case class ProjectsEndpointsHandler( ) val getAdminProjectsByProjectIriMembersHandler = - SecuredEndpointAndZioHandler( + SecuredEndpointHandler( projectsEndpoints.Secured.getAdminProjectsByProjectIriMembers, user => id => restService.getProjectMembers(user, id) ) val getAdminProjectsByProjectShortcodeMembersHandler = - SecuredEndpointAndZioHandler( + SecuredEndpointHandler( projectsEndpoints.Secured.getAdminProjectsByProjectShortcodeMembers, user => id => restService.getProjectMembers(user, id) ) val getAdminProjectsByProjectShortnameMembersHandler = - SecuredEndpointAndZioHandler( + SecuredEndpointHandler( projectsEndpoints.Secured.getAdminProjectsByProjectShortnameMembers, user => id => restService.getProjectMembers(user, id) ) val getAdminProjectsByProjectIriAdminMembersHandler = - SecuredEndpointAndZioHandler( + SecuredEndpointHandler( projectsEndpoints.Secured.getAdminProjectsByProjectIriAdminMembers, user => id => restService.getProjectAdminMembers(user, id) ) val getAdminProjectsByProjectShortcodeAdminMembersHandler = - SecuredEndpointAndZioHandler( + SecuredEndpointHandler( projectsEndpoints.Secured.getAdminProjectsByProjectShortcodeAdminMembers, user => id => restService.getProjectAdminMembers(user, id) ) val getAdminProjectsByProjectShortnameAdminMembersHandler = - SecuredEndpointAndZioHandler( + SecuredEndpointHandler( projectsEndpoints.Secured.getAdminProjectsByProjectShortnameAdminMembers, user => id => restService.getProjectAdminMembers(user, id) ) val deleteAdminProjectsByIriHandler = - SecuredEndpointAndZioHandler( + SecuredEndpointHandler( projectsEndpoints.Secured.deleteAdminProjectsByIri, user => (id: IriIdentifier) => restService.deleteProject(id, user) ) val getAdminProjectsExportsHandler = - SecuredEndpointAndZioHandler( + SecuredEndpointHandler( projectsEndpoints.Secured.getAdminProjectsExports, user => (_: Unit) => restService.listExports(user) ) val postAdminProjectsByShortcodeExportHandler = - SecuredEndpointAndZioHandler( + SecuredEndpointHandler( projectsEndpoints.Secured.postAdminProjectsByShortcodeExport, user => (id: ShortcodeIdentifier) => restService.exportProject(id, user) ) val postAdminProjectsByShortcodeImportHandler = - SecuredEndpointAndZioHandler( + SecuredEndpointHandler( projectsEndpoints.Secured.postAdminProjectsByShortcodeImport, user => (id: ShortcodeIdentifier) => restService.importProject(id, user) ) val postAdminProjectsHandler = - SecuredEndpointAndZioHandler( + SecuredEndpointHandler( projectsEndpoints.Secured.postAdminProjects, user => (createReq: ProjectCreateRequest) => restService.createProject(createReq, user) ) val putAdminProjectsByIriHandler = - SecuredEndpointAndZioHandler[(IriIdentifier, ProjectUpdateRequest), ProjectOperationResponseADM]( + SecuredEndpointHandler[(IriIdentifier, ProjectUpdateRequest), ProjectOperationResponseADM]( projectsEndpoints.Secured.putAdminProjectsByIri, user => { case (id: IriIdentifier, changeReq: ProjectUpdateRequest) => restService.updateProject(id, changeReq, user) @@ -211,7 +211,7 @@ final case class ProjectsEndpointsHandler( getAdminProjectByProjectIriRestrictedViewSettingsHandler, getAdminProjectByProjectShortcodeRestrictedViewSettingsHandler, getAdminProjectByProjectShortnameRestrictedViewSettingsHandler - ).map(mapper.mapEndpointAndHandler(_)) + ).map(mapper.mapPublicEndpointHandler(_)) private val secureHandlers = getAdminProjectsByIriAllDataHandler :: List( postAdminProjectsByProjectIriRestrictedViewSettingsHandler, @@ -228,7 +228,7 @@ final case class ProjectsEndpointsHandler( postAdminProjectsByShortcodeImportHandler, postAdminProjectsHandler, putAdminProjectsByIriHandler - ).map(mapper.mapEndpointAndHandler(_)) + ).map(mapper.mapSecuredEndpointHandler(_)) val allHanders = handlers ++ secureHandlers } diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/StoreEndpointsHandler.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/StoreEndpointsHandler.scala index 1d780f1b10..bfa2352cbf 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/StoreEndpointsHandler.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/StoreEndpointsHandler.scala @@ -10,8 +10,8 @@ import zio.ZLayer import org.knora.webapi.config.AppConfig import org.knora.webapi.messages.store.triplestoremessages.RdfDataObject import org.knora.webapi.slice.admin.api.service.StoreRestService -import org.knora.webapi.slice.common.api.EndpointAndZioHandler import org.knora.webapi.slice.common.api.HandlerMapper +import org.knora.webapi.slice.common.api.PublicEndpointHandler final case class StoreEndpointsHandler( endpoints: StoreEndpoints, @@ -21,7 +21,7 @@ final case class StoreEndpointsHandler( ) { private val postStoreResetTriplestoreContentHandler = - EndpointAndZioHandler[Unit, (Option[List[RdfDataObject]], Boolean), MessageResponse]( + PublicEndpointHandler[(Option[List[RdfDataObject]], Boolean), MessageResponse]( endpoints.postStoreResetTriplestoreContent, { case (rdfObjs: Option[List[RdfDataObject]], prependDefaults: Boolean) => storesResponder.resetTriplestoreContent(rdfObjs.getOrElse(List.empty), prependDefaults) @@ -31,7 +31,7 @@ final case class StoreEndpointsHandler( val allHandlers = { val handlerIfConfigured = if (appConfig.allowReloadOverHttp) Seq(postStoreResetTriplestoreContentHandler) else Seq.empty - handlerIfConfigured.map(mapper.mapEndpointAndHandler(_)) + handlerIfConfigured.map(mapper.mapPublicEndpointHandler(_)) } } diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/UsersEndpointsHandler.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/UsersEndpointsHandler.scala index 3011d90636..76a1272292 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/UsersEndpointsHandler.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/UsersEndpointsHandler.scala @@ -12,7 +12,7 @@ import org.knora.webapi.messages.admin.responder.usersmessages.UsersGetResponseA import org.knora.webapi.slice.admin.api.service.UsersRestService import org.knora.webapi.slice.admin.domain.model.UserIri import org.knora.webapi.slice.common.api.HandlerMapper -import org.knora.webapi.slice.common.api.SecuredEndpointAndZioHandler +import org.knora.webapi.slice.common.api.SecuredEndpointHandler case class UsersEndpointsHandler( usersEndpoints: UsersEndpoints, @@ -21,7 +21,7 @@ case class UsersEndpointsHandler( ) { private val getUsersHandler = - SecuredEndpointAndZioHandler[ + SecuredEndpointHandler[ Unit, UsersGetResponseADM ]( @@ -30,12 +30,12 @@ case class UsersEndpointsHandler( ) private val deleteUserByIriHandler = - SecuredEndpointAndZioHandler[UserIri, UserOperationResponseADM]( + SecuredEndpointHandler[UserIri, UserOperationResponseADM]( usersEndpoints.deleteUser, requestingUser => { case (userIri: UserIri) => restService.deleteUser(requestingUser, userIri) } ) - val allHanders = List(getUsersHandler, deleteUserByIriHandler).map(mapper.mapEndpointAndHandler(_)) + val allHanders = List(getUsersHandler, deleteUserByIriHandler).map(mapper.mapSecuredEndpointHandler(_)) } object UsersEndpointsHandler { diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/model/AdminQueryVariables.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/model/AdminQueryVariables.scala new file mode 100644 index 0000000000..800d18f308 --- /dev/null +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/model/AdminQueryVariables.scala @@ -0,0 +1,24 @@ +/* + * Copyright © 2021 - 2024 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.knora.webapi.slice.admin.api.model + +import sttp.tapir.Codec +import sttp.tapir.CodecFormat +import sttp.tapir.EndpointInput +import sttp.tapir.query + +import org.knora.webapi.slice.admin.api.Codecs.TapirCodec +import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri + +object AdminQueryVariables { + + private implicit val projectIriOptionCodec: Codec[List[String], Option[ProjectIri], CodecFormat.TextPlain] = + Codec.listHeadOption(TapirCodec.projectIri) + + val projectIriOption: EndpointInput.Query[Option[ProjectIri]] = query[Option[ProjectIri]]("projectIri") + .description("The (optional) IRI of the project.") + .example(Some(ProjectIri.unsafeFrom("http://rdfh.ch/projects/0042"))) +} diff --git a/webapi/src/main/scala/org/knora/webapi/slice/common/api/DocsGenerator.scala b/webapi/src/main/scala/org/knora/webapi/slice/common/api/DocsGenerator.scala index e2f5b939ef..567cfe5d16 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/common/api/DocsGenerator.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/common/api/DocsGenerator.scala @@ -26,6 +26,7 @@ import org.knora.webapi.routing.Authenticator import org.knora.webapi.slice.admin.api.AdminApiEndpoints import org.knora.webapi.slice.admin.api.FilesEndpoints import org.knora.webapi.slice.admin.api.GroupsEndpoints +import org.knora.webapi.slice.admin.api.ListsEndpoints import org.knora.webapi.slice.admin.api.MaintenanceEndpoints import org.knora.webapi.slice.admin.api.PermissionsEndpoints import org.knora.webapi.slice.admin.api.ProjectsEndpoints @@ -75,6 +76,7 @@ object DocsGenerator extends ZIOAppDefault { DocsNoopAuthenticator.layer, GroupsEndpoints.layer, FilesEndpoints.layer, + ListsEndpoints.layer, MaintenanceEndpoints.layer, PermissionsEndpoints.layer, ProjectsEndpoints.layer, diff --git a/webapi/src/main/scala/org/knora/webapi/slice/common/api/HandlerMapper.scala b/webapi/src/main/scala/org/knora/webapi/slice/common/api/HandlerMapper.scala index 7284352a5e..10f05f1541 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/common/api/HandlerMapper.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/common/api/HandlerMapper.scala @@ -24,12 +24,12 @@ object InputType { type SecurityIn = (Option[String], Option[String], Option[UsernamePassword]) } -case class EndpointAndZioHandler[SECURITY_INPUT, INPUT, OUTPUT]( - endpoint: Endpoint[SECURITY_INPUT, INPUT, RequestRejectedException, OUTPUT, Any], +case class PublicEndpointHandler[INPUT, OUTPUT]( + endpoint: Endpoint[Unit, INPUT, RequestRejectedException, OUTPUT, Any], handler: INPUT => Task[OUTPUT] ) -case class SecuredEndpointAndZioHandler[INPUT, OUTPUT]( +case class SecuredEndpointHandler[INPUT, OUTPUT]( endpoint: PartialServerEndpoint[ SecurityIn, User, @@ -44,13 +44,13 @@ case class SecuredEndpointAndZioHandler[INPUT, OUTPUT]( final case class HandlerMapper()(implicit val r: zio.Runtime[Any]) { - def mapEndpointAndHandler[INPUT, OUTPUT]( - handlerAndEndpoint: SecuredEndpointAndZioHandler[INPUT, OUTPUT] + def mapSecuredEndpointHandler[INPUT, OUTPUT]( + handlerAndEndpoint: SecuredEndpointHandler[INPUT, OUTPUT] ): Full[SecurityIn, User, INPUT, RequestRejectedException, OUTPUT, Any, Future] = handlerAndEndpoint.endpoint.serverLogic(user => in => { runToFuture(handlerAndEndpoint.handler(user)(in)) }) - def mapEndpointAndHandler[INPUT, OUTPUT]( - handlerAndEndpoint: EndpointAndZioHandler[Unit, INPUT, OUTPUT] + def mapPublicEndpointHandler[INPUT, OUTPUT]( + handlerAndEndpoint: PublicEndpointHandler[INPUT, OUTPUT] ): Full[Unit, Unit, INPUT, RequestRejectedException, OUTPUT, Any, Future] = handlerAndEndpoint.endpoint.serverLogic[Future](in => runToFuture(handlerAndEndpoint.handler(in))) diff --git a/webapi/src/main/scala/org/knora/webapi/slice/resourceinfo/api/ResourceInfoRoutes.scala b/webapi/src/main/scala/org/knora/webapi/slice/resourceinfo/api/ResourceInfoRoutes.scala index c0fa229424..24eb050c56 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/resourceinfo/api/ResourceInfoRoutes.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/resourceinfo/api/ResourceInfoRoutes.scala @@ -9,8 +9,8 @@ import org.apache.pekko.http.scaladsl.server.Route import zio.ZLayer import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM.IriIdentifier -import org.knora.webapi.slice.common.api.EndpointAndZioHandler import org.knora.webapi.slice.common.api.HandlerMapper +import org.knora.webapi.slice.common.api.PublicEndpointHandler import org.knora.webapi.slice.common.api.TapirToPekkoInterpreter import org.knora.webapi.slice.resourceinfo.api.model.ListResponseDto import org.knora.webapi.slice.resourceinfo.api.model.QueryParams.Asc @@ -27,7 +27,7 @@ final case class ResourceInfoRoutes( ) { val getResourcesInfoHandler = - EndpointAndZioHandler[Unit, (IriIdentifier, String, Option[Order], Option[OrderBy]), ListResponseDto]( + PublicEndpointHandler[(IriIdentifier, String, Option[Order], Option[OrderBy]), ListResponseDto]( endpoints.getResourcesInfo, { case (projectIri: IriIdentifier, resourceClass: String, order: Option[Order], orderBy: Option[OrderBy]) => resourceInfoService.findByProjectAndResourceClass( @@ -40,7 +40,7 @@ final case class ResourceInfoRoutes( ) val routes: Seq[Route] = List(getResourcesInfoHandler) - .map(it => mapper.mapEndpointAndHandler(it)) + .map(it => mapper.mapPublicEndpointHandler(it)) .map(interpreter.toRoute(_)) } object ResourceInfoRoutes { diff --git a/webapi/src/main/scala/org/knora/webapi/slice/search/api/SearchEndpoints.scala b/webapi/src/main/scala/org/knora/webapi/slice/search/api/SearchEndpoints.scala index 60df0226fa..56683fcbd3 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/search/api/SearchEndpoints.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/search/api/SearchEndpoints.scala @@ -179,31 +179,31 @@ final case class SearchApiRoutes( private type GravsearchQuery = String private val postGravsearch = - SecuredEndpointAndZioHandler[(GravsearchQuery, FormatOptions), (RenderedResponse, MediaType)]( + SecuredEndpointHandler[(GravsearchQuery, FormatOptions), (RenderedResponse, MediaType)]( searchEndpoints.postGravsearch, user => { case (query, opts) => searchRestService.gravsearch(query, opts, user) } ) private val getGravsearch = - SecuredEndpointAndZioHandler[(GravsearchQuery, FormatOptions), (RenderedResponse, MediaType)]( + SecuredEndpointHandler[(GravsearchQuery, FormatOptions), (RenderedResponse, MediaType)]( searchEndpoints.getGravsearch, user => { case (query, opts) => searchRestService.gravsearch(query, opts, user) } ) private val postGravsearchCount = - SecuredEndpointAndZioHandler[(GravsearchQuery, FormatOptions), (RenderedResponse, MediaType)]( + SecuredEndpointHandler[(GravsearchQuery, FormatOptions), (RenderedResponse, MediaType)]( searchEndpoints.postGravsearchCount, user => { case (query, opts) => searchRestService.gravsearchCount(query, opts, user) } ) private val getGravsearchCount = - SecuredEndpointAndZioHandler[(GravsearchQuery, FormatOptions), (RenderedResponse, MediaType)]( + SecuredEndpointHandler[(GravsearchQuery, FormatOptions), (RenderedResponse, MediaType)]( searchEndpoints.getGravsearchCount, user => { case (query, opts) => searchRestService.gravsearchCount(query, opts, user) } ) private val getSearchByLabel = - SecuredEndpointAndZioHandler[ + SecuredEndpointHandler[ (String, FormatOptions, Offset, Option[ProjectIri], Option[InputIri]), (RenderedResponse, MediaType) ]( @@ -214,7 +214,7 @@ final case class SearchApiRoutes( ) private val getSearchByLabelCount = - SecuredEndpointAndZioHandler[ + SecuredEndpointHandler[ (String, FormatOptions, Option[ProjectIri], Option[InputIri]), (RenderedResponse, MediaType) ]( @@ -225,7 +225,7 @@ final case class SearchApiRoutes( ) private val getFullTextSearch = - SecuredEndpointAndZioHandler[ + SecuredEndpointHandler[ (String, FormatOptions, Offset, Option[ProjectIri], Option[InputIri], Option[InputIri], Boolean), (RenderedResponse, MediaType) ]( @@ -236,7 +236,7 @@ final case class SearchApiRoutes( ) private val getFullTextSearchCount = - SecuredEndpointAndZioHandler[ + SecuredEndpointHandler[ (String, FormatOptions, Option[ProjectIri], Option[InputIri], Option[InputIri]), (RenderedResponse, MediaType) ]( @@ -257,7 +257,7 @@ final case class SearchApiRoutes( postGravsearchCount, getGravsearchCount ) - .map(it => mapper.mapEndpointAndHandler(it)) + .map(it => mapper.mapSecuredEndpointHandler(it)) .map(it => tapirToPekko.toRoute(it)) } diff --git a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/admin/getLists.scala.txt b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/admin/getLists.scala.txt deleted file mode 100644 index 9a310eff72..0000000000 --- a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/admin/getLists.scala.txt +++ /dev/null @@ -1,33 +0,0 @@ -@* - * Copyright © 2021 - 2024 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 - -@** - * Gets basic information about all lists. If the IRI of a project is supplied, - * then only lists belonging to this project are returned. - * - * @param maybeProjectIri the IRI of the project the list belongs to. - *@ -@(maybeProjectIri: Option[IRI]) - -PREFIX xsd: -PREFIX rdf: -PREFIX rdfs: -PREFIX knora-base: - -CONSTRUCT { ?s ?p ?o . } - -WHERE { - ?s rdf:type knora-base:ListNode . - - ?s knora-base:isRootNode "true"^^xsd:boolean . - - @if(maybeProjectIri.nonEmpty) { - ?s knora-base:attachedToProject <@maybeProjectIri.get> . - } - - ?s ?p ?o . -}