Skip to content

Commit

Permalink
refactor!: Port resourceinfo endpoint to tapir and remove code/zio-ht…
Browse files Browse the repository at this point in the history
…tp server running on port 5555 (DEV-2807) (#2880)
  • Loading branch information
seakayone authored Oct 20, 2023
1 parent 57f356a commit 557730e
Show file tree
Hide file tree
Showing 23 changed files with 366 additions and 595 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,9 @@ import org.knora.webapi.slice.ontology.repo.service.OntologyCache
import org.knora.webapi.slice.ontology.repo.service.OntologyCacheLive
import org.knora.webapi.slice.ontology.repo.service.OntologyRepoLive
import org.knora.webapi.slice.ontology.repo.service.PredicateRepositoryLive
import org.knora.webapi.slice.resourceinfo.api.ResourceInfoRoute
import org.knora.webapi.slice.resourceinfo.api.RestResourceInfoService
import org.knora.webapi.slice.resourceinfo.ResourceInfoLayers
import org.knora.webapi.slice.resourceinfo.api.service.RestResourceInfoService
import org.knora.webapi.slice.resourceinfo.domain.IriConverter
import org.knora.webapi.slice.resourceinfo.domain.ResourceInfoRepo
import org.knora.webapi.store.cache.CacheServiceRequestMessageHandler
import org.knora.webapi.store.cache.CacheServiceRequestMessageHandlerLive
import org.knora.webapi.store.cache.api.CacheService
Expand Down Expand Up @@ -109,7 +108,6 @@ object LayersTest {
with ProjectsResponderADM
with QueryTraverser
with RepositoryUpdater
with ResourceInfoRepo
with ResourceUtilV2
with ResourcesResponderV2
with RestCardinalityService
Expand Down Expand Up @@ -145,7 +143,6 @@ object LayersTest {
GroupsResponderADMLive.layer,
HandlerMapper.layer,
HttpServer.layer,
HttpServerZ.layer,
IIIFRequestMessageHandlerLive.layer,
InferenceOptimizationService.layer,
IriConverter.layer,
Expand Down Expand Up @@ -177,13 +174,11 @@ object LayersTest {
ProjectsResponderADMLive.layer,
QueryTraverser.layer,
RepositoryUpdater.layer,
ResourceInfoRepo.layer,
ResourceInfoRoute.layer,
ResourceInfoLayers.live,
ResourceUtilV2Live.layer,
ResourcesResponderV2Live.layer,
RestCardinalityServiceLive.layer,
RestPermissionServiceLive.layer,
RestResourceInfoService.layer,
SearchResponderV2Live.layer,
SipiResponderADMLive.layer,
StandoffResponderV2Live.layer,
Expand Down
29 changes: 0 additions & 29 deletions webapi/src/main/scala/org/knora/webapi/core/HttpServerZ.scala

This file was deleted.

12 changes: 5 additions & 7 deletions webapi/src/main/scala/org/knora/webapi/core/LayersLive.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ import org.knora.webapi.slice.ontology.repo.service.OntologyCache
import org.knora.webapi.slice.ontology.repo.service.OntologyCacheLive
import org.knora.webapi.slice.ontology.repo.service.OntologyRepoLive
import org.knora.webapi.slice.ontology.repo.service.PredicateRepositoryLive
import org.knora.webapi.slice.resourceinfo.api.ResourceInfoRoute
import org.knora.webapi.slice.resourceinfo.api.RestResourceInfoService
import org.knora.webapi.slice.resourceinfo.ResourceInfoLayers
import org.knora.webapi.slice.resourceinfo.api.service.RestResourceInfoService
import org.knora.webapi.slice.resourceinfo.api.service.RestResourceInfoServiceLive
import org.knora.webapi.slice.resourceinfo.domain.IriConverter
import org.knora.webapi.slice.resourceinfo.domain.ResourceInfoRepo
import org.knora.webapi.store.cache.CacheServiceRequestMessageHandler
import org.knora.webapi.store.cache.CacheServiceRequestMessageHandlerLive
import org.knora.webapi.store.cache.api.CacheService
Expand Down Expand Up @@ -145,7 +145,6 @@ object LayersLive {
GroupsResponderADMLive.layer,
HandlerMapper.layer,
HttpServer.layer,
HttpServerZ.layer, // this is the new ZIO HTTP server layer
IIIFRequestMessageHandlerLive.layer,
IIIFServiceSipiImpl.layer,
InferenceOptimizationService.layer,
Expand Down Expand Up @@ -179,13 +178,12 @@ object LayersLive {
ProjectsResponderADMLive.layer,
QueryTraverser.layer,
RepositoryUpdater.layer,
ResourceInfoRepo.layer,
ResourceInfoRoute.layer,
ResourceInfoLayers.live,
ResourceUtilV2Live.layer,
ResourcesResponderV2Live.layer,
RestCardinalityServiceLive.layer,
RestPermissionServiceLive.layer,
RestResourceInfoService.layer,
RestResourceInfoServiceLive.layer,
SearchResponderV2Live.layer,
SipiResponderADMLive.layer,
StandoffResponderV2Live.layer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@ import spray.json.DefaultJsonProtocol
import spray.json.JsValue
import spray.json.JsonFormat
import spray.json.RootJsonFormat
import sttp.tapir.Codec
import sttp.tapir.CodecFormat.TextPlain
import sttp.tapir.DecodeResult
import zio.json.DeriveJsonCodec
import zio.json.JsonCodec
import zio.prelude.Validation

import java.util.UUID

import dsp.errors.BadRequestException
import dsp.errors.OntologyConstraintException
import dsp.errors.ValidationException
import dsp.valueobjects.Iri
Expand Down Expand Up @@ -365,9 +369,21 @@ object ProjectIdentifierADM {
object IriIdentifier {

def from(projectIri: ProjectIri): IriIdentifier = IriIdentifier(projectIri)
def unsafeFrom(projectIri: String): IriIdentifier =
fromString(projectIri).fold(
err => throw new IllegalArgumentException(s"Invalid project IRI: $projectIri: ${err.head.msg}"),
identity
)

def fromString(value: String): Validation[ValidationException, IriIdentifier] =
ProjectIri.make(value).map(IriIdentifier(_))

implicit val tapirCodec: Codec[String, IriIdentifier, TextPlain] =
Codec.string.mapDecode(str =>
IriIdentifier
.fromString(str)
.fold(err => DecodeResult.Error(str, BadRequestException(err.head.msg)), DecodeResult.Value(_))
)(_.value.value)
}

/**
Expand Down
20 changes: 12 additions & 8 deletions webapi/src/main/scala/org/knora/webapi/routing/ApiRoutes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ import org.knora.webapi.slice.admin.api.ProjectsEndpointsHandler
import org.knora.webapi.slice.admin.api.service.ProjectADMRestService
import org.knora.webapi.slice.admin.domain.service.KnoraProjectRepo
import org.knora.webapi.slice.ontology.api.service.RestCardinalityService
import org.knora.webapi.slice.resourceinfo.api.RestResourceInfoService
import org.knora.webapi.slice.resourceinfo.api.ResourceInfoRoutes
import org.knora.webapi.slice.resourceinfo.api.service.RestResourceInfoService
import org.knora.webapi.slice.resourceinfo.domain.IriConverter

trait ApiRoutes {
Expand All @@ -52,6 +53,7 @@ object ApiRoutes {
with MessageRelay
with ProjectADMRestService
with ProjectsEndpointsHandler
with ResourceInfoRoutes
with RestCardinalityService
with RestResourceInfoService
with StringFormatter
Expand All @@ -62,11 +64,12 @@ object ApiRoutes {
] =
ZLayer {
for {
sys <- ZIO.service[ActorSystem]
router <- ZIO.service[AppRouter]
appConfig <- ZIO.service[AppConfig]
adminApiRoutes <- ZIO.service[AdminApiRoutes]
routeData <- ZIO.succeed(KnoraRouteData(sys.system, router.ref, appConfig))
sys <- ZIO.service[ActorSystem]
router <- ZIO.service[AppRouter]
appConfig <- ZIO.service[AppConfig]
adminApiRoutes <- ZIO.service[AdminApiRoutes]
resourceInfoRoutes <- ZIO.service[ResourceInfoRoutes]
routeData <- ZIO.succeed(KnoraRouteData(sys.system, router.ref, appConfig))
runtime <- ZIO.runtime[
AppConfig
with IriConverter
Expand All @@ -80,7 +83,7 @@ object ApiRoutes {
with core.State
with routing.Authenticator
]
} yield ApiRoutesImpl(routeData, adminApiRoutes, appConfig, runtime)
} yield ApiRoutesImpl(routeData, adminApiRoutes, resourceInfoRoutes, appConfig, runtime)
}
}

Expand All @@ -94,6 +97,7 @@ object ApiRoutes {
private final case class ApiRoutesImpl(
routeData: KnoraRouteData,
adminApiRoutes: AdminApiRoutes,
resourceInfoRoutes: ResourceInfoRoutes,
appConfig: AppConfig,
implicit val runtime: Runtime[
AppConfig
Expand Down Expand Up @@ -122,7 +126,7 @@ private final case class ApiRoutesImpl(
.withAllowedMethods(List(GET, PUT, POST, DELETE, PATCH, HEAD, OPTIONS))
) {
DSPApiDirectives.handleErrors(appConfig) {
adminApiRoutes.routes.reduce(_ ~ _) ~
(adminApiRoutes.routes ++ resourceInfoRoutes.routes).reduce(_ ~ _) ~
AuthenticationRouteV2().makeRoute ~
FilesRouteADM(routeData, runtime).makeRoute ~
GroupsRouteADM(routeData, runtime).makeRoute ~
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,8 @@ import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentif

object PathVariables {

private val projectIriCodec: Codec[String, IriIdentifier, TextPlain] =
Codec.string.mapDecode(str =>
IriIdentifier
.fromString(str)
.fold(err => DecodeResult.Error(str, BadRequestException(err.head.msg)), DecodeResult.Value(_))
)(_.value.value)

val projectIri: EndpointInput.PathCapture[IriIdentifier] =
path[IriIdentifier](projectIriCodec)
path[IriIdentifier]
.name("projectIri")
.description("The IRI of a project. Must be URL-encoded.")
.example(IriIdentifier.fromString("http://rdfh.ch/projects/0001").fold(e => throw e.head, identity))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@
package org.knora.webapi.routing.v2

import com.typesafe.scalalogging.LazyLogging
import org.apache.pekko
import zio.Exit.Failure
import zio.Exit.Success
import org.apache.pekko.http.scaladsl.server.Directives._
import org.apache.pekko.http.scaladsl.server.PathMatcher
import org.apache.pekko.http.scaladsl.server.Route
import zio._
import zio.json._

import java.time.Instant

Expand All @@ -33,23 +32,9 @@ import org.knora.webapi.messages.v2.responder.valuemessages._
import org.knora.webapi.routing.Authenticator
import org.knora.webapi.routing.RouteUtilV2
import org.knora.webapi.routing.RouteUtilZ
import org.knora.webapi.slice.resourceinfo.api.RestResourceInfoService
import org.knora.webapi.slice.resourceinfo.api.RestResourceInfoServiceLive.ASC
import org.knora.webapi.slice.resourceinfo.api.RestResourceInfoServiceLive.Order
import org.knora.webapi.slice.resourceinfo.api.RestResourceInfoServiceLive.OrderBy
import org.knora.webapi.slice.resourceinfo.api.RestResourceInfoServiceLive.lastModificationDate
import org.knora.webapi.slice.resourceinfo.api.service.RestResourceInfoService
import org.knora.webapi.slice.resourceinfo.domain.IriConverter

import pekko.http.scaladsl.model.ContentTypes.`application/json`
import pekko.http.scaladsl.model.HttpEntity
import pekko.http.scaladsl.model.HttpResponse
import pekko.http.scaladsl.model.StatusCodes.InternalServerError
import pekko.http.scaladsl.model.StatusCodes.OK
import pekko.http.scaladsl.server.Directives._
import pekko.http.scaladsl.server.PathMatcher
import pekko.http.scaladsl.server.RequestContext
import pekko.http.scaladsl.server.Route

/**
* Provides a routing function for API v2 routes that deal with resources.
*/
Expand Down Expand Up @@ -83,7 +68,6 @@ final case class ResourcesRouteV2(appConfig: AppConfig)(
getResourceHistory() ~
getResourceHistoryEvents() ~
getProjectResourceAndValueHistory() ~
getResourcesInfo ~
getResources() ~
getResourcesPreview() ~
getResourcesTei() ~
Expand Down Expand Up @@ -264,49 +248,6 @@ final case class ResourcesRouteV2(appConfig: AppConfig)(
}
}

private def getQueryParamsMap(requestContext: RequestContext): Map[String, String] =
requestContext.request.uri.query().toMap

private def getStringQueryParam(requestContext: RequestContext, key: String): Option[String] =
getQueryParamsMap(requestContext).get(key)

private def unsafeRunZioAndMapJsonResponse[R, E, A](
zioAction: ZIO[R, E, A]
)(implicit r: Runtime[R], encoder: JsonEncoder[A]) =
unsafeRunZio(zioAction) match {
case Failure(cause) => logger.error(cause.prettyPrint); HttpResponse(InternalServerError)
case Success(dto) => HttpResponse(status = OK, entity = HttpEntity(`application/json`, dto.toJson))
}

private def unsafeRunZio[R, E, A](zioAction: ZIO[R, E, A])(implicit r: Runtime[R]): Exit[E, A] =
Unsafe.unsafe(implicit u => r.unsafe.run(zioAction))

private def getResourcesInfo: Route = path(resourcesBasePath / "info") {
get { ctx =>
val getResourceClassIri = ZIO
.fromOption(getStringQueryParam(ctx, "resourceClass"))
.orElseFail(BadRequestException(s"This route requires the parameter 'resourceClass'"))
val getOrderBy: ZIO[Any, BadRequestException, OrderBy] = getStringQueryParam(ctx, "orderBy") match {
case None => ZIO.succeed(lastModificationDate)
case Some(s) =>
ZIO.fromOption(OrderBy.make(s)).orElseFail(BadRequestException(s"Invalid value '$s', for orderBy"))
}
val getOrder: IO[BadRequestException, Order] = getStringQueryParam(ctx, "order") match {
case None => ZIO.succeed(ASC)
case Some(s) =>
ZIO.fromOption(Order.make(s)).orElseFail(BadRequestException(s"Invalid value '$s', for order"))
}
val action = for {
resourceClassIri <- getResourceClassIri
orderBy <- getOrderBy
order <- getOrder
projectIri <- RouteUtilV2.getRequiredProjectIri(ctx)
result <-
RestResourceInfoService.findByProjectAndResourceClass(projectIri.toIri, resourceClassIri, (orderBy, order))
} yield result
ctx.complete(unsafeRunZioAndMapJsonResponse(action))
}
}
private def getResources(): Route = path(resourcesBasePath / Segments) { resIris: Seq[String] =>
get { requestContext =>
val targetSchemaTask = RouteUtilV2.getOntologySchema(requestContext)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright © 2021 - 2023 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.resourceinfo

import zio.ZLayer

import org.knora.webapi.slice.common.api.BaseEndpoints
import org.knora.webapi.slice.common.api.HandlerMapper
import org.knora.webapi.slice.common.api.TapirToPekkoInterpreter
import org.knora.webapi.slice.resourceinfo.api.ResourceInfoEndpoints
import org.knora.webapi.slice.resourceinfo.api.ResourceInfoRoutes
import org.knora.webapi.slice.resourceinfo.api.service.RestResourceInfoService
import org.knora.webapi.slice.resourceinfo.api.service.RestResourceInfoServiceLive
import org.knora.webapi.slice.resourceinfo.domain.IriConverter
import org.knora.webapi.slice.resourceinfo.repo.ResourceInfoRepoLive
import org.knora.webapi.store.triplestore.api.TriplestoreService

object ResourceInfoLayers {

val live: ZLayer[
TriplestoreService with IriConverter with BaseEndpoints with HandlerMapper with TapirToPekkoInterpreter,
Nothing,
RestResourceInfoService with ResourceInfoEndpoints with ResourceInfoRoutes
] =
ResourceInfoRepoLive.layer >>> RestResourceInfoServiceLive.layer >+> ResourceInfoEndpoints.layer >+> ResourceInfoRoutes.layer

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright © 2021 - 2023 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.resourceinfo.api

import sttp.tapir._
import sttp.tapir.generic.auto._
import sttp.tapir.json.zio._
import zio.ZLayer

import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM.IriIdentifier
import org.knora.webapi.routing.RouteUtilV2
import org.knora.webapi.slice.common.api.BaseEndpoints
import org.knora.webapi.slice.resourceinfo.api.model.ListResponseDto
import org.knora.webapi.slice.resourceinfo.api.model.QueryParams.Order
import org.knora.webapi.slice.resourceinfo.api.model.QueryParams.OrderBy

final case class ResourceInfoEndpoints(baseEndpoints: BaseEndpoints) {
val getResourcesInfo = baseEndpoints.publicEndpoint.get
.in("v2" / "resources" / "info")
.in(header[IriIdentifier](RouteUtilV2.PROJECT_HEADER))
.in(query[String]("resourceClass"))
.in(query[Option[Order]](Order.queryParamKey))
.in(query[Option[OrderBy]](OrderBy.queryParamKey))
.out(jsonBody[ListResponseDto])
}

object ResourceInfoEndpoints {
val layer = ZLayer.derive[ResourceInfoEndpoints]
}
Loading

0 comments on commit 557730e

Please sign in to comment.