Skip to content

Commit

Permalink
feat: Expose GET /admin/projects/iri/{iriUrlEncoded} as zio-http route (
Browse files Browse the repository at this point in the history
#2355)

Co-authored-by: Balduin Landolt <33053745+BalduinLandolt@users.noreply.github.com>
Co-authored-by: irinaschubert <irina.schubert@dasch.swiss>
Co-authored-by: Marcin Procyk <marcin.procyk@dasch.swiss>
  • Loading branch information
4 people committed Jan 2, 2023
1 parent a678dc5 commit 2f42906
Show file tree
Hide file tree
Showing 27 changed files with 616 additions and 218 deletions.
Expand Up @@ -56,53 +56,48 @@ class ProjectsResponderADMSpec extends CoreSpec with ImplicitSender {
}

"return information about a project identified by IRI" in {
appActor ! ProjectGetRequestADM(
identifier = IriIdentifier
appActor ! ProjectGetRequestADM(identifier =
IriIdentifier
.fromString(SharedTestDataADM.incunabulaProject.id)
.getOrElseWith(e => throw BadRequestException(e.head.getMessage)),
requestingUser = SharedTestDataADM.rootUser
.getOrElseWith(e => throw BadRequestException(e.head.getMessage))
)
expectMsg(ProjectGetResponseADM(SharedTestDataADM.incunabulaProject))

}

"return information about a project identified by shortname" in {
appActor ! ProjectGetRequestADM(
identifier = ShortnameIdentifier
appActor ! ProjectGetRequestADM(identifier =
ShortnameIdentifier
.fromString(SharedTestDataADM.incunabulaProject.shortname)
.getOrElseWith(e => throw BadRequestException(e.head.getMessage)),
requestingUser = SharedTestDataADM.rootUser
.getOrElseWith(e => throw BadRequestException(e.head.getMessage))
)
expectMsg(ProjectGetResponseADM(SharedTestDataADM.incunabulaProject))
}

"return 'NotFoundException' when the project IRI is unknown" in {
appActor ! ProjectGetRequestADM(
identifier = IriIdentifier
appActor ! ProjectGetRequestADM(identifier =
IriIdentifier
.fromString(notExistingProjectButValidProjectIri)
.getOrElseWith(e => throw BadRequestException(e.head.getMessage)),
requestingUser = SharedTestDataADM.rootUser
.getOrElseWith(e => throw BadRequestException(e.head.getMessage))
)
expectMsg(Failure(NotFoundException(s"Project '$notExistingProjectButValidProjectIri' not found")))

}

"return 'NotFoundException' when the project shortname is unknown " in {
appActor ! ProjectGetRequestADM(
identifier = ShortnameIdentifier
appActor ! ProjectGetRequestADM(identifier =
ShortnameIdentifier
.fromString("wrongshortname")
.getOrElseWith(e => throw BadRequestException(e.head.getMessage)),
requestingUser = SharedTestDataADM.rootUser
.getOrElseWith(e => throw BadRequestException(e.head.getMessage))
)
expectMsg(Failure(NotFoundException(s"Project 'wrongshortname' not found")))
}

"return 'NotFoundException' when the project shortcode is unknown " in {
appActor ! ProjectGetRequestADM(
identifier = ShortcodeIdentifier
appActor ! ProjectGetRequestADM(identifier =
ShortcodeIdentifier
.fromString("9999")
.getOrElseWith(e => throw BadRequestException(e.head.getMessage)),
requestingUser = SharedTestDataADM.rootUser
.getOrElseWith(e => throw BadRequestException(e.head.getMessage))
)
expectMsg(Failure(NotFoundException(s"Project '9999' not found")))
}
Expand Down
6 changes: 4 additions & 2 deletions webapi/src/main/scala/org/knora/webapi/core/LayersLive.scala
Expand Up @@ -13,8 +13,9 @@ import org.knora.webapi.config.AppConfig
import org.knora.webapi.messages.StringFormatter
import org.knora.webapi.responders.ActorDeps
import org.knora.webapi.responders.ActorToZioBridge
import org.knora.webapi.responders.admin.ProjectsService
import org.knora.webapi.responders.admin.RestProjectsService
import org.knora.webapi.routing.ApiRoutes
import org.knora.webapi.routing.admin.AuthenticatorService
import org.knora.webapi.routing.admin.ProjectsRouteZ
import org.knora.webapi.slice.resourceinfo.api.ResourceInfoRoute
import org.knora.webapi.slice.resourceinfo.api.RestResourceInfoService
Expand Down Expand Up @@ -63,6 +64,7 @@ object LayersLive {
ApiRoutes.layer,
AppConfig.live,
AppRouter.layer,
AuthenticatorService.layer,
CacheServiceInMemImpl.layer,
CacheServiceManager.layer,
HttpServer.layer,
Expand All @@ -72,11 +74,11 @@ object LayersLive {
IriConverter.layer,
JWTService.layer,
ProjectsRouteZ.layer,
ProjectsService.layer,
RepositoryUpdater.layer,
ResourceInfoRepo.layer,
ResourceInfoRoute.layer,
RestResourceInfoService.layer,
RestProjectsService.layer,
State.layer,
StringFormatter.live,
TriplestoreServiceHttpConnectorImpl.layer,
Expand Down
Expand Up @@ -11,9 +11,8 @@ import dsp.errors._
import org.knora.webapi.store.triplestore.errors.TriplestoreTimeoutException

/**
* Migrated from [[org.knora.webapi.http.status.ApiStatusCodes]]
*
* The possible values for the HTTP status code that is returned as part of each Knora API v2 response.
* Migrated from [[org.knora.webapi.http.status.ApiStatusCodes]]
*/
object ApiStatusCodesZ {

Expand Down
Expand Up @@ -26,13 +26,12 @@ import org.knora.webapi.IRI
import org.knora.webapi.messages.ResponderRequest.KnoraRequestADM
import org.knora.webapi.messages.StringFormatter
import org.knora.webapi.messages.admin.responder.KnoraResponseADM
import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM._
import org.knora.webapi.messages.admin.responder.usersmessages.UserADM
import org.knora.webapi.messages.store.triplestoremessages.StringLiteralV2
import org.knora.webapi.messages.store.triplestoremessages.TriplestoreJsonProtocol
import org.knora.webapi.messages.v1.responder.projectmessages.ProjectInfoV1

import ProjectIdentifierADM._

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

Expand Down Expand Up @@ -187,20 +186,15 @@ case class ProjectsGetADM(requestingUser: UserADM) extends ProjectsResponderRequ
* @param identifier the IRI, email, or username of the project.
* @param requestingUser the user making the request.
*/
case class ProjectGetRequestADM(
identifier: ProjectIdentifierADM,
requestingUser: UserADM
) extends ProjectsResponderRequestADM
case class ProjectGetRequestADM(identifier: ProjectIdentifierADM) extends ProjectsResponderRequestADM

/**
* Get info about a single project identified either through its IRI, shortname or shortcode. The response is in form
* of [[ProjectADM]]. Internal use only.
*
* @param identifier the IRI, email, or username of the project.
*/
case class ProjectGetADM(
identifier: ProjectIdentifierADM
) extends ProjectsResponderRequestADM
case class ProjectGetADM(identifier: ProjectIdentifierADM) extends ProjectsResponderRequestADM

/**
* Returns all users belonging to a project identified either through its IRI, shortname or shortcode.
Expand Down
Expand Up @@ -1570,11 +1570,10 @@ object ConstructResponseUtilV2 {
projectResponse: ProjectGetResponseADM <-
appActor
.ask(
ProjectGetRequestADM(
identifier = IriIdentifier
ProjectGetRequestADM(identifier =
IriIdentifier
.fromString(resourceAttachedToProject)
.getOrElseWith(e => throw BadRequestException(e.head.getMessage)),
requestingUser = requestingUser
.getOrElseWith(e => throw BadRequestException(e.head.getMessage))
)
)
.mapTo[ProjectGetResponseADM]
Expand Down
Expand Up @@ -701,11 +701,10 @@ object CreateResourceRequestV2 extends KnoraJsonLDRequestReaderV2[CreateResource
projectInfoResponse: ProjectGetResponseADM <-
appActor
.ask(
ProjectGetRequestADM(
identifier = IriIdentifier
ProjectGetRequestADM(identifier =
IriIdentifier
.fromString(projectIri.toString)
.getOrElseWith(e => throw BadRequestException(e.head.getMessage)),
requestingUser = requestingUser
.getOrElseWith(e => throw BadRequestException(e.head.getMessage))
)
)
.mapTo[ProjectGetResponseADM]
Expand Down
@@ -1,5 +1,4 @@
package org.knora.webapi.responders
import akka.actor.Actor
import akka.actor.ActorRef
import akka.pattern.ask
import akka.util.Timeout
Expand All @@ -9,8 +8,6 @@ import zio.URLayer
import zio.ZIO
import zio.ZLayer

import scala.reflect.ClassTag

import org.knora.webapi.messages.ResponderRequest

/**
Expand All @@ -23,23 +20,22 @@ trait ActorToZioBridge {
* casts and returns the response to the expected return type `R` as [[Task]].
*
* @param message The message sent to the actor
* @param tag implicit proof that the result type `R` has a [[ClassTag]]
*
* @tparam R The type of the expected success value
* @return A Task containing either the success `R` or the failure [[Throwable]],
* will fail during runtime with a [[ClassCastException]] if the `R` does not correspond
* to the response of the message being sent due to the untyped nature of the ask pattern
*/
def askAppActor[R: Tag](message: ResponderRequest)(implicit tag: ClassTag[R]): Task[R]
def askAppActor[R: Tag](message: ResponderRequest): Task[R]

}

final case class ActorToZioBridgeLive(actorDeps: ActorDeps) extends ActorToZioBridge {
private implicit val timeout: Timeout = actorDeps.timeout
private val appActor: ActorRef = actorDeps.appActor

override def askAppActor[R: Tag](message: ResponderRequest)(implicit tag: ClassTag[R]): Task[R] =
ZIO.fromFuture(_ => appActor.ask(message, Actor.noSender).mapTo[R])
override def askAppActor[R: Tag](message: ResponderRequest): Task[R] =
ZIO.fromFuture(implicit ec => appActor.ask(message).map(_.asInstanceOf[R]))
}

object ActorToZioBridge {
Expand Down
Expand Up @@ -65,8 +65,7 @@ final case class ProjectsResponderADM(actorDeps: ActorDeps, cacheServiceSettings
case ProjectsGetADM(requestingUser) => projectsGetADM(requestingUser)
case ProjectsGetRequestADM(requestingUser) => projectsGetRequestADM(requestingUser)
case ProjectGetADM(identifier) => getSingleProjectADM(identifier)
case ProjectGetRequestADM(identifier, requestingUser) =>
getSingleProjectADMRequest(identifier, requestingUser)
case ProjectGetRequestADM(identifier) => getSingleProjectADMRequest(identifier)
case ProjectMembersGetRequestADM(identifier, requestingUser) =>
projectMembersGetRequestADM(identifier, requestingUser)
case ProjectAdminMembersGetRequestADM(identifier, requestingUser) =>
Expand Down Expand Up @@ -251,26 +250,13 @@ final case class ProjectsResponderADM(actorDeps: ActorDeps, cacheServiceSettings
* as a [[ProjectGetResponseADM]].
*
* @param identifier the IRI, shortname, shortcode or UUID of the project.
* @param requestingUser the user making the request.
* @return information about the project as a [[ProjectGetResponseADM]].
* @throws NotFoundException when no project for the given IRI can be found
*/
def getSingleProjectADMRequest(
identifier: ProjectIdentifierADM,
requestingUser: UserADM
): Future[ProjectGetResponseADM] =
for {
maybeProject: Option[ProjectADM] <- getSingleProjectADM(
identifier = identifier
)

project = maybeProject match {
case Some(p) => p
case None => throw NotFoundException(s"Project '${getId(identifier)}' not found")
}
} yield ProjectGetResponseADM(
project = project
)
def getSingleProjectADMRequest(identifier: ProjectIdentifierADM): Future[ProjectGetResponseADM] = for {
maybeProject <- getSingleProjectADM(identifier)
project = maybeProject.getOrElse(throw NotFoundException(s"Project '${getId(identifier)}' not found"))
} yield ProjectGetResponseADM(project)

/**
* Gets the members of a project with the given IRI, shortname, shortcode or UUID. Returns an empty list
Expand Down

This file was deleted.

@@ -0,0 +1,45 @@
package org.knora.webapi.responders.admin
import zio.Task
import zio.URLayer
import zio.ZLayer

import org.knora.webapi.IRI
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.responders.ActorToZioBridge

final case class RestProjectsService(bridge: ActorToZioBridge) {

/**
* Finds the project by an [[IRI]] and returns the information as a [[ProjectGetResponseADM]].
*
* @param projectIri an [[IRI]] identifying the project
* @return
* '''success''': information about the project as a [[ProjectGetResponseADM]]
*
* '''error''': [[dsp.errors.NotFoundException]] when no project for the given IRI can be found
* [[dsp.errors.ValidationException]] if the given `projectIri` is invalid
*/
def getSingleProjectADMRequest(projectIri: IRI): Task[ProjectGetResponseADM] =
ProjectIdentifierADM.IriIdentifier
.fromString(projectIri)
.toZIO
.flatMap(getSingleProjectADMRequest(_))

/**
* Finds the project by its [[ProjectIdentifierADM]] and returns the information as a [[ProjectGetResponseADM]].
*
* @param identifier a [[ProjectIdentifierADM]] instance
* @return
* '''success''': information about the project as a [[ProjectGetResponseADM]]
*
* '''failure''': [[dsp.errors.NotFoundException]] when no project for the given IRI can be found
*/
def getSingleProjectADMRequest(identifier: ProjectIdentifierADM): Task[ProjectGetResponseADM] =
bridge.askAppActor(ProjectGetRequestADM(identifier))
}

object RestProjectsService {
val layer: URLayer[ActorToZioBridge, RestProjectsService] = ZLayer.fromFunction(RestProjectsService.apply _)
}
Expand Up @@ -1584,11 +1584,10 @@ class ResourcesResponderV1(responderData: ResponderData) extends Responder(respo
projectInfoResponse <-
appActor
.ask(
ProjectGetRequestADM(
identifier = IriIdentifier
ProjectGetRequestADM(identifier =
IriIdentifier
.fromString(projectIri)
.getOrElseWith(e => throw BadRequestException(e.head.getMessage)),
requestingUser = requestingUser
.getOrElseWith(e => throw BadRequestException(e.head.getMessage))
)
)
.mapTo[ProjectGetResponseADM]
Expand Down Expand Up @@ -2462,11 +2461,10 @@ class ResourcesResponderV1(responderData: ResponderData) extends Responder(respo
projectResponse <-
appActor
.ask(
ProjectGetRequestADM(
identifier = IriIdentifier
ProjectGetRequestADM(identifier =
IriIdentifier
.fromString(projectIri)
.getOrElseWith(e => throw BadRequestException(e.head.getMessage)),
requestingUser = userProfile
.getOrElseWith(e => throw BadRequestException(e.head.getMessage))
)
)
.mapTo[ProjectGetResponseADM]
Expand Down
Expand Up @@ -837,11 +837,10 @@ class ValuesResponderV1(responderData: ResponderData) extends Responder(responde
projectResponse <-
appActor
.ask(
ProjectGetRequestADM(
identifier = IriIdentifier
ProjectGetRequestADM(identifier =
IriIdentifier
.fromString(resourceInfoResponse.resource_info.get.project_id)
.getOrElseWith(e => throw BadRequestException(e.head.getMessage)),
requestingUser = changeFileValueRequest.userProfile
.getOrElseWith(e => throw BadRequestException(e.head.getMessage))
)
)
.mapTo[ProjectGetResponseADM]
Expand Down

0 comments on commit 2f42906

Please sign in to comment.