Skip to content

Commit

Permalink
Format code to new syntax | Rewrite app config provider to decrypting…
Browse files Browse the repository at this point in the history
… decorator
  • Loading branch information
ekuzmichev committed Aug 7, 2024
1 parent 87560bc commit 814785d
Show file tree
Hide file tree
Showing 28 changed files with 177 additions and 165 deletions.
7 changes: 7 additions & 0 deletions src/main/scala/app/AppArgs.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package ru.ekuzmichev
package app

import common.Sensitive
import util.lang.NamedToString

case class AppArgs(encryptionPassword: Sensitive[String]) extends NamedToString
65 changes: 35 additions & 30 deletions src/main/scala/app/AppLayers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ru.ekuzmichev
package app

import app.OzonPriceCheckerApp.getArgs
import common.Sensitive
import config.{AppConfig, AppConfigLayers}
import consumer.*
import encryption.EncDecLayers
Expand All @@ -13,36 +14,40 @@ import telegram.TelegramClientLayers
import util.lang.Throwables.failure

import org.telegram.telegrambots.longpolling.interfaces.LongPollingUpdateConsumer
import zio.{RLayer, ZIO, ZIOAppArgs, ZLayer}
import zio.{RLayer, Task, TaskLayer, URLayer, ZIO, ZIOAppArgs, ZLayer}

object AppLayers:
private type ROut = AppConfig & LongPollingUpdateConsumer & ConsumerRegisterer & ProductWatchingJobScheduler &
ProductStore & CacheStateRepository
type OzonPriceCheckerAppROut =
AppConfig & LongPollingUpdateConsumer & ConsumerRegisterer & ProductWatchingJobScheduler & ProductStore &
CacheStateRepository

val ozonPriceCheckerAppLayer: RLayer[ZIOAppArgs, ROut] =
ZLayer
.fromZIO(
getArgs.flatMap(args =>
args.headOption match
case Some(encryptionPassword) => ZIO.succeed(encryptionPassword)
case None => ZIO.fail(failure(s"No encryption password provided"))
)
)
.flatMap { encryptionPasswordEnv =>
ZLayer.make[ROut](
AppConfigLayers.impl,
ConsumerRegistererLayers.impl,
UpdateConsumerLayers.ozonPriceChecker,
TelegramClientLayers.okHttp,
ProductStoreLayers.cachedOverInMemory,
ProductFetcherLayers.ozon,
ZioSchedulerLayers.impl,
BrowserLayers.jsoup,
JobIdGeneratorLayers.alphaNumeric,
ProductIdParserLayers.ozon,
CommandProcessorLayers.ozonPriceChecker,
EncDecLayers.aes256(encryptionPasswordEnv.get),
ProductWatchingJobSchedulerLayers.impl,
CacheStateRepositoryLayers.file
)
}
private def parseAppArgs(args: Seq[String]): Task[AppArgs] =
args.headOption match
case Some(encryptionPassword) =>
val appArgs = AppArgs(Sensitive(encryptionPassword))
ZIO.log(s"Parsed $appArgs").as(appArgs)
case None =>
ZIO.fail(failure(s"No encryption password provided"))

private val appArgsLayer: RLayer[ZIOAppArgs, AppArgs] = ZLayer.fromZIO(getArgs.flatMap(parseAppArgs))

val ozonPriceCheckerAppLayer: RLayer[ZIOAppArgs, OzonPriceCheckerAppROut] =
appArgsLayer.flatMap(appArgsEnv => makeOzonPriceCheckerLayer(appArgsEnv.get.encryptionPassword))

private def makeOzonPriceCheckerLayer(encryptionPassword: Sensitive[String]): TaskLayer[OzonPriceCheckerAppROut] =
ZLayer.make[OzonPriceCheckerAppROut](
AppConfigLayers.decryptingOverImpl,
ConsumerRegistererLayers.impl,
UpdateConsumerLayers.ozonPriceChecker,
TelegramClientLayers.okHttp,
ProductStoreLayers.cachedOverInMemory,
ProductFetcherLayers.ozon,
ZioSchedulerLayers.impl,
BrowserLayers.jsoup,
JobIdGeneratorLayers.alphaNumeric,
ProductIdParserLayers.ozon,
CommandProcessorLayers.ozonPriceChecker,
EncDecLayers.aes256(encryptionPassword.value),
ProductWatchingJobSchedulerLayers.impl,
CacheStateRepositoryLayers.file
)
37 changes: 21 additions & 16 deletions src/main/scala/app/OzonPriceCheckerApp.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ru.ekuzmichev
package app

import app.AppLayers.{OzonPriceCheckerAppROut, ozonPriceCheckerAppLayer}
import config.AppConfig
import consumer.*
import store.ProductStore.{SourceId, SourceState}
Expand All @@ -19,16 +20,12 @@ object OzonPriceCheckerApp extends ZIOAppDefault:

override def run: ZIO[Any with ZIOAppArgs with Scope, Any, Any] =
runBot()
.catchAll(t => ZIO.fail(s"Got error while running ${this.getClass.getSimpleName}: $makeCauseSeqMessage(t)"))
.provideLayer(AppLayers.ozonPriceCheckerAppLayer)

