From 814785d853ee2d832bdd22fdecbb142ffcf1e7c3 Mon Sep 17 00:00:00 2001 From: ekuzmichev Date: Wed, 7 Aug 2024 22:22:46 +0300 Subject: [PATCH] Format code to new syntax | Rewrite app config provider to decrypting decorator --- src/main/scala/app/AppArgs.scala | 7 ++ src/main/scala/app/AppLayers.scala | 65 ++++++++++--------- src/main/scala/app/OzonPriceCheckerApp.scala | 37 ++++++----- src/main/scala/app/Schedulers.scala | 2 +- src/main/scala/common/CommonTypes.scala | 3 + .../{ConfigTypes.scala => AppConfig.scala} | 6 +- src/main/scala/config/AppConfigLayers.scala | 4 +- .../scala/config/AppConfigProviderImpl.scala | 33 ++++------ .../config/AppConfigProviderLayers.scala | 8 ++- .../config/DecryptingAppConfigProvider.scala | 18 +++++ .../scala/consumer/ConsumerRegisterer.scala | 4 +- .../consumer/ConsumerRegistererImpl.scala | 13 ++-- .../OzonPriceCheckerCommandProcessor.scala | 45 +++++-------- .../OzonPriceCheckerUpdateConsumer.scala | 13 ++-- .../ProductWatchingJobSchedulerImpl.scala | 3 +- .../ProductWatchingJobSchedulerLayers.scala | 4 +- .../scala/consumer/UpdateConsumerLayers.scala | 5 +- src/main/scala/encryption/EncDecLayers.scala | 4 +- .../scala/product/OzonProductIdParser.scala | 8 +-- .../scala/schedule/ZioSchedulerImpl.scala | 18 ++--- src/main/scala/store/CacheState.scala | 7 +- .../store/CacheStateRepositoryLayers.scala | 4 +- .../store/FileCacheStateRepository.scala | 5 +- .../scala/store/InMemoryProductStore.scala | 4 +- src/main/scala/store/ProductStoreLayers.scala | 9 ++- .../scala/telegram/TelegramClientLayers.scala | 5 +- src/main/scala/util/lang/Strings.scala | 2 +- src/main/scala/util/lang/Throwables.scala | 6 +- 28 files changed, 177 insertions(+), 165 deletions(-) create mode 100644 src/main/scala/app/AppArgs.scala rename src/main/scala/config/{ConfigTypes.scala => AppConfig.scala} (71%) create mode 100644 src/main/scala/config/DecryptingAppConfigProvider.scala diff --git a/src/main/scala/app/AppArgs.scala b/src/main/scala/app/AppArgs.scala new file mode 100644 index 0000000..ad03cde --- /dev/null +++ b/src/main/scala/app/AppArgs.scala @@ -0,0 +1,7 @@ +package ru.ekuzmichev +package app + +import common.Sensitive +import util.lang.NamedToString + +case class AppArgs(encryptionPassword: Sensitive[String]) extends NamedToString diff --git a/src/main/scala/app/AppLayers.scala b/src/main/scala/app/AppLayers.scala index 8b5a787..3c27c07 100644 --- a/src/main/scala/app/AppLayers.scala +++ b/src/main/scala/app/AppLayers.scala @@ -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 @@ -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 + ) diff --git a/src/main/scala/app/OzonPriceCheckerApp.scala b/src/main/scala/app/OzonPriceCheckerApp.scala index eac2132..064d35b 100644 --- a/src/main/scala/app/OzonPriceCheckerApp.scala +++ b/src/main/scala/app/OzonPriceCheckerApp.scala @@ -1,6 +1,7 @@ package ru.ekuzmichev package app +import app.AppLayers.{OzonPriceCheckerAppROut, ozonPriceCheckerAppLayer} import config.AppConfig import consumer.* import store.ProductStore.{SourceId, SourceState} @@ -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() @@ -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 diff --git a/src/main/scala/app/Schedulers.scala b/src/main/scala/app/Schedulers.scala index b849e6d..a244ba8 100644 --- a/src/main/scala/app/Schedulers.scala +++ b/src/main/scala/app/Schedulers.scala @@ -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 diff --git a/src/main/scala/common/CommonTypes.scala b/src/main/scala/common/CommonTypes.scala index cf60a21..360f0f6 100644 --- a/src/main/scala/common/CommonTypes.scala +++ b/src/main/scala/common/CommonTypes.scala @@ -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"***" \ No newline at end of file diff --git a/src/main/scala/config/ConfigTypes.scala b/src/main/scala/config/AppConfig.scala similarity index 71% rename from src/main/scala/config/ConfigTypes.scala rename to src/main/scala/config/AppConfig.scala index 8aeb296..6123e1d 100644 --- a/src/main/scala/config/ConfigTypes.scala +++ b/src/main/scala/config/AppConfig.scala @@ -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 diff --git a/src/main/scala/config/AppConfigLayers.scala b/src/main/scala/config/AppConfigLayers.scala index 58dd8fd..5774f55 100644 --- a/src/main/scala/config/AppConfigLayers.scala +++ b/src/main/scala/config/AppConfigLayers.scala @@ -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"))) ) diff --git a/src/main/scala/config/AppConfigProviderImpl.scala b/src/main/scala/config/AppConfigProviderImpl.scala index 079880c..e010a62 100644 --- a/src/main/scala/config/AppConfigProviderImpl.scala +++ b/src/main/scala/config/AppConfigProviderImpl.scala @@ -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) diff --git a/src/main/scala/config/AppConfigProviderLayers.scala b/src/main/scala/config/AppConfigProviderLayers.scala index 92a6f38..ba9d0e6 100644 --- a/src/main/scala/config/AppConfigProviderLayers.scala +++ b/src/main/scala/config/AppConfigProviderLayers.scala @@ -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(_, _)) diff --git a/src/main/scala/config/DecryptingAppConfigProvider.scala b/src/main/scala/config/DecryptingAppConfigProvider.scala new file mode 100644 index 0000000..6153eed --- /dev/null +++ b/src/main/scala/config/DecryptingAppConfigProvider.scala @@ -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) diff --git a/src/main/scala/consumer/ConsumerRegisterer.scala b/src/main/scala/consumer/ConsumerRegisterer.scala index adf10b7..b9d1568 100644 --- a/src/main/scala/consumer/ConsumerRegisterer.scala +++ b/src/main/scala/consumer/ConsumerRegisterer.scala @@ -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 @@ -9,5 +11,5 @@ trait ConsumerRegisterer: def registerConsumer( botsApplication: TelegramBotsLongPollingApplication, longPollingUpdateConsumer: LongPollingUpdateConsumer, - token: String + token: Sensitive[String] ): Task[BotSession] diff --git a/src/main/scala/consumer/ConsumerRegistererImpl.scala b/src/main/scala/consumer/ConsumerRegistererImpl.scala index b9e42bc..b2ba04e 100644 --- a/src/main/scala/consumer/ConsumerRegistererImpl.scala +++ b/src/main/scala/consumer/ConsumerRegistererImpl.scala @@ -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 @@ -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 diff --git a/src/main/scala/consumer/OzonPriceCheckerCommandProcessor.scala b/src/main/scala/consumer/OzonPriceCheckerCommandProcessor.scala index 66fae50..d27193f 100644 --- a/src/main/scala/consumer/OzonPriceCheckerCommandProcessor.scala +++ b/src/main/scala/consumer/OzonPriceCheckerCommandProcessor.scala @@ -16,18 +16,12 @@ class OzonPriceCheckerCommandProcessor(productStore: ProductStore, telegramClien private implicit val _telegramClient: TelegramClient = telegramClient def processCommand(sourceId: SourceId, text: String): Task[Unit] = - if (text == OzonPriceCheckerBotCommands.Start) - processStartCommand(sourceId) - else if (text == OzonPriceCheckerBotCommands.Stop) - processStopCommand(sourceId) - else if (text == OzonPriceCheckerBotCommands.Cancel) - processCancelCommand(sourceId) - else if (text == OzonPriceCheckerBotCommands.WatchNewProduct) - processWatchNewProductCommand(sourceId) - else if (text == OzonPriceCheckerBotCommands.UnwatchAllProducts) - processUnwatchAllProductsCommand(sourceId) - else if (text == OzonPriceCheckerBotCommands.ShowAllProducts) - processShowAllProductsCommand(sourceId) + if text == OzonPriceCheckerBotCommands.Start then processStartCommand(sourceId) + else if text == OzonPriceCheckerBotCommands.Stop then processStopCommand(sourceId) + else if text == OzonPriceCheckerBotCommands.Cancel then processCancelCommand(sourceId) + else if text == OzonPriceCheckerBotCommands.WatchNewProduct then processWatchNewProductCommand(sourceId) + else if text == OzonPriceCheckerBotCommands.UnwatchAllProducts then processUnwatchAllProductsCommand(sourceId) + else if text == OzonPriceCheckerBotCommands.ShowAllProducts then processShowAllProductsCommand(sourceId) else ZIO.log(s"Got unknown command $text") *> sendTextMessage(sourceId.chatId, s"I can not process command $text. Please send me a command known to me.") @@ -36,34 +30,32 @@ class OzonPriceCheckerCommandProcessor(productStore: ProductStore, telegramClien initializeStoreEntry(sourceId) .flatMap(initialized => val msg = - if (initialized) - "I have added you to the Store." - else - "You have been already added to the Store before." + if initialized then "I have added you to the Store." + else "You have been already added to the Store before." sendTextMessage(sourceId.chatId, msg) ) .logged(s"process command ${OzonPriceCheckerBotCommands.Start} ") private def initializeStoreEntry(sourceId: SourceId): Task[Boolean] = - for { + for initialized <- productStore.checkInitialized(sourceId) - _ <- ZIO.log(s"Source ID is ${if (initialized) "already" else "not"} initialized in store") + _ <- ZIO.log(s"Source ID is ${if initialized then "already" else "not"} initialized in store") _ <- ZIO.when(!initialized) { productStore.emptyState(sourceId).logged(s"initialize store entry") } - } yield !initialized + yield !initialized private def processStopCommand(sourceId: SourceId): Task[Unit] = - (for { + (for initialized <- productStore.checkInitialized(sourceId) _ <- ZIO.when(initialized)(productStore.clearState(sourceId)) msg = - if (initialized) "I have deleted you from the Store." + if initialized then "I have deleted you from the Store." else "You have been already removed from the Store before." _ <- sendTextMessage(sourceId.chatId, msg) - } yield ()) + yield ()) .logged(s"process command ${OzonPriceCheckerBotCommands.Stop} ") private def processCancelCommand(sourceId: SourceId): Task[Unit] = @@ -75,15 +67,14 @@ class OzonPriceCheckerCommandProcessor(productStore: ProductStore, telegramClien ) private def processWatchNewProductCommand(sourceId: SourceId): Task[Unit] = - (for { + (for initialized <- productStore.checkInitialized(sourceId) _ <- - if (initialized) + if initialized then sendTextMessage(sourceId.chatId, "Send me OZON URL or product ID.") *> productStore.updateProductCandidate(sourceId, WaitingProductId) - else - askToSendStartCommand(sourceId) - } yield ()) + else askToSendStartCommand(sourceId) + yield ()) .logged(s"process command ${OzonPriceCheckerBotCommands.WatchNewProduct}") private def processUnwatchAllProductsCommand(sourceId: SourceId): Task[Unit] = diff --git a/src/main/scala/consumer/OzonPriceCheckerUpdateConsumer.scala b/src/main/scala/consumer/OzonPriceCheckerUpdateConsumer.scala index 5e7844e..e361f92 100644 --- a/src/main/scala/consumer/OzonPriceCheckerUpdateConsumer.scala +++ b/src/main/scala/consumer/OzonPriceCheckerUpdateConsumer.scala @@ -42,10 +42,8 @@ class OzonPriceCheckerUpdateConsumer( ZIO.log(s"Received: $message").zipRight { ZIO.when(message.hasText) { val text: String = message.getText - if (message.isCommand) - commandProcessor.processCommand(sourceId, text) - else - processText(sourceId, text) + if message.isCommand then commandProcessor.processCommand(sourceId, text) + else processText(sourceId, text) } } } @@ -54,7 +52,7 @@ class OzonPriceCheckerUpdateConsumer( private def processText(sourceId: SourceId, text: ChatId): Task[Unit] = def sendDefaultMsg() = sendTextMessage(sourceId.chatId, "Send me a command known to me.") - for { + for _ <- ZIO.log(s"Got text '$text'") maybeSourceState <- productStore.readSourceState(sourceId) @@ -74,8 +72,7 @@ class OzonPriceCheckerUpdateConsumer( case WaitingPriceThreshold(productId, productPrice) => text.toIntOption match case Some(priceThreshold) => - if (priceThreshold < productPrice) - onPriceThreshold(sourceId, productId, priceThreshold) + if priceThreshold < productPrice then onPriceThreshold(sourceId, productId, priceThreshold) else sendTextMessage( sourceId.chatId, @@ -88,7 +85,7 @@ class OzonPriceCheckerUpdateConsumer( sendDefaultMsg() case None => sendDefaultMsg() - } yield () + yield () private def onProductId(sourceId: SourceId, productId: ProductId) = productStore.checkHasProductId(sourceId, productId).flatMap { diff --git a/src/main/scala/consumer/ProductWatchingJobSchedulerImpl.scala b/src/main/scala/consumer/ProductWatchingJobSchedulerImpl.scala index c09df9b..5341413 100644 --- a/src/main/scala/consumer/ProductWatchingJobSchedulerImpl.scala +++ b/src/main/scala/consumer/ProductWatchingJobSchedulerImpl.scala @@ -56,7 +56,7 @@ class ProductWatchingJobSchedulerImpl( } } - private def checkAndNotifyProductPrices(sourceId: SourceId, products: Seq[Product]) = { + private def checkAndNotifyProductPrices(sourceId: SourceId, products: Seq[Product]) = fetchProductInfos(products) .flatMap { (productInfos: Map[ProductId, ProductInfo]) => val productPriceInfosReachedThreshold = products @@ -77,7 +77,6 @@ class ProductWatchingJobSchedulerImpl( ) *> notifyProductsReachedThreshold(sourceId, productPriceInfosReachedThreshold) } - } private case class ProductPriceInfo(id: ProductId, currentPrice: Double, priceThreshold: Double) diff --git a/src/main/scala/consumer/ProductWatchingJobSchedulerLayers.scala b/src/main/scala/consumer/ProductWatchingJobSchedulerLayers.scala index ee6b05c..8ee028e 100644 --- a/src/main/scala/consumer/ProductWatchingJobSchedulerLayers.scala +++ b/src/main/scala/consumer/ProductWatchingJobSchedulerLayers.scala @@ -15,14 +15,14 @@ object ProductWatchingJobSchedulerLayers: ProductWatchingJobScheduler ] = ZLayer.fromZIO( - for { + for telegramClient <- ZIO.service[TelegramClient] productStore <- ZIO.service[ProductStore] productFetcher <- ZIO.service[ProductFetcher] zioScheduler <- ZIO.service[ZioScheduler] appConfig <- ZIO.service[AppConfig] scheduleFiberRuntimeRef <- Ref.make[Option[Fiber.Runtime[Any, Unit]]](None) - } yield new ProductWatchingJobSchedulerImpl( + yield new ProductWatchingJobSchedulerImpl( telegramClient, productStore, productFetcher, diff --git a/src/main/scala/consumer/UpdateConsumerLayers.scala b/src/main/scala/consumer/UpdateConsumerLayers.scala index 23bac57..ff12088 100644 --- a/src/main/scala/consumer/UpdateConsumerLayers.scala +++ b/src/main/scala/consumer/UpdateConsumerLayers.scala @@ -15,7 +15,7 @@ object UpdateConsumerLayers: LongPollingUpdateConsumer ] = ZLayer.fromZIO { - for { + for telegramClient <- ZIO.service[TelegramClient] productStore <- ZIO.service[ProductStore] productFetcher <- ZIO.service[ProductFetcher] @@ -23,8 +23,7 @@ object UpdateConsumerLayers: productIdParser <- ZIO.service[ProductIdParser] commandProcessor <- ZIO.service[CommandProcessor] runtime <- ZIO.runtime[Any] - - } yield new OzonPriceCheckerUpdateConsumer( + yield new OzonPriceCheckerUpdateConsumer( telegramClient, productStore, productFetcher, diff --git a/src/main/scala/encryption/EncDecLayers.scala b/src/main/scala/encryption/EncDecLayers.scala index 514f7e4..7e20653 100644 --- a/src/main/scala/encryption/EncDecLayers.scala +++ b/src/main/scala/encryption/EncDecLayers.scala @@ -6,8 +6,8 @@ import zio.{TaskLayer, ZIO, ZLayer} object EncDecLayers: def aes256(encryptionPassword: String): TaskLayer[EncDec] = ZLayer.fromZIO( - for { + for textEncryptor <- ZIO.attempt(new AES256TextEncryptor()) _ <- ZIO.attempt(textEncryptor.setPassword(encryptionPassword)) - } yield new JasyptEncDec(textEncryptor) + yield new JasyptEncDec(textEncryptor) ) diff --git a/src/main/scala/product/OzonProductIdParser.scala b/src/main/scala/product/OzonProductIdParser.scala index e73f557..29e4cc3 100644 --- a/src/main/scala/product/OzonProductIdParser.scala +++ b/src/main/scala/product/OzonProductIdParser.scala @@ -19,14 +19,12 @@ class OzonProductIdParser extends ProductIdParser: Url.parseTry(s).toEither.leftMap(makeCauseSeqMessage(_)) private def extractProductId(s: ProductId, url: Url): Either[String, ProductId] = - if (isOzonUrl(url)) + if isOzonUrl(url) then takeProductIdFromPath(url.path) match case Some(productId) => Right(productId) case None => Left(s"Not found product ID in URL path. URL: $url") - else if (url.hostOption.nonEmpty) - Left("URL has host other than *ozon.ru") - else - Right(s) + else if url.hostOption.nonEmpty then Left("URL has host other than *ozon.ru") + else Right(s) private def isOzonUrl(url: Url): Boolean = url.hostOption.map(_.value).exists(OzonHostRegex.matches) diff --git a/src/main/scala/schedule/ZioSchedulerImpl.scala b/src/main/scala/schedule/ZioSchedulerImpl.scala index b0b3e26..797b31e 100644 --- a/src/main/scala/schedule/ZioSchedulerImpl.scala +++ b/src/main/scala/schedule/ZioSchedulerImpl.scala @@ -24,17 +24,17 @@ class ZioSchedulerImpl(jobIdGenerator: JobIdGenerator) extends ZioScheduler: } private def makeJobLabel[A, E, R](label: String, jobId: String) = - if (label.trim.isEmpty) s"$jobId" else s"$label|$jobId" + if label.trim.isEmpty then s"$jobId" else s"$label|$jobId" private def doSchedule[R, E, A]( effect: ZIO[R, E, A], nextTimeProvider: NextTimeProvider ): ZIO[R, Any, Unit] = - for { + for jobRunNumberRef <- Ref.make(0) _ <- ZIO.logDebug(s"Scheduling job by time provider ${nextTimeProvider.info}") _ <- runAtNextTime(effect, nextTimeProvider, jobRunNumberRef).repeat(Schedule.forever) - } yield () + yield () private def runAtNextTime[R, E, A]( effect: ZIO[R, E, A], @@ -43,23 +43,23 @@ class ZioSchedulerImpl(jobIdGenerator: JobIdGenerator) extends ZioScheduler: ): ZIO[R, Any, Unit] = jobRunNumberRef.get.flatMap { jobRunNumber => ZIO.logAnnotate("jobRunNumber", jobRunNumber.toString) { - for { + for sleepDuration <- calculateSleepDuration(nextTimeProvider, jobRunNumber) _ <- ZIO.log(s"Sleeping ${sleepDuration.render} before the job run") _ <- runDelayed(effect, sleepDuration) _ <- jobRunNumberRef.update(_ + 1) - } yield () + yield () } } private def calculateSleepDuration(nextTimeProvider: NextTimeProvider, jobRunNumber: Int): Task[Duration] = - for { + for now <- getNow next <- ZIO .fromOption(nextTimeProvider.nexDateTime(now)) - .orElseFail(if (jobRunNumber == 0) TimeProviderVoid else TimeProviderExhausted) + .orElseFail(if jobRunNumber == 0 then TimeProviderVoid else TimeProviderExhausted) _ <- ZIO.log(s"Got next run time: $next") - } yield Duration.fromNanos(now.until(next, ChronoUnit.NANOS)) + yield Duration.fromNanos(now.until(next, ChronoUnit.NANOS)) private def getNow: UIO[LocalDateTime] = ZIO.clock.flatMap(_.localDateTime) @@ -72,7 +72,7 @@ class ZioSchedulerImpl(jobIdGenerator: JobIdGenerator) extends ZioScheduler: res => getNow.flatMap(now => ZIO.logDebug(s"Job finished at $now with result: $res")) ) .ignore - if (sleepDuration.isZero) io else io.delay(sleepDuration) + if sleepDuration.isZero then io else io.delay(sleepDuration) object ZioSchedulerImpl: case object TimeProviderVoid extends RuntimeException with NoStackTrace diff --git a/src/main/scala/store/CacheState.scala b/src/main/scala/store/CacheState.scala index d73fd11..a9cac76 100644 --- a/src/main/scala/store/CacheState.scala +++ b/src/main/scala/store/CacheState.scala @@ -10,12 +10,13 @@ import io.circe.generic.semiauto.deriveCodec case class CacheStateEntry(userName: UserName, chatId: ChatId, products: Seq[Product]) extends NamedToString -case class CacheState(entries: Seq[CacheStateEntry]) extends NamedToString +case class CacheState(entries: Seq[CacheStateEntry]) extends NamedToString: + def nonEmpty: Boolean = entries.nonEmpty + def size: Int = entries.size object CacheState: def empty: CacheState = CacheState(Seq.empty) - implicit val cacheStateCodec: Codec[CacheState] = { + implicit val cacheStateCodec: Codec[CacheState] = import io.circe.generic.auto.* deriveCodec[CacheState] - } diff --git a/src/main/scala/store/CacheStateRepositoryLayers.scala b/src/main/scala/store/CacheStateRepositoryLayers.scala index 53218c5..85fdea1 100644 --- a/src/main/scala/store/CacheStateRepositoryLayers.scala +++ b/src/main/scala/store/CacheStateRepositoryLayers.scala @@ -8,11 +8,11 @@ import zio.{RLayer, ZIO, ZLayer} object CacheStateRepositoryLayers: val file: RLayer[AppConfig, FileCacheStateRepository] = ZLayer.fromZIO { - for { + for appConfig <- ZIO.service[AppConfig] cacheStateFilePath = appConfig.cacheStateFilePath file <- ZIO.attempt(ScalaFile(cacheStateFilePath)) _ <- ZIO.log(s"Recreating file $file if not exists") _ <- ZIO.attempt(file.createIfNotExists(createParents = true)) - } yield new FileCacheStateRepository(cacheStateFilePath) + yield new FileCacheStateRepository(cacheStateFilePath) } diff --git a/src/main/scala/store/FileCacheStateRepository.scala b/src/main/scala/store/FileCacheStateRepository.scala index a2a3733..7dc8b73 100644 --- a/src/main/scala/store/FileCacheStateRepository.scala +++ b/src/main/scala/store/FileCacheStateRepository.scala @@ -12,12 +12,11 @@ class FileCacheStateRepository(filePath: String) extends CacheStateRepository: ZIO .attempt(asScalaFile) .flatMap(file => - if (file.exists) + if file.exists then ZIO .attempt(file.contentAsString) .flatMap(json => - if (json.isBlank) - ZIO.succeed(CacheState.empty) + if json.isBlank then ZIO.succeed(CacheState.empty) else ZIO .fromEither(decode[CacheState](json)) diff --git a/src/main/scala/store/InMemoryProductStore.scala b/src/main/scala/store/InMemoryProductStore.scala index 7a1379f..a450f52 100644 --- a/src/main/scala/store/InMemoryProductStore.scala +++ b/src/main/scala/store/InMemoryProductStore.scala @@ -50,14 +50,14 @@ class InMemoryProductStore(productStateRef: Ref[ProductState]) extends ProductSt sourceId: SourceId, updateSourceStateFn: SourceState => SourceState ): Task[Boolean] = - for { + for maybeSourceState <- readSourceState(sourceId) updated <- maybeSourceState match case Some(sourceState) => val newSourceState = updateSourceStateFn(sourceState) productStateRef.update(_ + (sourceId -> newSourceState)).as(true) case None => ZIO.succeed(false) - } yield updated + yield updated object InMemoryProductStore: type ProductState = Map[SourceId, SourceState] diff --git a/src/main/scala/store/ProductStoreLayers.scala b/src/main/scala/store/ProductStoreLayers.scala index 4b501cf..266cbe6 100644 --- a/src/main/scala/store/ProductStoreLayers.scala +++ b/src/main/scala/store/ProductStoreLayers.scala @@ -7,17 +7,16 @@ import zio.{RLayer, Ref, ULayer, ZIO, ZLayer} object ProductStoreLayers: val inMemory: ULayer[ProductStore] = ZLayer.fromZIO { - for { - productStateRef <- Ref.make(ProductState.empty) - } yield new InMemoryProductStore(productStateRef) + for productStateRef <- Ref.make(ProductState.empty) + yield new InMemoryProductStore(productStateRef) } val cachedOverInMemory: RLayer[CacheStateRepository, ProductStore] = ZLayer.environment[CacheStateRepository] ++ inMemory >>> ZLayer.fromZIO { - for { + for productStore <- ZIO.service[ProductStore] cacheStateRepository <- ZIO.service[CacheStateRepository] - } yield new CacheProductStore(productStore, cacheStateRepository) + yield new CacheProductStore(productStore, cacheStateRepository) } diff --git a/src/main/scala/telegram/TelegramClientLayers.scala b/src/main/scala/telegram/TelegramClientLayers.scala index 6216769..a63f894 100644 --- a/src/main/scala/telegram/TelegramClientLayers.scala +++ b/src/main/scala/telegram/TelegramClientLayers.scala @@ -9,7 +9,6 @@ import zio.{RLayer, ZIO, ZLayer} object TelegramClientLayers: val okHttp: RLayer[AppConfig, TelegramClient] = ZLayer.fromZIO { - for { - appConfig <- ZIO.service[AppConfig] - } yield new OkHttpTelegramClient(appConfig.botToken.value) + for appConfig <- ZIO.service[AppConfig] + yield new OkHttpTelegramClient(appConfig.botToken.value) } diff --git a/src/main/scala/util/lang/Strings.scala b/src/main/scala/util/lang/Strings.scala index c0b2ba2..ce57c44 100644 --- a/src/main/scala/util/lang/Strings.scala +++ b/src/main/scala/util/lang/Strings.scala @@ -3,4 +3,4 @@ package util.lang object Strings: def abbreviate(s: String, maxLength: Int = 1000): String = - if (s.length <= maxLength) s else s.substring(0, maxLength) + "..." + if s.length <= maxLength then s else s.substring(0, maxLength) + "..." diff --git a/src/main/scala/util/lang/Throwables.scala b/src/main/scala/util/lang/Throwables.scala index 2e89744..cd071b2 100644 --- a/src/main/scala/util/lang/Throwables.scala +++ b/src/main/scala/util/lang/Throwables.scala @@ -12,9 +12,9 @@ object Throwables: def makeCauseSeqMessage(t: Throwable, printStackTrace: Boolean = false, abbreviate: Boolean = true): String = val rawCauseSeqMessage = causeChainOf(t).mkString(" caused by ") - val causeSeqMessage = if (abbreviate) Strings.abbreviate(rawCauseSeqMessage) else rawCauseSeqMessage + val causeSeqMessage = if abbreviate then Strings.abbreviate(rawCauseSeqMessage) else rawCauseSeqMessage val stackTraceMessage = - if (printStackTrace) + if printStackTrace then "\n\nStack Trace ==> \n\n" + t.getStackTrace.mkString("\t", "\n\t", "\t") + "\n\n <== End of Stack Trace\n\n" @@ -25,7 +25,7 @@ object Throwables: @tailrec def loop(t: Throwable, chain: ArrayBuffer[Throwable]): ArrayBuffer[Throwable] = val cause = t.getCause - if (cause == null || chain.contains(cause)) chain + if cause == null || chain.contains(cause) then chain else loop(cause, chain :+ cause) loop(t, ArrayBuffer(t)).toList