-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from nationalarchives/setup-http4s-server
Setup http4s server
- Loading branch information
Showing
20 changed files
with
449 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,43 @@ | ||
import Dependencies.* | ||
|
||
ThisBuild / version := "0.0.1-SNAPSHOT" | ||
ThisBuild / scalaVersion := "2.13.14" | ||
ThisBuild / organization := "uk.gov.nationalarchives" | ||
|
||
lazy val root = (project in file(".")) | ||
.settings( | ||
name := "tdr-transfer-service" | ||
name := "tdr-transfer-service", | ||
libraryDependencies ++= Seq( | ||
authUtils, | ||
catsEffect, | ||
http4sCirce, | ||
http4sDsl, | ||
http4sEmberServer, | ||
keycloakMock % Test, | ||
logbackClassic, | ||
logBackEncoder, | ||
mockito % Test, | ||
pekkoTestKitHttp % Test, | ||
pureConfig, | ||
pureConfigCatsEffect, | ||
scalaTest % Test, | ||
tapirHttp4sServer, | ||
tapirJsonCirce, | ||
tapirSwaggerUI | ||
) | ||
) | ||
|
||
(Compile / run / mainClass) := Some("Main") | ||
(Compile / run / mainClass) := Some("uk.gov.nationalarchives.tdr.transfer.service.api.TransferServiceServer") | ||
|
||
(assembly / assemblyJarName) := "transferservice.jar" | ||
|
||
(assembly / assemblyMergeStrategy) := { | ||
case PathList("META-INF", x, xs @ _*) if x.toLowerCase == "services" => MergeStrategy.filterDistinctLines | ||
case PathList("META-INF", xs @ _*) => MergeStrategy.discard | ||
case PathList("reference.conf") => MergeStrategy.concat | ||
case _ => MergeStrategy.first | ||
case PathList("META-INF", "maven", "org.webjars", "swagger-ui", "pom.properties") => MergeStrategy.singleOrError | ||
case PathList("META-INF", "resources", "webjars", "swagger-ui", _*) => MergeStrategy.singleOrError | ||
case PathList("META-INF", x, xs @ _*) if x.toLowerCase == "services" => MergeStrategy.filterDistinctLines | ||
case PathList("META-INF", xs @ _*) => MergeStrategy.discard | ||
case PathList("reference.conf") => MergeStrategy.concat | ||
case _ => MergeStrategy.first | ||
} | ||
|
||
(assembly / mainClass) := Some("Main") | ||
(assembly / mainClass) := Some("uk.gov.nationalarchives.tdr.transfer.service.api.TransferServiceServer") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import sbt.* | ||
|
||
object Dependencies { | ||
private val http4sVersion = "0.23.26" | ||
private val pureConfigVersion = "0.17.6" | ||
private val tapirVersion = "1.10.7" | ||
|
||
lazy val authUtils = "uk.gov.nationalarchives" %% "tdr-auth-utils" % "0.0.196" | ||
|
||
lazy val catsEffect = "org.typelevel" %% "cats-effect" % "3.5.4" | ||
|
||
lazy val http4sCirce = "org.http4s" %% "http4s-circe" % http4sVersion | ||
lazy val http4sEmberServer = "org.http4s" %% "http4s-ember-server" % http4sVersion | ||
lazy val http4sDsl = "org.http4s" %% "http4s-dsl" % http4sVersion | ||
|
||
lazy val keycloakMock = "com.tngtech.keycloakmock" % "mock" % "0.16.0" | ||
|
||
lazy val logBackEncoder = "net.logstash.logback" % "logstash-logback-encoder" % "7.4" | ||
lazy val logbackClassic = "ch.qos.logback" % "logback-classic" % "1.5.6" | ||
|
||
lazy val mockito = "org.mockito" %% "mockito-scala" % "1.17.31" | ||
lazy val mockitoScalaTest = "org.mockito" %% "mockito-scala-scalatest" % "1.17.31" | ||
|
||
lazy val pekkoTestKitHttp = "org.apache.pekko" %% "pekko-http-testkit" % "1.0.1" | ||
lazy val pureConfig = "com.github.pureconfig" %% "pureconfig" % pureConfigVersion | ||
lazy val pureConfigCatsEffect = "com.github.pureconfig" %% "pureconfig-cats-effect" % pureConfigVersion | ||
|
||
lazy val tapirHttp4sServer = "com.softwaremill.sttp.tapir" %% "tapir-http4s-server" % tapirVersion | ||
lazy val tapirJsonCirce = "com.softwaremill.sttp.tapir" %% "tapir-json-circe" % tapirVersion | ||
lazy val tapirSwaggerUI = "com.softwaremill.sttp.tapir" %% "tapir-swagger-ui-bundle" % tapirVersion | ||
|
||
lazy val scalaTest = "org.scalatest" %% "scalatest" % "3.2.18" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
api { | ||
port = "8080" | ||
port = ${?API_PORT} | ||
} | ||
|
||
auth { | ||
url = "https://auth.tdr-integration.nationalarchives.gov.uk" | ||
url = ${?AUTH_URL} | ||
realm = "tdr" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<configuration> | ||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> | ||
<!-- encoders are assigned the type | ||
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default --> | ||
<encoder class="net.logstash.logback.encoder.LogstashEncoder"> | ||
<customFields>{"application":"transfer-service"}</customFields> | ||
</encoder> | ||
</appender> | ||
|
||
<root level="info"> | ||
<appender-ref ref="STDOUT" /> | ||
</root> | ||
</configuration> |
This file was deleted.
Oops, something went wrong.
16 changes: 16 additions & 0 deletions
16
src/main/scala/uk/gov/nationalarchives/tdr/transfer/service/ApplicationConfig.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package uk.gov.nationalarchives.tdr.transfer.service | ||
|
||
import pureconfig.ConfigSource | ||
import pureconfig.generic.auto._ | ||
|
||
object ApplicationConfig { | ||
case class Api(port: Int) | ||
case class Auth(url: String, realm: String) | ||
|
||
case class Configuration(auth: Auth, api: Api) | ||
|
||
val appConfig: Configuration = ConfigSource.default.load[Configuration] match { | ||
case Left(value) => throw new RuntimeException(s"Failed to load transfer service application configuration ${value.prettyPrint()}") | ||
case Right(value) => value | ||
} | ||
} |
51 changes: 51 additions & 0 deletions
51
src/main/scala/uk/gov/nationalarchives/tdr/transfer/service/api/TransferServiceServer.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package uk.gov.nationalarchives.tdr.transfer.service.api | ||
|
||
import cats.data.Kleisli | ||
import cats.effect.{ExitCode, IO, IOApp} | ||
import cats.implicits.toSemigroupKOps | ||
import com.comcast.ip4s.{IpLiteralSyntax, Port} | ||
import org.http4s.dsl.io._ | ||
import org.http4s.ember.server.EmberServerBuilder | ||
import org.http4s.server.middleware.Logger | ||
import org.http4s.{HttpRoutes, Request, Response} | ||
import sttp.apispec.openapi.Info | ||
import sttp.tapir.server.http4s.Http4sServerInterpreter | ||
import sttp.tapir.swagger.bundle.SwaggerInterpreter | ||
import uk.gov.nationalarchives.tdr.transfer.service.ApplicationConfig | ||
import uk.gov.nationalarchives.tdr.transfer.service.api.controllers.LoadController | ||
|
||
object TransferServiceServer extends IOApp { | ||
private val apiPort: Port = Port.fromInt(ApplicationConfig.appConfig.api.port).getOrElse(port"8080") | ||
|
||
private val infoTitle = "TDR Transfer Service API" | ||
private val infoVersion = "0.0.1" | ||
private val infoDescription = Some("APIs to allow client services to transfer records to TDR") | ||
|
||
private val openApiInfo: Info = Info(infoTitle, infoVersion, description = infoDescription) | ||
private val loadController = LoadController.apply() | ||
|
||
private val documentationEndpoints = | ||
SwaggerInterpreter().fromEndpoints[IO](loadController.endpoints, openApiInfo) | ||
|
||
val healthCheckRoute: HttpRoutes[IO] = HttpRoutes.of[IO] { case GET -> Root / "healthcheck" => | ||
Ok("Healthy") | ||
} | ||
|
||
private val allRoutes = | ||
Http4sServerInterpreter[IO]().toRoutes(documentationEndpoints) <+> loadController.routes <+> healthCheckRoute | ||
|
||
private val app: Kleisli[IO, Request[IO], Response[IO]] = allRoutes.orNotFound | ||
|
||
private val finalApp = Logger.httpApp(logHeaders = true, logBody = false)(app) | ||
|
||
private val transferServiceServer = EmberServerBuilder | ||
.default[IO] | ||
.withHost(ipv4"0.0.0.0") | ||
.withPort(apiPort) | ||
.withHttpApp(finalApp) | ||
.build | ||
|
||
override def run(args: List[String]): IO[ExitCode] = { | ||
transferServiceServer.use(_ => IO.never).as(ExitCode.Success) | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
...main/scala/uk/gov/nationalarchives/tdr/transfer/service/api/auth/TokenAuthenticator.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package uk.gov.nationalarchives.tdr.transfer.service.api.auth | ||
|
||
import cats.effect.IO | ||
import uk.gov.nationalarchives.tdr.keycloak.{KeycloakUtils, TdrKeycloakDeployment, Token} | ||
import uk.gov.nationalarchives.tdr.transfer.service.ApplicationConfig | ||
import uk.gov.nationalarchives.tdr.transfer.service.api.errors.BackendException.AuthenticationError | ||
|
||
import scala.concurrent.ExecutionContext | ||
|
||
case class AuthenticatedContext(token: Token) | ||
|
||
class TokenAuthenticator { | ||
implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global | ||
private val appConfig = ApplicationConfig.appConfig | ||
|
||
private val authUrl = appConfig.auth.url | ||
private val realm = appConfig.auth.realm | ||
|
||
implicit val tdrKeycloakDeployment: TdrKeycloakDeployment = | ||
TdrKeycloakDeployment(s"$authUrl", realm, 8080) | ||
|
||
def authenticateUserToken(bearer: String): IO[Either[AuthenticationError, AuthenticatedContext]] = { | ||
IO { | ||
KeycloakUtils().token(bearer) match { | ||
case Right(t) => Right(AuthenticatedContext(t)) | ||
case Left(e) => | ||
Left { | ||
println(e.getMessage) | ||
AuthenticationError(e.getMessage) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
object TokenAuthenticator { | ||
def apply() = new TokenAuthenticator | ||
} |
25 changes: 25 additions & 0 deletions
25
...n/scala/uk/gov/nationalarchives/tdr/transfer/service/api/controllers/BaseController.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package uk.gov.nationalarchives.tdr.transfer.service.api.controllers | ||
|
||
import cats.effect.IO | ||
import sttp.model.StatusCode | ||
import sttp.tapir.json.circe.jsonBody | ||
import sttp.tapir.server.PartialServerEndpoint | ||
import sttp.tapir.{Endpoint, auth, endpoint, statusCode} | ||
import uk.gov.nationalarchives.tdr.transfer.service.api.auth.{AuthenticatedContext, TokenAuthenticator} | ||
import uk.gov.nationalarchives.tdr.transfer.service.api.errors.BackendException.AuthenticationError | ||
import uk.gov.nationalarchives.tdr.transfer.service.api.model.Serializers._ | ||
|
||
trait BaseController { | ||
|
||
private val tokenAuthenticator = TokenAuthenticator() | ||
|
||
private val securedWithBearerEndpoint: Endpoint[String, Unit, AuthenticationError, Unit, Any] = endpoint | ||
.securityIn(auth.bearer[String]()) | ||
.errorOut(statusCode(StatusCode.Unauthorized)) | ||
.errorOut(jsonBody[AuthenticationError]) | ||
|
||
val securedWithBearer: PartialServerEndpoint[String, AuthenticatedContext, Unit, AuthenticationError, Unit, Any, IO] = securedWithBearerEndpoint | ||
.serverSecurityLogic( | ||
tokenAuthenticator.authenticateUserToken | ||
) | ||
} |
32 changes: 32 additions & 0 deletions
32
...n/scala/uk/gov/nationalarchives/tdr/transfer/service/api/controllers/LoadController.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package uk.gov.nationalarchives.tdr.transfer.service.api.controllers | ||
|
||
import cats.effect.IO | ||
import org.http4s.HttpRoutes | ||
import sttp.tapir.{Endpoint, _} | ||
import sttp.tapir.json.circe.jsonBody | ||
import sttp.tapir.server.PartialServerEndpoint | ||
import sttp.tapir.server.http4s.Http4sServerInterpreter | ||
import uk.gov.nationalarchives.tdr.transfer.service.api.auth.AuthenticatedContext | ||
import uk.gov.nationalarchives.tdr.transfer.service.api.errors.BackendException | ||
import uk.gov.nationalarchives.tdr.transfer.service.api.model.Consignment.ConsignmentDetails | ||
import uk.gov.nationalarchives.tdr.transfer.service.api.model.Serializers._ | ||
import uk.gov.nationalarchives.tdr.transfer.service.services.ConsignmentService | ||
|
||
class LoadController(consignmentService: ConsignmentService) extends BaseController { | ||
def endpoints: List[Endpoint[String, Unit, BackendException.AuthenticationError, ConsignmentDetails, Any]] = List(initiateLoadEndpoint.endpoint) | ||
|
||
def routes: HttpRoutes[IO] = initiateLoadRoute | ||
|
||
private val initiateLoadEndpoint: PartialServerEndpoint[String, AuthenticatedContext, Unit, BackendException.AuthenticationError, ConsignmentDetails, Any, IO] = securedWithBearer | ||
.summary("Initiate the load of records and metadata") | ||
.post | ||
.in("load" / "sharepoint" / "initiate") | ||
.out(jsonBody[ConsignmentDetails]) | ||
|
||
val initiateLoadRoute: HttpRoutes[IO] = | ||
Http4sServerInterpreter[IO]().toRoutes(initiateLoadEndpoint.serverLogicSuccess(ac => _ => consignmentService.createConsignment(ac.token))) | ||
} | ||
|
||
object LoadController { | ||
def apply() = new LoadController(ConsignmentService()) | ||
} |
7 changes: 7 additions & 0 deletions
7
...main/scala/uk/gov/nationalarchives/tdr/transfer/service/api/errors/BackendException.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package uk.gov.nationalarchives.tdr.transfer.service.api.errors | ||
|
||
sealed trait BackendException extends Exception | ||
|
||
object BackendException { | ||
case class AuthenticationError(message: String) extends BackendException | ||
} |
9 changes: 9 additions & 0 deletions
9
src/main/scala/uk/gov/nationalarchives/tdr/transfer/service/api/model/Consignment.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package uk.gov.nationalarchives.tdr.transfer.service.api.model | ||
|
||
import java.util.UUID | ||
|
||
sealed trait ConsignmentModel | ||
|
||
object Consignment { | ||
case class ConsignmentDetails(consignmentId: UUID) extends ConsignmentModel | ||
} |
9 changes: 9 additions & 0 deletions
9
src/main/scala/uk/gov/nationalarchives/tdr/transfer/service/api/model/Serializers.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package uk.gov.nationalarchives.tdr.transfer.service.api.model | ||
|
||
import io.circe.generic.AutoDerivation | ||
import sttp.tapir.generic.auto.SchemaDerivation | ||
import sttp.tapir.generic.{Configuration => TapirConfiguration} | ||
|
||
object Serializers extends AutoDerivation with SchemaDerivation { | ||
implicit val schemaConfiguration: TapirConfiguration = TapirConfiguration.default.withDiscriminator("type") | ||
} |
19 changes: 19 additions & 0 deletions
19
...main/scala/uk/gov/nationalarchives/tdr/transfer/service/services/ConsignmentService.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package uk.gov.nationalarchives.tdr.transfer.service.services | ||
|
||
import cats.effect.IO | ||
import cats.implicits.catsSyntaxApplicativeId | ||
import uk.gov.nationalarchives.tdr.keycloak.Token | ||
import uk.gov.nationalarchives.tdr.transfer.service.api.model.Consignment.ConsignmentDetails | ||
|
||
import java.util.UUID | ||
|
||
class ConsignmentService { | ||
def createConsignment(token: Token): IO[ConsignmentDetails] = { | ||
// For now just return dummy response | ||
ConsignmentDetails(UUID.fromString("ae4b7cad-ee83-46bd-b952-80bc8263c6c2")).pure[IO] | ||
} | ||
} | ||
|
||
object ConsignmentService { | ||
def apply() = new ConsignmentService | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
api { | ||
port = "8080" | ||
} | ||
|
||
auth { | ||
url = "http://localhost:8000/auth" | ||
realm = "tdr" | ||
} |
38 changes: 38 additions & 0 deletions
38
src/test/scala/uk/gov/nationalarchives/tdr/transfer/service/TestUtils.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package uk.gov.nationalarchives.tdr.transfer.service | ||
|
||
import com.tngtech.keycloakmock.api.TokenConfig.aTokenConfig | ||
import com.tngtech.keycloakmock.api.{KeycloakMock, ServerConfig} | ||
import org.apache.pekko.http.scaladsl.model.headers.OAuth2BearerToken | ||
import org.scalatest.BeforeAndAfterEach | ||
import org.scalatest.flatspec.AnyFlatSpec | ||
import org.scalatest.matchers.should.Matchers | ||
|
||
import java.util.UUID | ||
|
||
object TestUtils extends AnyFlatSpec with Matchers with BeforeAndAfterEach { | ||
private val tdrKeycloakMock: KeycloakMock = createServer("tdr", 8000) | ||
private val testKeycloakMock: KeycloakMock = createServer("test", 8001) | ||
|
||
val userId: UUID = UUID.fromString("4ab14990-ed63-4615-8336-56fbb9960300") | ||
|
||
def validUserToken(userId: UUID = userId, body: String = "Code", standardUser: String = "true"): OAuth2BearerToken = | ||
OAuth2BearerToken( | ||
tdrKeycloakMock.getAccessToken( | ||
aTokenConfig() | ||
.withResourceRole("tdr", "tdr_user") | ||
.withClaim("body", body) | ||
.withClaim("user_id", userId) | ||
.withClaim("standard_user", standardUser) | ||
.build | ||
) | ||
) | ||
|
||
def invalidToken: OAuth2BearerToken = OAuth2BearerToken(testKeycloakMock.getAccessToken(aTokenConfig().build)) | ||
|
||
private def createServer(realm: String, port: Int): KeycloakMock = { | ||
val config = ServerConfig.aServerConfig().withPort(port).withDefaultRealm(realm).build() | ||
val mock: KeycloakMock = new KeycloakMock(config) | ||
mock.start() | ||
mock | ||
} | ||
} |
Oops, something went wrong.