private def runBot(): RIO[
LongPollingUpdateConsumer & ConsumerRegisterer & AppConfig & ProductWatchingJobScheduler & ProductStore &
CacheStateRepository,
Unit
] =
.catchAll(t => ZIO.fail(s"Got error while running ${this.getClass.getSimpleName}: ${makeCauseSeqMessage(t)}"))
.provideLayer(ozonPriceCheckerAppLayer)

private def runBot(): RIO[OzonPriceCheckerAppROut, Unit] =
ZIO.scoped {
for {
for
startDateTime <- getCurrentDateTime
botsApplication <- BotsApplicationScopes.makeLongPollingBotsApplication()

Expand All @@ -39,22 +36,30 @@ object OzonPriceCheckerApp extends ZIOAppDefault:
productStore <- ZIO.service[ProductStore]
cacheStateRepository <- ZIO.service[CacheStateRepository]

_ <- consumerRegisterer.registerConsumer(botsApplication, longPollingUpdateConsumer, appConfig.botToken.value)
_ <- consumerRegisterer.registerConsumer(botsApplication, longPollingUpdateConsumer, appConfig.botToken)

_ <- ZIO.log(s"Initialized bot messages consumer at $startDateTime")

_ <- productWatchingJobScheduler.scheduleProductsWatching()

_ <- ZIO.log(s"Scheduled product watching job")

cacheState <- cacheStateRepository.read()
_ <- ZIO.when(cacheState.entries.nonEmpty)(

_ <- ZIO.when(cacheState.nonEmpty)(
productStore
.preInitialize(toSourceStatesBySourceId(cacheState))
.zipLeft(ZIO.log(s"Pre-initialized product store with $cacheState"))
.zipLeft(ZIO.log(s"Pre-initialized product store with state of size ${cacheState.size}"))
)

_ <- Schedulers.scheduleBotStatusLogging(startDateTime, appConfig.logBotStatusInterval)
} yield ()
yield ()
}

private def toSourceStatesBySourceId(cacheState: CacheState): Map[SourceId, SourceState] =
cacheState.entries
.map(entry => SourceId(entry.userName, entry.chatId) -> SourceState(products = entry.products, None))
.toMap
cacheState.entries.map { entry =>
val sourceId = SourceId(entry.userName, entry.chatId)
val sourceState = SourceState(products = entry.products, None)

sourceId -> sourceState
}.toMap
2 changes: 1 addition & 1 deletion src/main/scala/app/Schedulers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ object Schedulers:
.flatMap { currentDateTime =>
def calculateDurationInSecs: Long = toEpochSeconds(currentDateTime) - toEpochSeconds(startDateTime)

ZIO.log(s"Bot is running already ${printDuration(calculateDurationInSecs)}")
ZIO.log(s"Bot is running already ${printDuration(calculateDurationInSecs)} since $startDateTime")
}
.schedule(Schedule.fixed(zio.Duration.fromScala(logBotStatusInterval)))
.unit
3 changes: 3 additions & 0 deletions src/main/scala/common/CommonTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ package common
type UserName = String
type ChatId = String
type ProductId = String

case class Sensitive[T](value: T):
override def toString: String = s"***"
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package ru.ekuzmichev
package config

import common.Sensitive
import util.lang.NamedToString

import scala.concurrent.duration.Duration

case class Sensitive(value: String):
override def toString: String = s"***"

case class AppConfig(
botToken: Sensitive,
botToken: Sensitive[String],
priceCheckingCron: String,
logBotStatusInterval: Duration,
cacheStateFilePath: String
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/config/AppConfigLayers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import encryption.EncDec
import zio.{RLayer, ZIO, ZLayer}

object AppConfigLayers:
val impl: RLayer[EncDec, AppConfig] =
AppConfigProviderLayers.impl.flatMap(env =>
val decryptingOverImpl: RLayer[EncDec, AppConfig] =
AppConfigProviderLayers.decryptingOverImpl.flatMap(env =>
ZLayer.fromZIO(env.get.provideAppConfig().tap(appConfig => ZIO.log(s"Parsed: $appConfig")))
)
33 changes: 11 additions & 22 deletions src/main/scala/config/AppConfigProviderImpl.scala
Original file line number Diff line number Diff line change
@@ -1,43 +1,32 @@
package ru.ekuzmichev
package config

import config.AppConfigProviderImpl.makeAppConfigZioConfig
import common.Sensitive
import config.AppConfigProviderImpl.AppConfigZioConfig
import config.TypeSafeConfigProviders.makeFromResourceFile
import encryption.EncDec

import cron4s.Cron
import zio.Config.*
import zio.config.*
import zio.config.magnolia.*
import zio.{Config, IO, Task, ZIO}
import zio.{Config, Task}

import scala.concurrent.duration.Duration

class AppConfigProviderImpl(encDec: EncDec) extends AppConfigProvider:
class AppConfigProviderImpl extends AppConfigProvider:
override def provideAppConfig(): Task[AppConfig] =
makeConfigProvider()
.flatMap(_.load(makeAppConfigZioConfig(encDec)))
.flatMap(rawAppConfig =>
encDec
.decrypt(rawAppConfig.botToken.value)
.map(decryptedBotTokenValue => rawAppConfig.copy(botToken = Sensitive.apply(decryptedBotTokenValue)))
)
.tap(appConfig => validateCron(appConfig.priceCheckingCron))

private def validateCron(cron: String): IO[cron4s.Error, Unit] =
ZIO.fromEither(Cron(cron)).tapError(error => ZIO.logError(s"Failed to validate cron $cron: $error")).unit
makeConfigProvider().flatMap(_.load(AppConfigZioConfig))

private def makeConfigProvider() =
for {
for
baseConfigProvider <- TypeSafeConfigProviders.makeFromResourceFile("app.conf")
localConfigProvider <- TypeSafeConfigProviders.makeFromResourceFile("app.local.conf")
} yield baseConfigProvider.orElse(localConfigProvider)
yield baseConfigProvider.orElse(localConfigProvider)

object AppConfigProviderImpl:
private def makeAppConfigZioConfig(encDec: EncDec): Config[AppConfig] =
implicit val sensitiveDeriveConfig: DeriveConfig[Sensitive] =
DeriveConfig[String].map(Sensitive.apply)
private val AppConfigZioConfig: Config[AppConfig] =
implicit def sensitiveDeriveConfig[T: DeriveConfig]: DeriveConfig[Sensitive[T]] =
DeriveConfig[T].map(Sensitive.apply)

implicit val durationDeriveConfig: DeriveConfig[Duration] =
DeriveConfig[String].map(Duration.apply)

Expand Down
8 changes: 6 additions & 2 deletions src/main/scala/config/AppConfigProviderLayers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ package config

import encryption.EncDec

import zio.{RLayer, ZLayer}
import zio.{RLayer, ULayer, ZLayer}

object AppConfigProviderLayers:
val impl: RLayer[EncDec, AppConfigProvider] = ZLayer.fromFunction(new AppConfigProviderImpl(_))
val impl: ULayer[AppConfigProvider] = ZLayer.succeed(new AppConfigProviderImpl)

val decryptingOverImpl: RLayer[EncDec, AppConfigProvider] =
ZLayer.environment[EncDec] ++ impl >>>
ZLayer.fromFunction(new DecryptingAppConfigProvider(_, _))
18 changes: 18 additions & 0 deletions src/main/scala/config/DecryptingAppConfigProvider.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package ru.ekuzmichev
package config
import common.Sensitive
import encryption.EncDec

import zio.Task

class DecryptingAppConfigProvider(decoratee: AppConfigProvider, encDec: EncDec) extends AppConfigProvider:
override def provideAppConfig(): Task[AppConfig] =
decoratee
.provideAppConfig()
.flatMap { appConfig =>
decrypt(appConfig.botToken)
.map(decryptedBotToken => appConfig.copy(botToken = decryptedBotToken))
}

private def decrypt(sensitive: Sensitive[String]): Task[Sensitive[String]] =
encDec.decrypt(sensitive.value).map(Sensitive.apply)
4 changes: 3 additions & 1 deletion src/main/scala/consumer/ConsumerRegisterer.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package ru.ekuzmichev
package consumer

import common.Sensitive

import org.telegram.telegrambots.longpolling.interfaces.LongPollingUpdateConsumer
import org.telegram.telegrambots.longpolling.{BotSession, TelegramBotsLongPollingApplication}
import zio.Task
Expand All @@ -9,5 +11,5 @@ trait ConsumerRegisterer:
def registerConsumer(
botsApplication: TelegramBotsLongPollingApplication,
longPollingUpdateConsumer: LongPollingUpdateConsumer,
token: String
token: Sensitive[String]
): Task[BotSession]
13 changes: 6 additions & 7 deletions src/main/scala/consumer/ConsumerRegistererImpl.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package ru.ekuzmichev
package consumer

import store.InMemoryProductStore
import common.Sensitive
import util.zio.ZioLoggingImplicits.Ops

import org.telegram.telegrambots.longpolling.interfaces.LongPollingUpdateConsumer
Expand All @@ -12,13 +12,12 @@ class ConsumerRegistererImpl extends ConsumerRegisterer:
override def registerConsumer(
botsApplication: TelegramBotsLongPollingApplication,
longPollingUpdateConsumer: LongPollingUpdateConsumer,
token: String
token: Sensitive[String]
): Task[BotSession] =
for {
botSession <- ZIO
.attempt(botsApplication.registerBot(token, longPollingUpdateConsumer))
for botSession <- ZIO
.attempt(botsApplication.registerBot(token.value, longPollingUpdateConsumer))
.logged(
s"register updates consumer ${longPollingUpdateConsumer.getClass.getSimpleName}",
printResult = session => s"Session is ${if (session.isRunning) "" else "not "}running"
printResult = session => s"Session is ${if session.isRunning then "" else "not "}running"
)
} yield botSession
yield botSession
Loading

0 comments on commit 814785d

Please sign in to comment.