From 7555b2d183e8159a5d09076a24a25e3efd41a9b0 Mon Sep 17 00:00:00 2001 From: Ishan Shah Date: Sat, 20 Aug 2022 12:35:03 +0530 Subject: [PATCH 01/10] adding client --- .scalafix.conf | 14 ++- build.sbt | 37 +++++-- .../github/nullptr7/client/ApiClients.scala | 6 ++ .../nullptr7/client/ServiceClient.scala | 46 ++++++++ .../com/github/nullptr7/client/TimePass.scala | 49 +++++++++ .../client/TransportServiceClient.scala | 9 ++ .../client/exception/ClientException.scala | 9 ++ .../client/module/BlazeClientModule.scala | 31 ++++++ .../configurations/ApplicationResources.scala | 5 +- .../nullptr7/configurations/types.scala | 45 ++++++-- .../scala/com/github/nullptr7/models.scala | 90 +++++++++++++--- project/plugins.sbt | 2 +- server/src/it/resources/dbInit.sql | 2 +- .../nullptr7/protocol/IntegrationSuite.scala | 66 +++++++++--- server/src/main/resources/dbInit.sql | 2 +- server/src/main/resources/reference.conf | 9 ++ .../github/nullptr7/entrypoint/Startup.scala | 65 +++++++++--- .../modules/ApplicationResourceModule.scala | 10 +- .../modules/BlazeServerModule.scala | 29 +++-- .../entrypoint/modules/RepositoryModule.scala | 39 +++++++ .../entrypoint/modules/RoutingModule.scala | 36 ------- .../modules/ServiceLogicModule.scala | 100 ++++++++++++++++++ .../github/nullptr7/protocol/Contracts.scala | 2 +- .../nullptr7/protocol/EmployeeContracts.scala | 8 +- .../nullptr7/protocol/ServiceLogic.scala | 78 -------------- .../protocol/AddressServiceLogicTest.scala | 12 +-- .../protocol/EmployeeServiceLogicTest.scala | 59 +++++++++-- .../protocol/ServiceLogicTestHelper.scala | 10 +- 28 files changed, 630 insertions(+), 240 deletions(-) create mode 100644 client/src/main/scala/com/github/nullptr7/client/ApiClients.scala create mode 100644 client/src/main/scala/com/github/nullptr7/client/ServiceClient.scala create mode 100644 client/src/main/scala/com/github/nullptr7/client/TimePass.scala create mode 100644 client/src/main/scala/com/github/nullptr7/client/TransportServiceClient.scala create mode 100644 client/src/main/scala/com/github/nullptr7/client/exception/ClientException.scala create mode 100644 client/src/main/scala/com/github/nullptr7/client/module/BlazeClientModule.scala create mode 100644 server/src/main/scala/com/github/nullptr7/entrypoint/modules/RepositoryModule.scala delete mode 100644 server/src/main/scala/com/github/nullptr7/entrypoint/modules/RoutingModule.scala create mode 100644 server/src/main/scala/com/github/nullptr7/entrypoint/modules/ServiceLogicModule.scala delete mode 100644 server/src/main/scala/com/github/nullptr7/protocol/ServiceLogic.scala diff --git a/.scalafix.conf b/.scalafix.conf index af4524c..2ea3771 100644 --- a/.scalafix.conf +++ b/.scalafix.conf @@ -1,6 +1,12 @@ OrganizeImports { - groupedImports = Merge - coalesceToWildcardImportThreshold = 3 - groups = ["re:javax?\\.", "scala.", "org.", "cats.", "sttp.", "io.", "skunk.", "pureconfig.", "monocle."] - removeUnused = true + blankLines = Auto + coalesceToWildcardImportThreshold = 5 + expandRelative = false + groupExplicitlyImportedImplicitsSeparately = false + groupedImports = Merge + groups = ["re:javax?\\.", "scala.", "org.", "cats.", "sttp.", "io.", "skunk.", "pureconfig.", "monocle."] + importSelectorsOrder = Ascii + importsOrder = Ascii + preset = INTELLIJ_2020_3 + removeUnused = true } \ No newline at end of file diff --git a/build.sbt b/build.sbt index 048fa6d..04b0d99 100644 --- a/build.sbt +++ b/build.sbt @@ -9,9 +9,10 @@ val scalafixCommonSettings = val Versions = new { - val tapir = "1.0.0" - val log4jcats = "2.4.0" - val circe = "0.14.1" + val tapir = "1.0.3" + val log4cats = "2.4.0" + val circe = "0.14.2" + val http4s = "0.23.12" } lazy val commonSettings: Seq[Setting[_]] = Seq( @@ -19,8 +20,8 @@ lazy val commonSettings: Seq[Setting[_]] = Seq( // scalafixCommonSettings, libraryDependencies ++= Seq( "ch.qos.logback" % "logback-classic" % "1.2.11", - "org.typelevel" %% "log4cats-slf4j" % Versions.log4jcats, - "org.typelevel" %% "log4cats-noop" % Versions.log4jcats, + "org.typelevel" %% "log4cats-slf4j" % Versions.log4cats, + "org.typelevel" %% "log4cats-noop" % Versions.log4cats, // "org.apache.logging.log4j" % "log4j-core" % Versions.log4j, // "org.apache.logging.log4j" % "log4j-api" % Versions.log4j, // "org.apache.logging.log4j" % "log4j-slf4j-impl" % Versions.log4j, @@ -57,7 +58,6 @@ lazy val models = commonSettings, // scalafixCommonSettings, libraryDependencies ++= Seq( - "com.softwaremill.sttp.tapir" %% "tapir-json-circe" % Versions.tapir, "io.circe" %% "circe-generic-extras" % Versions.circe, "dev.optics" %% "monocle-core" % "3.1.0" ) @@ -65,6 +65,19 @@ lazy val models = lazy val includeTestandIt = "it,test" +lazy val client = + project + .settings( + commonSettings, + libraryDependencies ++= Seq( + "org.http4s" %% "http4s-blaze-client" % Versions.http4s, + "io.circe" %% "circe-generic-extras" % Versions.circe, + "com.softwaremill.sttp.tapir" %% "tapir-http4s-client" % Versions.tapir, + "com.softwaremill.sttp.tapir" %% "tapir-json-circe" % Versions.tapir + ) + ) + .dependsOn(configs) + lazy val server = project .configs(IntegrationTest) @@ -74,12 +87,14 @@ lazy val server = scalafixCommonSettings, libraryDependencies ++= Seq( "com.softwaremill.sttp.tapir" %% "tapir-http4s-server" % Versions.tapir, - "org.http4s" %% "http4s-blaze-server" % "0.23.12", + "com.softwaremill.sttp.tapir" %% "tapir-json-circe" % Versions.tapir, + "org.http4s" %% "http4s-blaze-server" % Versions.http4s, + "org.http4s" %% "http4s-circe" % Versions.http4s, "io.circe" %% "circe-generic-extras" % Versions.circe, "com.softwaremill.sttp.tapir" %% "tapir-sttp-stub-server" % Versions.tapir % includeTestandIt, "org.scalatest" %% "scalatest" % "3.2.12" % includeTestandIt, - "com.softwaremill.sttp.client3" %% "async-http-client-backend-cats" % "3.6.2" % includeTestandIt, - "com.softwaremill.sttp.client3" %% "circe" % "3.6.2" % includeTestandIt, + "com.softwaremill.sttp.client3" %% "async-http-client-backend-cats" % "3.7.2" % includeTestandIt, + "com.softwaremill.sttp.client3" %% "circe" % "3.7.2" % includeTestandIt, "org.mockito" %% "mockito-scala" % "1.17.7" % Test, "org.tpolecat" %% "skunk-core" % "0.3.1" % IntegrationTest, "org.typelevel" %% "cats-effect-testing-specs2" % "1.4.0" % IntegrationTest @@ -89,13 +104,13 @@ lazy val server = // "com.softwaremill.sttp.tapir" %% "tapir-tests" % "1.0.0" ) ) - .dependsOn(models, database) + .dependsOn(models, database, client) lazy val app = project .in(file(".")) .settings(publish := {}, publish / skip := true) - .aggregate(models, server, database) + .aggregate(models, server, database, client) lazy val deleteBloop = taskKey[Unit]("Delete Existing Bloop Directory") deleteBloop := { diff --git a/client/src/main/scala/com/github/nullptr7/client/ApiClients.scala b/client/src/main/scala/com/github/nullptr7/client/ApiClients.scala new file mode 100644 index 0000000..21286be --- /dev/null +++ b/client/src/main/scala/com/github/nullptr7/client/ApiClients.scala @@ -0,0 +1,6 @@ +package com.github.nullptr7 +package client + +case class ApiClients[F[_]]( + transportServiceClient: TransportServiceClient[F] +) diff --git a/client/src/main/scala/com/github/nullptr7/client/ServiceClient.scala b/client/src/main/scala/com/github/nullptr7/client/ServiceClient.scala new file mode 100644 index 0000000..bbc2781 --- /dev/null +++ b/client/src/main/scala/com/github/nullptr7/client/ServiceClient.scala @@ -0,0 +1,46 @@ +package com.github.nullptr7 +package client + +import org.http4s._ +import org.http4s.client.Client +import org.typelevel.ci.CIString +import org.typelevel.log4cats.Logger + +import cats.effect.kernel.Async +import cats.implicits._ + +import configurations.types.ClientDetails +import exception.ServiceClientException + +trait ServiceClient[F[_]] { + + protected[client] val client: Client[F] + + protected[client] val clientDetails: ClientDetails + + // We can do unsafe here as we know this is already accepted by java.net.URI + final def sendAndReceive[Req, Res]( + body: Option[Req] + )(implicit encoder: EntityEncoder[F, Req], decoder: EntityDecoder[F, Res], async: Async[F], logger: Logger[F]): F[Res] = { + val request: Request[F] = body match { + case None => + Request[F](Method.GET, Uri.unsafeFromString(clientDetails.url.toString)) + .withHeaders(Header.Raw(CIString("Authorization"), s"Bearer ${clientDetails.password.toString + clientDetails.password}")) + case Some(body) => + Request[F]( + method = Method.POST, + uri = Uri.unsafeFromString(clientDetails.url.toString), + headers = Headers(Header.Raw(CIString("x-mock-match-request-body"), "true")) + ).withEntity(body) + } + + client + .run(request) + .use(_.as[Res]) + .handleErrorWith { t: Throwable => + logger.error(t)("Service called failed") *> async.raiseError(ServiceClientException(t.getMessage)) + } + + } + +} diff --git a/client/src/main/scala/com/github/nullptr7/client/TimePass.scala b/client/src/main/scala/com/github/nullptr7/client/TimePass.scala new file mode 100644 index 0000000..4a2eb55 --- /dev/null +++ b/client/src/main/scala/com/github/nullptr7/client/TimePass.scala @@ -0,0 +1,49 @@ +package com.github.nullptr7 +package client + +import org.http4s.Uri +import org.http4s.blaze.client.BlazeClientBuilder +import org.http4s.client.Client + +import cats.effect.IO +import cats.effect.kernel.Resource +import cats.effect.unsafe.implicits.global + +import sttp.tapir._ +import sttp.tapir.client.http4s.Http4sClientInterpreter +import sttp.tapir.generic.auto._ +import sttp.tapir.json.circe.jsonBody + +import io.circe.generic.auto.{exportDecoder, exportEncoder} + +object TimePass extends App { + + private val value: EndpointInput[Unit] = "api" / "get" / "employee-transport-data" + + case class Something(code: String) + + private val userEndpoint: Endpoint[Unit, Something, Unit, String, Any] = + endpoint + .post + .in(value) + .in(header("x-mock-match-request-body", "true")) + .in(jsonBody[Something]) + .out(stringBody) + + val (req, resp) = + Http4sClientInterpreter[IO]() + .toRequest(userEndpoint, Some(Uri.unsafeFromString("https://2ff8313d-c02d-4113-9768-501060fa697d.mock.pstmn.io"))) + .apply(Something("4a5f132a-084b-445f-b0b0-3e1f1f36521c")) + + val client: Resource[IO, Client[IO]] = BlazeClientBuilder[IO].resource + + println( + client + .use( + _.run(req) + .use(resp) + ) + .unsafeRunSync() + ) + +} diff --git a/client/src/main/scala/com/github/nullptr7/client/TransportServiceClient.scala b/client/src/main/scala/com/github/nullptr7/client/TransportServiceClient.scala new file mode 100644 index 0000000..0fa82df --- /dev/null +++ b/client/src/main/scala/com/github/nullptr7/client/TransportServiceClient.scala @@ -0,0 +1,9 @@ +package com.github.nullptr7 +package client + +import org.http4s.client.Client + +import configurations.types.TransportApiClientDetails + +final class TransportServiceClient[F[_]](override val client: Client[F], override val clientDetails: TransportApiClientDetails) + extends ServiceClient[F] diff --git a/client/src/main/scala/com/github/nullptr7/client/exception/ClientException.scala b/client/src/main/scala/com/github/nullptr7/client/exception/ClientException.scala new file mode 100644 index 0000000..8ab3345 --- /dev/null +++ b/client/src/main/scala/com/github/nullptr7/client/exception/ClientException.scala @@ -0,0 +1,9 @@ +package com.github.nullptr7 +package client +package exception + +sealed abstract class ClientException(msg: String) extends Exception { + override def getMessage: String = msg +} + +case class ServiceClientException(msg: String) extends ClientException(msg) diff --git a/client/src/main/scala/com/github/nullptr7/client/module/BlazeClientModule.scala b/client/src/main/scala/com/github/nullptr7/client/module/BlazeClientModule.scala new file mode 100644 index 0000000..bc1f0a4 --- /dev/null +++ b/client/src/main/scala/com/github/nullptr7/client/module/BlazeClientModule.scala @@ -0,0 +1,31 @@ +package com.github.nullptr7 +package client +package module + +import org.http4s.blaze.client.BlazeClientBuilder +import org.http4s.client.Client + +import cats.effect.kernel.{Async, Resource} + +import configurations.types.ClientConfig + +sealed abstract class BlazeClientModule[F[_]] { + + def make(clientConfig: ClientConfig): Resource[F, Client[F]] +} + +object BlazeClientModule { + + def apply[F[_]: BlazeClientModule]: BlazeClientModule[F] = implicitly + + implicit def clientForAsync[F[_]: Async]: BlazeClientModule[F] = + new BlazeClientModule[F] { + + override def make(clientConfig: ClientConfig): Resource[F, Client[F]] = + BlazeClientBuilder[F] + .withConnectTimeout(clientConfig.timeout) + .resource + + } + +} diff --git a/configs/src/main/scala/com/github/nullptr7/configurations/ApplicationResources.scala b/configs/src/main/scala/com/github/nullptr7/configurations/ApplicationResources.scala index 82d5c2f..ac93c36 100644 --- a/configs/src/main/scala/com/github/nullptr7/configurations/ApplicationResources.scala +++ b/configs/src/main/scala/com/github/nullptr7/configurations/ApplicationResources.scala @@ -1,9 +1,10 @@ package com.github.nullptr7 package configurations -import types.{DatabaseConfig, ServerConfig} +import types.{ClientConfig, DatabaseConfig, ServerConfig} final case class ApplicationResources( databaseConfig: DatabaseConfig, - serverConfig: ServerConfig + serverConfig: ServerConfig, + clientConfig: ClientConfig ) diff --git a/configs/src/main/scala/com/github/nullptr7/configurations/types.scala b/configs/src/main/scala/com/github/nullptr7/configurations/types.scala index f6de252..248b5dc 100644 --- a/configs/src/main/scala/com/github/nullptr7/configurations/types.scala +++ b/configs/src/main/scala/com/github/nullptr7/configurations/types.scala @@ -1,7 +1,11 @@ package com.github.nullptr7 package configurations -package types { +import scala.concurrent.duration.Duration + +import java.net.URI + +object types { final case class Sensitive(value: String) extends AnyVal { override def toString: String = "MASKED" @@ -12,30 +16,51 @@ package types { final case class Namespace(value: String) extends AnyVal - trait ConfigType { + sealed trait ConfigType { val namespace: Namespace } object ConfigType { - implicit object Blaze extends ConfigType { - val namespace: Namespace = Namespace("server") + implicit object Server extends ConfigType { + override val namespace: Namespace = Namespace("server") } implicit object Postgres extends ConfigType { - val namespace: Namespace = Namespace("db") + override val namespace: Namespace = Namespace("db") + } + + implicit object Client extends ConfigType { + override val namespace: Namespace = Namespace("client") } } + final case class ClientConfig( + timeout: Duration, + transport: TransportApiClientDetails + ) + final case class ServerConfig(host: Hostname, port: Port) - final case class DatabaseConfig( - val host: Hostname, - val port: Port, - val user: String, - val database: String, + sealed trait ClientDetails { + val url: URI + val username: String val password: Sensitive + } + + final case class TransportApiClientDetails( + override val url: URI, + override val username: String, + override val password: Sensitive + ) extends ClientDetails + + final case class DatabaseConfig( + host: Hostname, + port: Port, + user: String, + database: String, + password: Sensitive ) } diff --git a/models/src/main/scala/com/github/nullptr7/models.scala b/models/src/main/scala/com/github/nullptr7/models.scala index e5377c8..e971c61 100644 --- a/models/src/main/scala/com/github/nullptr7/models.scala +++ b/models/src/main/scala/com/github/nullptr7/models.scala @@ -1,16 +1,16 @@ package com.github.nullptr7 -import java.util.UUID +package models { -import io.circe.Codec -import io.circe.generic.extras.semiauto.deriveUnwrappedCodec -import io.circe.generic.semiauto.deriveCodec + import java.util.UUID -import monocle.Iso + import io.circe._ + import io.circe.generic.extras.semiauto.{deriveEnumerationCodec, deriveUnwrappedCodec} + import io.circe.generic.semiauto.deriveCodec -import optics.IsUUID + import monocle.Iso -package models { + import optics.IsUUID final case class EmployeeCode(value: UUID) extends AnyVal @@ -32,9 +32,61 @@ package models { address: Address ) + sealed trait Shift extends Enumeration + + case object DAY extends Shift + + case object AFTERNOON extends Shift + + case object EVENING extends Shift + + case object NIGHT extends Shift + + case object NA extends Shift + + final case class TransportRequest(code: EmployeeCode) + + final case class TransportResponse(employeeCode: EmployeeCode, routes: Int, numberOfNoShows: Int, shift: Shift) + + object TransportResponse { + + def apply(employeeCode: EmployeeCode): TransportResponse = TransportResponse( + employeeCode = employeeCode, + routes = 0, + numberOfNoShows = 0, + shift = NA + ) + + } + + final case class EmployeeWithTransport( + id: EmployeeId, + code: EmployeeCode, + name: String, + age: Int, + salary: Double, + address: Address, + transportDetails: TransportResponse + ) + + object EmployeeWithTransport { + + def apply(employee: Employee, transportDetails: TransportResponse): EmployeeWithTransport = + new EmployeeWithTransport( + employee.id, + employee.code, + employee.name, + employee.age, + employee.salary, + employee.address, + transportDetails + ) + + } + final case class AddressId(value: UUID) extends AnyVal - final object implicits { + object implicits { implicit object EmployeeCodeIso extends IsUUID[EmployeeCode] { @@ -65,15 +117,19 @@ package models { zip: String ) - final object codecs { - - implicit val employeeCodeCodec: Codec[EmployeeCode] = deriveUnwrappedCodec[EmployeeCode] - implicit val createEmployeeCodec: Codec[CreateEmployee] = deriveCodec[CreateEmployee] - implicit val employeeIdCodec: Codec[EmployeeId] = deriveUnwrappedCodec[EmployeeId] - implicit val employeeCodec: Codec[Employee] = deriveCodec[Employee] - implicit val addressCodec: Codec[Address] = deriveCodec[Address] - implicit val createAddressCodec: Codec[CreateAddress] = deriveCodec[CreateAddress] - implicit val addressIdCodec: Codec[AddressId] = deriveUnwrappedCodec[AddressId] + object codecs { + + implicit lazy val employeeCodeCodec: Codec[EmployeeCode] = deriveUnwrappedCodec[EmployeeCode] + implicit lazy val employeeIdCodec: Codec[EmployeeId] = deriveUnwrappedCodec[EmployeeId] + implicit lazy val createEmployeeCodec: Codec[CreateEmployee] = deriveCodec[CreateEmployee] + implicit lazy val employeeCodec: Codec[Employee] = deriveCodec[Employee] + implicit lazy val addressCodec: Codec[Address] = deriveCodec[Address] + implicit lazy val createAddressCodec: Codec[CreateAddress] = deriveCodec[CreateAddress] + implicit lazy val addressIdCodec: Codec[AddressId] = deriveUnwrappedCodec[AddressId] + implicit lazy val shiftCodec: Codec[Shift] = deriveEnumerationCodec[Shift] + implicit lazy val transportResponseCodec: Codec[TransportResponse] = deriveCodec[TransportResponse] + implicit lazy val transportRequestCodec: Codec[TransportRequest] = deriveCodec[TransportRequest] + implicit lazy val employeeWithTransportCodec: Codec[EmployeeWithTransport] = deriveCodec[EmployeeWithTransport] } } diff --git a/project/plugins.sbt b/project/plugins.sbt index 52ce984..fca19fc 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.3.1") -addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.5.2") +addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.5.3-12-a0ca82de") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.10.0") addSbtPlugin("com.geirsson" %% "sbt-scalafmt" % "1.5.1") libraryDependencies ++= Seq( diff --git a/server/src/it/resources/dbInit.sql b/server/src/it/resources/dbInit.sql index 9e5e503..de504da 100644 --- a/server/src/it/resources/dbInit.sql +++ b/server/src/it/resources/dbInit.sql @@ -31,7 +31,7 @@ INSERT INTO VALUES ( 1, - '75b107cb-2bef-431f-8b33-b2074d51bd08', + '4a5f132a-084b-445f-b0b0-3e1f1f36521c', 'Paul', 32, 20000.00, diff --git a/server/src/it/scala/com/github/nullptr7/protocol/IntegrationSuite.scala b/server/src/it/scala/com/github/nullptr7/protocol/IntegrationSuite.scala index 89169b1..a5bcdee 100644 --- a/server/src/it/scala/com/github/nullptr7/protocol/IntegrationSuite.scala +++ b/server/src/it/scala/com/github/nullptr7/protocol/IntegrationSuite.scala @@ -1,8 +1,11 @@ package com.github.nullptr7 package protocol +import java.net.URI import java.util.UUID +import scala.concurrent.duration.DurationInt + import org.specs2.mutable.SpecificationLike import org.typelevel.log4cats.Logger import org.typelevel.log4cats.slf4j.Slf4jLogger @@ -19,12 +22,15 @@ import sttp.tapir.server.stub.TapirStubInterpreter import fs2.io.net.Network +import client.{ApiClients, TransportServiceClient} +import client.module.BlazeClientModule +import configurations.types.{ClientConfig, Sensitive, TransportApiClientDetails} +import entrypoint.modules.{RepositoryModule, ServiceLogicModule} import helper.PostgresSessionHelper import models._ import models.codecs._ -import storage._ -class IntegrationSuite extends CatsResource[IO, ServiceLogic[IO]] with SpecificationLike with CatsEffect with PostgresSessionHelper[IO] { +class IntegrationSuite extends CatsResource[IO, ServiceLogicModule[IO]] with SpecificationLike with CatsEffect with PostgresSessionHelper[IO] { sequential implicit override val concurrent: Concurrent[IO] = IO.asyncForIO implicit override val console: std.Console[IO] = IO.consoleForIO @@ -33,16 +39,36 @@ class IntegrationSuite extends CatsResource[IO, ServiceLogic[IO]] with Specifica implicit val logger: Logger[IO] = Slf4jLogger.getLogger[IO] private val employeeId1: EmployeeId = EmployeeId(1) - private val employeeCode1: EmployeeCode = EmployeeCode(UUID.fromString("75b107cb-2bef-431f-8b33-b2074d51bd08")) + private val employeeCode1: EmployeeCode = EmployeeCode(UUID.fromString("4a5f132a-084b-445f-b0b0-3e1f1f36521c")) private val addressId: AddressId = AddressId(UUID.fromString("20d88c49-01e9-40d0-b568-982100e676ba")) - override val resource: Resource[IO, ServiceLogic[IO]] = + override val resource: Resource[IO, ServiceLogicModule[IO]] = for { session <- sessionR - serviceLogic <- Resource.pure[IO, ServiceLogic[IO]]( - new ServiceLogic[IO]( - EmployeeRepository.apply[IO](session), - AddressRepository.apply[IO](session) + module <- RepositoryModule.make[IO](session) + client <- BlazeClientModule[IO].make( + ClientConfig( + 30.seconds, + TransportApiClientDetails( + URI.create("https://2ff8313d-c02d-4113-9768-501060fa697d.mock.pstmn.io/api/get/employee-transport-data"), + "scott", + Sensitive("tiger") + ) + ) + ) + serviceLogic <- Resource.pure[IO, ServiceLogicModule[IO]]( + new ServiceLogicModule[IO]( + module, + ApiClients( + new TransportServiceClient[IO]( + client, + TransportApiClientDetails( + URI.create("https://2ff8313d-c02d-4113-9768-501060fa697d.mock.pstmn.io/api/get/employee-transport-data"), + "scott", + Sensitive("tiger") + ) + ) + ) ) ) } yield serviceLogic @@ -89,18 +115,26 @@ class IntegrationSuite extends CatsResource[IO, ServiceLogic[IO]] with Specifica basicRequest .get(uri"http://localhost:8080/employees/get/all") .header("X-AuthMode", "admin") - .response(asJson[List[Employee]]) + .response(asJson[List[EmployeeWithTransport]]) .send(allEmployeeEndpointStub) }.map { resp => lazy val paul = - Employee( - id = employeeId1, - code = employeeCode1, - name = "Paul", - age = 32, - salary = 20000.0, - address = Address(addressId, "Some Street Name", "Some City", "Some State", "123456") + EmployeeWithTransport( + Employee( + id = employeeId1, + code = employeeCode1, + name = "Paul", + age = 32, + salary = 20000.0, + address = Address(addressId, "Some Street Name", "Some City", "Some State", "123456") + ), + transportDetails = TransportResponse( + employeeCode = employeeCode1, + routes = 1, + numberOfNoShows = 4, + shift = DAY + ) ) resp.body must beRight diff --git a/server/src/main/resources/dbInit.sql b/server/src/main/resources/dbInit.sql index 9e5e503..de504da 100644 --- a/server/src/main/resources/dbInit.sql +++ b/server/src/main/resources/dbInit.sql @@ -31,7 +31,7 @@ INSERT INTO VALUES ( 1, - '75b107cb-2bef-431f-8b33-b2074d51bd08', + '4a5f132a-084b-445f-b0b0-3e1f1f36521c', 'Paul', 32, 20000.00, diff --git a/server/src/main/resources/reference.conf b/server/src/main/resources/reference.conf index 72dc33d..2d270ca 100644 --- a/server/src/main/resources/reference.conf +++ b/server/src/main/resources/reference.conf @@ -9,4 +9,13 @@ db: { server: { host: "localhost" port: 8080 +} + +client { + timeout: 3000 + transport: { + url: "https://2ff8313d-c02d-4113-9768-501060fa697d.mock.pstmn.io/api/get/employee-transport-data" + username: scott + password: tiger + } } \ No newline at end of file diff --git a/server/src/main/scala/com/github/nullptr7/entrypoint/Startup.scala b/server/src/main/scala/com/github/nullptr7/entrypoint/Startup.scala index c4c472f..ffd26fc 100644 --- a/server/src/main/scala/com/github/nullptr7/entrypoint/Startup.scala +++ b/server/src/main/scala/com/github/nullptr7/entrypoint/Startup.scala @@ -1,21 +1,54 @@ package com.github.nullptr7 package entrypoint -import org.typelevel.log4cats.Logger -import org.typelevel.log4cats.slf4j.Slf4jLogger - -import cats.effect.{IO, IOApp} - -import natchez.Trace.Implicits.noop - -import modules.BlazeServerModule - -object Startup extends BlazeServerModule[IO] with IOApp.Simple { - - override val logger: Logger[IO] = Slf4jLogger.getLogger[IO] - - override def run: IO[Unit] = - logger.info("Starting Tapir Crud Application...") *> - server.use(_ => IO.never[Unit]) +import org.http4s.client.Client + +import cats.effect.IOApp +import cats.effect.kernel.Resource + +import client.module.BlazeClientModule +import client.{ApiClients, TransportServiceClient} +import configurations.types.{ClientConfig, TransportApiClientDetails} +import modules.{ApplicationResourceModule, BlazeServerModuleV2, RepositoryModule, ServiceLogicModule} +import storage.DatabaseSession + +object Startup extends IOApp.Simple with ApplicationResourceModule { + + import cats.effect.IO + import natchez.Trace.Implicits.noop + import org.typelevel.log4cats.Logger + import org.typelevel.log4cats.slf4j.Slf4jLogger + + implicit val logger: Logger[IO] = Slf4jLogger.getLogger[IO] + + override def run: IO[Unit] = { + val app = for { + res <- appResources[IO] + session <- DatabaseSession[IO].make(res.databaseConfig) + repo <- RepositoryModule.make[IO](session) + server <- BlazeServerModuleV2.make[IO](res.serverConfig) + httpClient <- BlazeClientModule[IO].make(res.clientConfig) + clients <- initializeClient[IO](res.clientConfig, httpClient) + logic <- new ServiceLogicModule[IO](repo, clients).make + routes <- Routes.make[IO](logic) + serve <- server.serve(routes) + } yield serve + app.useForever + } + + def initializeClient[F[_]](app: ClientConfig, client: Client[F]): Resource[F, ApiClients[F]] = + Resource + .pure( + ApiClients[F]( + new TransportServiceClient[F]( + client, + TransportApiClientDetails( + app.transport.url, + app.transport.username, + app.transport.password + ) + ) + ) + ) } diff --git a/server/src/main/scala/com/github/nullptr7/entrypoint/modules/ApplicationResourceModule.scala b/server/src/main/scala/com/github/nullptr7/entrypoint/modules/ApplicationResourceModule.scala index 04a581a..a77557a 100644 --- a/server/src/main/scala/com/github/nullptr7/entrypoint/modules/ApplicationResourceModule.scala +++ b/server/src/main/scala/com/github/nullptr7/entrypoint/modules/ApplicationResourceModule.scala @@ -8,17 +8,17 @@ import cats.implicits._ import pureconfig.generic.auto.exportReader import configurations.ApplicationResources -import configurations.types.ConfigType.{Postgres, Blaze} -import configurations.types.DatabaseConfig -import configurations.types.ServerConfig +import configurations.types.ConfigType.{Client, Server, Postgres} +import configurations.types.{ClientConfig, DatabaseConfig, ServerConfig} import helpers.ConfigLoader trait ApplicationResourceModule { - final private[modules] def appResources[F[_]: Async]: Resource[F, ApplicationResources] = + final def appResources[F[_]: Async]: Resource[F, ApplicationResources] = ( ConfigLoader[F].load[DatabaseConfig, Postgres.type], - ConfigLoader[F].load[ServerConfig, Blaze.type] + ConfigLoader[F].load[ServerConfig, Server.type], + ConfigLoader[F].load[ClientConfig, Client.type] ).parMapN(ApplicationResources) } diff --git a/server/src/main/scala/com/github/nullptr7/entrypoint/modules/BlazeServerModule.scala b/server/src/main/scala/com/github/nullptr7/entrypoint/modules/BlazeServerModule.scala index 6b91863..7fbb307 100644 --- a/server/src/main/scala/com/github/nullptr7/entrypoint/modules/BlazeServerModule.scala +++ b/server/src/main/scala/com/github/nullptr7/entrypoint/modules/BlazeServerModule.scala @@ -6,27 +6,26 @@ import org.http4s.HttpApp import org.http4s.blaze.server.BlazeServerBuilder import org.http4s.server.Server -import cats.effect.kernel.{Async, Resource} -import cats.effect.std - -import fs2.io.net.Network -import natchez.Trace +import cats.effect.Resource +import cats.effect.kernel.Async import configurations.types.ServerConfig -abstract class BlazeServerModule[F[_]: Async: std.Console: Network: Trace] extends RoutingModule[F] with ApplicationResourceModule { - - final protected[entrypoint] lazy val server: Resource[F, Server] = - for { - app <- appResources - routes <- withRoutes(app.databaseConfig) - serve <- withServer(routes, app.serverConfig) - } yield serve +abstract class BlazeServerModuleV2[F[_]: Async](serverConfig: ServerConfig) { - final private[this] def withServer(routes: HttpApp[F], serverConfig: ServerConfig): Resource[F, Server] = + final def serve(route: HttpApp[F]): Resource[F, Server] = BlazeServerBuilder[F] + .withHttpApp(route) .bindHttp(serverConfig.port.value, serverConfig.host.value) - .withHttpApp(routes) .resource } + +object BlazeServerModuleV2 { + + def make[F[_]: Async](serverConfig: ServerConfig): Resource[F, BlazeServerModuleV2[F]] = + Resource.pure( + new BlazeServerModuleV2[F](serverConfig) {} + ) + +} diff --git a/server/src/main/scala/com/github/nullptr7/entrypoint/modules/RepositoryModule.scala b/server/src/main/scala/com/github/nullptr7/entrypoint/modules/RepositoryModule.scala new file mode 100644 index 0000000..67ea244 --- /dev/null +++ b/server/src/main/scala/com/github/nullptr7/entrypoint/modules/RepositoryModule.scala @@ -0,0 +1,39 @@ +package com.github.nullptr7 +package entrypoint +package modules + +import org.typelevel.log4cats.Logger + +import cats.effect.kernel.{Async, Resource} + +import skunk.Session + +import models._ +import storage.{AddressRepository, EmployeeRepository} + +abstract class RepositoryModule[F[_]] private ( + employeeRepository: EmployeeRepository[F], + addressRepository: AddressRepository[F] +) { + + def findAllEmployees: F[List[Employee]] = employeeRepository.findAllEmployees + + def addEmployee(createEmployee: CreateEmployee): F[EmployeeId] = employeeRepository.addEmployee(createEmployee) + + def findEmployeeById(id: Long): F[Option[Employee]] = employeeRepository.findById(id) + + def addAddress(createAddress: CreateAddress): F[AddressId] = addressRepository.addAddress(createAddress) + + def findAddressById(id: AddressId): F[Option[Address]] = addressRepository.findAddressById(id) + + def findAddressByZip(pincode: String): F[Option[Address]] = addressRepository.findAddressByZip(pincode) +} + +object RepositoryModule { + + def make[F[_]: Async: Logger](session: Session[F]): Resource[F, RepositoryModule[F]] = + Resource.pure( + new RepositoryModule[F](EmployeeRepository(session), AddressRepository(session)) {} + ) + +} diff --git a/server/src/main/scala/com/github/nullptr7/entrypoint/modules/RoutingModule.scala b/server/src/main/scala/com/github/nullptr7/entrypoint/modules/RoutingModule.scala deleted file mode 100644 index 13753fa..0000000 --- a/server/src/main/scala/com/github/nullptr7/entrypoint/modules/RoutingModule.scala +++ /dev/null @@ -1,36 +0,0 @@ -package com.github.nullptr7 -package entrypoint -package modules - -import org.http4s.HttpApp -import org.typelevel.log4cats.Logger - -import cats.effect._ - -import skunk.Session - -import fs2.io.net.Network -import natchez.Trace - -import storage._ -import protocol.ServiceLogic -import configurations.types._ - -abstract class RoutingModule[F[_]: Async: std.Console: Trace: Network] { - - implicit val logger: Logger[F] - - final private[modules] def withRoutes(dbConfig: DatabaseConfig): Resource[F, HttpApp[F]] = - for { - session <- DatabaseSession[F].make(dbConfig) - serverLogic <- initServiceLogic(session).make - routes <- Routes.make[F](serverLogic) - } yield routes - - private[this] def initServiceLogic(session: Session[F]): ServiceLogic[F] = { - lazy val employeeRepo = EmployeeRepository(session) - lazy val addressRepo = AddressRepository(session) - new ServiceLogic[F](employeeRepo, addressRepo) - } - -} diff --git a/server/src/main/scala/com/github/nullptr7/entrypoint/modules/ServiceLogicModule.scala b/server/src/main/scala/com/github/nullptr7/entrypoint/modules/ServiceLogicModule.scala new file mode 100644 index 0000000..70ef44c --- /dev/null +++ b/server/src/main/scala/com/github/nullptr7/entrypoint/modules/ServiceLogicModule.scala @@ -0,0 +1,100 @@ +package com.github.nullptr7 +package entrypoint +package modules + +import java.util.UUID + +import org.http4s.circe.CirceEntityCodec.{circeEntityDecoder, circeEntityEncoder} +import org.typelevel.log4cats.Logger + +import cats.effect.Resource +import cats.effect.kernel.Async + +import io.circe.syntax.EncoderOps + +import client.ApiClients +import exceptions.ErrorResponse._ +import models.{AddressId, EmployeeWithTransport, TransportRequest, TransportResponse} +import protocol._ + +final class ServiceLogicModule[F[_]: Async: Logger](val repositoryModule: RepositoryModule[F], val clients: ApiClients[F]) + extends EmployeeContracts[F] + with AddressContracts[F] { + + import cats.implicits._ + import clients._ + + private def logAuthModeRequest(authMode: AuthMode): F[Unit] = Logger[F].info(s"Requested with auth mode as ${authMode.toString}") + + lazy val addEmployeeEndpoint: ServerEndpointF = + addEmployeeEP.serverLogicRecoverErrors[F] { case (authMode, employeeBody) => + handle(authMode)(repositoryModule.addEmployee(employeeBody)) + } + + lazy val allEmployeeEndpoint: ServerEndpointF = + allEmployeesEP.serverLogicRecoverErrors[F](handle(_) { + + for { + a <- repositoryModule.findAllEmployees + b <- a.traverse { emp => + import models.codecs._ + Logger[F].info(s"sending request ${TransportRequest(emp.code).asJson.toString}") *> + Logger[F].info(s"emp - $emp") *> + transportServiceClient + .sendAndReceive[TransportRequest, TransportResponse](Some(TransportRequest(emp.code))) + .handleErrorWith { t: Throwable => + Logger[F].warn(t)("Error Received from the server") *> + TransportResponse.apply(emp.code).pure[F] + } + .map(EmployeeWithTransport.apply(emp, _)) + } + } yield b + }) + + lazy val empByIdEndpoint: ServerEndpointF = + employeeEP.serverLogicRecoverErrors[F] { case (authMode, id) => + handle(authMode)(repositoryModule.findEmployeeById(id.toLong)) + } + + lazy val addressByIdEndpoint: ServerEndpointF = + addressById.serverLogicRecoverErrors[F] { case (authMode, id) => + handle(authMode)(repositoryModule.findAddressById(AddressId(UUID.fromString(id)))) + } + + lazy val addressByZipEndpoint: ServerEndpointF = + addressByPincode.serverLogicRecoverErrors[F] { case (authMode, pincode) => + handle(authMode)(repositoryModule.findAddressByZip(pincode)) + } + + lazy val addAddressEndpoint: ServerEndpointF = + addAddress.serverLogicRecoverErrors[F] { case (authMode, address) => + handle(authMode)(repositoryModule.addAddress(address)) + } + + override val make: Resource[F, List[ServerEndpointF]] = + Resource + .pure( + List( + allEmployeeEndpoint, + empByIdEndpoint, + addressByIdEndpoint, + addressByZipEndpoint, + addAddressEndpoint, + addEmployeeEndpoint + ) + ) + .evalTap(_ => Logger[F].info("Loading endpoints...")) + + private[this] def handle[O](authMode: AuthMode)(fo: => F[O]): F[O] = + logAuthModeRequest(authMode) *> (authMode match { + case Admin => + fo.attemptTap { + case Left(t) => Logger[F].error(t.getMessage) *> GenericException("Internal Server Error").raiseError[F, O] + case Right(value) => value.pure[F] + } + case MissingAuthMode => MissingAuthException.raiseError[F, O] + case NonAdmin => UnauthorizedAuthException.raiseError[F, O] + case InvalidMode => InvalidAuthException.raiseError[F, O] + }) + +} diff --git a/server/src/main/scala/com/github/nullptr7/protocol/Contracts.scala b/server/src/main/scala/com/github/nullptr7/protocol/Contracts.scala index 0d866a7..b1132fa 100644 --- a/server/src/main/scala/com/github/nullptr7/protocol/Contracts.scala +++ b/server/src/main/scala/com/github/nullptr7/protocol/Contracts.scala @@ -16,5 +16,5 @@ trait Contracts[F[_]] { .in("employees") .in(header[AuthMode]("X-AuthMode").default(MissingAuthMode)) - protected[protocol] val make: Resource[F, List[ServerEndpointF]] + val make: Resource[F, List[ServerEndpointF]] } diff --git a/server/src/main/scala/com/github/nullptr7/protocol/EmployeeContracts.scala b/server/src/main/scala/com/github/nullptr7/protocol/EmployeeContracts.scala index 00d451a..03a4142 100644 --- a/server/src/main/scala/com/github/nullptr7/protocol/EmployeeContracts.scala +++ b/server/src/main/scala/com/github/nullptr7/protocol/EmployeeContracts.scala @@ -12,14 +12,14 @@ import ErrorResponse._ trait EmployeeContracts[F[_]] extends Contracts[F] { - protected[protocol] lazy val allEmployeesEP: Endpoint[Unit, AuthMode, ServiceResponseException, List[Employee], Any] = + lazy val allEmployeesEP: Endpoint[Unit, AuthMode, ServiceResponseException, List[EmployeeWithTransport], Any] = base .get .in("get" / "all") - .out(jsonBody[List[Employee]]) + .out(jsonBody[List[EmployeeWithTransport]]) .errorOut(jsonBody[ServiceResponseException]) - protected[protocol] lazy val employeeEP: Endpoint[Unit, (AuthMode, String), ServiceResponseException, Option[Employee], Any] = + lazy val employeeEP: Endpoint[Unit, (AuthMode, String), ServiceResponseException, Option[Employee], Any] = base .get .in("get" / "employee") @@ -35,7 +35,7 @@ trait EmployeeContracts[F[_]] extends Contracts[F] { // ) // ) - protected[protocol] lazy val addEmployeeEP = + lazy val addEmployeeEP = base .post .in("add" / "employee") diff --git a/server/src/main/scala/com/github/nullptr7/protocol/ServiceLogic.scala b/server/src/main/scala/com/github/nullptr7/protocol/ServiceLogic.scala deleted file mode 100644 index 2da6c81..0000000 --- a/server/src/main/scala/com/github/nullptr7/protocol/ServiceLogic.scala +++ /dev/null @@ -1,78 +0,0 @@ -package com.github.nullptr7 -package protocol - -import java.util.UUID - -import org.typelevel.log4cats.Logger - -import cats.effect.{Async, Resource} - -import exceptions.ErrorResponse._ -import storage.{AddressRepository, EmployeeRepository} -import models.AddressId - -class ServiceLogic[F[_]: Async: Logger]( - private[protocol] val employeeRepo: EmployeeRepository[F], - private[protocol] val addressRepo: AddressRepository[F] -) extends EmployeeContracts[F] - with AddressContracts[F] { - - import cats.implicits._ - - private def logAuthModeRequest(authMode: AuthMode): F[Unit] = Logger[F].info(s"Requested with auth mode as ${authMode.toString}") - - private[protocol] lazy val addEmployeeEndpoint: ServerEndpointF = - addEmployeeEP.serverLogicRecoverErrors[F] { case (authMode, employeeBody) => - handle(authMode)(employeeRepo.addEmployee(employeeBody)) - } - - private[protocol] lazy val allEmployeeEndpoint: ServerEndpointF = - allEmployeesEP.serverLogicRecoverErrors[F](handle(_)(employeeRepo.findAllEmployees)) - - private[protocol] lazy val empByIdEndpoint: ServerEndpointF = - employeeEP.serverLogicRecoverErrors[F] { case (authMode, id) => - handle(authMode)(employeeRepo.findById(id.toLong)) - } - - private[protocol] lazy val addressByIdEndpoint: ServerEndpointF = - addressById.serverLogicRecoverErrors[F] { case (authMode, id) => - handle(authMode)(addressRepo.findAddressById(AddressId(UUID.fromString(id)))) - } - - private[protocol] lazy val addressByZipEndpoint: ServerEndpointF = - addressByPincode.serverLogicRecoverErrors[F] { case (authMode, pincode) => - handle(authMode)(addressRepo.findAddressByZip(pincode)) - } - - private[protocol] lazy val addAddressEndpoint: ServerEndpointF = - addAddress.serverLogicRecoverErrors[F] { case (authMode, address) => - handle(authMode)(addressRepo.addAddress(address)) - } - - override val make: Resource[F, List[ServerEndpointF]] = - Resource - .pure( - List( - allEmployeeEndpoint, - empByIdEndpoint, - addressByIdEndpoint, - addressByZipEndpoint, - addAddressEndpoint, - addEmployeeEndpoint - ) - ) - .evalTap(_ => Logger[F].info("Loading endpoints...")) - - private[this] def handle[O](authMode: AuthMode)(fo: => F[O]): F[O] = - logAuthModeRequest(authMode) *> (authMode match { - case Admin => - fo.attemptTap { - case Left(t) => Logger[F].error(t.getMessage) *> GenericException("Internal Server Error").raiseError[F, O] - case Right(value) => value.pure[F] - } - case MissingAuthMode => MissingAuthException.raiseError[F, O] - case NonAdmin => UnauthorizedAuthException.raiseError[F, O] - case InvalidMode => InvalidAuthException.raiseError[F, O] - }) - -} diff --git a/server/src/test/scala/com/github/nullptr7/protocol/AddressServiceLogicTest.scala b/server/src/test/scala/com/github/nullptr7/protocol/AddressServiceLogicTest.scala index 0c63696..b6c6154 100644 --- a/server/src/test/scala/com/github/nullptr7/protocol/AddressServiceLogicTest.scala +++ b/server/src/test/scala/com/github/nullptr7/protocol/AddressServiceLogicTest.scala @@ -29,7 +29,7 @@ import mocks.data._ class AddressServiceLogicTest extends BaseTest with ServiceLogicTestHelper[IO] { - import serviceLogic._ + import serverLogicModule._ implicit override protected def async: Async[IO] = IO.asyncForIO implicit override protected def logger: Logger[IO] = Slf4jLogger.getLogger[IO] @@ -54,7 +54,7 @@ class AddressServiceLogicTest extends BaseTest with ServiceLogicTestHelper[IO] { "Address Service By ID endpoint" should "work when admin and valid id" in { - when(serviceLogic.addressRepo.findAddressById(addressId)) + when(serverLogicModule.repositoryModule.findAddressById(addressId)) .thenReturn(IO.pure(allAddresses.find(_.id == addressId))) val response = basicRequest @@ -78,7 +78,7 @@ class AddressServiceLogicTest extends BaseTest with ServiceLogicTestHelper[IO] { it should "return empty response when admin and incorrect id" in { - when(serviceLogic.addressRepo.findAddressById(addressId)) + when(serverLogicModule.repositoryModule.findAddressById(addressId)) .thenReturn(IO.pure(Option.empty[Address])) val response = basicRequest @@ -123,7 +123,7 @@ class AddressServiceLogicTest extends BaseTest with ServiceLogicTestHelper[IO] { "Address Service By Zip endpoint" should "work when admin and valid zip" in { - when(serviceLogic.addressRepo.findAddressByZip("12345")) + when(serverLogicModule.repositoryModule.findAddressByZip("12345")) .thenReturn(IO.pure(allAddresses.find(_.zip.toInt == 12345))) val response = basicRequest @@ -147,7 +147,7 @@ class AddressServiceLogicTest extends BaseTest with ServiceLogicTestHelper[IO] { it should "return empty response when admin and incorrect zip" in { - when(serviceLogic.addressRepo.findAddressByZip("963963963")) + when(serverLogicModule.repositoryModule.findAddressByZip("963963963")) .thenReturn(IO.pure(Option.empty[Address])) val response = basicRequest @@ -178,7 +178,7 @@ class AddressServiceLogicTest extends BaseTest with ServiceLogicTestHelper[IO] { val givenUUID = UUID.randomUUID() val addressInRequest = CreateAddress("street", "city", "state", "123456") - when(serviceLogic.addressRepo.addAddress(addressInRequest)) + when(serverLogicModule.repositoryModule.addAddress(addressInRequest)) .thenReturn(IO.pure(AddressId(givenUUID))) val response = basicRequest diff --git a/server/src/test/scala/com/github/nullptr7/protocol/EmployeeServiceLogicTest.scala b/server/src/test/scala/com/github/nullptr7/protocol/EmployeeServiceLogicTest.scala index bcf08c1..013c207 100644 --- a/server/src/test/scala/com/github/nullptr7/protocol/EmployeeServiceLogicTest.scala +++ b/server/src/test/scala/com/github/nullptr7/protocol/EmployeeServiceLogicTest.scala @@ -1,13 +1,21 @@ package com.github.nullptr7 package protocol -import org.mockito.MockitoSugar.when +import java.net.URI +import java.util.UUID + +import org.http4s +import org.http4s.Status +import org.http4s.circe.CirceEntityCodec.circeEntityEncoder +import org.http4s.client.Client +import org.mockito.ArgumentMatchers.any +import org.mockito.MockitoSugar.{mock, when} import org.typelevel.log4cats.Logger import org.typelevel.log4cats.slf4j.Slf4jLogger -import cats.effect.IO import cats.effect.kernel.Async import cats.effect.unsafe.implicits.global +import cats.effect.{IO, Resource} import sttp.client3._ import sttp.client3.circe._ @@ -19,15 +27,17 @@ import sttp.tapir.server.stub.TapirStubInterpreter import io.circe.parser._ import io.circe.syntax.EncoderOps -import models.codecs._ +import client.TransportServiceClient +import configurations.types.{Sensitive, TransportApiClientDetails} +import common.BaseTest import exceptions.ErrorResponse._ import mocks.data._ -import common.BaseTest +import models.codecs._ class EmployeeServiceLogicTest extends BaseTest with ServiceLogicTestHelper[IO] { - import serviceLogic._ import models._ + import serverLogicModule._ implicit override protected def logger: Logger[IO] = Slf4jLogger.getLogger[IO] implicit override protected def async: Async[IO] = IO.asyncForIO @@ -52,9 +62,38 @@ class EmployeeServiceLogicTest extends BaseTest with ServiceLogicTestHelper[IO] "All Employee Endpoint with authMode header" should "work when admin" in { - when(serviceLogic.employeeRepo.findAllEmployees) + when(serverLogicModule.repositoryModule.findAllEmployees) .thenReturn(IO.pure(allEmployees)) + import models.DAY + + val client = mock[Client[IO]] + + when(client.run(any[http4s.Request[IO]])) + .thenReturn( + Resource.pure[IO, http4s.Response[IO]]( + http4s + .Response[IO]( + Status.Ok + ) + .withEntity( + TransportResponse(EmployeeCode(UUID.randomUUID()), 1, 1, DAY) + ) + ) + ) + + when(serverLogicModule.clients.transportServiceClient) + .thenReturn( + new TransportServiceClient[IO]( + client, + TransportApiClientDetails( + URI.create("https://2ff8313d-c02d-4113-9768-501060fa697d.mock.pstmn.io/api/get/employee-transport-data"), + "scott", + Sensitive("tiger") + ) + ) + ) + val response = basicRequest .get(uri"http://localhost:8080/employees/get/all") .header("X-AuthMode", "admin") @@ -99,7 +138,7 @@ class EmployeeServiceLogicTest extends BaseTest with ServiceLogicTestHelper[IO] "EmployeeById endpoint with authMode header" should "work when admin and valid id" in { - when(serviceLogic.employeeRepo.findById(1)) + when(serverLogicModule.repositoryModule.findEmployeeById(1)) .thenReturn(IO.pure(allEmployees.find(_.id == employeeId1))) // when val response = basicRequest @@ -119,7 +158,7 @@ class EmployeeServiceLogicTest extends BaseTest with ServiceLogicTestHelper[IO] it should "return none when the authMode is admin but id is not available from the list" in { - when(serviceLogic.employeeRepo.findById(9)) + when(serverLogicModule.repositoryModule.findEmployeeById(9)) .thenReturn(IO.pure(Option.empty[Employee])) // when @@ -169,8 +208,8 @@ class EmployeeServiceLogicTest extends BaseTest with ServiceLogicTestHelper[IO] val scott = CreateEmployee("Scott", 12, 10.0, CreateAddress("Street1", "City1", "State1", "123456")) when( - serviceLogic - .employeeRepo + serverLogicModule + .repositoryModule .addEmployee(scott) ) .thenReturn(IO.pure(EmployeeId(1))) diff --git a/server/src/test/scala/com/github/nullptr7/protocol/ServiceLogicTestHelper.scala b/server/src/test/scala/com/github/nullptr7/protocol/ServiceLogicTestHelper.scala index 556779f..599feae 100644 --- a/server/src/test/scala/com/github/nullptr7/protocol/ServiceLogicTestHelper.scala +++ b/server/src/test/scala/com/github/nullptr7/protocol/ServiceLogicTestHelper.scala @@ -6,7 +6,8 @@ import org.typelevel.log4cats.Logger import cats.effect.kernel.Async -import storage._ +import client.ApiClients +import entrypoint.modules.{RepositoryModule, ServiceLogicModule} trait ServiceLogicTestHelper[F[_]] { @@ -14,10 +15,7 @@ trait ServiceLogicTestHelper[F[_]] { implicit protected def async: Async[F] - protected lazy val serviceLogic: ServiceLogic[F] = - new ServiceLogic[F]( - mock[EmployeeRepository[F]], - mock[AddressRepository[F]] - ) + protected lazy val serverLogicModule: ServiceLogicModule[F] = + new ServiceLogicModule[F](mock[RepositoryModule[F]], mock[ApiClients[F]]) } From 579416783a0abf7693e66c3ede5de5b5c1481394 Mon Sep 17 00:00:00 2001 From: Ishan Shah Date: Sat, 20 Aug 2022 14:41:57 +0530 Subject: [PATCH 02/10] Removing unwanted file --- .../com/github/nullptr7/client/TimePass.scala | 49 ------------------- 1 file changed, 49 deletions(-) delete mode 100644 client/src/main/scala/com/github/nullptr7/client/TimePass.scala diff --git a/client/src/main/scala/com/github/nullptr7/client/TimePass.scala b/client/src/main/scala/com/github/nullptr7/client/TimePass.scala deleted file mode 100644 index 4a2eb55..0000000 --- a/client/src/main/scala/com/github/nullptr7/client/TimePass.scala +++ /dev/null @@ -1,49 +0,0 @@ -package com.github.nullptr7 -package client - -import org.http4s.Uri -import org.http4s.blaze.client.BlazeClientBuilder -import org.http4s.client.Client - -import cats.effect.IO -import cats.effect.kernel.Resource -import cats.effect.unsafe.implicits.global - -import sttp.tapir._ -import sttp.tapir.client.http4s.Http4sClientInterpreter -import sttp.tapir.generic.auto._ -import sttp.tapir.json.circe.jsonBody - -import io.circe.generic.auto.{exportDecoder, exportEncoder} - -object TimePass extends App { - - private val value: EndpointInput[Unit] = "api" / "get" / "employee-transport-data" - - case class Something(code: String) - - private val userEndpoint: Endpoint[Unit, Something, Unit, String, Any] = - endpoint - .post - .in(value) - .in(header("x-mock-match-request-body", "true")) - .in(jsonBody[Something]) - .out(stringBody) - - val (req, resp) = - Http4sClientInterpreter[IO]() - .toRequest(userEndpoint, Some(Uri.unsafeFromString("https://2ff8313d-c02d-4113-9768-501060fa697d.mock.pstmn.io"))) - .apply(Something("4a5f132a-084b-445f-b0b0-3e1f1f36521c")) - - val client: Resource[IO, Client[IO]] = BlazeClientBuilder[IO].resource - - println( - client - .use( - _.run(req) - .use(resp) - ) - .unsafeRunSync() - ) - -} From 986c3f141d9753b578bdbf5cdf00bbaa6bfbf5b3 Mon Sep 17 00:00:00 2001 From: Ishan Shah Date: Sat, 20 Aug 2022 14:45:59 +0530 Subject: [PATCH 03/10] Removing duplicate loggers --- .../modules/ServiceLogicModule.scala | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/server/src/main/scala/com/github/nullptr7/entrypoint/modules/ServiceLogicModule.scala b/server/src/main/scala/com/github/nullptr7/entrypoint/modules/ServiceLogicModule.scala index 70ef44c..7acb5f5 100644 --- a/server/src/main/scala/com/github/nullptr7/entrypoint/modules/ServiceLogicModule.scala +++ b/server/src/main/scala/com/github/nullptr7/entrypoint/modules/ServiceLogicModule.scala @@ -35,20 +35,19 @@ final class ServiceLogicModule[F[_]: Async: Logger](val repositoryModule: Reposi allEmployeesEP.serverLogicRecoverErrors[F](handle(_) { for { - a <- repositoryModule.findAllEmployees - b <- a.traverse { emp => - import models.codecs._ - Logger[F].info(s"sending request ${TransportRequest(emp.code).asJson.toString}") *> - Logger[F].info(s"emp - $emp") *> - transportServiceClient - .sendAndReceive[TransportRequest, TransportResponse](Some(TransportRequest(emp.code))) - .handleErrorWith { t: Throwable => - Logger[F].warn(t)("Error Received from the server") *> - TransportResponse.apply(emp.code).pure[F] - } - .map(EmployeeWithTransport.apply(emp, _)) - } - } yield b + allEmployees <- repositoryModule.findAllEmployees + employeesWithTransportDetails <- allEmployees.traverse { emp => + import models.codecs._ + Logger[F].info(s"sending request ${TransportRequest(emp.code).asJson.toString}") *> + transportServiceClient + .sendAndReceive[TransportRequest, TransportResponse](Some(TransportRequest(emp.code))) + .handleErrorWith { t: Throwable => + Logger[F].warn(t)("Error Received from the server") *> + TransportResponse.apply(emp.code).pure[F] + } + .map(EmployeeWithTransport.apply(emp, _)) + } + } yield employeesWithTransportDetails }) lazy val empByIdEndpoint: ServerEndpointF = From 4bb7e3cbc8ab628a06029be339e1deae840ca4da Mon Sep 17 00:00:00 2001 From: Ishan Shah Date: Sat, 20 Aug 2022 14:51:07 +0530 Subject: [PATCH 04/10] Update logging while sending transport request --- .../nullptr7/entrypoint/modules/ServiceLogicModule.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/server/src/main/scala/com/github/nullptr7/entrypoint/modules/ServiceLogicModule.scala b/server/src/main/scala/com/github/nullptr7/entrypoint/modules/ServiceLogicModule.scala index 7acb5f5..69b4535 100644 --- a/server/src/main/scala/com/github/nullptr7/entrypoint/modules/ServiceLogicModule.scala +++ b/server/src/main/scala/com/github/nullptr7/entrypoint/modules/ServiceLogicModule.scala @@ -10,8 +10,6 @@ import org.typelevel.log4cats.Logger import cats.effect.Resource import cats.effect.kernel.Async -import io.circe.syntax.EncoderOps - import client.ApiClients import exceptions.ErrorResponse._ import models.{AddressId, EmployeeWithTransport, TransportRequest, TransportResponse} @@ -38,7 +36,7 @@ final class ServiceLogicModule[F[_]: Async: Logger](val repositoryModule: Reposi allEmployees <- repositoryModule.findAllEmployees employeesWithTransportDetails <- allEmployees.traverse { emp => import models.codecs._ - Logger[F].info(s"sending request ${TransportRequest(emp.code).asJson.toString}") *> + Logger[F].info(s"Finding transport details for ${emp.code.value}") *> transportServiceClient .sendAndReceive[TransportRequest, TransportResponse](Some(TransportRequest(emp.code))) .handleErrorWith { t: Throwable => From b7b37ad0b0ed7be6e4ddf9d9a8d36f48b002a063 Mon Sep 17 00:00:00 2001 From: Ishan Shah Date: Sat, 20 Aug 2022 14:58:36 +0530 Subject: [PATCH 05/10] Reducing the indentation by removing for-comprehension usage --- .../modules/ServiceLogicModule.scala | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/server/src/main/scala/com/github/nullptr7/entrypoint/modules/ServiceLogicModule.scala b/server/src/main/scala/com/github/nullptr7/entrypoint/modules/ServiceLogicModule.scala index 69b4535..5fe9be8 100644 --- a/server/src/main/scala/com/github/nullptr7/entrypoint/modules/ServiceLogicModule.scala +++ b/server/src/main/scala/com/github/nullptr7/entrypoint/modules/ServiceLogicModule.scala @@ -32,20 +32,19 @@ final class ServiceLogicModule[F[_]: Async: Logger](val repositoryModule: Reposi lazy val allEmployeeEndpoint: ServerEndpointF = allEmployeesEP.serverLogicRecoverErrors[F](handle(_) { - for { - allEmployees <- repositoryModule.findAllEmployees - employeesWithTransportDetails <- allEmployees.traverse { emp => - import models.codecs._ - Logger[F].info(s"Finding transport details for ${emp.code.value}") *> - transportServiceClient - .sendAndReceive[TransportRequest, TransportResponse](Some(TransportRequest(emp.code))) - .handleErrorWith { t: Throwable => - Logger[F].warn(t)("Error Received from the server") *> - TransportResponse.apply(emp.code).pure[F] - } - .map(EmployeeWithTransport.apply(emp, _)) - } - } yield employeesWithTransportDetails + repositoryModule + .findAllEmployees + .flatMap(_.traverse { emp => + import models.codecs._ + Logger[F].info(s"Finding transport details for ${emp.code.value}") *> + transportServiceClient + .sendAndReceive[TransportRequest, TransportResponse](Some(TransportRequest(emp.code))) + .handleErrorWith { t: Throwable => + Logger[F].warn(t)("Error Received from the server") *> + TransportResponse.apply(emp.code).pure[F] + } + .map(EmployeeWithTransport.apply(emp, _)) + }) }) lazy val empByIdEndpoint: ServerEndpointF = From 968c04e3d9e386e9805c651aa4e7b8b4a648aec9 Mon Sep 17 00:00:00 2001 From: Ishan Shah Date: Sat, 20 Aug 2022 20:52:25 +0530 Subject: [PATCH 06/10] updating unit tests --- .../com/github/nullptr7/mocks/data.scala | 8 -------- .../protocol/EmployeeServiceLogicTest.scala | 19 +++++++++++++++---- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/server/src/test/scala/com/github/nullptr7/mocks/data.scala b/server/src/test/scala/com/github/nullptr7/mocks/data.scala index 9882fdf..60c8fca 100644 --- a/server/src/test/scala/com/github/nullptr7/mocks/data.scala +++ b/server/src/test/scala/com/github/nullptr7/mocks/data.scala @@ -21,14 +21,6 @@ object data { age = 12, salary = 1000, address = Address(addressId, "Main Street", "Anytown", "CA", "12345") - ), - Employee( - id = employeeId2, - code = employeeCode2, - name = "Doe", - age = 17, - salary = 1000, - address = Address(addressId, "Main Street", "Anytown", "CA", "12345") ) ) diff --git a/server/src/test/scala/com/github/nullptr7/protocol/EmployeeServiceLogicTest.scala b/server/src/test/scala/com/github/nullptr7/protocol/EmployeeServiceLogicTest.scala index 013c207..60bb3fc 100644 --- a/server/src/test/scala/com/github/nullptr7/protocol/EmployeeServiceLogicTest.scala +++ b/server/src/test/scala/com/github/nullptr7/protocol/EmployeeServiceLogicTest.scala @@ -2,7 +2,6 @@ package com.github.nullptr7 package protocol import java.net.URI -import java.util.UUID import org.http4s import org.http4s.Status @@ -77,7 +76,7 @@ class EmployeeServiceLogicTest extends BaseTest with ServiceLogicTestHelper[IO] Status.Ok ) .withEntity( - TransportResponse(EmployeeCode(UUID.randomUUID()), 1, 1, DAY) + TransportResponse(employeeCode1, 1, 1, DAY) ) ) ) @@ -97,10 +96,22 @@ class EmployeeServiceLogicTest extends BaseTest with ServiceLogicTestHelper[IO] val response = basicRequest .get(uri"http://localhost:8080/employees/get/all") .header("X-AuthMode", "admin") - .response(asJson[List[Employee]]) + .response(asJson[List[EmployeeWithTransport]]) .send(allEmployeeEndpointStub) - response.unsafeRunSync().body shouldBe Right(allEmployees) + response.unsafeRunSync().body shouldBe Right( + List( + EmployeeWithTransport( + id = employeeId1, + code = employeeCode1, + name = "John", + age = 12, + salary = 1000, + address = Address(addressId, "Main Street", "Anytown", "CA", "12345"), + transportDetails = TransportResponse(employeeCode1, 1, 1, DAY) + ) + ) + ) } it should "fail when not admin" in { From f52cdc4849b1659a0ab629978b566658a1012f64 Mon Sep 17 00:00:00 2001 From: Ishan Shah Date: Sun, 21 Aug 2022 19:28:19 +0530 Subject: [PATCH 07/10] Rearranging the application startup process --- .../src/main/scala/com/github/nullptr7/entrypoint/Startup.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/scala/com/github/nullptr7/entrypoint/Startup.scala b/server/src/main/scala/com/github/nullptr7/entrypoint/Startup.scala index ffd26fc..c1e496f 100644 --- a/server/src/main/scala/com/github/nullptr7/entrypoint/Startup.scala +++ b/server/src/main/scala/com/github/nullptr7/entrypoint/Startup.scala @@ -26,11 +26,11 @@ object Startup extends IOApp.Simple with ApplicationResourceModule { res <- appResources[IO] session <- DatabaseSession[IO].make(res.databaseConfig) repo <- RepositoryModule.make[IO](session) - server <- BlazeServerModuleV2.make[IO](res.serverConfig) httpClient <- BlazeClientModule[IO].make(res.clientConfig) clients <- initializeClient[IO](res.clientConfig, httpClient) logic <- new ServiceLogicModule[IO](repo, clients).make routes <- Routes.make[IO](logic) + server <- BlazeServerModuleV2.make[IO](res.serverConfig) serve <- server.serve(routes) } yield serve app.useForever From e99989fdd52eaae2d2009b4dc31deeda64677d1c Mon Sep 17 00:00:00 2001 From: Ishan Shah Date: Sun, 21 Aug 2022 21:23:26 +0530 Subject: [PATCH 08/10] Renaming BlazeServerModule --- .../scala/com/github/nullptr7/entrypoint/Startup.scala | 4 ++-- .../nullptr7/entrypoint/modules/BlazeServerModule.scala | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/server/src/main/scala/com/github/nullptr7/entrypoint/Startup.scala b/server/src/main/scala/com/github/nullptr7/entrypoint/Startup.scala index c1e496f..b4fde4f 100644 --- a/server/src/main/scala/com/github/nullptr7/entrypoint/Startup.scala +++ b/server/src/main/scala/com/github/nullptr7/entrypoint/Startup.scala @@ -9,7 +9,7 @@ import cats.effect.kernel.Resource import client.module.BlazeClientModule import client.{ApiClients, TransportServiceClient} import configurations.types.{ClientConfig, TransportApiClientDetails} -import modules.{ApplicationResourceModule, BlazeServerModuleV2, RepositoryModule, ServiceLogicModule} +import modules.{ApplicationResourceModule, BlazeServerModule, RepositoryModule, ServiceLogicModule} import storage.DatabaseSession object Startup extends IOApp.Simple with ApplicationResourceModule { @@ -30,7 +30,7 @@ object Startup extends IOApp.Simple with ApplicationResourceModule { clients <- initializeClient[IO](res.clientConfig, httpClient) logic <- new ServiceLogicModule[IO](repo, clients).make routes <- Routes.make[IO](logic) - server <- BlazeServerModuleV2.make[IO](res.serverConfig) + server <- BlazeServerModule.make[IO](res.serverConfig) serve <- server.serve(routes) } yield serve app.useForever diff --git a/server/src/main/scala/com/github/nullptr7/entrypoint/modules/BlazeServerModule.scala b/server/src/main/scala/com/github/nullptr7/entrypoint/modules/BlazeServerModule.scala index 7fbb307..4d91d28 100644 --- a/server/src/main/scala/com/github/nullptr7/entrypoint/modules/BlazeServerModule.scala +++ b/server/src/main/scala/com/github/nullptr7/entrypoint/modules/BlazeServerModule.scala @@ -11,7 +11,7 @@ import cats.effect.kernel.Async import configurations.types.ServerConfig -abstract class BlazeServerModuleV2[F[_]: Async](serverConfig: ServerConfig) { +abstract class BlazeServerModule[F[_]: Async](serverConfig: ServerConfig) { final def serve(route: HttpApp[F]): Resource[F, Server] = BlazeServerBuilder[F] @@ -21,11 +21,11 @@ abstract class BlazeServerModuleV2[F[_]: Async](serverConfig: ServerConfig) { } -object BlazeServerModuleV2 { +object BlazeServerModule { - def make[F[_]: Async](serverConfig: ServerConfig): Resource[F, BlazeServerModuleV2[F]] = + def make[F[_]: Async](serverConfig: ServerConfig): Resource[F, BlazeServerModule[F]] = Resource.pure( - new BlazeServerModuleV2[F](serverConfig) {} + new BlazeServerModule[F](serverConfig) {} ) } From a496bdab6473271fb5eab756a1646910a1066931 Mon Sep 17 00:00:00 2001 From: Ishan Shah Date: Tue, 11 Oct 2022 22:34:27 +0100 Subject: [PATCH 09/10] upgrading sbt and dependencies --- build.sbt | 35 ++++++++++++++--------------------- project/build.properties | 2 +- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/build.sbt b/build.sbt index 04b0d99..bb470ce 100644 --- a/build.sbt +++ b/build.sbt @@ -9,23 +9,20 @@ val scalafixCommonSettings = val Versions = new { - val tapir = "1.0.3" - val log4cats = "2.4.0" - val circe = "0.14.2" - val http4s = "0.23.12" + val tapir = "1.1.2" + val log4cats = "2.5.0" + val circe = "0.14.2" + val http4s = "0.23.12" + val http4sCirce = "0.23.16" } lazy val commonSettings: Seq[Setting[_]] = Seq( scalacOptions -= "-Xfatal-warnings", // scalafixCommonSettings, libraryDependencies ++= Seq( - "ch.qos.logback" % "logback-classic" % "1.2.11", + "ch.qos.logback" % "logback-classic" % "1.4.3", "org.typelevel" %% "log4cats-slf4j" % Versions.log4cats, "org.typelevel" %% "log4cats-noop" % Versions.log4cats, - // "org.apache.logging.log4j" % "log4j-core" % Versions.log4j, - // "org.apache.logging.log4j" % "log4j-api" % Versions.log4j, - // "org.apache.logging.log4j" % "log4j-slf4j-impl" % Versions.log4j, - // "org.typelevel" %% "log4cats-core" % "2.4.0", "com.github.liancheng" %% "organize-imports" % "0.6.0" ) ) @@ -46,8 +43,8 @@ lazy val database = commonSettings, // scalafixCommonSettings, libraryDependencies ++= Seq( - "org.tpolecat" %% "skunk-core" % "0.3.1", - "org.tpolecat" %% "skunk-circe" % "0.3.1" + "org.tpolecat" %% "skunk-core" % "0.3.2", + "org.tpolecat" %% "skunk-circe" % "0.3.2" ) ) .dependsOn(models, configs) @@ -89,19 +86,15 @@ lazy val server = "com.softwaremill.sttp.tapir" %% "tapir-http4s-server" % Versions.tapir, "com.softwaremill.sttp.tapir" %% "tapir-json-circe" % Versions.tapir, "org.http4s" %% "http4s-blaze-server" % Versions.http4s, - "org.http4s" %% "http4s-circe" % Versions.http4s, + "org.http4s" %% "http4s-circe" % Versions.http4sCirce, "io.circe" %% "circe-generic-extras" % Versions.circe, "com.softwaremill.sttp.tapir" %% "tapir-sttp-stub-server" % Versions.tapir % includeTestandIt, - "org.scalatest" %% "scalatest" % "3.2.12" % includeTestandIt, - "com.softwaremill.sttp.client3" %% "async-http-client-backend-cats" % "3.7.2" % includeTestandIt, - "com.softwaremill.sttp.client3" %% "circe" % "3.7.2" % includeTestandIt, - "org.mockito" %% "mockito-scala" % "1.17.7" % Test, - "org.tpolecat" %% "skunk-core" % "0.3.1" % IntegrationTest, + "org.scalatest" %% "scalatest" % "3.2.14" % includeTestandIt, + "com.softwaremill.sttp.client3" %% "async-http-client-backend-cats" % "3.8.2" % includeTestandIt, + "com.softwaremill.sttp.client3" %% "circe" % "3.8.2" % includeTestandIt, + "org.mockito" %% "mockito-scala" % "1.17.12" % Test, + "org.tpolecat" %% "skunk-core" % "0.3.2" % IntegrationTest, "org.typelevel" %% "cats-effect-testing-specs2" % "1.4.0" % IntegrationTest - // "org.http4s" %% "http4s-testing" % "0.21.33" % Test - // "com.softwaremill.sttp.tapir" %% "tapir-server-tests" % "1.0.0", - // "com.softwaremill.sttp.tapir" %% "tapir-testing" % "1.0.0", - // "com.softwaremill.sttp.tapir" %% "tapir-tests" % "1.0.0" ) ) .dependsOn(models, database, client) diff --git a/project/build.properties b/project/build.properties index c8fcab5..563a014 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.6.2 +sbt.version=1.7.2 From 58d8c1f83ef08a0932fb23d1742ed95ed3eb6657 Mon Sep 17 00:00:00 2001 From: Ishan Shah Date: Tue, 11 Oct 2022 22:39:52 +0100 Subject: [PATCH 10/10] updating scala version --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index bb470ce..422401d 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ ThisBuild / version := "1.0.0" -ThisBuild / scalaVersion := "2.13.8" +ThisBuild / scalaVersion := "2.13.9" ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.6.0"