From e36e6fb5fae31e62c9b26584893d960fef662dff Mon Sep 17 00:00:00 2001 From: Jonas Natten Date: Wed, 30 Aug 2023 15:13:18 +0200 Subject: [PATCH] eecg --- .jvmopts | 1 + .../no/ndla/audioapi/ComponentRegistry.scala | 11 +- .../main/scala/no/ndla/audioapi/Main.scala | 2 +- .../scala/no/ndla/audioapi/MainClass.scala | 83 +++++++- .../audioapi/controller/AudioController.scala | 21 ++- .../controller/HealthController.scala | 8 +- .../controller/InternController.scala | 178 +++++++++--------- .../controller/SeriesController.scala | 17 +- .../scala/no/ndla/network/tapir/Routes.scala | 84 +++++---- .../scala/no/ndla/network/tapir/Service.scala | 5 +- .../network/tapir/TapirErrorHelpers.scala | 6 + .../network/tapir/TapirHealthController.scala | 26 ++- project/Dependencies.scala | 3 +- project/Module.scala | 4 +- 14 files changed, 271 insertions(+), 178 deletions(-) diff --git a/.jvmopts b/.jvmopts index a5243bdeea..91543cd8b8 100644 --- a/.jvmopts +++ b/.jvmopts @@ -9,3 +9,4 @@ --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.desktop/java.awt.event=ALL-UNNAMED --add-opens=java.desktop/java.awt=ALL-UNNAMED +--enable-preview diff --git a/audio-api/src/main/scala/no/ndla/audioapi/ComponentRegistry.scala b/audio-api/src/main/scala/no/ndla/audioapi/ComponentRegistry.scala index 3b3c28364b..1308f3c23e 100644 --- a/audio-api/src/main/scala/no/ndla/audioapi/ComponentRegistry.scala +++ b/audio-api/src/main/scala/no/ndla/audioapi/ComponentRegistry.scala @@ -106,15 +106,14 @@ class ComponentRegistry(properties: AudioApiProperties) lazy val clock = new SystemClock - private val services: List[Service] = List( - audioApiController, - seriesController, + private val services: List[SwaggerService] = List( +// audioApiController, +// seriesController, internController, healthController ) - private val swaggerDocController = new SwaggerController(services, SwaggerDocControllerConfig.swaggerInfo) - - def routes: Kleisli[IO, Request[IO], Response[IO]] = Routes.build(services :+ swaggerDocController) +// private val swaggerDocController = new SwaggerController(services, SwaggerDocControllerConfig.swaggerInfo) + val allServices = services } diff --git a/audio-api/src/main/scala/no/ndla/audioapi/Main.scala b/audio-api/src/main/scala/no/ndla/audioapi/Main.scala index 382ddc2787..73e39e32e6 100644 --- a/audio-api/src/main/scala/no/ndla/audioapi/Main.scala +++ b/audio-api/src/main/scala/no/ndla/audioapi/Main.scala @@ -16,6 +16,6 @@ object Main extends IOApp { setPropsFromEnv() val props = new AudioApiProperties val mainClass = new MainClass(props) - mainClass.run(args) + IO.never.as(ExitCode.Success) } } diff --git a/audio-api/src/main/scala/no/ndla/audioapi/MainClass.scala b/audio-api/src/main/scala/no/ndla/audioapi/MainClass.scala index 7be984355c..75cb99d94f 100644 --- a/audio-api/src/main/scala/no/ndla/audioapi/MainClass.scala +++ b/audio-api/src/main/scala/no/ndla/audioapi/MainClass.scala @@ -8,18 +8,42 @@ package no.ndla.audioapi -import cats.data.Kleisli -import cats.effect.IO +import io.circe.generic.auto._ import no.ndla.common.Warmup -import no.ndla.network.tapir.NdlaTapirMain -import org.http4s.{Request, Response} +import org.log4s.getLogger +import sttp.tapir.server.ServerEndpoint +import sttp.tapir.server.interceptor.RequestResult +import sttp.tapir.server.jdkhttp.{Id, JdkHttpServer, JdkHttpServerOptions} +import no.ndla.common.DateParser +import no.ndla.common.model.NDLADate +import no.ndla.network.tapir.ErrorBody +import no.ndla.network.tapir.NoNullJsonPrinter._ +import org.http4s.circe.CirceEntityCodec.circeEntityEncoder +import org.http4s.headers.`Content-Type` +import org.http4s.server.Router +import org.http4s.{Headers, HttpRoutes, MediaType, Request, Response} +import org.log4s.{Logger, getLogger} +import sttp.model.StatusCode +import sttp.monad.MonadError +import sttp.tapir.generic.auto.schemaForCaseClass +import sttp.tapir.server.http4s.{Http4sServerInterpreter, Http4sServerOptions} +import sttp.tapir.server.interceptor.RequestResult +import sttp.tapir.server.interceptor.decodefailure.DefaultDecodeFailureHandler +import sttp.tapir.server.interceptor.exception.{ExceptionContext, ExceptionHandler} +import sttp.tapir.server.interceptor.reject.{RejectHandler, RejectInterceptor} +import sttp.tapir.server.model.ValuedEndpointOutput +import sttp.tapir.{EndpointInput, statusCode} -class MainClass(override val props: AudioApiProperties) extends NdlaTapirMain { - private val componentRegistry = new ComponentRegistry(props) - override val app: Kleisli[IO, Request[IO], Response[IO]] = componentRegistry.routes +import java.time.LocalDateTime +import java.util.concurrent.Executors + +class MainClass(val props: AudioApiProperties) { + val logger = getLogger + private val componentRegistry = new ComponentRegistry(props) +// override val app: Kleisli[IO, Request[IO], Response[IO]] = componentRegistry.routes private def warmupRequest = (path, params) => Warmup.warmupRequest(props.ApplicationPort, path, params) - override def warmup(): Unit = { + def warmup(): Unit = { warmupRequest("/audio-api/v1/audio", Map("query" -> "norge", "fallback" -> "true")) warmupRequest("/audio-api/v1/audio/1", Map("language" -> "nb")) warmupRequest("/audio-api/v1/series", Map("language" -> "nb")) @@ -29,10 +53,51 @@ class MainClass(override val props: AudioApiProperties) extends NdlaTapirMain { componentRegistry.healthController.setWarmedUp() } - override def beforeStart(): Unit = { + def beforeStart(): Unit = { logger.info("Starting DB Migration") val dBstartMillis = System.currentTimeMillis() componentRegistry.migrator.migrate(): Unit logger.info(s"Done DB Migration took ${System.currentTimeMillis() - dBstartMillis} ms") } + + private def hasMethodMismatch(f: RequestResult.Failure): Boolean = f.failures.map(_.failingInput).exists { + case _: EndpointInput.FixedMethod[_] => true + case _ => false + } + + case class NdlaRejectHandler[F[_]]() extends RejectHandler[F] { + override def apply( + failure: RequestResult.Failure + )(implicit monad: MonadError[F]): F[Option[ValuedEndpointOutput[_]]] = { + val statusCodeAndBody = if (hasMethodMismatch(failure)) { + ValuedEndpointOutput(jsonBody[ErrorBody], ErrorBody("methodmismatch", "TODO", NDLADate.now(), 405)) + .prepend(statusCode, StatusCode.MethodNotAllowed) + } else { + ValuedEndpointOutput(jsonBody[ErrorBody], ErrorBody("notfound", "TODO", NDLADate.now(), 404)) + .prepend(statusCode, StatusCode.NotFound) + } + monad.unit(Some(statusCodeAndBody)) + } + + } + + val tapirEndpoints: List[ServerEndpoint[Any, Id]] = componentRegistry.allServices.flatMap(_.builtEndpoints) + val handler = new NdlaRejectHandler[Id]() + val inter = new RejectInterceptor[Id](handler) + + val opts = JdkHttpServerOptions.Default + .appendInterceptor(inter) + + beforeStart() + + private val executor = Executors.newVirtualThreadPerTaskExecutor() + + JdkHttpServer() + .options(opts) + .executor(executor) + .addEndpoints(tapirEndpoints) + .port(props.ApplicationPort) + .start() + + warmup() } diff --git a/audio-api/src/main/scala/no/ndla/audioapi/controller/AudioController.scala b/audio-api/src/main/scala/no/ndla/audioapi/controller/AudioController.scala index 7fe9627ba1..8ef5f0dd43 100644 --- a/audio-api/src/main/scala/no/ndla/audioapi/controller/AudioController.scala +++ b/audio-api/src/main/scala/no/ndla/audioapi/controller/AudioController.scala @@ -31,6 +31,7 @@ import sttp.tapir.generic.auto._ import sttp.tapir.model.CommaSeparated import sttp.tapir.server.ServerEndpoint import sttp.tapir._ +import sttp.tapir.server.jdkhttp.Id import java.io.File import java.nio.file.Files @@ -267,16 +268,16 @@ trait AudioController { readService.getAllTags(query.underlyingOrElse(""), pageSize, pageNo, language).handleErrorsOrOk } - override val endpoints: List[ServerEndpoint[Any, IO]] = List( - getSearch, - postSearch, - getIds, - getSingle, - deleteAudio, - deleteLanguage, - postNewAudio, - putUpdateAudio, - tagSearch + override val endpoints: List[ServerEndpoint[Any, Id]] = List( +// getSearch, +// postSearch, +// getIds, +// getSingle, +// deleteAudio, +// deleteLanguage, +// postNewAudio, +// putUpdateAudio, +// tagSearch ) def getBytesAndDeleteFile(file: Part[File]): Part[Array[Byte]] = { diff --git a/audio-api/src/main/scala/no/ndla/audioapi/controller/HealthController.scala b/audio-api/src/main/scala/no/ndla/audioapi/controller/HealthController.scala index 04f6a733c3..5ec340b157 100644 --- a/audio-api/src/main/scala/no/ndla/audioapi/controller/HealthController.scala +++ b/audio-api/src/main/scala/no/ndla/audioapi/controller/HealthController.scala @@ -31,12 +31,12 @@ trait HealthController { private def getReturnCode(imageResponse: Response[String]) = { imageResponse.code.code match { - case 200 => Ok() - case _ => InternalServerError() + case 200 => Right("Healthy") + case _ => Left("Internal server error") } } - override def checkHealth(): IO[http4s.Response[IO]] = { + override def checkHealth(): Either[String, String] = { audioRepository .getRandomAudio() .map(audio => { @@ -44,7 +44,7 @@ trait HealthController { val previewUrl = s"http://$localhost:$localport${props.AudioControllerPath}$id" getReturnCode(getApiResponse(previewUrl)) }) - .getOrElse(Ok()) + .getOrElse(Right("Healthy")) } } diff --git a/audio-api/src/main/scala/no/ndla/audioapi/controller/InternController.scala b/audio-api/src/main/scala/no/ndla/audioapi/controller/InternController.scala index 9010fc412d..fa162e9a3f 100644 --- a/audio-api/src/main/scala/no/ndla/audioapi/controller/InternController.scala +++ b/audio-api/src/main/scala/no/ndla/audioapi/controller/InternController.scala @@ -9,6 +9,7 @@ package no.ndla.audioapi.controller import cats.effect.IO +import cats.effect.unsafe.implicits.global import cats.implicits._ import io.circe.generic.auto._ import no.ndla.audioapi.Props @@ -25,6 +26,7 @@ import sttp.model.StatusCode import sttp.tapir._ import sttp.tapir.generic.auto._ import sttp.tapir.server.ServerEndpoint +import sttp.tapir.server.jdkhttp.Id import scala.util.{Failure, Success} @@ -46,7 +48,7 @@ trait InternController { override val enableSwagger = false private val internalErrorStringBody = statusCode(StatusCode.InternalServerError).and(stringBody) - override val endpoints: List[ServerEndpoint[Any, IO]] = List( + override val endpoints: List[ServerEndpoint[Any, Id]] = List( endpoint.get .in("external") .in(path[String]("external_id")) @@ -54,91 +56,95 @@ trait InternController { .out(jsonBody[Option[api.AudioMetaInformation]]) .serverLogicPure { case (externalId, language) => readService.withExternalId(externalId, language).asRight - }, - endpoint.post - .in("index") - .in(query[Option[Int]]("numShards")) - .out(stringBody) - .errorOut(internalErrorStringBody) - .serverLogic { numShards => - val result = IO( - ( - audioIndexService.indexDocuments(numShards), - tagIndexService.indexDocuments(numShards), - seriesIndexService.indexDocuments(numShards) - ) - ) - - result.flatMap { - case (Success(audioReindexResult), Success(tagReindexResult), Success(seriesReIndexResult)) => - val result = - s"""Completed indexing of ${audioReindexResult.totalIndexed} documents in ${audioReindexResult.millisUsed} (audios) ms. - |Completed indexing of ${tagReindexResult.totalIndexed} documents in ${tagReindexResult.millisUsed} (tags) ms. - |Completed indexing of ${seriesReIndexResult.totalIndexed} documents in ${seriesReIndexResult.millisUsed} (series) ms.""".stripMargin - logger.info(result) >> - IO.pure(result.asRight) - case (Failure(f), _, _) => - logger.warn(f.getMessage, f) >> - IO.pure(f.getMessage.asLeft) - case (_, Failure(f), _) => - logger.warn(f.getMessage, f) >> - IO.pure(f.getMessage.asLeft) - case (_, _, Failure(f)) => - logger.warn(f.getMessage, f) >> - IO.pure(f.getMessage.asLeft) - } - }, - endpoint.delete - .in("index") - .errorOut(internalErrorStringBody) - .out(stringBody) - .serverLogic { _ => - def pluralIndex(n: Int) = if (n == 1) "1 index" else s"$n indexes" - audioIndexService.findAllIndexes(props.SearchIndex) match { - case Failure(f) => IO.pure(f.getMessage.asLeft) - case Success(indexes) => - val deletes = indexes.traverse(index => { - logger.info(s"Deleting index $index") >> - IO.pure(audioIndexService.deleteIndexWithName(Option(index))) - }) - deletes.map { deleteResults => - val (errors, successes) = deleteResults.partition(_.isFailure) - if (errors.nonEmpty) { - val message = s"Failed to delete ${pluralIndex(errors.length)}: " + - s"${errors.map(_.failed.get.getMessage).mkString(", ")}. " + - s"${pluralIndex(successes.length)} were deleted successfully." - message.asLeft - } else { - s"Deleted ${pluralIndex(successes.length)}".asRight - } - } - } - }, - endpoint.get - .in("dump" / "audio") - .in(query[Int]("page").default(1)) - .in(query[Int]("page-size").default(250)) - .out(jsonBody[AudioMetaDomainDump]) - .errorOut(errorOutputsFor(400, 500)) - .serverLogicPure { case (pageNo, pageSize) => - readService.getMetaAudioDomainDump(pageNo, pageSize).asRight - }, - endpoint.get - .in("dump" / "audio") - .in(path[Long]("id")) - .errorOut(errorOutputsFor(400, 404)) - .out(jsonBody[AudioMetaInformation]) - .serverLogic { id => - audioRepository.withId(id) match { - case Some(image) => IO(image.asRight) - case None => returnLeftError(new NotFoundException(s"Could not find audio with id: '$id'")) - } - }, - endpoint.post - .in("dump" / "audio") - .in(jsonBody[AudioMetaInformation]) - .out(jsonBody[AudioMetaInformation]) - .serverLogicPure { domainMeta => audioRepository.insert(domainMeta).asRight } + } +// endpoint.post +// .in("index") +// .in(query[Option[Int]]("numShards")) +// .out(stringBody) +// .errorOut(internalErrorStringBody) +// .serverLogic { numShards => +// val result = IO( +// ( +// audioIndexService.indexDocuments(numShards), +// tagIndexService.indexDocuments(numShards), +// seriesIndexService.indexDocuments(numShards) +// ) +// ) +// +// result +// .flatMap { +// case (Success(audioReindexResult), Success(tagReindexResult), Success(seriesReIndexResult)) => +// val result = +// s"""Completed indexing of ${audioReindexResult.totalIndexed} documents in ${audioReindexResult.millisUsed} (audios) ms. +// |Completed indexing of ${tagReindexResult.totalIndexed} documents in ${tagReindexResult.millisUsed} (tags) ms. +// |Completed indexing of ${seriesReIndexResult.totalIndexed} documents in ${seriesReIndexResult.millisUsed} (series) ms.""".stripMargin +// logger.info(result) >> +// IO.pure(result.asRight) +// case (Failure(f), _, _) => +// logger.warn(f.getMessage, f) >> +// IO.pure(f.getMessage.asLeft) +// case (_, Failure(f), _) => +// logger.warn(f.getMessage, f) >> +// IO.pure(f.getMessage.asLeft) +// case (_, _, Failure(f)) => +// logger.warn(f.getMessage, f) >> +// IO.pure(f.getMessage.asLeft) +// } +// .unsafeRunSync() +// }, +// endpoint.delete +// .in("index") +// .errorOut(internalErrorStringBody) +// .out(stringBody) +// .serverLogic { _ => +// def pluralIndex(n: Int) = if (n == 1) "1 index" else s"$n indexes" +// val x = audioIndexService.findAllIndexes(props.SearchIndex) match { +// case Failure(f) => IO.pure(f.getMessage.asLeft) +// case Success(indexes) => +// val deletes = indexes.traverse(index => { +// logger.info(s"Deleting index $index") >> +// IO.pure(audioIndexService.deleteIndexWithName(Option(index))) +// }) +// deletes.map { deleteResults => +// val (errors, successes) = deleteResults.partition(_.isFailure) +// if (errors.nonEmpty) { +// val message = s"Failed to delete ${pluralIndex(errors.length)}: " + +// s"${errors.map(_.failed.get.getMessage).mkString(", ")}. " + +// s"${pluralIndex(successes.length)} were deleted successfully." +// message.asLeft +// } else { +// s"Deleted ${pluralIndex(successes.length)}".asRight +// } +// } +// } +// // TODO: Ikke io eru grei +// x.unsafeRunSync() +// }, +// endpoint.get +// .in("dump" / "audio") +// .in(query[Int]("page").default(1)) +// .in(query[Int]("page-size").default(250)) +// .out(jsonBody[AudioMetaDomainDump]) +// .errorOut(errorOutputsFor(400, 500)) +// .serverLogicPure { case (pageNo, pageSize) => +// readService.getMetaAudioDomainDump(pageNo, pageSize).asRight +// }, +// endpoint.get +// .in("dump" / "audio") +// .in(path[Long]("id")) +// .errorOut(errorOutputsFor(400, 404)) +// .out(jsonBody[AudioMetaInformation]) +// .serverLogic { id => +// audioRepository.withId(id) match { +// case Some(image) => image.asRight +// case None => returnLeftPureError(new NotFoundException(s"Could not find audio with id: '$id'")) +// } +// }, +// endpoint.post +// .in("dump" / "audio") +// .in(jsonBody[AudioMetaInformation]) +// .out(jsonBody[AudioMetaInformation]) +// .serverLogicPure { domainMeta => audioRepository.insert(domainMeta).asRight } ) } } diff --git a/audio-api/src/main/scala/no/ndla/audioapi/controller/SeriesController.scala b/audio-api/src/main/scala/no/ndla/audioapi/controller/SeriesController.scala index f02c51a8d7..0e8d75c83f 100644 --- a/audio-api/src/main/scala/no/ndla/audioapi/controller/SeriesController.scala +++ b/audio-api/src/main/scala/no/ndla/audioapi/controller/SeriesController.scala @@ -29,6 +29,7 @@ import sttp.tapir.EndpointIO.annotations.{header, jsonbody} import sttp.tapir.generic.auto._ import sttp.tapir.server.ServerEndpoint import sttp.tapir._ +import sttp.tapir.server.jdkhttp.Id import scala.util.{Failure, Success, Try} @@ -261,14 +262,14 @@ trait SeriesController { case _ => orFunction } - override protected val endpoints: List[ServerEndpoint[Any, IO]] = List( - getSeriesSearch, - postSeriesSearch, - getSingleSeries, - deleteSeries, - deleteLanguage, - postNewSeries, - putUpdateSeries + override protected val endpoints: List[ServerEndpoint[Any, Id]] = List( +// getSeriesSearch, +// postSeriesSearch, +// getSingleSeries, +// deleteSeries, +// deleteLanguage, +// postNewSeries, +// putUpdateSeries ) } diff --git a/network/src/main/scala/no/ndla/network/tapir/Routes.scala b/network/src/main/scala/no/ndla/network/tapir/Routes.scala index 16b013198a..f1a3260619 100644 --- a/network/src/main/scala/no/ndla/network/tapir/Routes.scala +++ b/network/src/main/scala/no/ndla/network/tapir/Routes.scala @@ -39,18 +39,22 @@ trait Routes { implicit val dateTimeEncoder: Encoder[LocalDateTime] = DateParser.Circe.localDateTimeEncoder implicit val dateTimeDecoder: Decoder[LocalDateTime] = DateParser.Circe.localDateTimeDecoder - val logger: Logger = getLogger - private def buildBindings(routes: List[Service]): List[(String, HttpRoutes[IO])] = { - val (docServices, noDocServices) = routes.partitionMap { - case swaggerService: SwaggerService => Left(swaggerService) - case serviceWithoutDoc: NoDocService => Right(serviceWithoutDoc) - } + class JDKServer() { - // Full paths are already prefixed in the endpoints to make nice documentation - val swaggerBinding = "/" -> swaggerServicesToRoutes(docServices) - noDocServices.map(_.getBinding) :+ swaggerBinding } + val logger: Logger = getLogger +// private def buildBindings(routes: List[Service]): List[(String, HttpRoutes[IO])] = { +// val (docServices, noDocServices) = routes.partitionMap { +// case swaggerService: SwaggerService => Left(swaggerService) +// case serviceWithoutDoc: NoDocService => Right(serviceWithoutDoc) +// } +// +// // Full paths are already prefixed in the endpoints to make nice documentation +// val swaggerBinding = "/" -> swaggerServicesToRoutes(docServices) +// noDocServices.map(_.getBinding) :+ swaggerBinding +// } + private def failureResponse(error: String, exception: Option[Throwable]): ValuedEndpointOutput[_] = { val logMsg = s"Failure handler got: $error" exception match { @@ -70,16 +74,16 @@ trait Routes { ) }) - private case class NdlaExceptionHandler() extends ExceptionHandler[IO] { - override def apply(ctx: ExceptionContext)(implicit monad: MonadError[IO]): IO[Option[ValuedEndpointOutput[_]]] = - for { - errorToReturn <- returnError(ctx.e) - sc = StatusCode(errorToReturn.statusCode) - resp = ValuedEndpointOutput(jsonBody[AllErrors], errorToReturn) - withsc = resp.prepend(statusCode, sc) - result <- monad.unit(Some(withsc)) - } yield result - } +// private case class NdlaExceptionHandler[F[_]]() extends ExceptionHandler[F] { +// override def apply(ctx: ExceptionContext)(implicit monad: MonadError[F]): F[Option[ValuedEndpointOutput[_]]] = +// for { +// errorToReturn <- returnError(ctx.e) +// sc = StatusCode(errorToReturn.statusCode) +// resp = ValuedEndpointOutput(jsonBody[AllErrors], errorToReturn) +// withsc = resp.prepend(statusCode, sc) +// result <- monad.unit(Some(withsc)) +// } yield result +// } private def hasMethodMismatch(f: RequestResult.Failure): Boolean = f.failures.map(_.failingInput).exists { case _: EndpointInput.FixedMethod[_] => true @@ -102,33 +106,33 @@ trait Routes { } - private def swaggerServicesToRoutes(services: List[SwaggerService]): HttpRoutes[IO] = { - val swaggerEndpoints = services.flatMap(_.builtEndpoints) - val options = Http4sServerOptions - .customiseInterceptors[IO] - .defaultHandlers(err => failureResponse(err, None)) - .rejectHandler(NdlaRejectHandler[IO]()) - .exceptionHandler(NdlaExceptionHandler()) - .decodeFailureHandler(decodeFailureHandler) - .options - Http4sServerInterpreter[IO](options).toRoutes(swaggerEndpoints) - } +// private def swaggerServicesToRoutes(services: List[SwaggerService]): HttpRoutes[IO] = { +// val swaggerEndpoints = services.flatMap(_.builtEndpoints) +// val options = Http4sServerOptions +// .customiseInterceptors[IO] +// .defaultHandlers(err => failureResponse(err, None)) +// .rejectHandler(NdlaRejectHandler[IO]()) +// .exceptionHandler(NdlaExceptionHandler()) +// .decodeFailureHandler(decodeFailureHandler) +// .options +// Http4sServerInterpreter[IO](options).toRoutes(swaggerEndpoints) +// } private def getFallbackRoute: Response[IO] = { val headers = Headers(`Content-Type`(MediaType.application.json)) Response.notFound[IO].withEntity(ErrorHelpers.notFound).withHeaders(headers) } - def build(routes: List[Service]): Kleisli[IO, Request[IO], Response[IO]] = { - logger.info("Building swagger service") - val bindings = buildBindings(routes) - val router = Router[IO](bindings: _*) - Kleisli[IO, Request[IO], Response[IO]](req => { - val ran = router.run(req) - val res = ran.getOrElse { getFallbackRoute } - NdlaMiddleware(req, res) - }) - } +// def build(routes: List[Service]): Kleisli[IO, Request[IO], Response[IO]] = { +// logger.info("Building swagger service") +// val bindings = buildBindings(routes) +// val router = Router[IO](bindings: _*) +// Kleisli[IO, Request[IO], Response[IO]](req => { +// val ran = router.run(req) +// val res = ran.getOrElse { getFallbackRoute } +// NdlaMiddleware(req, res) +// }) +// } } } diff --git a/network/src/main/scala/no/ndla/network/tapir/Service.scala b/network/src/main/scala/no/ndla/network/tapir/Service.scala index 5d04aaed7a..8101441792 100644 --- a/network/src/main/scala/no/ndla/network/tapir/Service.scala +++ b/network/src/main/scala/no/ndla/network/tapir/Service.scala @@ -14,6 +14,7 @@ import org.http4s.HttpRoutes import sttp.model.StatusCode import sttp.tapir._ import sttp.tapir.server.ServerEndpoint +import sttp.tapir.server.jdkhttp.Id import java.time.LocalDateTime @@ -44,9 +45,9 @@ trait Service { val enableSwagger: Boolean = true val serviceName: String = this.getClass.getSimpleName protected val prefix: EndpointInput[Unit] - protected val endpoints: List[ServerEndpoint[Any, IO]] + protected val endpoints: List[ServerEndpoint[Any, Id]] - lazy val builtEndpoints: List[ServerEndpoint[Any, IO]] = { + lazy val builtEndpoints: List[ServerEndpoint[Any, Id]] = { this.endpoints.map(e => { ServerEndpoint( endpoint = e.endpoint.prependIn(this.prefix).tag(this.serviceName), diff --git a/network/src/main/scala/no/ndla/network/tapir/TapirErrorHelpers.scala b/network/src/main/scala/no/ndla/network/tapir/TapirErrorHelpers.scala index 8d59e444a7..559bebb148 100644 --- a/network/src/main/scala/no/ndla/network/tapir/TapirErrorHelpers.scala +++ b/network/src/main/scala/no/ndla/network/tapir/TapirErrorHelpers.scala @@ -113,6 +113,12 @@ trait TapirErrorHelpers extends FLogging { def returnLeftError[R](ex: Throwable): IO[Either[AllErrors, R]] = returnError(ex).map(_.asLeft[R]) + def returnLeftPureError[R](ex: Throwable): Either[AllErrors, R] = { + // TODO: Obviously we rewrite this + import cats.effect.unsafe.implicits.global + returnError(ex).unsafeRunSync().asLeft[R] + } + implicit class handleErrorOrOkClass[T](t: Try[T]) { import cats.implicits._ diff --git a/network/src/main/scala/no/ndla/network/tapir/TapirHealthController.scala b/network/src/main/scala/no/ndla/network/tapir/TapirHealthController.scala index 6499e7dfab..57ea320704 100644 --- a/network/src/main/scala/no/ndla/network/tapir/TapirHealthController.scala +++ b/network/src/main/scala/no/ndla/network/tapir/TapirHealthController.scala @@ -7,20 +7,28 @@ package no.ndla.network.tapir -import cats.effect.IO import no.ndla.common.Warmup -import org.http4s.dsl.io._ -import org.http4s.{HttpRoutes, Response} +import sttp.tapir.EndpointInput +import sttp.tapir.server.ServerEndpoint +import sttp.tapir.server.jdkhttp.Id +import sttp.tapir._ trait TapirHealthController { this: Service => - class TapirHealthController extends Warmup with NoDocService { - protected def checkHealth(): IO[Response[IO]] = Ok("Health check succeeded") + class TapirHealthController extends Warmup with SwaggerService { + override val enableSwagger: Boolean = false + val prefix: EndpointInput[Unit] = "health" - override def getBinding: (String, HttpRoutes[IO]) = "/health" -> HttpRoutes.of[IO] { case GET -> Root => - if (!isWarmedUp) InternalServerError("Warmup hasn't finished") - else checkHealth() - } + protected def checkHealth(): Either[String, String] = Right("Health check succeeded") + + override protected val endpoints: List[ServerEndpoint[Any, Id]] = List( + endpoint.get + .out(stringBody) + .errorOut(stringBody) + .serverLogicPure { _ => + checkHealth() + } + ) } } diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 719af3ff2c..be5486a313 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -27,7 +27,7 @@ object Dependencies { val PostgresV = "42.5.1" val ScalaTsiV = "0.6.0" val Http4sV = "0.23.23" - val TapirV = "1.7.0" + val TapirV = "1.7.3" val ApiSpecV = "0.6.0" val SttpV = "3.9.0" val CirceV = "0.14.2" @@ -82,6 +82,7 @@ object Dependencies { "com.softwaremill.sttp.tapir" %% "tapir-http4s-server" % TapirV, "com.softwaremill.sttp.tapir" %% "tapir-swagger-ui-bundle" % TapirV, "com.softwaremill.sttp.tapir" %% "tapir-json-circe" % TapirV, + "com.softwaremill.sttp.tapir" %% "tapir-jdkhttp-server" % TapirV, "com.softwaremill.sttp.apispec" %% "openapi-circe-yaml" % ApiSpecV ) diff --git a/project/Module.scala b/project/Module.scala index f7cc28aa74..251a67e015 100644 --- a/project/Module.scala +++ b/project/Module.scala @@ -49,7 +49,7 @@ trait Module { organization := "ndla", version := "0.0.1", scalaVersion := ScalaV, - javacOptions ++= Seq("-source", "17", "-target", "17"), + javacOptions ++= Seq("-source", "20", "-target", "20"), javaOptions ++= reflectiveAccessOptions, tpolecatScalacOptions ++= scalacOptions, tpolecatExcludeOptions ++= excludeOptions, @@ -140,7 +140,7 @@ trait Module { Seq("-jar", artifactTargetPath) new Dockerfile { - from("eclipse-temurin:17-jdk") + from("eclipse-temurin:20-jdk") add(artifact, artifactTargetPath) entryPoint(entry: _*) }