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: Add shortcode query param for GET /admin/lists #3369

Merged
merged 9 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
package org.knora.webapi.responders.admin

import org.apache.pekko.testkit.*
import zio.ZIO

import java.util.UUID

import dsp.errors.BadRequestException
import dsp.errors.DuplicateValueException
import dsp.errors.NotFoundException
import dsp.errors.UpdateNotPerformedException
import dsp.valueobjects.Iri
import org.knora.webapi.*
Expand All @@ -26,6 +28,7 @@ import org.knora.webapi.slice.admin.api.Requests.ListChangeRequest
import org.knora.webapi.slice.admin.api.Requests.ListCreateChildNodeRequest
import org.knora.webapi.slice.admin.api.Requests.ListCreateRootNodeRequest
import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri
import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortcode
import org.knora.webapi.slice.admin.domain.model.ListProperties.*
import org.knora.webapi.util.MutableTestIri
import org.knora.webapi.util.ZioScalaTestUtil.assertFailsWithA
Expand Down Expand Up @@ -54,20 +57,46 @@ class ListsResponderSpec extends CoreSpec with ImplicitSender {

private val treeListChildNodes: Seq[ListNodeADM] = SharedListsTestDataADM.treeListChildNodes

private val listsResponder = ZIO.serviceWithZIO[ListsResponder]

"The Lists Responder" when {
"used to query information about lists" should {
"return all lists" in {
val actual = UnsafeZioRun.runOrThrow(ListsResponder.getLists(None))
val actual = UnsafeZioRun.runOrThrow(listsResponder(_.getLists(None)))
actual.lists.size should be(9)
}

"return all lists belonging to the images project" in {
val actual = UnsafeZioRun.runOrThrow(ListsResponder.getLists(Some(ProjectIri.unsafeFrom(imagesProjectIri))))
"return all lists belonging to the images project by iri" in {
val actual =
UnsafeZioRun.runOrThrow(listsResponder(_.getLists(Some(Left(ProjectIri.unsafeFrom(imagesProjectIri))))))
actual.lists.size should be(4)
}

"return all lists belonging to the images project by shortcode" in {
val actual =
UnsafeZioRun.runOrThrow(listsResponder(_.getLists(Some(Right(imagesProjectShortcode)))))
actual.lists.size should be(4)
}

"getLists should fail if project by iri was not found" in {
val exit =
UnsafeZioRun.run(
listsResponder(_.getLists(Some(Left(ProjectIri.unsafeFrom("http://rdfh.ch/projects/unknown"))))),
)
assertFailsWithA[NotFoundException](exit)
}

"getLists should fail if project by shortcode was not found" in {
val exit =
UnsafeZioRun.run(
listsResponder(_.getLists(Some(Right(Shortcode.unsafeFrom("9999"))))),
)
assertFailsWithA[NotFoundException](exit)
}

"return all lists belonging to the anything project" in {
val actual = UnsafeZioRun.runOrThrow(ListsResponder.getLists(Some(ProjectIri.unsafeFrom(anythingProjectIri))))
val actual =
UnsafeZioRun.runOrThrow(listsResponder(_.getLists(Some(Left(ProjectIri.unsafeFrom(anythingProjectIri))))))
actual.lists.size should be(4)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import org.knora.webapi.messages.admin.responder.permissionsmessages.PermissionA
import org.knora.webapi.messages.admin.responder.permissionsmessages.PermissionsDataADM
import org.knora.webapi.sharedtestdata
import org.knora.webapi.sharedtestdata.SharedOntologyTestDataADM.IMAGES_ONTOLOGY_IRI
import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortcode
import org.knora.webapi.slice.admin.domain.model.Permission
import org.knora.webapi.slice.admin.domain.service.KnoraGroupRepo

Expand Down Expand Up @@ -88,7 +89,8 @@ object SharedTestDataADM2 {
/**
* **********************************
*/
val imagesProjectIri = "http://rdfh.ch/projects/00FF"
val imagesProjectIri = "http://rdfh.ch/projects/00FF"
val imagesProjectShortcode = Shortcode.unsafeFrom("00FF")

/* represents 'user01' as found in admin-data.ttl */
def imagesUser01 = sharedtestdata.UserProfile(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import org.knora.webapi.responders.admin.ListsResponder.Queries
import org.knora.webapi.slice.admin.api.Requests.*
import org.knora.webapi.slice.admin.domain.model.KnoraProject
import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri
import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortcode
import org.knora.webapi.slice.admin.domain.model.ListProperties.ListIri
import org.knora.webapi.slice.admin.domain.model.ListProperties.ListName
import org.knora.webapi.slice.admin.domain.model.User
Expand Down Expand Up @@ -63,10 +64,23 @@ final case class ListsResponder(
* [[None]] if all lists are to be queried.
* @return a [[ListsGetResponseADM]].
*/
def getLists(projectIri: Option[ProjectIri]): Task[ListsGetResponseADM] =
def getLists(iriShortcode: Option[Either[ProjectIri, Shortcode]]): Task[ListsGetResponseADM] =
for {
project <- iriShortcode match {
case Some(Left(iri)) =>
knoraProjectService
.findById(iri)
.someOrFail(NotFoundException(s"Project with IRI '$iri' not found"))
.asSome
case Some(Right(code)) =>
knoraProjectService
.findByShortcode(code)
.someOrFail(NotFoundException(s"Project with shortcode '$code' not found"))
.asSome
case None => ZIO.none
}
statements <-
triplestore.query(Construct(Queries.getListsQuery(projectIri))).flatMap(_.asExtended).map(_.statements)
triplestore.query(Construct(Queries.getListsQuery(project.map(_.id)))).flatMap(_.asExtended).map(_.statements)
lists <-
ZIO.foreach(statements.toList) { case (listIri: SubjectV2, objs: ConstructPredicateObjects) =>
for {
Expand Down Expand Up @@ -1531,9 +1545,6 @@ object ListsResponder {
|}""".stripMargin
}

def getLists(projectIri: Option[ProjectIri]): ZIO[ListsResponder, Throwable, ListsGetResponseADM] =
ZIO.serviceWithZIO[ListsResponder](_.getLists(projectIri))

def listGetRequestADM(nodeIri: IRI): ZIO[ListsResponder, Throwable, ListItemGetResponseADM] =
ZIO.serviceWithZIO[ListsResponder](_.listGetRequestADM(nodeIri))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import org.knora.webapi.slice.admin.api.Requests.ListCreateChildNodeRequest
import org.knora.webapi.slice.admin.api.Requests.ListCreateRootNodeRequest
import org.knora.webapi.slice.admin.api.model.AdminQueryVariables
import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri
import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortcode
import org.knora.webapi.slice.admin.domain.model.ListProperties.*
import org.knora.webapi.slice.common.api.BaseEndpoints

Expand All @@ -32,9 +33,12 @@ case class ListsEndpoints(baseEndpoints: BaseEndpoints) {

val getListsQueryByProjectIriOption = baseEndpoints.publicEndpoint.get
.in(base)
.in(AdminQueryVariables.projectIriOption)
.in(AdminQueryVariables.projectIriOrShortcodeQueryOption)
.out(jsonBody[ListsGetResponseADM].description("Contains the list of all root nodes of each found list."))
.description("Get all lists or all lists belonging to a project.")
.description(
"Get all lists or all lists belonging to a project. " +
"Note that you can provide either a project IRI or a project shortcode.",
)

private val listIriPathVar = path[ListIri].description("The IRI of the list.")
val getListsByIri = baseEndpoints.publicEndpoint.get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ import org.knora.webapi.messages.admin.responder.listsmessages.NodeInfoGetRespon
import org.knora.webapi.messages.admin.responder.listsmessages.NodePositionChangeResponseADM
import org.knora.webapi.responders.admin.ListsResponder
import org.knora.webapi.slice.admin.api.Requests.*
import org.knora.webapi.slice.admin.domain.model.KnoraProject
import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri
import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortcode
import org.knora.webapi.slice.admin.domain.model.ListProperties.ListIri
import org.knora.webapi.slice.admin.domain.model.User
import org.knora.webapi.slice.common.api.HandlerMapper
Expand All @@ -34,7 +36,7 @@ final case class ListsEndpointsHandlers(

private val getListsQueryByProjectIriHandler = PublicEndpointHandler(
listsEndpoints.getListsQueryByProjectIriOption,
(iri: Option[ProjectIri]) => listsResponder.getLists(iri),
(iriShortcode: Option[Either[ProjectIri, Shortcode]]) => listsResponder.getLists(iriShortcode),
)

private val getListsByIriHandler = PublicEndpointHandler(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,46 @@ package org.knora.webapi.slice.admin.api.model

import sttp.tapir.Codec
import sttp.tapir.CodecFormat
import sttp.tapir.DecodeResult
import sttp.tapir.EndpointInput
import sttp.tapir.query

import dsp.errors.BadRequestException
import org.knora.webapi.slice.admin.api.Codecs.TapirCodec
import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri
import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortcode

object AdminQueryVariables {

private implicit val projectIriOptionCodec: Codec[List[String], Option[ProjectIri], CodecFormat.TextPlain] =
Codec.listHeadOption(TapirCodec.projectIri)

private implicit val shortcodeOptionCodec: Codec[List[String], Option[Shortcode], CodecFormat.TextPlain] =
Codec.listHeadOption(TapirCodec.shortcode)

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")))

val projectShortcodeOption: EndpointInput.Query[Option[Shortcode]] = query[Option[Shortcode]]("projectShortcode")
.description("The (optional) shortcode of the project.")
.example(Some(Shortcode.unsafeFrom("0042")))

val projectIriOrShortcodeQueryOption =
projectIriOption
.and(projectShortcodeOption)
.mapDecode[Option[Either[ProjectIri, Shortcode]]] {
case (Some(iri), None) => DecodeResult.Value(Some(Left(iri)))
case (None, Some(shortcode)) => DecodeResult.Value(Some(Right(shortcode)))
case (Some(_), Some(_)) =>
DecodeResult.Error(
"Query params project IRI and shortcode are mutually exclusive",
BadRequestException("Provide either a project IRI or a project shortcode"),
)
case _ => DecodeResult.Value(None)
} {
case Some(Left(iri)) => (Some(iri), None)
case Some(Right(shortcode)) => (None, Some(shortcode));
case None => (None, None)
}
}
Loading