Skip to content

Commit

Permalink
feat: expose GET /admin/projects as ZIO HTTP route (#2366)
Browse files Browse the repository at this point in the history
  • Loading branch information
irinaschubert committed Jan 5, 2023
1 parent 8554e3b commit b19f81c
Show file tree
Hide file tree
Showing 8 changed files with 263 additions and 152 deletions.
Expand Up @@ -46,9 +46,7 @@ class ProjectsResponderADMSpec extends CoreSpec with ImplicitSender {
"The ProjectsResponderADM" when {
"used to query for project information" should {
"return information for every project" in {
appActor ! ProjectsGetRequestADM(
requestingUser = rootUser
)
appActor ! ProjectsGetRequestADM()
val received = expectMsgType[ProjectsGetResponseADM](timeout)

assert(received.projects.contains(SharedTestDataADM.imagesProject))
Expand Down Expand Up @@ -648,9 +646,7 @@ class ProjectsResponderADMSpec extends CoreSpec with ImplicitSender {
"used to query keywords" should {

"return all unique keywords for all projects" in {
appActor ! ProjectsKeywordsGetRequestADM(
SharedTestDataADM.rootUser
)
appActor ! ProjectsKeywordsGetRequestADM()
val received: ProjectsKeywordsGetResponseADM = expectMsgType[ProjectsKeywordsGetResponseADM](timeout)
received.keywords.size should be(21)
}
Expand Down
Expand Up @@ -166,17 +166,14 @@ sealed trait ProjectsResponderRequestADM extends KnoraRequestADM
/**
* Get all information about all projects in form of [[ProjectsGetResponseADM]]. The ProjectsGetRequestV1 returns either
* something or a NotFound exception if there are no projects found. Administration permission checking is performed.
*
* @param requestingUser the user making the request.
*/
case class ProjectsGetRequestADM(requestingUser: UserADM) extends ProjectsResponderRequestADM
case class ProjectsGetRequestADM() extends ProjectsResponderRequestADM

/**
* Get info about a single project identified either through its IRI, shortname or shortcode. The response is in form
* of [[ProjectGetResponseADM]]. External use.
*
* @param identifier the IRI, email, or username of the project.
* @param requestingUser the user making the request.
*/
case class ProjectGetRequestADM(identifier: ProjectIdentifierADM) extends ProjectsResponderRequestADM

Expand Down Expand Up @@ -212,10 +209,8 @@ case class ProjectAdminMembersGetRequestADM(

/**
* Returns all unique keywords for all projects.
*
* @param requestingUser the user making the request.
*/
case class ProjectsKeywordsGetRequestADM(requestingUser: UserADM) extends ProjectsResponderRequestADM
case class ProjectsKeywordsGetRequestADM() extends ProjectsResponderRequestADM

/**
* Returns all keywords for a project identified through IRI.
Expand Down
Expand Up @@ -63,15 +63,14 @@ final case class ProjectsResponderADM(actorDeps: ActorDeps, cacheServiceSettings
* Receives a message extending [[ProjectsResponderRequestADM]], and returns an appropriate response message.
*/
def receive(msg: ProjectsResponderRequestADM) = msg match {
case ProjectsGetRequestADM(requestingUser) => projectsGetRequestADM(requestingUser)
case ProjectGetADM(identifier) => getSingleProjectADM(identifier)
case ProjectGetRequestADM(identifier) => getSingleProjectADMRequest(identifier)
case ProjectsGetRequestADM() => projectsGetRequestADM()
case ProjectGetADM(identifier) => getSingleProjectADM(identifier)
case ProjectGetRequestADM(identifier) => getSingleProjectADMRequest(identifier)
case ProjectMembersGetRequestADM(identifier, requestingUser) =>
projectMembersGetRequestADM(identifier, requestingUser)
case ProjectAdminMembersGetRequestADM(identifier, requestingUser) =>
projectAdminMembersGetRequestADM(identifier, requestingUser)
case ProjectsKeywordsGetRequestADM(requestingUser) =>
projectsKeywordsGetRequestADM(requestingUser)
case ProjectsKeywordsGetRequestADM() => projectsKeywordsGetRequestADM()
case ProjectKeywordsGetRequestADM(projectIri, requestingUser) =>
projectKeywordsGetRequestADM(projectIri, requestingUser)
case ProjectRestrictedViewSettingsGetADM(identifier, requestingUser) =>
Expand Down Expand Up @@ -100,12 +99,9 @@ final case class ProjectsResponderADM(actorDeps: ActorDeps, cacheServiceSettings
/**
* Gets all the projects and returns them as a sequence containing [[ProjectADM]].
*
* @param requestingUser the user making the request.
* @return all the projects as a sequence containing [[ProjectADM]].
*/
private def projectsGetADM(
requestingUser: UserADM
): Future[Seq[ProjectADM]] =
private def projectsGetADM(): Future[Seq[ProjectADM]] =
for {
sparqlQueryString <-
Future(
Expand All @@ -121,7 +117,7 @@ final case class ProjectsResponderADM(actorDeps: ActorDeps, cacheServiceSettings
projectsResponse <- appActor.ask(request).mapTo[SparqlExtendedConstructResponse]
projectIris = projectsResponse.statements.keySet.map(_.toString)

ontologiesForProjects: Map[IRI, Seq[IRI]] <- getOntologiesForProjects(projectIris, requestingUser)
ontologiesForProjects: Map[IRI, Seq[IRI]] <- getOntologiesForProjects(projectIris)
projects =
projectsResponse.statements.toList.map {
case (projectIriSubject: SubjectV2, propsMap: Map[SmartIri, Seq[LiteralV2]]) =>
Expand All @@ -139,18 +135,17 @@ final case class ProjectsResponderADM(actorDeps: ActorDeps, cacheServiceSettings
* Given a set of project IRIs, gets the ontologies that belong to each project.
*
* @param projectIris a set of project IRIs. If empty, returns the ontologies for all projects.
* @param requestingUser the requesting user.
* @return a map of project IRIs to sequences of ontology IRIs.
*/
private def getOntologiesForProjects(projectIris: Set[IRI], requestingUser: UserADM): Future[Map[IRI, Seq[IRI]]] = {
private def getOntologiesForProjects(projectIris: Set[IRI]): Future[Map[IRI, Seq[IRI]]] = {
def getIriPair(ontology: OntologyMetadataV2) =
ontology.projectIri.fold(
throw InconsistentRepositoryDataException(s"Ontology ${ontology.ontologyIri} has no project")
)(project => (project.toString, ontology.ontologyIri.toString))

val request = OntologyMetadataGetByProjectRequestV2(
projectIris = projectIris.map(_.toSmartIri),
requestingUser = requestingUser
requestingUser = KnoraSystemInstances.Users.SystemUser
)

for {
Expand All @@ -164,15 +159,12 @@ final case class ProjectsResponderADM(actorDeps: ActorDeps, cacheServiceSettings
/**
* Gets all the projects and returns them as a [[ProjectADM]].
*
* @param requestingUser the user that is making the request.
* @return all the projects as a [[ProjectADM]].
* @throws NotFoundException if no projects are found.
*/
private def projectsGetRequestADM(
requestingUser: UserADM
): Future[ProjectsGetResponseADM] =
private def projectsGetRequestADM(): Future[ProjectsGetResponseADM] =
for {
projects <- projectsGetADM(requestingUser = requestingUser)
projects <- projectsGetADM()
result = if (projects.nonEmpty) { ProjectsGetResponseADM(projects = projects) }
else { throw NotFoundException(s"No projects found") }
} yield result
Expand Down Expand Up @@ -381,16 +373,11 @@ final case class ProjectsResponderADM(actorDeps: ActorDeps, cacheServiceSettings
/**
* Gets all unique keywords for all projects and returns them. Returns an empty list if none are found.
*
* @param requestingUser the user making the request.
* @return all keywords for all projects as [[ProjectsKeywordsGetResponseADM]]
*/
private def projectsKeywordsGetRequestADM(
requestingUser: UserADM
): Future[ProjectsKeywordsGetResponseADM] =
private def projectsKeywordsGetRequestADM(): Future[ProjectsKeywordsGetResponseADM] =
for {
projects <- projectsGetADM(
requestingUser = KnoraSystemInstances.Users.SystemUser
)
projects <- projectsGetADM()

keywords: Seq[String] = projects.flatMap(_.keywords).distinct.sorted

Expand Down Expand Up @@ -1175,7 +1162,7 @@ final case class ProjectsResponderADM(actorDeps: ActorDeps, cacheServiceSettings

ontologies <-
if (projectResponse.statements.nonEmpty) {
getOntologiesForProjects(projectIris, KnoraSystemInstances.Users.SystemUser)
getOntologiesForProjects(projectIris)
} else {
FastFuture.successful(Map.empty[IRI, Seq[IRI]])
}
Expand Down
Expand Up @@ -6,10 +6,23 @@ import zio.ZLayer
import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectGetRequestADM
import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectGetResponseADM
import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM
import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectsGetRequestADM
import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectsGetResponseADM
import org.knora.webapi.responders.ActorToZioBridge

final case class RestProjectsService(bridge: ActorToZioBridge) {

/**
* Returns all projects as a [[ProjectsGetResponseADM]].
*
* @return
* '''success''': information about the projects as a [[ProjectsGetResponseADM]]
*
* '''failure''': [[dsp.errors.NotFoundException]] when no project was found
*/
def getProjectsADMRequest(): Task[ProjectsGetResponseADM] =
bridge.askAppActor(ProjectsGetRequestADM())

/**
* Finds the project by its [[ProjectIdentifierADM]] and returns the information as a [[ProjectGetResponseADM]].
*
Expand Down
Expand Up @@ -414,9 +414,7 @@ class OntologyResponderV1(responderData: ResponderData) extends Responder(respon
for {
projectsResponse <- appActor
.ask(
ProjectsGetRequestADM(
requestingUser = userProfile
)
ProjectsGetRequestADM()
)
.mapTo[ProjectsGetResponseADM]

Expand Down
Expand Up @@ -74,17 +74,8 @@ class ProjectsRouteADM(routeData: KnoraRouteData)
private def getProjects(): Route = path(projectsBasePath) {
get { requestContext =>
log.info("All projects requested.")
val requestMessage: Future[ProjectsGetRequestADM] = for {
requestingUser <- getUserADM(
requestContext = requestContext,
routeData.appConfig
)
} yield ProjectsGetRequestADM(
requestingUser = requestingUser
)

RouteUtilADM.runJsonRoute(
requestMessageF = requestMessage,
requestMessageF = FastFuture.successful(ProjectsGetRequestADM()),
requestContext = requestContext,
appActor = appActor,
log = log
Expand Down Expand Up @@ -137,17 +128,8 @@ class ProjectsRouteADM(routeData: KnoraRouteData)
*/
private def getKeywords(): Route = path(projectsBasePath / "Keywords") {
get { requestContext =>
val requestMessage: Future[ProjectsKeywordsGetRequestADM] = for {
requestingUser <- getUserADM(
requestContext = requestContext,
routeData.appConfig
)
} yield ProjectsKeywordsGetRequestADM(
requestingUser = requestingUser
)

RouteUtilADM.runJsonRoute(
requestMessageF = requestMessage,
requestMessageF = FastFuture.successful(ProjectsKeywordsGetRequestADM()),
requestContext = requestContext,
appActor = appActor,
log = log
Expand Down
@@ -1,3 +1,8 @@
/*
* Copyright © 2021 - 2022 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.routing.admin

import zhttp.http._
Expand All @@ -22,6 +27,7 @@ final case class ProjectsRouteZ(
val route: HttpApp[Any, Nothing] =
Http
.collectZIO[Request] {
case Method.GET -> !! / "admin" / "projects" => getProjects()
case Method.GET -> !! / "admin" / "projects" / "iri" / iriUrlEncoded => getProjectByIriEncoded(iriUrlEncoded)
case Method.GET -> !! / "admin" / "projects" / "shortname" / shortname => getProjectByShortname(shortname)
case Method.GET -> !! / "admin" / "projects" / "shortcode" / shortcode => getProjectByShortcode(shortcode)
Expand All @@ -31,6 +37,11 @@ final case class ProjectsRouteZ(
case InternalServerException(e) => ExceptionHandlerZ.exceptionToJsonHttpResponseZ(e, appConfig)
}

private def getProjects(): Task[Response] =
for {
projectGetResponse <- projectsService.getProjectsADMRequest()
} yield Response.json(projectGetResponse.toJsValue.toString())

private def getProjectByIriEncoded(iriUrlEncoded: String): Task[Response] =
for {
iriDecoded <- RouteUtilZ.urlDecode(iriUrlEncoded, s"Failed to URL decode IRI parameter $iriUrlEncoded.")
Expand Down

0 comments on commit b19f81c

Please sign in to comment.