From 35cdbc19983501963b440e328f7ca018a88cc2a3 Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Mon, 27 Dec 2021 14:04:24 +0100 Subject: [PATCH 01/10] Use autowire --- .../com/softwaremill/bootzooka/Main.scala | 36 +++++++------ .../softwaremill/bootzooka/http/HttpApi.scala | 53 +++++++++++-------- .../bootzooka/infrastructure/DB.scala | 25 +++++---- build.sbt | 8 ++- 4 files changed, 72 insertions(+), 50 deletions(-) diff --git a/backend/src/main/scala/com/softwaremill/bootzooka/Main.scala b/backend/src/main/scala/com/softwaremill/bootzooka/Main.scala index 53e973eaa..fd01af118 100644 --- a/backend/src/main/scala/com/softwaremill/bootzooka/Main.scala +++ b/backend/src/main/scala/com/softwaremill/bootzooka/Main.scala @@ -7,6 +7,7 @@ import com.softwaremill.bootzooka.metrics.Metrics import com.typesafe.scalalogging.StrictLogging import doobie.util.transactor import sttp.client3.SttpBackend +import com.softwaremill.macwire.autocats._ object Main extends StrictLogging { def main(args: Array[String]): Unit = { @@ -14,25 +15,30 @@ object Main extends StrictLogging { Thread.setDefaultUncaughtExceptionHandler((t, e) => logger.error("Uncaught exception in thread: " + t, e)) val initModule = new InitModule {} + initModule.logConfig() - val mainTask = initModule.db.transactorResource.use { _xa => - initModule.baseSttpBackend.use { _baseSttpBackend => - val modules = new MainModule { - override def xa: transactor.Transactor[IO] = _xa - override def baseSttpBackend: SttpBackend[IO, Any] = _baseSttpBackend - override def config: Config = initModule.config - } + def buildMainModule(_xa: transactor.Transactor[IO], _baseSttpBackend: SttpBackend[IO, Any], _config: Config) = new MainModule { + override def xa: transactor.Transactor[IO] = _xa + override def baseSttpBackend: SttpBackend[IO, Any] = _baseSttpBackend + override def config: Config = _config + } - /* - Sequencing two tasks using the >> operator: - - the first starts the background processes (such as an email sender) - - the second allocates the http api resource, and never releases it (so that the http server is available - as long as our application runs) - */ - modules.startBackgroundProcesses >> modules.httpApi.resource.use(_ => IO.never) - } + val mainTask = autowire[MainModule]( + initModule.db.transactorResource, + initModule.baseSttpBackend, + initModule.config, + buildMainModule _ + ).use { modules => + /* + Sequencing two tasks using the >> operator: + - the first starts the background processes (such as an email sender) + - the second allocates the http api resource, and never releases it (so that the http server is available + as long as our application runs) + */ + modules.startBackgroundProcesses >> modules.httpApi.resource.use(_ => IO.never) } + mainTask.unsafeRunSync() } } diff --git a/backend/src/main/scala/com/softwaremill/bootzooka/http/HttpApi.scala b/backend/src/main/scala/com/softwaremill/bootzooka/http/HttpApi.scala index e3c192037..8aae68343 100644 --- a/backend/src/main/scala/com/softwaremill/bootzooka/http/HttpApi.scala +++ b/backend/src/main/scala/com/softwaremill/bootzooka/http/HttpApi.scala @@ -8,7 +8,7 @@ import com.typesafe.scalalogging.StrictLogging import io.prometheus.client.CollectorRegistry import org.http4s.blaze.server.BlazeServerBuilder import org.http4s.metrics.prometheus.Prometheus -import org.http4s.server.Router +import org.http4s.server.{Router, Server} import org.http4s.server.middleware.{CORS, Metrics} import org.http4s.server.staticcontent._ import org.http4s.{HttpApp, HttpRoutes, Request, Response} @@ -40,29 +40,36 @@ class HttpApi( /** The resource describing the HTTP server; binds when the resource is allocated. */ lazy val resource: Resource[IO, org.http4s.server.Server] = { - val prometheusHttp4sMetrics = Prometheus.metricsOps[IO](collectorRegistry) - prometheusHttp4sMetrics - .map(m => Metrics[IO](m)(mainRoutes)) - .flatMap { monitoredRoutes => - val app: HttpApp[IO] = Router( - // for /api/v1 requests, first trying the API; then the docs; then, returning 404 - s"/${apiContextPath.mkString("/")}" -> { - CORS.policy.withAllowOriginAll - .withAllowCredentials(false) - .apply(monitoredRoutes <+> docsRoutes <+> respondWithNotFound) - }, - "/admin" -> adminRoutes, - // for all other requests, first trying getting existing webapp resource; - // otherwise, returning index.html; this is needed to support paths in the frontend apps (e.g. /login) - // the frontend app will handle displaying appropriate error messages - "" -> (webappRoutes <+> indexResponse()) - ).orNotFound + import com.softwaremill.macwire.autocats._ - BlazeServerBuilder[IO] - .bindHttp(config.port, config.host) - .withHttpApp(app) - .resource - } + val monitoredRoues = + Prometheus.metricsOps[IO](collectorRegistry).map(m => Metrics[IO](m)(mainRoutes)) + + def buildApp(monitoredRoutes: HttpRoutes[IO]): HttpApp[IO] = Router( + // for /api/v1 requests, first trying the API; then the docs; then, returning 404 + s"/${apiContextPath.mkString("/")}" -> { + CORS.policy + .withAllowOriginAll + .withAllowCredentials(false) + .apply(monitoredRoutes <+> docsRoutes <+> respondWithNotFound) + }, + "/admin" -> adminRoutes, + // for all other requests, first trying getting existing webapp resource; + // otherwise, returning index.html; this is needed to support paths in the frontend apps (e.g. /login) + // the frontend app will handle displaying appropriate error messages + "" -> (webappRoutes <+> indexResponse()) + ).orNotFound + + def buildServer(app: HttpApp[IO]): Resource[IO, Server] = BlazeServerBuilder[IO] + .bindHttp(config.port, config.host) + .withHttpApp(app) + .resource + + autowire[Server]( + monitoredRoues, + buildApp _, + buildServer _ + ) } private def indexResponse(): HttpRoutes[IO] = { diff --git a/backend/src/main/scala/com/softwaremill/bootzooka/infrastructure/DB.scala b/backend/src/main/scala/com/softwaremill/bootzooka/infrastructure/DB.scala index a2f8331b9..f7d99070e 100644 --- a/backend/src/main/scala/com/softwaremill/bootzooka/infrastructure/DB.scala +++ b/backend/src/main/scala/com/softwaremill/bootzooka/infrastructure/DB.scala @@ -10,6 +10,9 @@ import scala.concurrent.duration._ import Doobie._ import com.softwaremill.bootzooka.config.Sensitive +import scala.concurrent.ExecutionContext +import com.softwaremill.macwire.autocats._ + /** Configures the database, setting up the connection pool and performing migrations. */ class DB(_config: DBConfig) extends StrictLogging { @@ -36,17 +39,17 @@ class DB(_config: DBConfig) extends StrictLogging { * * See also: https://tpolecat.github.io/doobie/docs/14-Managing-Connections.html#about-threading */ - for { - connectEC <- doobie.util.ExecutionContexts.fixedThreadPool[IO](config.connectThreadPoolSize) - xa <- HikariTransactor.newHikariTransactor[IO]( - config.driver, - config.url, - config.username, - config.password.value, - connectEC - ) - _ <- Resource.eval(connectAndMigrate(xa)) - } yield xa + def buildTransactor(ec: ExecutionContext) = HikariTransactor.newHikariTransactor[IO]( + config.driver, + config.url, + config.username, + config.password.value, + ec + ) + autowire[Transactor[IO]]( + doobie.util.ExecutionContexts.fixedThreadPool[IO](config.connectThreadPoolSize), + buildTransactor _ + ).evalTap(connectAndMigrate) } private def connectAndMigrate(xa: Transactor[IO]): IO[Unit] = { diff --git a/build.sbt b/build.sbt index 87550c73d..b6a5c8633 100644 --- a/build.sbt +++ b/build.sbt @@ -17,6 +17,7 @@ val tsecVersion = "0.4.0" val sttpVersion = "3.5.0" val prometheusVersion = "0.15.0" val tapirVersion = "0.20.0-M9" +val macwireVersion = "2.5.3" val dbDependencies = Seq( "org.tpolecat" %% "doobie-core" % doobieVersion, @@ -81,6 +82,11 @@ val emailDependencies = Seq( ) val scalatest = "org.scalatest" %% "scalatest" % "3.2.11" % Test +val macwireDependencies = Seq( + "com.softwaremill.macwire" %% "macros" % macwireVersion, + "com.softwaremill.macwire" %% "macrosautocats" % macwireVersion +).map(_ % Provided) + val unitTestingStack = Seq(scalatest) val embeddedPostgres = "com.opentable.components" % "otj-pg-embedded" % "1.0.0" % Test @@ -190,7 +196,7 @@ lazy val rootProject = (project in file(".")) lazy val backend: Project = (project in file("backend")) .settings( - libraryDependencies ++= dbDependencies ++ httpDependencies ++ jsonDependencies ++ apiDocsDependencies ++ monitoringDependencies ++ dbTestingStack ++ securityDependencies ++ emailDependencies, + libraryDependencies ++= dbDependencies ++ httpDependencies ++ jsonDependencies ++ apiDocsDependencies ++ monitoringDependencies ++ dbTestingStack ++ securityDependencies ++ emailDependencies ++ macwireDependencies, Compile / mainClass := Some("com.softwaremill.bootzooka.Main") ) .enablePlugins(BuildInfoPlugin) From cd9a92852b3f164314abecd3e0e40106de9e9878 Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Wed, 29 Dec 2021 10:46:45 +0100 Subject: [PATCH 02/10] Simplify modules init with wire --- .../bootzooka/email/EmailModule.scala | 4 +++- .../passwordreset/PasswordResetModule.scala | 18 +++++------------- .../bootzooka/security/SecurityModule.scala | 3 ++- .../bootzooka/user/UserModule.scala | 7 +++++-- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/backend/src/main/scala/com/softwaremill/bootzooka/email/EmailModule.scala b/backend/src/main/scala/com/softwaremill/bootzooka/email/EmailModule.scala index 4a1e3279e..fcab28961 100644 --- a/backend/src/main/scala/com/softwaremill/bootzooka/email/EmailModule.scala +++ b/backend/src/main/scala/com/softwaremill/bootzooka/email/EmailModule.scala @@ -3,12 +3,14 @@ package com.softwaremill.bootzooka.email import cats.effect.IO import com.softwaremill.bootzooka.email.sender.{DummyEmailSender, EmailSender, MailgunEmailSender, SmtpEmailSender} import com.softwaremill.bootzooka.util.BaseModule +import com.softwaremill.macwire._ import sttp.client3.SttpBackend import doobie.util.transactor.Transactor trait EmailModule extends BaseModule { lazy val emailModel = new EmailModel - lazy val emailService = new EmailService(emailModel, idGenerator, emailSender, config.email, xa) + private lazy val emailConfig = config.email + lazy val emailService = wire[EmailService] // the EmailService implements the EmailScheduler functionality - hence, creating an alias for this dependency lazy val emailScheduler: EmailScheduler = emailService lazy val emailTemplates = new EmailTemplates() diff --git a/backend/src/main/scala/com/softwaremill/bootzooka/passwordreset/PasswordResetModule.scala b/backend/src/main/scala/com/softwaremill/bootzooka/passwordreset/PasswordResetModule.scala index 5c19a9877..6d59313a9 100644 --- a/backend/src/main/scala/com/softwaremill/bootzooka/passwordreset/PasswordResetModule.scala +++ b/backend/src/main/scala/com/softwaremill/bootzooka/passwordreset/PasswordResetModule.scala @@ -7,22 +7,14 @@ import com.softwaremill.bootzooka.security.Auth import com.softwaremill.bootzooka.user.UserModel import com.softwaremill.bootzooka.util.BaseModule import com.softwaremill.bootzooka.infrastructure.Doobie._ +import com.softwaremill.macwire._ trait PasswordResetModule extends BaseModule { lazy val passwordResetCodeModel = new PasswordResetCodeModel - lazy val passwordResetService = - new PasswordResetService( - userModel, - passwordResetCodeModel, - emailScheduler, - emailTemplates, - passwordResetCodeAuth, - idGenerator, - config.passwordReset, - clock, - xa - ) - lazy val passwordResetApi = new PasswordResetApi(http, passwordResetService, xa) + private lazy val passwordResetConfig = config.passwordReset + lazy val passwordResetService = wire[PasswordResetService] + + lazy val passwordResetApi = wire[PasswordResetApi] def userModel: UserModel def http: Http diff --git a/backend/src/main/scala/com/softwaremill/bootzooka/security/SecurityModule.scala b/backend/src/main/scala/com/softwaremill/bootzooka/security/SecurityModule.scala index 0b9c2f9bb..6504ebf1e 100644 --- a/backend/src/main/scala/com/softwaremill/bootzooka/security/SecurityModule.scala +++ b/backend/src/main/scala/com/softwaremill/bootzooka/security/SecurityModule.scala @@ -3,11 +3,12 @@ package com.softwaremill.bootzooka.security import cats.effect.IO import com.softwaremill.bootzooka.passwordreset.{PasswordResetAuthToken, PasswordResetCode, PasswordResetCodeModel} import com.softwaremill.bootzooka.util.BaseModule +import com.softwaremill.macwire._ import doobie.util.transactor.Transactor trait SecurityModule extends BaseModule { lazy val apiKeyModel = new ApiKeyModel - lazy val apiKeyService = new ApiKeyService(apiKeyModel, idGenerator, clock) + lazy val apiKeyService = wire[ApiKeyService] lazy val apiKeyAuth: Auth[ApiKey] = new Auth(new ApiKeyAuthToken(apiKeyModel), xa, clock) lazy val passwordResetCodeAuth: Auth[PasswordResetCode] = new Auth(new PasswordResetAuthToken(passwordResetCodeModel), xa, clock) diff --git a/backend/src/main/scala/com/softwaremill/bootzooka/user/UserModule.scala b/backend/src/main/scala/com/softwaremill/bootzooka/user/UserModule.scala index 36517b58e..c693bff84 100644 --- a/backend/src/main/scala/com/softwaremill/bootzooka/user/UserModule.scala +++ b/backend/src/main/scala/com/softwaremill/bootzooka/user/UserModule.scala @@ -5,12 +5,15 @@ import com.softwaremill.bootzooka.email.{EmailScheduler, EmailTemplates} import com.softwaremill.bootzooka.http.Http import com.softwaremill.bootzooka.security.{ApiKey, ApiKeyService, Auth} import com.softwaremill.bootzooka.util.BaseModule +import com.softwaremill.macwire._ import doobie.util.transactor.Transactor trait UserModule extends BaseModule { lazy val userModel = new UserModel - lazy val userApi = new UserApi(http, apiKeyAuth, userService, xa) - lazy val userService = new UserService(userModel, emailScheduler, emailTemplates, apiKeyService, idGenerator, clock, config.user) + private lazy val userConfig = config.user + + lazy val userService = wire[UserService] + lazy val userApi = wire[UserApi] def http: Http def apiKeyAuth: Auth[ApiKey] From 5b5152348fea6b9c2a1cc55202d3033bcb22cd39 Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Wed, 5 Jan 2022 09:30:40 +0100 Subject: [PATCH 03/10] Remove modules layer --- .../bootzooka/DependenciesFactory.scala | 67 +++++++++++++++++++ .../softwaremill/bootzooka/InitModule.scala | 17 ----- .../com/softwaremill/bootzooka/Main.scala | 56 ++++++++++------ .../softwaremill/bootzooka/MainModule.scala | 35 ---------- .../bootzooka/email/EmailModule.scala | 28 -------- .../bootzooka/infrastructure/DB.scala | 2 +- .../infrastructure/InfrastructureModule.scala | 12 ---- .../bootzooka/metrics/MetricsModule.scala | 12 ---- .../passwordreset/PasswordResetModule.scala | 25 ------- .../bootzooka/security/SecurityModule.scala | 17 ----- .../bootzooka/user/UserModule.scala | 24 ------- .../passwordreset/PasswordResetApiTest.scala | 33 +++------ .../bootzooka/test/AppDependencies.scala | 32 +++++++++ .../bootzooka/test/HttpTestSupport.scala | 3 - .../bootzooka/test/Requests.scala | 15 ++--- .../bootzooka/user/UserApiTest.scala | 22 ++---- 16 files changed, 154 insertions(+), 246 deletions(-) create mode 100644 backend/src/main/scala/com/softwaremill/bootzooka/DependenciesFactory.scala delete mode 100644 backend/src/main/scala/com/softwaremill/bootzooka/InitModule.scala delete mode 100644 backend/src/main/scala/com/softwaremill/bootzooka/MainModule.scala delete mode 100644 backend/src/main/scala/com/softwaremill/bootzooka/email/EmailModule.scala delete mode 100644 backend/src/main/scala/com/softwaremill/bootzooka/infrastructure/InfrastructureModule.scala delete mode 100644 backend/src/main/scala/com/softwaremill/bootzooka/metrics/MetricsModule.scala delete mode 100644 backend/src/main/scala/com/softwaremill/bootzooka/passwordreset/PasswordResetModule.scala delete mode 100644 backend/src/main/scala/com/softwaremill/bootzooka/security/SecurityModule.scala delete mode 100644 backend/src/main/scala/com/softwaremill/bootzooka/user/UserModule.scala create mode 100644 backend/src/test/scala/com/softwaremill/bootzooka/test/AppDependencies.scala diff --git a/backend/src/main/scala/com/softwaremill/bootzooka/DependenciesFactory.scala b/backend/src/main/scala/com/softwaremill/bootzooka/DependenciesFactory.scala new file mode 100644 index 000000000..2038f2534 --- /dev/null +++ b/backend/src/main/scala/com/softwaremill/bootzooka/DependenciesFactory.scala @@ -0,0 +1,67 @@ +package com.softwaremill.bootzooka + +import cats.data.NonEmptyList +import cats.effect.{IO, Resource} +import com.softwaremill.bootzooka.config.Config +import com.softwaremill.bootzooka.email.{EmailConfig, EmailModel, EmailService} +import com.softwaremill.bootzooka.email.sender.{DummyEmailSender, EmailSender, MailgunEmailSender, SmtpEmailSender} +import com.softwaremill.bootzooka.http.{Http, HttpApi, HttpConfig} +import com.softwaremill.bootzooka.infrastructure.{DB, DBConfig} +import com.softwaremill.bootzooka.metrics.{MetricsApi, VersionApi} +import com.softwaremill.bootzooka.passwordreset.{PasswordResetApi, PasswordResetAuthToken, PasswordResetCodeModel} +import com.softwaremill.bootzooka.security.{ApiKeyAuthToken, ApiKeyModel} +import com.softwaremill.bootzooka.user.UserApi +import com.softwaremill.bootzooka.util.{Clock, DefaultIdGenerator, IdGenerator} +import com.softwaremill.macwire.autocats.autowire +import doobie.util.transactor.Transactor +import io.prometheus.client.CollectorRegistry +import sttp.client3.SttpBackend + +object DependenciesFactory { + private case class Modules(api: HttpApi, emailService: EmailService) + + def resource(config: Config, sttpBackend: Resource[IO, SttpBackend[IO, Any]], xa: Resource[IO, Transactor[IO]], clock: Clock): Resource[IO, (HttpApi, EmailService)] = { + lazy val collectorRegistry: CollectorRegistry = CollectorRegistry.defaultRegistry + lazy val idGenerator: IdGenerator = DefaultIdGenerator + + def buildHttpApi(http: Http, userApi: UserApi, passwordResetApi: PasswordResetApi, metricsApi: MetricsApi, versionApi: VersionApi, collectorRegistry: CollectorRegistry, cfg: HttpConfig) = + new HttpApi( + http, + userApi.endpoints concatNel passwordResetApi.endpoints, + NonEmptyList.of(metricsApi.metricsEndpoint, versionApi.versionEndpoint), + collectorRegistry, + cfg) + + def buildApiKeyAuthToken(apiKeyModel: ApiKeyModel): ApiKeyAuthToken = new ApiKeyAuthToken(apiKeyModel) + + def buildPasswordResetAuthToken(passwordResetCodeModel: PasswordResetCodeModel): PasswordResetAuthToken = new PasswordResetAuthToken(passwordResetCodeModel) + + def buildEmailSender(sttpBackend: SttpBackend[IO, Any], config: EmailConfig): EmailSender = if (config.mailgun.enabled) { + new MailgunEmailSender(config.mailgun, sttpBackend) + } else if (config.smtp.enabled) { + new SmtpEmailSender(config.smtp) + } else { + DummyEmailSender + } + + def buildEmailScheduler(emailModel: EmailModel, idGenerator: IdGenerator, emailSender: EmailSender, config: EmailConfig, xa: Transactor[IO]) = + new EmailService(emailModel, idGenerator, emailSender, config, xa) + + autowire[Modules]( + config.api, + config.user, + config.passwordReset, + config.email, + idGenerator, + clock, + collectorRegistry, + sttpBackend, + xa, + buildHttpApi _, + buildApiKeyAuthToken _, + buildEmailScheduler _, + buildEmailSender _, + buildPasswordResetAuthToken _ + ).map(modules => (modules.api, modules.emailService)) + } +} diff --git a/backend/src/main/scala/com/softwaremill/bootzooka/InitModule.scala b/backend/src/main/scala/com/softwaremill/bootzooka/InitModule.scala deleted file mode 100644 index 83795cf88..000000000 --- a/backend/src/main/scala/com/softwaremill/bootzooka/InitModule.scala +++ /dev/null @@ -1,17 +0,0 @@ -package com.softwaremill.bootzooka - -import cats.effect.{IO, Resource} -import com.softwaremill.bootzooka.config.ConfigModule -import com.softwaremill.bootzooka.infrastructure.DB -import sttp.capabilities.WebSockets -import sttp.capabilities.fs2.Fs2Streams -import sttp.client3.SttpBackend -import sttp.client3.asynchttpclient.fs2.AsyncHttpClientFs2Backend - -/** Initialised resources needed by the application to start. - */ -trait InitModule extends ConfigModule { - lazy val db: DB = new DB(config.db) - lazy val baseSttpBackend: Resource[IO, SttpBackend[IO, Fs2Streams[IO] with WebSockets]] = - AsyncHttpClientFs2Backend.resource() -} diff --git a/backend/src/main/scala/com/softwaremill/bootzooka/Main.scala b/backend/src/main/scala/com/softwaremill/bootzooka/Main.scala index fd01af118..50ae77f31 100644 --- a/backend/src/main/scala/com/softwaremill/bootzooka/Main.scala +++ b/backend/src/main/scala/com/softwaremill/bootzooka/Main.scala @@ -1,44 +1,58 @@ package com.softwaremill.bootzooka -import cats.effect.IO import cats.effect.unsafe.implicits.global -import com.softwaremill.bootzooka.config.Config +import cats.effect.{IO, Resource} +import com.softwaremill.bootzooka.config.ConfigModule +import com.softwaremill.bootzooka.email.EmailService +import com.softwaremill.bootzooka.http.HttpApi +import com.softwaremill.bootzooka.infrastructure.DB import com.softwaremill.bootzooka.metrics.Metrics +import com.softwaremill.bootzooka.util.{Clock, DefaultClock} import com.typesafe.scalalogging.StrictLogging -import doobie.util.transactor +import sttp.capabilities.WebSockets +import sttp.capabilities.fs2.Fs2Streams import sttp.client3.SttpBackend -import com.softwaremill.macwire.autocats._ +import sttp.client3.asynchttpclient.fs2.AsyncHttpClientFs2Backend +import sttp.client3.logging.slf4j.Slf4jLoggingBackend +import sttp.client3.prometheus.PrometheusBackend object Main extends StrictLogging { def main(args: Array[String]): Unit = { Metrics.init() Thread.setDefaultUncaughtExceptionHandler((t, e) => logger.error("Uncaught exception in thread: " + t, e)) - val initModule = new InitModule {} + val configModule = new ConfigModule {} + configModule.logConfig() + val config = configModule.config - initModule.logConfig() + lazy val clock: Clock = DefaultClock - def buildMainModule(_xa: transactor.Transactor[IO], _baseSttpBackend: SttpBackend[IO, Any], _config: Config) = new MainModule { - override def xa: transactor.Transactor[IO] = _xa - override def baseSttpBackend: SttpBackend[IO, Any] = _baseSttpBackend - override def config: Config = _config - } + lazy val sttpBackend: Resource[IO, SttpBackend[IO, Fs2Streams[IO] with WebSockets]] = + AsyncHttpClientFs2Backend + .resource[IO]() + .map(baseSttpBackend => Slf4jLoggingBackend(PrometheusBackend(baseSttpBackend), includeTiming = true)) - val mainTask = autowire[MainModule]( - initModule.db.transactorResource, - initModule.baseSttpBackend, - initModule.config, - buildMainModule _ - ).use { modules => - /* + lazy val xa = new DB(config.db).transactorResource + + val mainTask = DependenciesFactory + .resource( + config = config, + sttpBackend = sttpBackend, + xa = xa, + clock = clock + ) + .use { case (httpApi, emailService) => + /* Sequencing two tasks using the >> operator: - the first starts the background processes (such as an email sender) - the second allocates the http api resource, and never releases it (so that the http server is available as long as our application runs) - */ - modules.startBackgroundProcesses >> modules.httpApi.resource.use(_ => IO.never) - } + */ + emailService.startProcesses().void >> httpApi.resource.use(_ => IO.never) + } mainTask.unsafeRunSync() } } + +case class Modules(emailService: EmailService, httpApi: HttpApi) diff --git a/backend/src/main/scala/com/softwaremill/bootzooka/MainModule.scala b/backend/src/main/scala/com/softwaremill/bootzooka/MainModule.scala deleted file mode 100644 index 5988693c0..000000000 --- a/backend/src/main/scala/com/softwaremill/bootzooka/MainModule.scala +++ /dev/null @@ -1,35 +0,0 @@ -package com.softwaremill.bootzooka - -import cats.data.NonEmptyList -import cats.effect.IO -import com.softwaremill.bootzooka.email.EmailModule -import com.softwaremill.bootzooka.http.{Http, HttpApi} -import com.softwaremill.bootzooka.infrastructure.InfrastructureModule -import com.softwaremill.bootzooka.metrics.MetricsModule -import com.softwaremill.bootzooka.passwordreset.PasswordResetModule -import com.softwaremill.bootzooka.security.SecurityModule -import com.softwaremill.bootzooka.user.UserModule -import com.softwaremill.bootzooka.util.{Clock, DefaultClock, DefaultIdGenerator, IdGenerator, ServerEndpoints} - -/** Main application module. Depends on resources initialised in [[InitModule]]. - */ -trait MainModule - extends SecurityModule - with EmailModule - with UserModule - with PasswordResetModule - with MetricsModule - with InfrastructureModule { - - override lazy val idGenerator: IdGenerator = DefaultIdGenerator - override lazy val clock: Clock = DefaultClock - - lazy val http: Http = new Http() - - private lazy val endpoints: ServerEndpoints = userApi.endpoints concatNel passwordResetApi.endpoints - private lazy val adminEndpoints: ServerEndpoints = NonEmptyList.of(metricsApi.metricsEndpoint, versionApi.versionEndpoint) - - lazy val httpApi: HttpApi = new HttpApi(http, endpoints, adminEndpoints, collectorRegistry, config.api) - - lazy val startBackgroundProcesses: IO[Unit] = emailService.startProcesses().void -} diff --git a/backend/src/main/scala/com/softwaremill/bootzooka/email/EmailModule.scala b/backend/src/main/scala/com/softwaremill/bootzooka/email/EmailModule.scala deleted file mode 100644 index fcab28961..000000000 --- a/backend/src/main/scala/com/softwaremill/bootzooka/email/EmailModule.scala +++ /dev/null @@ -1,28 +0,0 @@ -package com.softwaremill.bootzooka.email - -import cats.effect.IO -import com.softwaremill.bootzooka.email.sender.{DummyEmailSender, EmailSender, MailgunEmailSender, SmtpEmailSender} -import com.softwaremill.bootzooka.util.BaseModule -import com.softwaremill.macwire._ -import sttp.client3.SttpBackend -import doobie.util.transactor.Transactor - -trait EmailModule extends BaseModule { - lazy val emailModel = new EmailModel - private lazy val emailConfig = config.email - lazy val emailService = wire[EmailService] - // the EmailService implements the EmailScheduler functionality - hence, creating an alias for this dependency - lazy val emailScheduler: EmailScheduler = emailService - lazy val emailTemplates = new EmailTemplates() - // depending on the configuration, creating the appropriate EmailSender instance - lazy val emailSender: EmailSender = if (config.email.mailgun.enabled) { - new MailgunEmailSender(config.email.mailgun, sttpBackend) - } else if (config.email.smtp.enabled) { - new SmtpEmailSender(config.email.smtp) - } else { - DummyEmailSender - } - - def xa: Transactor[IO] - def sttpBackend: SttpBackend[IO, Any] -} diff --git a/backend/src/main/scala/com/softwaremill/bootzooka/infrastructure/DB.scala b/backend/src/main/scala/com/softwaremill/bootzooka/infrastructure/DB.scala index f7d99070e..4b4849e8d 100644 --- a/backend/src/main/scala/com/softwaremill/bootzooka/infrastructure/DB.scala +++ b/backend/src/main/scala/com/softwaremill/bootzooka/infrastructure/DB.scala @@ -9,9 +9,9 @@ import org.flywaydb.core.Flyway import scala.concurrent.duration._ import Doobie._ import com.softwaremill.bootzooka.config.Sensitive +import com.softwaremill.macwire.autocats.autowire import scala.concurrent.ExecutionContext -import com.softwaremill.macwire.autocats._ /** Configures the database, setting up the connection pool and performing migrations. */ diff --git a/backend/src/main/scala/com/softwaremill/bootzooka/infrastructure/InfrastructureModule.scala b/backend/src/main/scala/com/softwaremill/bootzooka/infrastructure/InfrastructureModule.scala deleted file mode 100644 index 374f86bb5..000000000 --- a/backend/src/main/scala/com/softwaremill/bootzooka/infrastructure/InfrastructureModule.scala +++ /dev/null @@ -1,12 +0,0 @@ -package com.softwaremill.bootzooka.infrastructure - -import cats.effect.IO -import sttp.client3.SttpBackend -import sttp.client3.prometheus.PrometheusBackend -import sttp.client3.logging.slf4j.Slf4jLoggingBackend - -trait InfrastructureModule { - implicit lazy val sttpBackend: SttpBackend[IO, Any] = Slf4jLoggingBackend(PrometheusBackend(baseSttpBackend), includeTiming = true) - - def baseSttpBackend: SttpBackend[IO, Any] -} diff --git a/backend/src/main/scala/com/softwaremill/bootzooka/metrics/MetricsModule.scala b/backend/src/main/scala/com/softwaremill/bootzooka/metrics/MetricsModule.scala deleted file mode 100644 index ff994b179..000000000 --- a/backend/src/main/scala/com/softwaremill/bootzooka/metrics/MetricsModule.scala +++ /dev/null @@ -1,12 +0,0 @@ -package com.softwaremill.bootzooka.metrics - -import com.softwaremill.bootzooka.http.Http -import io.prometheus.client.CollectorRegistry - -trait MetricsModule { - lazy val metricsApi = new MetricsApi(http, collectorRegistry) - lazy val versionApi = new VersionApi(http) - lazy val collectorRegistry: CollectorRegistry = CollectorRegistry.defaultRegistry - - def http: Http -} diff --git a/backend/src/main/scala/com/softwaremill/bootzooka/passwordreset/PasswordResetModule.scala b/backend/src/main/scala/com/softwaremill/bootzooka/passwordreset/PasswordResetModule.scala deleted file mode 100644 index 6d59313a9..000000000 --- a/backend/src/main/scala/com/softwaremill/bootzooka/passwordreset/PasswordResetModule.scala +++ /dev/null @@ -1,25 +0,0 @@ -package com.softwaremill.bootzooka.passwordreset - -import cats.effect.IO -import com.softwaremill.bootzooka.email.{EmailScheduler, EmailTemplates} -import com.softwaremill.bootzooka.http.Http -import com.softwaremill.bootzooka.security.Auth -import com.softwaremill.bootzooka.user.UserModel -import com.softwaremill.bootzooka.util.BaseModule -import com.softwaremill.bootzooka.infrastructure.Doobie._ -import com.softwaremill.macwire._ - -trait PasswordResetModule extends BaseModule { - lazy val passwordResetCodeModel = new PasswordResetCodeModel - private lazy val passwordResetConfig = config.passwordReset - lazy val passwordResetService = wire[PasswordResetService] - - lazy val passwordResetApi = wire[PasswordResetApi] - - def userModel: UserModel - def http: Http - def passwordResetCodeAuth: Auth[PasswordResetCode] - def emailScheduler: EmailScheduler - def emailTemplates: EmailTemplates - def xa: Transactor[IO] -} diff --git a/backend/src/main/scala/com/softwaremill/bootzooka/security/SecurityModule.scala b/backend/src/main/scala/com/softwaremill/bootzooka/security/SecurityModule.scala deleted file mode 100644 index 6504ebf1e..000000000 --- a/backend/src/main/scala/com/softwaremill/bootzooka/security/SecurityModule.scala +++ /dev/null @@ -1,17 +0,0 @@ -package com.softwaremill.bootzooka.security - -import cats.effect.IO -import com.softwaremill.bootzooka.passwordreset.{PasswordResetAuthToken, PasswordResetCode, PasswordResetCodeModel} -import com.softwaremill.bootzooka.util.BaseModule -import com.softwaremill.macwire._ -import doobie.util.transactor.Transactor - -trait SecurityModule extends BaseModule { - lazy val apiKeyModel = new ApiKeyModel - lazy val apiKeyService = wire[ApiKeyService] - lazy val apiKeyAuth: Auth[ApiKey] = new Auth(new ApiKeyAuthToken(apiKeyModel), xa, clock) - lazy val passwordResetCodeAuth: Auth[PasswordResetCode] = new Auth(new PasswordResetAuthToken(passwordResetCodeModel), xa, clock) - - def passwordResetCodeModel: PasswordResetCodeModel - def xa: Transactor[IO] -} diff --git a/backend/src/main/scala/com/softwaremill/bootzooka/user/UserModule.scala b/backend/src/main/scala/com/softwaremill/bootzooka/user/UserModule.scala deleted file mode 100644 index c693bff84..000000000 --- a/backend/src/main/scala/com/softwaremill/bootzooka/user/UserModule.scala +++ /dev/null @@ -1,24 +0,0 @@ -package com.softwaremill.bootzooka.user - -import cats.effect.IO -import com.softwaremill.bootzooka.email.{EmailScheduler, EmailTemplates} -import com.softwaremill.bootzooka.http.Http -import com.softwaremill.bootzooka.security.{ApiKey, ApiKeyService, Auth} -import com.softwaremill.bootzooka.util.BaseModule -import com.softwaremill.macwire._ -import doobie.util.transactor.Transactor - -trait UserModule extends BaseModule { - lazy val userModel = new UserModel - private lazy val userConfig = config.user - - lazy val userService = wire[UserService] - lazy val userApi = wire[UserApi] - - def http: Http - def apiKeyAuth: Auth[ApiKey] - def emailScheduler: EmailScheduler - def emailTemplates: EmailTemplates - def apiKeyService: ApiKeyService - def xa: Transactor[IO] -} diff --git a/backend/src/test/scala/com/softwaremill/bootzooka/passwordreset/PasswordResetApiTest.scala b/backend/src/test/scala/com/softwaremill/bootzooka/passwordreset/PasswordResetApiTest.scala index fa2d21999..833f35b41 100644 --- a/backend/src/test/scala/com/softwaremill/bootzooka/passwordreset/PasswordResetApiTest.scala +++ b/backend/src/test/scala/com/softwaremill/bootzooka/passwordreset/PasswordResetApiTest.scala @@ -1,33 +1,16 @@ package com.softwaremill.bootzooka.passwordreset import cats.effect.IO -import com.softwaremill.bootzooka.MainModule -import com.softwaremill.bootzooka.config.Config import com.softwaremill.bootzooka.email.sender.DummyEmailSender -import com.softwaremill.bootzooka.infrastructure.Doobie._ import com.softwaremill.bootzooka.infrastructure.Json._ -import com.softwaremill.bootzooka.passwordreset.PasswordResetApi.{ - ForgotPassword_IN, - ForgotPassword_OUT, - PasswordReset_IN, - PasswordReset_OUT -} -import com.softwaremill.bootzooka.test.{BaseTest, Requests, TestConfig, TestEmbeddedPostgres} +import com.softwaremill.bootzooka.passwordreset.PasswordResetApi.{ForgotPassword_IN, ForgotPassword_OUT, PasswordReset_IN, PasswordReset_OUT} +import com.softwaremill.bootzooka.test.{AppDependencies, BaseTest, Requests} import org.http4s._ import org.http4s.syntax.all._ import org.scalatest.concurrent.Eventually -import sttp.client3.SttpBackend -import sttp.client3.asynchttpclient.fs2.AsyncHttpClientFs2Backend - -class PasswordResetApiTest extends BaseTest with TestEmbeddedPostgres with Eventually { - lazy val modules: MainModule = new MainModule { - override def xa: Transactor[IO] = currentDb.xa - - override lazy val baseSttpBackend: SttpBackend[IO, Any] = AsyncHttpClientFs2Backend.stub[IO] - override lazy val config: Config = TestConfig - } - val requests = new Requests(modules) +class PasswordResetApiTest extends BaseTest with Eventually with AppDependencies { + val requests = new Requests(httpApi) import requests._ @@ -110,18 +93,18 @@ class PasswordResetApiTest extends BaseTest with TestEmbeddedPostgres with Event val request = Request[IO](method = POST, uri = uri"/passwordreset/forgot") .withEntity(ForgotPassword_IN(loginOrEmail)) - modules.httpApi.mainRoutes(request).unwrap + httpApi.mainRoutes(request).unwrap } def resetPassword(code: String, password: String): Response[IO] = { val request = Request[IO](method = POST, uri = uri"/passwordreset/reset") .withEntity(PasswordReset_IN(code, password)) - modules.httpApi.mainRoutes(request).unwrap + httpApi.mainRoutes(request).unwrap } def codeSentToEmail(email: String): String = { - modules.emailService.sendBatch().unwrap + emailService.sendBatch().unwrap val emailData = DummyEmailSender .findSentEmail(email, "SoftwareMill Bootzooka password reset") @@ -132,7 +115,7 @@ class PasswordResetApiTest extends BaseTest with TestEmbeddedPostgres with Event } def codeWasNotSentToEmail(email: String): Unit = { - modules.emailService.sendBatch().unwrap + emailService.sendBatch().unwrap val maybeEmail = DummyEmailSender.findSentEmail(email, "SoftwareMill Bootzooka password reset") maybeEmail match { diff --git a/backend/src/test/scala/com/softwaremill/bootzooka/test/AppDependencies.scala b/backend/src/test/scala/com/softwaremill/bootzooka/test/AppDependencies.scala new file mode 100644 index 000000000..3f751e5ba --- /dev/null +++ b/backend/src/test/scala/com/softwaremill/bootzooka/test/AppDependencies.scala @@ -0,0 +1,32 @@ +package com.softwaremill.bootzooka.test + +import cats.effect.{IO, Resource} +import com.softwaremill.bootzooka.DependenciesFactory +import com.softwaremill.bootzooka.email.EmailService +import com.softwaremill.bootzooka.http.HttpApi +import org.scalatest.{BeforeAndAfterAll, Suite} +import sttp.client3.asynchttpclient.fs2.AsyncHttpClientFs2Backend + +trait AppDependencies extends BeforeAndAfterAll with TestEmbeddedPostgres { self: Suite with BaseTest => + + var httpApi: HttpApi = _ + var emailService: EmailService = _ + + override protected def beforeAll(): Unit = { + super.beforeAll() + + val deps = { + import cats.effect.unsafe.implicits.global + + DependenciesFactory.resource( + config = TestConfig, + sttpBackend = Resource.pure(AsyncHttpClientFs2Backend.stub[IO]), + xa = Resource.pure(currentDb.xa), + clock = testClock + ).allocated.unsafeRunSync()._1 + } + + httpApi = deps._1 + emailService = deps._2 + } +} diff --git a/backend/src/test/scala/com/softwaremill/bootzooka/test/HttpTestSupport.scala b/backend/src/test/scala/com/softwaremill/bootzooka/test/HttpTestSupport.scala index 0c74a392f..fcf5148b7 100644 --- a/backend/src/test/scala/com/softwaremill/bootzooka/test/HttpTestSupport.scala +++ b/backend/src/test/scala/com/softwaremill/bootzooka/test/HttpTestSupport.scala @@ -3,7 +3,6 @@ package com.softwaremill.bootzooka.test import cats.data.OptionT import cats.effect.kernel.Concurrent import cats.effect.{IO, Sync} -import com.softwaremill.bootzooka.MainModule import com.softwaremill.bootzooka.http.Error_OUT import com.softwaremill.bootzooka.infrastructure.Json._ import io.circe.{Decoder, Encoder} @@ -20,8 +19,6 @@ import scala.reflect.ClassTag trait HttpTestSupport extends Http4sDsl[IO] with Matchers { - val modules: MainModule - // in tests we are using the http4s client, hence we need http4s entity encoders/decoders to send/receive data implicit def entityEncoderFromCirce[F[_]: Sync, T: Encoder]: EntityEncoder[F, T] = { org.http4s.circe.jsonEncoderWithPrinterOf[F, T](noNullsPrinter) diff --git a/backend/src/test/scala/com/softwaremill/bootzooka/test/Requests.scala b/backend/src/test/scala/com/softwaremill/bootzooka/test/Requests.scala index afd55a60e..55c16d9ad 100644 --- a/backend/src/test/scala/com/softwaremill/bootzooka/test/Requests.scala +++ b/backend/src/test/scala/com/softwaremill/bootzooka/test/Requests.scala @@ -1,7 +1,7 @@ package com.softwaremill.bootzooka.test import cats.effect.IO -import com.softwaremill.bootzooka.MainModule +import com.softwaremill.bootzooka.http.HttpApi import com.softwaremill.bootzooka.infrastructure.Json._ import com.softwaremill.bootzooka.user.UserApi._ import org.http4s._ @@ -9,8 +9,7 @@ import org.http4s.syntax.all._ import scala.util.Random -class Requests(val modules: MainModule) extends HttpTestSupport { - +class Requests(httpApi: => HttpApi) extends HttpTestSupport { case class RegisteredUser(login: String, email: String, password: String, apiKey: String) private val random = new Random() @@ -22,7 +21,7 @@ class Requests(val modules: MainModule) extends HttpTestSupport { val request = Request[IO](method = POST, uri = uri"/user/register") .withEntity(Register_IN(login, email, password)) - modules.httpApi.mainRoutes(request).unwrap + httpApi.mainRoutes(request).unwrap } def newRegisteredUsed(): RegisteredUser = { @@ -35,26 +34,26 @@ class Requests(val modules: MainModule) extends HttpTestSupport { val request = Request[IO](method = POST, uri = uri"/user/login") .withEntity(Login_IN(loginOrEmail, password, apiKeyValidHours)) - modules.httpApi.mainRoutes(request).unwrap + httpApi.mainRoutes(request).unwrap } def getUser(apiKey: String): Response[IO] = { val request = Request[IO](method = GET, uri = uri"/user") - modules.httpApi.mainRoutes(authorizedRequest(apiKey, request)).unwrap + httpApi.mainRoutes(authorizedRequest(apiKey, request)).unwrap } def changePassword(apiKey: String, password: String, newPassword: String): Response[IO] = { val request = Request[IO](method = POST, uri = uri"/user/changepassword") .withEntity(ChangePassword_IN(password, newPassword)) - modules.httpApi.mainRoutes(authorizedRequest(apiKey, request)).unwrap + httpApi.mainRoutes(authorizedRequest(apiKey, request)).unwrap } def updateUser(apiKey: String, login: String, email: String): Response[IO] = { val request = Request[IO](method = POST, uri = uri"/user") .withEntity(UpdateUser_IN(login, email)) - modules.httpApi.mainRoutes(authorizedRequest(apiKey, request)).unwrap + httpApi.mainRoutes(authorizedRequest(apiKey, request)).unwrap } } diff --git a/backend/src/test/scala/com/softwaremill/bootzooka/user/UserApiTest.scala b/backend/src/test/scala/com/softwaremill/bootzooka/user/UserApiTest.scala index 7d48adf4c..20ffccf05 100644 --- a/backend/src/test/scala/com/softwaremill/bootzooka/user/UserApiTest.scala +++ b/backend/src/test/scala/com/softwaremill/bootzooka/user/UserApiTest.scala @@ -1,31 +1,17 @@ package com.softwaremill.bootzooka.user -import cats.effect.IO -import com.softwaremill.bootzooka.MainModule -import com.softwaremill.bootzooka.config.Config import com.softwaremill.bootzooka.email.sender.DummyEmailSender -import com.softwaremill.bootzooka.infrastructure.Doobie._ import com.softwaremill.bootzooka.infrastructure.Json._ -import com.softwaremill.bootzooka.test.{BaseTest, Requests, TestConfig, TestEmbeddedPostgres} +import com.softwaremill.bootzooka.test.{BaseTest, AppDependencies, Requests} import com.softwaremill.bootzooka.user.UserApi._ -import com.softwaremill.bootzooka.util.Clock import org.http4s.Status import org.scalatest.concurrent.Eventually -import sttp.client3.SttpBackend -import sttp.client3.asynchttpclient.fs2.AsyncHttpClientFs2Backend import scala.concurrent.duration._ -class UserApiTest extends BaseTest with TestEmbeddedPostgres with Eventually { +class UserApiTest extends BaseTest with Eventually with AppDependencies { + val requests = new Requests(httpApi) - lazy val modules: MainModule = new MainModule { - override def xa: Transactor[IO] = currentDb.xa - override lazy val baseSttpBackend: SttpBackend[IO, Any] = AsyncHttpClientFs2Backend.stub[IO] - override lazy val config: Config = TestConfig - override lazy val clock: Clock = testClock - } - - val requests = new Requests(modules) import requests._ "/user/register" should "register" in { @@ -94,7 +80,7 @@ class UserApiTest extends BaseTest with TestEmbeddedPostgres with Eventually { val RegisteredUser(login, email, _, _) = newRegisteredUsed() // then - modules.emailService.sendBatch().unwrap + emailService.sendBatch().unwrap DummyEmailSender.findSentEmail(email, s"registration confirmation for user $login").isDefined shouldBe true } From a5c00f6207e42b4ec01158340bcc7616c5f9e17a Mon Sep 17 00:00:00 2001 From: adamw Date: Thu, 20 Jan 2022 14:01:45 +0100 Subject: [PATCH 04/10] Simplify dependencies --- build.sbt | 1 - 1 file changed, 1 deletion(-) diff --git a/build.sbt b/build.sbt index b6a5c8633..581011a8c 100644 --- a/build.sbt +++ b/build.sbt @@ -83,7 +83,6 @@ val emailDependencies = Seq( val scalatest = "org.scalatest" %% "scalatest" % "3.2.11" % Test val macwireDependencies = Seq( - "com.softwaremill.macwire" %% "macros" % macwireVersion, "com.softwaremill.macwire" %% "macrosautocats" % macwireVersion ).map(_ % Provided) From e848512108ee7775daf4ab7b323194315ddb929b Mon Sep 17 00:00:00 2001 From: adamw Date: Thu, 20 Jan 2022 14:24:31 +0100 Subject: [PATCH 05/10] Move email sender creation to companion object --- .../bootzooka/DependenciesFactory.scala | 13 ++----------- .../bootzooka/email/sender/EmailSender.scala | 13 ++++++++++++- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/backend/src/main/scala/com/softwaremill/bootzooka/DependenciesFactory.scala b/backend/src/main/scala/com/softwaremill/bootzooka/DependenciesFactory.scala index 2038f2534..356648888 100644 --- a/backend/src/main/scala/com/softwaremill/bootzooka/DependenciesFactory.scala +++ b/backend/src/main/scala/com/softwaremill/bootzooka/DependenciesFactory.scala @@ -3,10 +3,9 @@ package com.softwaremill.bootzooka import cats.data.NonEmptyList import cats.effect.{IO, Resource} import com.softwaremill.bootzooka.config.Config +import com.softwaremill.bootzooka.email.sender.EmailSender import com.softwaremill.bootzooka.email.{EmailConfig, EmailModel, EmailService} -import com.softwaremill.bootzooka.email.sender.{DummyEmailSender, EmailSender, MailgunEmailSender, SmtpEmailSender} import com.softwaremill.bootzooka.http.{Http, HttpApi, HttpConfig} -import com.softwaremill.bootzooka.infrastructure.{DB, DBConfig} import com.softwaremill.bootzooka.metrics.{MetricsApi, VersionApi} import com.softwaremill.bootzooka.passwordreset.{PasswordResetApi, PasswordResetAuthToken, PasswordResetCodeModel} import com.softwaremill.bootzooka.security.{ApiKeyAuthToken, ApiKeyModel} @@ -36,14 +35,6 @@ object DependenciesFactory { def buildPasswordResetAuthToken(passwordResetCodeModel: PasswordResetCodeModel): PasswordResetAuthToken = new PasswordResetAuthToken(passwordResetCodeModel) - def buildEmailSender(sttpBackend: SttpBackend[IO, Any], config: EmailConfig): EmailSender = if (config.mailgun.enabled) { - new MailgunEmailSender(config.mailgun, sttpBackend) - } else if (config.smtp.enabled) { - new SmtpEmailSender(config.smtp) - } else { - DummyEmailSender - } - def buildEmailScheduler(emailModel: EmailModel, idGenerator: IdGenerator, emailSender: EmailSender, config: EmailConfig, xa: Transactor[IO]) = new EmailService(emailModel, idGenerator, emailSender, config, xa) @@ -60,7 +51,7 @@ object DependenciesFactory { buildHttpApi _, buildApiKeyAuthToken _, buildEmailScheduler _, - buildEmailSender _, + EmailSender.create _, buildPasswordResetAuthToken _ ).map(modules => (modules.api, modules.emailService)) } diff --git a/backend/src/main/scala/com/softwaremill/bootzooka/email/sender/EmailSender.scala b/backend/src/main/scala/com/softwaremill/bootzooka/email/sender/EmailSender.scala index 5c92a3193..9e53c16f1 100644 --- a/backend/src/main/scala/com/softwaremill/bootzooka/email/sender/EmailSender.scala +++ b/backend/src/main/scala/com/softwaremill/bootzooka/email/sender/EmailSender.scala @@ -1,8 +1,19 @@ package com.softwaremill.bootzooka.email.sender import cats.effect.IO -import com.softwaremill.bootzooka.email.EmailData +import com.softwaremill.bootzooka.email.{EmailConfig, EmailData} +import sttp.client3.SttpBackend trait EmailSender { def apply(email: EmailData): IO[Unit] } + +object EmailSender { + def create(sttpBackend: SttpBackend[IO, Any], config: EmailConfig): EmailSender = if (config.mailgun.enabled) { + new MailgunEmailSender(config.mailgun, sttpBackend) + } else if (config.smtp.enabled) { + new SmtpEmailSender(config.smtp) + } else { + DummyEmailSender + } +} From e41fc4d4867259aa14b55e8a9a1b46bfc456e5e2 Mon Sep 17 00:00:00 2001 From: adamw Date: Thu, 20 Jan 2022 14:25:37 +0100 Subject: [PATCH 06/10] Simplify dependency creation --- .../bootzooka/DependenciesFactory.scala | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/backend/src/main/scala/com/softwaremill/bootzooka/DependenciesFactory.scala b/backend/src/main/scala/com/softwaremill/bootzooka/DependenciesFactory.scala index 356648888..543a98846 100644 --- a/backend/src/main/scala/com/softwaremill/bootzooka/DependenciesFactory.scala +++ b/backend/src/main/scala/com/softwaremill/bootzooka/DependenciesFactory.scala @@ -3,11 +3,11 @@ package com.softwaremill.bootzooka import cats.data.NonEmptyList import cats.effect.{IO, Resource} import com.softwaremill.bootzooka.config.Config +import com.softwaremill.bootzooka.email.EmailService import com.softwaremill.bootzooka.email.sender.EmailSender -import com.softwaremill.bootzooka.email.{EmailConfig, EmailModel, EmailService} import com.softwaremill.bootzooka.http.{Http, HttpApi, HttpConfig} import com.softwaremill.bootzooka.metrics.{MetricsApi, VersionApi} -import com.softwaremill.bootzooka.passwordreset.{PasswordResetApi, PasswordResetAuthToken, PasswordResetCodeModel} +import com.softwaremill.bootzooka.passwordreset.{PasswordResetApi, PasswordResetAuthToken} import com.softwaremill.bootzooka.security.{ApiKeyAuthToken, ApiKeyModel} import com.softwaremill.bootzooka.user.UserApi import com.softwaremill.bootzooka.util.{Clock, DefaultIdGenerator, IdGenerator} @@ -33,11 +33,6 @@ object DependenciesFactory { def buildApiKeyAuthToken(apiKeyModel: ApiKeyModel): ApiKeyAuthToken = new ApiKeyAuthToken(apiKeyModel) - def buildPasswordResetAuthToken(passwordResetCodeModel: PasswordResetCodeModel): PasswordResetAuthToken = new PasswordResetAuthToken(passwordResetCodeModel) - - def buildEmailScheduler(emailModel: EmailModel, idGenerator: IdGenerator, emailSender: EmailSender, config: EmailConfig, xa: Transactor[IO]) = - new EmailService(emailModel, idGenerator, emailSender, config, xa) - autowire[Modules]( config.api, config.user, @@ -50,9 +45,9 @@ object DependenciesFactory { xa, buildHttpApi _, buildApiKeyAuthToken _, - buildEmailScheduler _, + new EmailService(_, _, _, _, _), EmailSender.create _, - buildPasswordResetAuthToken _ + new PasswordResetAuthToken(_), ).map(modules => (modules.api, modules.emailService)) } } From d70ed6a6509326214bb69bf3584ab27098e8531d Mon Sep 17 00:00:00 2001 From: adamw Date: Thu, 20 Jan 2022 14:26:32 +0100 Subject: [PATCH 07/10] Simplify dependency creation --- .../bootzooka/DependenciesFactory.scala | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/backend/src/main/scala/com/softwaremill/bootzooka/DependenciesFactory.scala b/backend/src/main/scala/com/softwaremill/bootzooka/DependenciesFactory.scala index 543a98846..705b384f6 100644 --- a/backend/src/main/scala/com/softwaremill/bootzooka/DependenciesFactory.scala +++ b/backend/src/main/scala/com/softwaremill/bootzooka/DependenciesFactory.scala @@ -8,9 +8,9 @@ import com.softwaremill.bootzooka.email.sender.EmailSender import com.softwaremill.bootzooka.http.{Http, HttpApi, HttpConfig} import com.softwaremill.bootzooka.metrics.{MetricsApi, VersionApi} import com.softwaremill.bootzooka.passwordreset.{PasswordResetApi, PasswordResetAuthToken} -import com.softwaremill.bootzooka.security.{ApiKeyAuthToken, ApiKeyModel} +import com.softwaremill.bootzooka.security.ApiKeyAuthToken import com.softwaremill.bootzooka.user.UserApi -import com.softwaremill.bootzooka.util.{Clock, DefaultIdGenerator, IdGenerator} +import com.softwaremill.bootzooka.util.{Clock, DefaultIdGenerator} import com.softwaremill.macwire.autocats.autowire import doobie.util.transactor.Transactor import io.prometheus.client.CollectorRegistry @@ -20,9 +20,6 @@ object DependenciesFactory { private case class Modules(api: HttpApi, emailService: EmailService) def resource(config: Config, sttpBackend: Resource[IO, SttpBackend[IO, Any]], xa: Resource[IO, Transactor[IO]], clock: Clock): Resource[IO, (HttpApi, EmailService)] = { - lazy val collectorRegistry: CollectorRegistry = CollectorRegistry.defaultRegistry - lazy val idGenerator: IdGenerator = DefaultIdGenerator - def buildHttpApi(http: Http, userApi: UserApi, passwordResetApi: PasswordResetApi, metricsApi: MetricsApi, versionApi: VersionApi, collectorRegistry: CollectorRegistry, cfg: HttpConfig) = new HttpApi( http, @@ -31,20 +28,18 @@ object DependenciesFactory { collectorRegistry, cfg) - def buildApiKeyAuthToken(apiKeyModel: ApiKeyModel): ApiKeyAuthToken = new ApiKeyAuthToken(apiKeyModel) - autowire[Modules]( config.api, config.user, config.passwordReset, config.email, - idGenerator, + DefaultIdGenerator, clock, - collectorRegistry, + CollectorRegistry.defaultRegistry, sttpBackend, xa, buildHttpApi _, - buildApiKeyAuthToken _, + new ApiKeyAuthToken(_), new EmailService(_, _, _, _, _), EmailSender.create _, new PasswordResetAuthToken(_), From 6d598d2bfa56899d841b536a288e6f7b020dc56e Mon Sep 17 00:00:00 2001 From: adamw Date: Thu, 20 Jan 2022 14:36:09 +0100 Subject: [PATCH 08/10] Simplify config reading --- .../com/softwaremill/bootzooka/Main.scala | 15 ++++---- .../bootzooka/config/Config.scala | 34 ++++++++++++++++-- .../bootzooka/config/ConfigModule.scala | 36 ------------------- .../bootzooka/infrastructure/DB.scala | 3 +- .../softwaremill/bootzooka/test/package.scala | 4 +-- 5 files changed, 41 insertions(+), 51 deletions(-) delete mode 100644 backend/src/main/scala/com/softwaremill/bootzooka/config/ConfigModule.scala diff --git a/backend/src/main/scala/com/softwaremill/bootzooka/Main.scala b/backend/src/main/scala/com/softwaremill/bootzooka/Main.scala index 50ae77f31..17324ccf5 100644 --- a/backend/src/main/scala/com/softwaremill/bootzooka/Main.scala +++ b/backend/src/main/scala/com/softwaremill/bootzooka/Main.scala @@ -2,12 +2,12 @@ package com.softwaremill.bootzooka import cats.effect.unsafe.implicits.global import cats.effect.{IO, Resource} -import com.softwaremill.bootzooka.config.ConfigModule +import com.softwaremill.bootzooka.config.Config import com.softwaremill.bootzooka.email.EmailService import com.softwaremill.bootzooka.http.HttpApi import com.softwaremill.bootzooka.infrastructure.DB import com.softwaremill.bootzooka.metrics.Metrics -import com.softwaremill.bootzooka.util.{Clock, DefaultClock} +import com.softwaremill.bootzooka.util.DefaultClock import com.typesafe.scalalogging.StrictLogging import sttp.capabilities.WebSockets import sttp.capabilities.fs2.Fs2Streams @@ -21,25 +21,22 @@ object Main extends StrictLogging { Metrics.init() Thread.setDefaultUncaughtExceptionHandler((t, e) => logger.error("Uncaught exception in thread: " + t, e)) - val configModule = new ConfigModule {} - configModule.logConfig() - val config = configModule.config - - lazy val clock: Clock = DefaultClock + val config = Config.read + Config.log(config) lazy val sttpBackend: Resource[IO, SttpBackend[IO, Fs2Streams[IO] with WebSockets]] = AsyncHttpClientFs2Backend .resource[IO]() .map(baseSttpBackend => Slf4jLoggingBackend(PrometheusBackend(baseSttpBackend), includeTiming = true)) - lazy val xa = new DB(config.db).transactorResource + val xa = new DB(config.db).transactorResource val mainTask = DependenciesFactory .resource( config = config, sttpBackend = sttpBackend, xa = xa, - clock = clock + clock = DefaultClock ) .use { case (httpApi, emailService) => /* diff --git a/backend/src/main/scala/com/softwaremill/bootzooka/config/Config.scala b/backend/src/main/scala/com/softwaremill/bootzooka/config/Config.scala index 782285cd7..2de3798a5 100644 --- a/backend/src/main/scala/com/softwaremill/bootzooka/config/Config.scala +++ b/backend/src/main/scala/com/softwaremill/bootzooka/config/Config.scala @@ -5,7 +5,37 @@ import com.softwaremill.bootzooka.http.HttpConfig import com.softwaremill.bootzooka.infrastructure.DBConfig import com.softwaremill.bootzooka.passwordreset.PasswordResetConfig import com.softwaremill.bootzooka.user.UserConfig +import com.softwaremill.bootzooka.version.BuildInfo +import com.typesafe.scalalogging.StrictLogging +import pureconfig.ConfigSource +import pureconfig.generic.auto._ -/** Maps to the `application.conf` file. Configuration for all modules of the application. - */ +import scala.collection.immutable.TreeMap + +/** Maps to the `application.conf` file. Configuration for all modules of the application. */ case class Config(db: DBConfig, api: HttpConfig, email: EmailConfig, passwordReset: PasswordResetConfig, user: UserConfig) + +object Config extends StrictLogging { + def log(config: Config): Unit = { + val baseInfo = s""" + |Bootzooka configuration: + |----------------------- + |DB: ${config.db} + |API: ${config.api} + |Email: ${config.email} + |Password reset: ${config.passwordReset} + |User: ${config.user} + | + |Build & env info: + |----------------- + |""".stripMargin + + val info = TreeMap(BuildInfo.toMap.toSeq: _*).foldLeft(baseInfo) { case (str, (k, v)) => + str + s"$k: $v\n" + } + + logger.info(info) + } + + def read: Config = ConfigSource.default.loadOrThrow[Config] +} diff --git a/backend/src/main/scala/com/softwaremill/bootzooka/config/ConfigModule.scala b/backend/src/main/scala/com/softwaremill/bootzooka/config/ConfigModule.scala deleted file mode 100644 index b2f27deef..000000000 --- a/backend/src/main/scala/com/softwaremill/bootzooka/config/ConfigModule.scala +++ /dev/null @@ -1,36 +0,0 @@ -package com.softwaremill.bootzooka.config - -import com.softwaremill.bootzooka.version.BuildInfo -import com.typesafe.scalalogging.StrictLogging -import pureconfig.ConfigSource -import pureconfig.generic.auto._ - -import scala.collection.immutable.TreeMap - -/** Reads and gives access to the configuration object. - */ -trait ConfigModule extends StrictLogging { - - lazy val config: Config = ConfigSource.default.loadOrThrow[Config] - - def logConfig(): Unit = { - val baseInfo = s""" - |Bootzooka configuration: - |----------------------- - |DB: ${config.db} - |API: ${config.api} - |Email: ${config.email} - |Password reset: ${config.passwordReset} - |User: ${config.user} - | - |Build & env info: - |----------------- - |""".stripMargin - - val info = TreeMap(BuildInfo.toMap.toSeq: _*).foldLeft(baseInfo) { case (str, (k, v)) => - str + s"$k: $v\n" - } - - logger.info(info) - } -} diff --git a/backend/src/main/scala/com/softwaremill/bootzooka/infrastructure/DB.scala b/backend/src/main/scala/com/softwaremill/bootzooka/infrastructure/DB.scala index 4b4849e8d..6faa37c57 100644 --- a/backend/src/main/scala/com/softwaremill/bootzooka/infrastructure/DB.scala +++ b/backend/src/main/scala/com/softwaremill/bootzooka/infrastructure/DB.scala @@ -13,8 +13,7 @@ import com.softwaremill.macwire.autocats.autowire import scala.concurrent.ExecutionContext -/** Configures the database, setting up the connection pool and performing migrations. - */ +/** Configures the database, setting up the connection pool and performing migrations. */ class DB(_config: DBConfig) extends StrictLogging { private val config: DBConfig = { diff --git a/backend/src/test/scala/com/softwaremill/bootzooka/test/package.scala b/backend/src/test/scala/com/softwaremill/bootzooka/test/package.scala index 287fcd31c..927bfe510 100644 --- a/backend/src/test/scala/com/softwaremill/bootzooka/test/package.scala +++ b/backend/src/test/scala/com/softwaremill/bootzooka/test/package.scala @@ -1,11 +1,11 @@ package com.softwaremill.bootzooka -import com.softwaremill.bootzooka.config.{Config, ConfigModule} +import com.softwaremill.bootzooka.config.Config import com.softwaremill.quicklens._ import scala.concurrent.duration._ package object test { - val DefaultConfig: Config = new ConfigModule {}.config + val DefaultConfig: Config = Config.read val TestConfig: Config = DefaultConfig.modify(_.email.emailSendInterval).setTo(100.milliseconds) } From f9c7cb08dce66b66f9bd07680453171c609f3210 Mon Sep 17 00:00:00 2001 From: adamw Date: Thu, 20 Jan 2022 14:41:04 +0100 Subject: [PATCH 09/10] Make Dependencies a first-class concept --- ...enciesFactory.scala => Dependencies.scala} | 10 +++---- .../com/softwaremill/bootzooka/Main.scala | 26 ++++++------------- .../passwordreset/PasswordResetApiTest.scala | 15 +++++------ ...endencies.scala => TestDependencies.scala} | 17 ++++-------- .../bootzooka/user/UserApiTest.scala | 9 +++---- 5 files changed, 29 insertions(+), 48 deletions(-) rename backend/src/main/scala/com/softwaremill/bootzooka/{DependenciesFactory.scala => Dependencies.scala} (81%) rename backend/src/test/scala/com/softwaremill/bootzooka/test/{AppDependencies.scala => TestDependencies.scala} (55%) diff --git a/backend/src/main/scala/com/softwaremill/bootzooka/DependenciesFactory.scala b/backend/src/main/scala/com/softwaremill/bootzooka/Dependencies.scala similarity index 81% rename from backend/src/main/scala/com/softwaremill/bootzooka/DependenciesFactory.scala rename to backend/src/main/scala/com/softwaremill/bootzooka/Dependencies.scala index 705b384f6..82ec3fb34 100644 --- a/backend/src/main/scala/com/softwaremill/bootzooka/DependenciesFactory.scala +++ b/backend/src/main/scala/com/softwaremill/bootzooka/Dependencies.scala @@ -16,10 +16,10 @@ import doobie.util.transactor.Transactor import io.prometheus.client.CollectorRegistry import sttp.client3.SttpBackend -object DependenciesFactory { - private case class Modules(api: HttpApi, emailService: EmailService) +case class Dependencies(api: HttpApi, emailService: EmailService) - def resource(config: Config, sttpBackend: Resource[IO, SttpBackend[IO, Any]], xa: Resource[IO, Transactor[IO]], clock: Clock): Resource[IO, (HttpApi, EmailService)] = { +object Dependencies { + def wire(config: Config, sttpBackend: Resource[IO, SttpBackend[IO, Any]], xa: Resource[IO, Transactor[IO]], clock: Clock): Resource[IO, Dependencies] = { def buildHttpApi(http: Http, userApi: UserApi, passwordResetApi: PasswordResetApi, metricsApi: MetricsApi, versionApi: VersionApi, collectorRegistry: CollectorRegistry, cfg: HttpConfig) = new HttpApi( http, @@ -28,7 +28,7 @@ object DependenciesFactory { collectorRegistry, cfg) - autowire[Modules]( + autowire[Dependencies]( config.api, config.user, config.passwordReset, @@ -43,6 +43,6 @@ object DependenciesFactory { new EmailService(_, _, _, _, _), EmailSender.create _, new PasswordResetAuthToken(_), - ).map(modules => (modules.api, modules.emailService)) + ) } } diff --git a/backend/src/main/scala/com/softwaremill/bootzooka/Main.scala b/backend/src/main/scala/com/softwaremill/bootzooka/Main.scala index 17324ccf5..9c2fc739b 100644 --- a/backend/src/main/scala/com/softwaremill/bootzooka/Main.scala +++ b/backend/src/main/scala/com/softwaremill/bootzooka/Main.scala @@ -3,8 +3,6 @@ package com.softwaremill.bootzooka import cats.effect.unsafe.implicits.global import cats.effect.{IO, Resource} import com.softwaremill.bootzooka.config.Config -import com.softwaremill.bootzooka.email.EmailService -import com.softwaremill.bootzooka.http.HttpApi import com.softwaremill.bootzooka.infrastructure.DB import com.softwaremill.bootzooka.metrics.Metrics import com.softwaremill.bootzooka.util.DefaultClock @@ -31,25 +29,17 @@ object Main extends StrictLogging { val xa = new DB(config.db).transactorResource - val mainTask = DependenciesFactory - .resource( - config = config, - sttpBackend = sttpBackend, - xa = xa, - clock = DefaultClock - ) - .use { case (httpApi, emailService) => + Dependencies + .wire(config, sttpBackend, xa, DefaultClock) + .use { case Dependencies(httpApi, emailService) => /* - Sequencing two tasks using the >> operator: - - the first starts the background processes (such as an email sender) - - the second allocates the http api resource, and never releases it (so that the http server is available - as long as our application runs) + Sequencing two tasks using the >> operator: + - the first starts the background processes (such as an email sender) + - the second allocates the http api resource, and never releases it (so that the http server is available + as long as our application runs) */ emailService.startProcesses().void >> httpApi.resource.use(_ => IO.never) } - - mainTask.unsafeRunSync() + .unsafeRunSync() } } - -case class Modules(emailService: EmailService, httpApi: HttpApi) diff --git a/backend/src/test/scala/com/softwaremill/bootzooka/passwordreset/PasswordResetApiTest.scala b/backend/src/test/scala/com/softwaremill/bootzooka/passwordreset/PasswordResetApiTest.scala index 833f35b41..f0ce68396 100644 --- a/backend/src/test/scala/com/softwaremill/bootzooka/passwordreset/PasswordResetApiTest.scala +++ b/backend/src/test/scala/com/softwaremill/bootzooka/passwordreset/PasswordResetApiTest.scala @@ -4,14 +4,13 @@ import cats.effect.IO import com.softwaremill.bootzooka.email.sender.DummyEmailSender import com.softwaremill.bootzooka.infrastructure.Json._ import com.softwaremill.bootzooka.passwordreset.PasswordResetApi.{ForgotPassword_IN, ForgotPassword_OUT, PasswordReset_IN, PasswordReset_OUT} -import com.softwaremill.bootzooka.test.{AppDependencies, BaseTest, Requests} +import com.softwaremill.bootzooka.test.{TestDependencies, BaseTest, Requests} import org.http4s._ import org.http4s.syntax.all._ import org.scalatest.concurrent.Eventually -class PasswordResetApiTest extends BaseTest with Eventually with AppDependencies { - val requests = new Requests(httpApi) - +class PasswordResetApiTest extends BaseTest with Eventually with TestDependencies { + lazy val requests = new Requests(dependencies.api) import requests._ "/passwordreset" should "reset the password" in { @@ -93,18 +92,18 @@ class PasswordResetApiTest extends BaseTest with Eventually with AppDependencies val request = Request[IO](method = POST, uri = uri"/passwordreset/forgot") .withEntity(ForgotPassword_IN(loginOrEmail)) - httpApi.mainRoutes(request).unwrap + dependencies.api.mainRoutes(request).unwrap } def resetPassword(code: String, password: String): Response[IO] = { val request = Request[IO](method = POST, uri = uri"/passwordreset/reset") .withEntity(PasswordReset_IN(code, password)) - httpApi.mainRoutes(request).unwrap + dependencies.api.mainRoutes(request).unwrap } def codeSentToEmail(email: String): String = { - emailService.sendBatch().unwrap + dependencies.emailService.sendBatch().unwrap val emailData = DummyEmailSender .findSentEmail(email, "SoftwareMill Bootzooka password reset") @@ -115,7 +114,7 @@ class PasswordResetApiTest extends BaseTest with Eventually with AppDependencies } def codeWasNotSentToEmail(email: String): Unit = { - emailService.sendBatch().unwrap + dependencies.emailService.sendBatch().unwrap val maybeEmail = DummyEmailSender.findSentEmail(email, "SoftwareMill Bootzooka password reset") maybeEmail match { diff --git a/backend/src/test/scala/com/softwaremill/bootzooka/test/AppDependencies.scala b/backend/src/test/scala/com/softwaremill/bootzooka/test/TestDependencies.scala similarity index 55% rename from backend/src/test/scala/com/softwaremill/bootzooka/test/AppDependencies.scala rename to backend/src/test/scala/com/softwaremill/bootzooka/test/TestDependencies.scala index 3f751e5ba..fc4c19f73 100644 --- a/backend/src/test/scala/com/softwaremill/bootzooka/test/AppDependencies.scala +++ b/backend/src/test/scala/com/softwaremill/bootzooka/test/TestDependencies.scala @@ -1,32 +1,25 @@ package com.softwaremill.bootzooka.test import cats.effect.{IO, Resource} -import com.softwaremill.bootzooka.DependenciesFactory -import com.softwaremill.bootzooka.email.EmailService -import com.softwaremill.bootzooka.http.HttpApi +import com.softwaremill.bootzooka.Dependencies import org.scalatest.{BeforeAndAfterAll, Suite} import sttp.client3.asynchttpclient.fs2.AsyncHttpClientFs2Backend -trait AppDependencies extends BeforeAndAfterAll with TestEmbeddedPostgres { self: Suite with BaseTest => - - var httpApi: HttpApi = _ - var emailService: EmailService = _ +trait TestDependencies extends BeforeAndAfterAll with TestEmbeddedPostgres { self: Suite with BaseTest => + var dependencies: Dependencies = _ override protected def beforeAll(): Unit = { super.beforeAll() - val deps = { + dependencies = { import cats.effect.unsafe.implicits.global - DependenciesFactory.resource( + Dependencies.wire( config = TestConfig, sttpBackend = Resource.pure(AsyncHttpClientFs2Backend.stub[IO]), xa = Resource.pure(currentDb.xa), clock = testClock ).allocated.unsafeRunSync()._1 } - - httpApi = deps._1 - emailService = deps._2 } } diff --git a/backend/src/test/scala/com/softwaremill/bootzooka/user/UserApiTest.scala b/backend/src/test/scala/com/softwaremill/bootzooka/user/UserApiTest.scala index 20ffccf05..251d9e96d 100644 --- a/backend/src/test/scala/com/softwaremill/bootzooka/user/UserApiTest.scala +++ b/backend/src/test/scala/com/softwaremill/bootzooka/user/UserApiTest.scala @@ -2,16 +2,15 @@ package com.softwaremill.bootzooka.user import com.softwaremill.bootzooka.email.sender.DummyEmailSender import com.softwaremill.bootzooka.infrastructure.Json._ -import com.softwaremill.bootzooka.test.{BaseTest, AppDependencies, Requests} +import com.softwaremill.bootzooka.test.{BaseTest, TestDependencies, Requests} import com.softwaremill.bootzooka.user.UserApi._ import org.http4s.Status import org.scalatest.concurrent.Eventually import scala.concurrent.duration._ -class UserApiTest extends BaseTest with Eventually with AppDependencies { - val requests = new Requests(httpApi) - +class UserApiTest extends BaseTest with Eventually with TestDependencies { + lazy val requests = new Requests(dependencies.api) import requests._ "/user/register" should "register" in { @@ -80,7 +79,7 @@ class UserApiTest extends BaseTest with Eventually with AppDependencies { val RegisteredUser(login, email, _, _) = newRegisteredUsed() // then - emailService.sendBatch().unwrap + dependencies.emailService.sendBatch().unwrap DummyEmailSender.findSentEmail(email, s"registration confirmation for user $login").isDefined shouldBe true } From b8ee7085b8526d460b8ffd8a336a59bf04247b07 Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Wed, 16 Feb 2022 19:43:31 +0100 Subject: [PATCH 10/10] Upgrade macwire --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 581011a8c..35e74f941 100644 --- a/build.sbt +++ b/build.sbt @@ -17,7 +17,7 @@ val tsecVersion = "0.4.0" val sttpVersion = "3.5.0" val prometheusVersion = "0.15.0" val tapirVersion = "0.20.0-M9" -val macwireVersion = "2.5.3" +val macwireVersion = "2.5.6" val dbDependencies = Seq( "org.tpolecat" %% "doobie-core" % doobieVersion,