Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
TomJKing committed Jun 25, 2024
1 parent da74c32 commit 1133f51
Show file tree
Hide file tree
Showing 19 changed files with 239 additions and 63 deletions.
2 changes: 2 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ lazy val root = (project in file("."))
libraryDependencies ++= Seq(
authUtils,
catsEffect,
generatedGraphql,
graphqlClient,
http4sCirce,
http4sDsl,
http4sEmberServer,
Expand Down
3 changes: 3 additions & 0 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ object Dependencies {

lazy val catsEffect = "org.typelevel" %% "cats-effect" % "3.5.4"

lazy val generatedGraphql = "uk.gov.nationalarchives" %% "tdr-generated-graphql" % "0.0.372"
lazy val graphqlClient = "uk.gov.nationalarchives" %% "tdr-graphql-client" % "0.0.144"

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
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/application.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
api {
url = "https://api.tdr-integration.nationalarchives.gov.uk/graphql"
url = ${?API_URL}
port = "8080"
port = ${?API_PORT}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package uk.gov.nationalarchives.tdr.transfer.service

object Config {
case class Api(port: Int)
object ApplicationConfig {
case class Api(port: Int, url: String)
case class Auth(url: String, realm: String)

case class Configuration(auth: Auth, api: Api)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ import org.http4s.{HttpRoutes, Request, Response}
import pureconfig.ConfigSource
import pureconfig.generic.auto._
import sttp.apispec.openapi.Info
import sttp.client3.quick.backend
import sttp.client3.{HttpURLConnectionBackend, Identity, SttpBackend}
import sttp.tapir.server.http4s.Http4sServerInterpreter
import sttp.tapir.swagger.bundle.SwaggerInterpreter
import uk.gov.nationalarchives.tdr.transfer.service.Config.Configuration
import uk.gov.nationalarchives.tdr.keycloak.TdrKeycloakDeployment
import uk.gov.nationalarchives.tdr.transfer.service.ApplicationConfig.Configuration
import uk.gov.nationalarchives.tdr.transfer.service.api.controllers.LoadController

object TransferServiceServer extends IOApp {
Expand All @@ -23,6 +26,12 @@ object TransferServiceServer extends IOApp {
case Right(value) => value
}

private val authUrl = config.auth.url
private val realm = config.auth.realm

implicit val backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend()
implicit val keycloakDeployment: TdrKeycloakDeployment = TdrKeycloakDeployment(s"$authUrl", realm, 8080)

private val apiPort: Port = Port.fromInt(config.api.port).getOrElse(port"8080")

private val infoTitle = "TDR Transfer Service API"
Expand All @@ -44,7 +53,7 @@ object TransferServiceServer extends IOApp {

private val app: Kleisli[IO, Request[IO], Response[IO]] = allRoutes.orNotFound

private val finalApp = Logger.httpApp(true, true)(app)
private val finalApp = Logger.httpApp(logHeaders = true, logBody = true)(app)

private val transferServiceServer = EmberServerBuilder
.default[IO]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ 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.Config.Configuration
import uk.gov.nationalarchives.tdr.transfer.service.ApplicationConfig.Configuration
import uk.gov.nationalarchives.tdr.transfer.service.api.errors.BackendException.AuthenticationError

import scala.concurrent.ExecutionContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ 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.Config.Configuration
import uk.gov.nationalarchives.tdr.transfer.service.ApplicationConfig.Configuration
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._
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,43 @@ package uk.gov.nationalarchives.tdr.transfer.service.api.controllers

import cats.effect.IO
import org.http4s.HttpRoutes
import sttp.tapir.{Endpoint, _}
import sttp.client3.{Identity, SttpBackend}
import sttp.tapir._
import sttp.tapir.json.circe.jsonBody
import sttp.tapir.server.PartialServerEndpoint
import sttp.tapir.server.http4s.Http4sServerInterpreter
import uk.gov.nationalarchives.tdr.keycloak.TdrKeycloakDeployment
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.LoadModel.{AWSS3LoadDestination, LoadDetails}
import uk.gov.nationalarchives.tdr.transfer.service.api.model.Serializers._
import uk.gov.nationalarchives.tdr.transfer.service.services.ConsignmentService
import uk.gov.nationalarchives.tdr.transfer.service.services.GraphQlApiService

class LoadController(consignmentService: ConsignmentService) extends BaseController {
def endpoints: List[Endpoint[String, Unit, BackendException.AuthenticationError, ConsignmentDetails, Any]] = List(initiateLoadEndpoint.endpoint)
import java.util.UUID

class LoadController(graphqlApiService: GraphQlApiService) extends BaseController {
def endpoints: List[Endpoint[String, Unit, BackendException.AuthenticationError, LoadDetails, Any]] = List(initiateLoadEndpoint.endpoint)

def routes: HttpRoutes[IO] = initiateLoadRoute

val initiateLoadEndpoint: PartialServerEndpoint[String, AuthenticatedContext, Unit, BackendException.AuthenticationError, ConsignmentDetails, Any, IO] = securedWithBearer
private val initiateLoadEndpoint: PartialServerEndpoint[String, AuthenticatedContext, Unit, BackendException.AuthenticationError, LoadDetails, Any, IO] = securedWithBearer
.summary("Initiate the load of records and metadata")
.post
.in("load" / "sharepoint" / "initiate")
.out(jsonBody[ConsignmentDetails])
.out(jsonBody[LoadDetails])

private def loadDetails(consignmentId: UUID): IO[LoadDetails] = {
val recordsS3Bucket = AWSS3LoadDestination("s3BucketNameRecords", "s3BucketKeyRecords")
val metadataS3Bucket = AWSS3LoadDestination("s3BucketNameMetadata", "s3BucketKeyMetadata")
IO(LoadDetails(consignmentId, recordsLoadDestination = recordsS3Bucket, metadataLoadDestination = metadataS3Bucket))
}

val initiateLoadRoute: HttpRoutes[IO] =
Http4sServerInterpreter[IO]().toRoutes(initiateLoadEndpoint.serverLogicSuccess(ac => _ => consignmentService.createConsignment(ac.token)))
Http4sServerInterpreter[IO]().toRoutes(
initiateLoadEndpoint.serverLogicSuccess(ac => _ => graphqlApiService.createConsignment(ac.token).flatMap(c => loadDetails(c.consignmentid.get)))
)
}

object LoadController {
def apply() = new LoadController(ConsignmentService.apply())
def apply()(implicit backend: SttpBackend[Identity, Any], keycloakDeployment: TdrKeycloakDeployment) = new LoadController(GraphQlApiService.apply())
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package uk.gov.nationalarchives.tdr.transfer.service.api.model

import java.util.UUID

sealed trait LoadModel
sealed trait LoadDestinationModel

object LoadModel {
case class AWSS3LoadDestination(bucketName: String, bucketKey: String) extends LoadDestinationModel
case class LoadDetails(consignmentId: UUID, recordsLoadDestination: AWSS3LoadDestination, metadataLoadDestination: AWSS3LoadDestination) extends LoadModel
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package uk.gov.nationalarchives.tdr.transfer.service.services

import cats.effect.IO
import cats.implicits.{catsSyntaxApplicativeId, catsSyntaxOptionId}
import com.typesafe.scalalogging.Logger
import graphql.codegen.AddConsignment
import sttp.client3.{Identity, SttpBackend}
import uk.gov.nationalarchives.tdr.GraphQLClient
import graphql.codegen.AddConsignment.{addConsignment => ac}
import graphql.codegen.types.AddConsignmentInput
import uk.gov.nationalarchives.tdr.keycloak.{TdrKeycloakDeployment, Token}
import uk.gov.nationalarchives.tdr.transfer.service.api.model.LoadModel.LoadDetails

import java.util.UUID
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

class GraphQlApiService()(implicit keycloakDeployment: TdrKeycloakDeployment, backend: SttpBackend[Identity, Any]) {

implicit class FutureUtils[T](f: Future[T]) {
def toIO: IO[T] = IO.fromFuture(IO(f))
}

private val addConsignmentClient = new GraphQLClient[ac.Data, ac.Variables]("apiUrl")

def createConsignment(token: Token): IO[AddConsignment.addConsignment.AddConsignment] = {
for {
result <- addConsignmentClient.getResult(token.bearerAccessToken, ac.document, ac.Variables(AddConsignmentInput(None, "standard")).some).toIO
data = result.data.get
} yield data.addConsignment
}
}

object GraphQlApiService {
def apply()(implicit
backend: SttpBackend[Identity, Any],
keycloakDeployment: TdrKeycloakDeployment
) = new GraphQlApiService
}
1 change: 1 addition & 0 deletions src/test/resources/application.conf
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
api {
port = "8080"
url = "http://localhost:9001/graphql"
}

auth {
Expand Down
9 changes: 9 additions & 0 deletions src/test/resources/json/add_consignment_response.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"data": {
"addConsignment": {
"consignmentid": "6e3b76c4-1745-4467-8ac5-b4dd736e1b3e",
"userid": "4ab14990-ed63-4615-8336-56fbb9960300",
"consignmentType": "standard"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import pureconfig.ConfigSource
import pureconfig.generic.auto._
import uk.gov.nationalarchives.tdr.transfer.service.Config.Configuration
import uk.gov.nationalarchives.tdr.transfer.service.ApplicationConfig.Configuration

import java.util.UUID

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,17 @@ import org.http4s.{Header, Headers, Method, Request, Status}
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import org.typelevel.ci.CIString
import sttp.client3.{HttpURLConnectionBackend, Identity, SttpBackend}
import uk.gov.nationalarchives.tdr.keycloak.TdrKeycloakDeployment
import uk.gov.nationalarchives.tdr.transfer.service.TestUtils.{invalidToken, validUserToken}
import uk.gov.nationalarchives.tdr.transfer.service.api.controllers.LoadController

import scala.concurrent.ExecutionContextExecutor

class TransferServiceServerSpec extends AnyFlatSpec with Matchers {
implicit val executionContext: ExecutionContextExecutor = scala.concurrent.ExecutionContext.global
implicit val backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend()
implicit val tdrKeycloakDeployment: TdrKeycloakDeployment = TdrKeycloakDeployment("authUrl", "realm", 60)

"'healthcheck' endpoint" should "return 200 if server running" in {
val getHealthCheck = Request[IO](Method.GET, uri"/healthcheck")
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package uk.gov.nationalarchives.tdr.transfer.service.services

import com.github.tomakehurst.wiremock.WireMockServer
import com.github.tomakehurst.wiremock.client.WireMock._
import com.github.tomakehurst.wiremock.stubbing.StubMapping
import org.scalatest.concurrent.ScalaFutures
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import org.scalatest.time.{Millis, Seconds, Span}
import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach}

import java.io.File
import scala.io.Source.fromResource
import scala.reflect.io.Directory

class ExternalServicesSpec extends AnyFlatSpec with BeforeAndAfterEach with BeforeAndAfterAll with ScalaFutures {
override implicit def patienceConfig: PatienceConfig = PatienceConfig(timeout = scaled(Span(5, Seconds)), interval = scaled(Span(100, Millis)))

val wiremockGraphqlServer = new WireMockServer(9001)
val wiremockAuthServer = new WireMockServer(9002)
// val wiremockSsmServer = new WireMockServer(9004)
// val wiremockS3 = new WireMockServer(8003)

// def setupSsmServer(): Unit = {
// wiremockSsmServer
// .stubFor(
// post(urlEqualTo("/"))
// .willReturn(okJson("{\"Parameter\":{\"Name\":\"string\",\"Value\":\"string\"}}"))
// )
// }

val graphQlPath = "/graphql"
val authPath = "/auth/realms/tdr/protocol/openid-connect/token"

def graphQlUrl: String = wiremockGraphqlServer.url(graphQlPath)

def graphqlOkJson(saveMetadata: Boolean = false): Unit = {
wiremockGraphqlServer.stubFor(
post(urlEqualTo(graphQlPath))
.withRequestBody(containing("addConsignment"))
.willReturn(okJson(fromResource(s"json/add_consignment_response.json").mkString))
)

wiremockGraphqlServer.stubFor(
post(urlEqualTo(graphQlPath))
.withRequestBody(containing("displayProperties"))
.willReturn(okJson(fromResource(s"json/display_properties.json").mkString))
)

wiremockGraphqlServer.stubFor(
post(urlEqualTo(graphQlPath))
.withRequestBody(containing("updateConsignmentStatus"))
.willReturn(ok("""{"data": {"updateConsignmentStatus": 1}}""".stripMargin))
)
if (saveMetadata) {
wiremockGraphqlServer.stubFor(
post(urlEqualTo(graphQlPath))
.withRequestBody(containing("addOrUpdateBulkFileMetadata"))
.willReturn(ok("""{"data": {"addOrUpdateBulkFileMetadata": []}}""".stripMargin))
)
}
}

def authOkJson(): StubMapping = wiremockAuthServer.stubFor(
post(urlEqualTo(authPath))
.willReturn(okJson("""{"access_token": "abcde"}"""))
)

def authUnavailable: StubMapping = wiremockAuthServer.stubFor(post(urlEqualTo(authPath)).willReturn(serverError()))

def graphqlUnavailable: StubMapping = wiremockGraphqlServer.stubFor(post(urlEqualTo(graphQlPath)).willReturn(serverError()))

override def beforeAll(): Unit = {
wiremockGraphqlServer.start()
wiremockAuthServer.start()
}

override def afterAll(): Unit = {
wiremockGraphqlServer.stop()
wiremockAuthServer.stop()
}

override def afterEach(): Unit = {
wiremockAuthServer.resetAll()
wiremockGraphqlServer.resetAll()
val runningFiles = new File(s"./src/test/resources/testfiles/running-files/")
if (runningFiles.exists()) {
new Directory(runningFiles).deleteRecursively()
}
}
}
Loading

0 comments on commit 1133f51

Please sign in to comment.