From d75225086d1bcd56a26a5fcb65684631f301d2a5 Mon Sep 17 00:00:00 2001 From: Constantine Kormashev Date: Sat, 15 Aug 2020 14:59:51 +0300 Subject: [PATCH 1/3] updated homework 1_1 --- src/main/scala/ru/otus/sc/App.scala | 84 +++++++++++++++++-- .../ru/otus/sc/counter/dao/CounterDao.scala | 6 ++ .../sc/counter/dao/impl/CounterDaoImpl.scala | 17 ++++ .../sc/counter/model/CounterRequest.scala | 3 + .../sc/counter/model/CounterResponse.scala | 3 + .../sc/counter/service/CounterService.scala | 7 ++ .../service/impl/CounterServiceImpl.scala | 13 +++ .../scala/ru/otus/sc/echo/dao/EchoDao.scala | 6 ++ .../otus/sc/echo/dao/impl/EchoDaoImpl.scala | 20 +++++ .../ru/otus/sc/echo/model/EchoRequest.scala | 3 + .../ru/otus/sc/echo/model/EchoResponse.scala | 12 +++ .../ru/otus/sc/echo/service/EchoService.scala | 7 ++ .../echo/service/impl/EchoServiceImpl.scala | 13 +++ .../ru/otus/sc/reverse/dao/ReverseDao.scala | 5 ++ .../sc/reverse/dao/impl/ReverseDaoImpl.scala | 19 +++++ .../sc/reverse/model/ReverseRequest.scala | 3 + .../sc/reverse/model/ReverseResponse.scala | 3 + .../sc/reverse/service/ReverseService.scala | 7 ++ .../service/impl/ReverseServiceImpl.scala | 12 +++ .../ru/otus/sc/storage/dao/StorageDao.scala | 5 ++ .../sc/storage/dao/impl/StorageDaoImpl.scala | 13 +++ .../sc/storage/model/StorageRequest.scala | 3 + .../sc/storage/model/StorageResponse.scala | 3 + .../sc/storage/service/StorageService.scala | 7 ++ .../service/impl/StorageServiceImpl.scala | 15 ++++ .../scala/ru/otus/sc/sum/dao/SumDao.scala | 5 ++ .../ru/otus/sc/sum/dao/impl/SumDaoImpl.scala | 31 +++++++ .../ru/otus/sc/sum/model/SumRequest.scala | 3 + .../ru/otus/sc/sum/model/SumResponse.scala | 3 + .../ru/otus/sc/sum/service/SumService.scala | 7 ++ .../sc/sum/service/impl/SumServiceImpl.scala | 12 +++ 31 files changed, 343 insertions(+), 7 deletions(-) create mode 100644 src/main/scala/ru/otus/sc/counter/dao/CounterDao.scala create mode 100644 src/main/scala/ru/otus/sc/counter/dao/impl/CounterDaoImpl.scala create mode 100644 src/main/scala/ru/otus/sc/counter/model/CounterRequest.scala create mode 100644 src/main/scala/ru/otus/sc/counter/model/CounterResponse.scala create mode 100644 src/main/scala/ru/otus/sc/counter/service/CounterService.scala create mode 100644 src/main/scala/ru/otus/sc/counter/service/impl/CounterServiceImpl.scala create mode 100644 src/main/scala/ru/otus/sc/echo/dao/EchoDao.scala create mode 100644 src/main/scala/ru/otus/sc/echo/dao/impl/EchoDaoImpl.scala create mode 100644 src/main/scala/ru/otus/sc/echo/model/EchoRequest.scala create mode 100644 src/main/scala/ru/otus/sc/echo/model/EchoResponse.scala create mode 100644 src/main/scala/ru/otus/sc/echo/service/EchoService.scala create mode 100644 src/main/scala/ru/otus/sc/echo/service/impl/EchoServiceImpl.scala create mode 100644 src/main/scala/ru/otus/sc/reverse/dao/ReverseDao.scala create mode 100644 src/main/scala/ru/otus/sc/reverse/dao/impl/ReverseDaoImpl.scala create mode 100644 src/main/scala/ru/otus/sc/reverse/model/ReverseRequest.scala create mode 100644 src/main/scala/ru/otus/sc/reverse/model/ReverseResponse.scala create mode 100644 src/main/scala/ru/otus/sc/reverse/service/ReverseService.scala create mode 100644 src/main/scala/ru/otus/sc/reverse/service/impl/ReverseServiceImpl.scala create mode 100644 src/main/scala/ru/otus/sc/storage/dao/StorageDao.scala create mode 100644 src/main/scala/ru/otus/sc/storage/dao/impl/StorageDaoImpl.scala create mode 100644 src/main/scala/ru/otus/sc/storage/model/StorageRequest.scala create mode 100644 src/main/scala/ru/otus/sc/storage/model/StorageResponse.scala create mode 100644 src/main/scala/ru/otus/sc/storage/service/StorageService.scala create mode 100644 src/main/scala/ru/otus/sc/storage/service/impl/StorageServiceImpl.scala create mode 100644 src/main/scala/ru/otus/sc/sum/dao/SumDao.scala create mode 100644 src/main/scala/ru/otus/sc/sum/dao/impl/SumDaoImpl.scala create mode 100644 src/main/scala/ru/otus/sc/sum/model/SumRequest.scala create mode 100644 src/main/scala/ru/otus/sc/sum/model/SumResponse.scala create mode 100644 src/main/scala/ru/otus/sc/sum/service/SumService.scala create mode 100644 src/main/scala/ru/otus/sc/sum/service/impl/SumServiceImpl.scala diff --git a/src/main/scala/ru/otus/sc/App.scala b/src/main/scala/ru/otus/sc/App.scala index a110bab..9002946 100644 --- a/src/main/scala/ru/otus/sc/App.scala +++ b/src/main/scala/ru/otus/sc/App.scala @@ -1,22 +1,92 @@ package ru.otus.sc +import ru.otus.sc.counter.dao.impl.CounterDaoImpl +import ru.otus.sc.counter.model.{CounterRequest, CounterResponse} +import ru.otus.sc.counter.service.CounterService +import ru.otus.sc.counter.service.impl.CounterServiceImpl +import ru.otus.sc.echo.dao.impl.EchoDaoImpl +import ru.otus.sc.echo.model.{EchoRequest, EchoResponse} +import ru.otus.sc.echo.service.EchoService +import ru.otus.sc.echo.service.impl.EchoServiceImpl import ru.otus.sc.greet.dao.impl.GreetingDaoImpl import ru.otus.sc.greet.model.{GreetRequest, GreetResponse} import ru.otus.sc.greet.service.GreetingService import ru.otus.sc.greet.service.impl.GreetingServiceImpl +import ru.otus.sc.reverse.dao.impl.ReverseDaoImpl +import ru.otus.sc.reverse.model.{ReverseRequest, ReverseResponse} +import ru.otus.sc.reverse.service.ReverseService +import ru.otus.sc.reverse.service.impl.ReverseServiceImpl +import ru.otus.sc.storage.dao.impl.StorageDaoImpl +import ru.otus.sc.storage.model.{StorageRequest, StorageResponse} +import ru.otus.sc.storage.service.StorageService +import ru.otus.sc.storage.service.impl.StorageServiceImpl +import ru.otus.sc.sum.dao.impl.SumDaoImpl +import ru.otus.sc.sum.model.{SumRequest, SumResponse} +import ru.otus.sc.sum.service.SumService +import ru.otus.sc.sum.service.impl.SumServiceImpl + +// Helper class which aggregates service entries to single point +case class Config( + counting: CounterService, + echoing: EchoService, + getting: StorageService, + greeting: GreetingService, + reversing: ReverseService, + summing: SumService +) + +object Config { + def apply(): Config = { + val CounterDao = new CounterDaoImpl + val counterService = new CounterServiceImpl(CounterDao) + val echoDao = new EchoDaoImpl + val echoService = new EchoServiceImpl(echoDao) + val greetingDao = new GreetingDaoImpl + val greetingService = new GreetingServiceImpl(greetingDao) + val storageDao = new StorageDaoImpl + val storageService = new StorageServiceImpl(storageDao) + val reverseDao = new ReverseDaoImpl + val reverseService = new ReverseServiceImpl(reverseDao) + val sumDao = new SumDaoImpl + val sumService = new SumServiceImpl(sumDao) + + Config(counterService, echoService, storageService, greetingService, reverseService, sumService) + } +} trait App { + // provide counter started from 1 with auto-increase value + // every call increases counter value by 1 + // counter can be reset to initial value with `clear` flag + def getCount(request: CounterRequest): CounterResponse + // reply on requested value with the same value + // echo value can be multiplied up to 5 times with `repeatNum` value + // no multiply answer by default + def echo(request: EchoRequest): EchoResponse + // provide value by requested key + // in current implementation keys are one, two, three + def get(request: StorageRequest): Option[StorageResponse] + // greet with provided name value + // panic if `isHuman` flag is unset def greet(request: GreetRequest): GreetResponse + // reverse given string from end to begin + def reverse(request: ReverseRequest): ReverseResponse + // sum 2 given values locally + // sum can be performed externally with `external` flag + // in current implementation external computation is artificial delay + // which was implemented with lazy value + def sum(request: SumRequest): SumResponse } object App { - private class AppImpl(greeting: GreetingService) extends App { - def greet(request: GreetRequest): GreetResponse = greeting.greet(request) - } + def apply(): App = new AppImpl(Config()) - def apply(): App = { - val greetingDao = new GreetingDaoImpl - val greetingService = new GreetingServiceImpl(greetingDao) - new AppImpl(greetingService) + private class AppImpl(config: Config) extends App { + def getCount(request: CounterRequest): CounterResponse = config.counting.getCount(request) + def echo(request: EchoRequest): EchoResponse = config.echoing.echo(request) + def get(request: StorageRequest): Option[StorageResponse] = config.getting.get(request) + def greet(request: GreetRequest): GreetResponse = config.greeting.greet(request) + def reverse(request: ReverseRequest): ReverseResponse = config.reversing.reverse(request) + def sum(request: SumRequest): SumResponse = config.summing.sum(request) } } diff --git a/src/main/scala/ru/otus/sc/counter/dao/CounterDao.scala b/src/main/scala/ru/otus/sc/counter/dao/CounterDao.scala new file mode 100644 index 0000000..e88d142 --- /dev/null +++ b/src/main/scala/ru/otus/sc/counter/dao/CounterDao.scala @@ -0,0 +1,6 @@ +package ru.otus.sc.counter.dao + +trait CounterDao { + def getCount: String + def clearCount: String +} diff --git a/src/main/scala/ru/otus/sc/counter/dao/impl/CounterDaoImpl.scala b/src/main/scala/ru/otus/sc/counter/dao/impl/CounterDaoImpl.scala new file mode 100644 index 0000000..b6d1419 --- /dev/null +++ b/src/main/scala/ru/otus/sc/counter/dao/impl/CounterDaoImpl.scala @@ -0,0 +1,17 @@ +package ru.otus.sc.counter.dao.impl + +import ru.otus.sc.counter.dao.CounterDao + +class CounterDaoImpl extends CounterDao { + private var count: Long = 0 + + def clearCount: String = { + count = 0 + getCount + } + + def getCount: String = { + count += 1 + count.toString + } +} diff --git a/src/main/scala/ru/otus/sc/counter/model/CounterRequest.scala b/src/main/scala/ru/otus/sc/counter/model/CounterRequest.scala new file mode 100644 index 0000000..291ff23 --- /dev/null +++ b/src/main/scala/ru/otus/sc/counter/model/CounterRequest.scala @@ -0,0 +1,3 @@ +package ru.otus.sc.counter.model + +case class CounterRequest(clear: Boolean = false, initialValue: Long = 0) diff --git a/src/main/scala/ru/otus/sc/counter/model/CounterResponse.scala b/src/main/scala/ru/otus/sc/counter/model/CounterResponse.scala new file mode 100644 index 0000000..0ced945 --- /dev/null +++ b/src/main/scala/ru/otus/sc/counter/model/CounterResponse.scala @@ -0,0 +1,3 @@ +package ru.otus.sc.counter.model + +case class CounterResponse(count: String) diff --git a/src/main/scala/ru/otus/sc/counter/service/CounterService.scala b/src/main/scala/ru/otus/sc/counter/service/CounterService.scala new file mode 100644 index 0000000..a9cf3f1 --- /dev/null +++ b/src/main/scala/ru/otus/sc/counter/service/CounterService.scala @@ -0,0 +1,7 @@ +package ru.otus.sc.counter.service + +import ru.otus.sc.counter.model.{CounterRequest, CounterResponse} + +trait CounterService { + def getCount(request: CounterRequest): CounterResponse +} diff --git a/src/main/scala/ru/otus/sc/counter/service/impl/CounterServiceImpl.scala b/src/main/scala/ru/otus/sc/counter/service/impl/CounterServiceImpl.scala new file mode 100644 index 0000000..d36dbab --- /dev/null +++ b/src/main/scala/ru/otus/sc/counter/service/impl/CounterServiceImpl.scala @@ -0,0 +1,13 @@ +package ru.otus.sc.counter.service.impl + +import ru.otus.sc.counter.dao.CounterDao +import ru.otus.sc.counter.model.{CounterRequest, CounterResponse} +import ru.otus.sc.counter.service.CounterService + +class CounterServiceImpl(dao: CounterDao) extends CounterService { + def getCount(request: CounterRequest): CounterResponse = { + if (!request.clear) CounterResponse(dao.getCount) + else CounterResponse(dao.clearCount) + } + +} diff --git a/src/main/scala/ru/otus/sc/echo/dao/EchoDao.scala b/src/main/scala/ru/otus/sc/echo/dao/EchoDao.scala new file mode 100644 index 0000000..a7b8db2 --- /dev/null +++ b/src/main/scala/ru/otus/sc/echo/dao/EchoDao.scala @@ -0,0 +1,6 @@ +package ru.otus.sc.echo.dao + +trait EchoDao { + def echoPrefix: String + def getResponse(request: String, repeatNum: Int): String +} diff --git a/src/main/scala/ru/otus/sc/echo/dao/impl/EchoDaoImpl.scala b/src/main/scala/ru/otus/sc/echo/dao/impl/EchoDaoImpl.scala new file mode 100644 index 0000000..b70820a --- /dev/null +++ b/src/main/scala/ru/otus/sc/echo/dao/impl/EchoDaoImpl.scala @@ -0,0 +1,20 @@ +package ru.otus.sc.echo.dao.impl + +import ru.otus.sc.echo.dao.EchoDao + +import scala.annotation.tailrec + +class EchoDaoImpl extends EchoDao { + val echoPrefix: String = "Echo reply:" + + def getResponse(request: String, repeatNum: Int): String = { + s"$echoPrefix ${multiplyResponse(request)()(repeatNum)}" + } + + // Can not use collections, so use recursion + @tailrec + private def multiplyResponse(response: String)(acc: String = "")(numResponse: Int): String = { + if (numResponse <= 0) acc.trim + else multiplyResponse(response)(s"$response $acc")(numResponse - 1) + } +} diff --git a/src/main/scala/ru/otus/sc/echo/model/EchoRequest.scala b/src/main/scala/ru/otus/sc/echo/model/EchoRequest.scala new file mode 100644 index 0000000..d1bbccd --- /dev/null +++ b/src/main/scala/ru/otus/sc/echo/model/EchoRequest.scala @@ -0,0 +1,3 @@ +package ru.otus.sc.echo.model + +case class EchoRequest(echoRequest: String, repeatNum: Int = 1) diff --git a/src/main/scala/ru/otus/sc/echo/model/EchoResponse.scala b/src/main/scala/ru/otus/sc/echo/model/EchoResponse.scala new file mode 100644 index 0000000..25ad221 --- /dev/null +++ b/src/main/scala/ru/otus/sc/echo/model/EchoResponse.scala @@ -0,0 +1,12 @@ +package ru.otus.sc.echo.model + +sealed trait EchoResponse { + def isValid: Boolean +} + +case class EchoResponseAnswer(answer: String) extends EchoResponse { + def isValid = true +} +case class EchoResponseError(error: String) extends EchoResponse { + def isValid = false +} diff --git a/src/main/scala/ru/otus/sc/echo/service/EchoService.scala b/src/main/scala/ru/otus/sc/echo/service/EchoService.scala new file mode 100644 index 0000000..2232913 --- /dev/null +++ b/src/main/scala/ru/otus/sc/echo/service/EchoService.scala @@ -0,0 +1,7 @@ +package ru.otus.sc.echo.service + +import ru.otus.sc.echo.model.{EchoRequest, EchoResponse} + +trait EchoService { + def echo(request: EchoRequest): EchoResponse +} diff --git a/src/main/scala/ru/otus/sc/echo/service/impl/EchoServiceImpl.scala b/src/main/scala/ru/otus/sc/echo/service/impl/EchoServiceImpl.scala new file mode 100644 index 0000000..5642b54 --- /dev/null +++ b/src/main/scala/ru/otus/sc/echo/service/impl/EchoServiceImpl.scala @@ -0,0 +1,13 @@ +package ru.otus.sc.echo.service.impl + +import ru.otus.sc.echo.dao.EchoDao +import ru.otus.sc.echo.model.{EchoRequest, EchoResponse, EchoResponseAnswer, EchoResponseError} +import ru.otus.sc.echo.service.EchoService + +class EchoServiceImpl(dao: EchoDao) extends EchoService { + + def echo(request: EchoRequest): EchoResponse = + if (request.repeatNum > 0 & request.repeatNum <= 5) + EchoResponseAnswer(s"${dao.getResponse(request.echoRequest, request.repeatNum)}") + else EchoResponseError("Number of replies has to be from 1 to 5") +} diff --git a/src/main/scala/ru/otus/sc/reverse/dao/ReverseDao.scala b/src/main/scala/ru/otus/sc/reverse/dao/ReverseDao.scala new file mode 100644 index 0000000..f3ff8bb --- /dev/null +++ b/src/main/scala/ru/otus/sc/reverse/dao/ReverseDao.scala @@ -0,0 +1,5 @@ +package ru.otus.sc.reverse.dao + +trait ReverseDao { + def reverse(word: String): String +} diff --git a/src/main/scala/ru/otus/sc/reverse/dao/impl/ReverseDaoImpl.scala b/src/main/scala/ru/otus/sc/reverse/dao/impl/ReverseDaoImpl.scala new file mode 100644 index 0000000..5c1befa --- /dev/null +++ b/src/main/scala/ru/otus/sc/reverse/dao/impl/ReverseDaoImpl.scala @@ -0,0 +1,19 @@ +package ru.otus.sc.reverse.dao.impl + +import ru.otus.sc.reverse.dao.ReverseDao + +import scala.annotation.tailrec + +class ReverseDaoImpl extends ReverseDao { + def reverse(word: String): String = { + if (word.length < 2) word + else reverseWord(word)()(word.length - 1) + } + + // Can not use .reverse, so use recursion + @tailrec + private def reverseWord(source: String)(acc: String = "")(position: Int): String = { + if (position < 0) acc + else reverseWord(source)(s"$acc${source.charAt(position)}")(position - 1) + } +} diff --git a/src/main/scala/ru/otus/sc/reverse/model/ReverseRequest.scala b/src/main/scala/ru/otus/sc/reverse/model/ReverseRequest.scala new file mode 100644 index 0000000..d8b4625 --- /dev/null +++ b/src/main/scala/ru/otus/sc/reverse/model/ReverseRequest.scala @@ -0,0 +1,3 @@ +package ru.otus.sc.reverse.model + +case class ReverseRequest(requestedWord: String) diff --git a/src/main/scala/ru/otus/sc/reverse/model/ReverseResponse.scala b/src/main/scala/ru/otus/sc/reverse/model/ReverseResponse.scala new file mode 100644 index 0000000..64bc6cf --- /dev/null +++ b/src/main/scala/ru/otus/sc/reverse/model/ReverseResponse.scala @@ -0,0 +1,3 @@ +package ru.otus.sc.reverse.model + +case class ReverseResponse(reversedWord: String) diff --git a/src/main/scala/ru/otus/sc/reverse/service/ReverseService.scala b/src/main/scala/ru/otus/sc/reverse/service/ReverseService.scala new file mode 100644 index 0000000..3b71d14 --- /dev/null +++ b/src/main/scala/ru/otus/sc/reverse/service/ReverseService.scala @@ -0,0 +1,7 @@ +package ru.otus.sc.reverse.service + +import ru.otus.sc.reverse.model.{ReverseRequest, ReverseResponse} + +trait ReverseService { + def reverse(request: ReverseRequest): ReverseResponse +} diff --git a/src/main/scala/ru/otus/sc/reverse/service/impl/ReverseServiceImpl.scala b/src/main/scala/ru/otus/sc/reverse/service/impl/ReverseServiceImpl.scala new file mode 100644 index 0000000..91712b2 --- /dev/null +++ b/src/main/scala/ru/otus/sc/reverse/service/impl/ReverseServiceImpl.scala @@ -0,0 +1,12 @@ +package ru.otus.sc.reverse.service.impl + +import ru.otus.sc.reverse.dao.ReverseDao +import ru.otus.sc.reverse.model.{ReverseRequest, ReverseResponse} +import ru.otus.sc.reverse.service.ReverseService + +class ReverseServiceImpl(dao: ReverseDao) extends ReverseService { + + def reverse(request: ReverseRequest): ReverseResponse = { + ReverseResponse(dao.reverse(request.requestedWord)) + } +} diff --git a/src/main/scala/ru/otus/sc/storage/dao/StorageDao.scala b/src/main/scala/ru/otus/sc/storage/dao/StorageDao.scala new file mode 100644 index 0000000..2f30c99 --- /dev/null +++ b/src/main/scala/ru/otus/sc/storage/dao/StorageDao.scala @@ -0,0 +1,5 @@ +package ru.otus.sc.storage.dao + +trait StorageDao { + def get(key: String): Option[String] +} diff --git a/src/main/scala/ru/otus/sc/storage/dao/impl/StorageDaoImpl.scala b/src/main/scala/ru/otus/sc/storage/dao/impl/StorageDaoImpl.scala new file mode 100644 index 0000000..b6d3354 --- /dev/null +++ b/src/main/scala/ru/otus/sc/storage/dao/impl/StorageDaoImpl.scala @@ -0,0 +1,13 @@ +package ru.otus.sc.storage.dao.impl + +import ru.otus.sc.storage.dao.StorageDao + +class StorageDaoImpl extends StorageDao { + // Can not use collections, so use if/else + def get(key: String): Option[String] = { + if (key == "one") Some("1") + else if (key == "two") Some("2") + else if (key == "three") Some("3") + else None + } +} diff --git a/src/main/scala/ru/otus/sc/storage/model/StorageRequest.scala b/src/main/scala/ru/otus/sc/storage/model/StorageRequest.scala new file mode 100644 index 0000000..d67619a --- /dev/null +++ b/src/main/scala/ru/otus/sc/storage/model/StorageRequest.scala @@ -0,0 +1,3 @@ +package ru.otus.sc.storage.model + +case class StorageRequest(storageKey: String) diff --git a/src/main/scala/ru/otus/sc/storage/model/StorageResponse.scala b/src/main/scala/ru/otus/sc/storage/model/StorageResponse.scala new file mode 100644 index 0000000..b272d2d --- /dev/null +++ b/src/main/scala/ru/otus/sc/storage/model/StorageResponse.scala @@ -0,0 +1,3 @@ +package ru.otus.sc.storage.model + +case class StorageResponse(storageValue: String) diff --git a/src/main/scala/ru/otus/sc/storage/service/StorageService.scala b/src/main/scala/ru/otus/sc/storage/service/StorageService.scala new file mode 100644 index 0000000..2e0d1e5 --- /dev/null +++ b/src/main/scala/ru/otus/sc/storage/service/StorageService.scala @@ -0,0 +1,7 @@ +package ru.otus.sc.storage.service + +import ru.otus.sc.storage.model.{StorageRequest, StorageResponse} + +trait StorageService { + def get(request: StorageRequest): Option[StorageResponse] +} diff --git a/src/main/scala/ru/otus/sc/storage/service/impl/StorageServiceImpl.scala b/src/main/scala/ru/otus/sc/storage/service/impl/StorageServiceImpl.scala new file mode 100644 index 0000000..318bb26 --- /dev/null +++ b/src/main/scala/ru/otus/sc/storage/service/impl/StorageServiceImpl.scala @@ -0,0 +1,15 @@ +package ru.otus.sc.storage.service.impl + +import ru.otus.sc.storage.dao.StorageDao +import ru.otus.sc.storage.model.{StorageRequest, StorageResponse} +import ru.otus.sc.storage.service.StorageService + +class StorageServiceImpl(dao: StorageDao) extends StorageService { + + def get(request: StorageRequest): Option[StorageResponse] = { + val value = dao.get(request.storageKey) + // Can not use pattern matching or .map, so use if/else + if (value.nonEmpty) Some(StorageResponse(value.get)) + else None + } +} diff --git a/src/main/scala/ru/otus/sc/sum/dao/SumDao.scala b/src/main/scala/ru/otus/sc/sum/dao/SumDao.scala new file mode 100644 index 0000000..17059ab --- /dev/null +++ b/src/main/scala/ru/otus/sc/sum/dao/SumDao.scala @@ -0,0 +1,5 @@ +package ru.otus.sc.sum.dao + +trait SumDao { + def sum(a: Long, b: Long, external: Boolean): String +} diff --git a/src/main/scala/ru/otus/sc/sum/dao/impl/SumDaoImpl.scala b/src/main/scala/ru/otus/sc/sum/dao/impl/SumDaoImpl.scala new file mode 100644 index 0000000..fe85c66 --- /dev/null +++ b/src/main/scala/ru/otus/sc/sum/dao/impl/SumDaoImpl.scala @@ -0,0 +1,31 @@ +package ru.otus.sc.sum.dao.impl + +import ru.otus.sc.sum.dao.SumDao + +import scala.annotation.tailrec + +class SumDaoImpl extends SumDao { + def sum(a: Long, b: Long, external: Boolean): String = { + lazy val externalDelay = fib(10000000000L) + val result = { + if (!external) a + b + else { + externalDelay + 1 + a + b + } + } + + result.toString + } + + // Can not use Thread.sleep, so emulate delay with fibonacci 10000000000 calculation + private def fib(n: Long): Long = { + @tailrec + def fib_tail(n: Long, a: Long, b: Long): Long = { + if (n == 0) a + else fib_tail(n - 1L, b, a + b) + } + + fib_tail(n, 0L, 1L) + } +} diff --git a/src/main/scala/ru/otus/sc/sum/model/SumRequest.scala b/src/main/scala/ru/otus/sc/sum/model/SumRequest.scala new file mode 100644 index 0000000..0fe8c76 --- /dev/null +++ b/src/main/scala/ru/otus/sc/sum/model/SumRequest.scala @@ -0,0 +1,3 @@ +package ru.otus.sc.sum.model + +case class SumRequest(a: Long, b: Long, external: Boolean = false) diff --git a/src/main/scala/ru/otus/sc/sum/model/SumResponse.scala b/src/main/scala/ru/otus/sc/sum/model/SumResponse.scala new file mode 100644 index 0000000..08ce4e8 --- /dev/null +++ b/src/main/scala/ru/otus/sc/sum/model/SumResponse.scala @@ -0,0 +1,3 @@ +package ru.otus.sc.sum.model + +case class SumResponse(sumValue: String) diff --git a/src/main/scala/ru/otus/sc/sum/service/SumService.scala b/src/main/scala/ru/otus/sc/sum/service/SumService.scala new file mode 100644 index 0000000..52a34f5 --- /dev/null +++ b/src/main/scala/ru/otus/sc/sum/service/SumService.scala @@ -0,0 +1,7 @@ +package ru.otus.sc.sum.service + +import ru.otus.sc.sum.model.{SumRequest, SumResponse} + +trait SumService { + def sum(request: SumRequest): SumResponse +} diff --git a/src/main/scala/ru/otus/sc/sum/service/impl/SumServiceImpl.scala b/src/main/scala/ru/otus/sc/sum/service/impl/SumServiceImpl.scala new file mode 100644 index 0000000..aec2f0a --- /dev/null +++ b/src/main/scala/ru/otus/sc/sum/service/impl/SumServiceImpl.scala @@ -0,0 +1,12 @@ +package ru.otus.sc.sum.service.impl + +import ru.otus.sc.sum.dao.SumDao +import ru.otus.sc.sum.model.{SumRequest, SumResponse} +import ru.otus.sc.sum.service.SumService + +class SumServiceImpl(dao: SumDao) extends SumService { + + def sum(request: SumRequest): SumResponse = { + SumResponse(dao.sum(request.a, request.b, request.external)) + } +} From 8fd21a3b5b6c7c96cfb68cf62cecb054737c4a53 Mon Sep 17 00:00:00 2001 From: Constantine Kormashev Date: Sun, 16 Aug 2020 11:00:59 +0300 Subject: [PATCH 2/3] added countdown --- src/main/scala/ru/otus/sc/App.scala | 71 +++++++++++++------ .../otus/sc/countdown/dao/CountdownDao.scala | 6 ++ .../countdown/dao/impl/CountdownDaoImpl.scala | 18 +++++ .../sc/countdown/model/CountdownRequest.scala | 6 ++ .../countdown/model/CountdownResponse.scala | 13 ++++ .../countdown/service/CountdownService.scala | 7 ++ .../service/impl/CountdownServiceImpl.scala | 20 ++++++ .../ru/otus/sc/echo/model/EchoResponse.scala | 4 +- .../echo/service/impl/EchoServiceImpl.scala | 6 +- 9 files changed, 126 insertions(+), 25 deletions(-) create mode 100644 src/main/scala/ru/otus/sc/countdown/dao/CountdownDao.scala create mode 100644 src/main/scala/ru/otus/sc/countdown/dao/impl/CountdownDaoImpl.scala create mode 100644 src/main/scala/ru/otus/sc/countdown/model/CountdownRequest.scala create mode 100644 src/main/scala/ru/otus/sc/countdown/model/CountdownResponse.scala create mode 100644 src/main/scala/ru/otus/sc/countdown/service/CountdownService.scala create mode 100644 src/main/scala/ru/otus/sc/countdown/service/impl/CountdownServiceImpl.scala diff --git a/src/main/scala/ru/otus/sc/App.scala b/src/main/scala/ru/otus/sc/App.scala index 9002946..2f2ac73 100644 --- a/src/main/scala/ru/otus/sc/App.scala +++ b/src/main/scala/ru/otus/sc/App.scala @@ -1,5 +1,9 @@ package ru.otus.sc +import ru.otus.sc.countdown.dao.impl.CountdownDaoImpl +import ru.otus.sc.countdown.model.{CountdownRequest, CountdownResponse} +import ru.otus.sc.countdown.service.CountdownService +import ru.otus.sc.countdown.service.impl.CountdownServiceImpl import ru.otus.sc.counter.dao.impl.CounterDaoImpl import ru.otus.sc.counter.model.{CounterRequest, CounterResponse} import ru.otus.sc.counter.service.CounterService @@ -27,6 +31,7 @@ import ru.otus.sc.sum.service.impl.SumServiceImpl // Helper class which aggregates service entries to single point case class Config( + countdowning: CountdownService, counting: CounterService, echoing: EchoService, getting: StorageService, @@ -37,20 +42,30 @@ case class Config( object Config { def apply(): Config = { - val CounterDao = new CounterDaoImpl - val counterService = new CounterServiceImpl(CounterDao) - val echoDao = new EchoDaoImpl - val echoService = new EchoServiceImpl(echoDao) - val greetingDao = new GreetingDaoImpl - val greetingService = new GreetingServiceImpl(greetingDao) - val storageDao = new StorageDaoImpl - val storageService = new StorageServiceImpl(storageDao) - val reverseDao = new ReverseDaoImpl - val reverseService = new ReverseServiceImpl(reverseDao) - val sumDao = new SumDaoImpl - val sumService = new SumServiceImpl(sumDao) + val CountdownDao = new CountdownDaoImpl + val countdownService = new CountdownServiceImpl(CountdownDao) + val CounterDao = new CounterDaoImpl + val counterService = new CounterServiceImpl(CounterDao) + val echoDao = new EchoDaoImpl + val echoService = new EchoServiceImpl(echoDao) + val greetingDao = new GreetingDaoImpl + val greetingService = new GreetingServiceImpl(greetingDao) + val storageDao = new StorageDaoImpl + val storageService = new StorageServiceImpl(storageDao) + val reverseDao = new ReverseDaoImpl + val reverseService = new ReverseServiceImpl(reverseDao) + val sumDao = new SumDaoImpl + val sumService = new SumServiceImpl(sumDao) - Config(counterService, echoService, storageService, greetingService, reverseService, sumService) + Config( + countdownService, + counterService, + echoService, + storageService, + greetingService, + reverseService, + sumService + ) } } @@ -59,9 +74,19 @@ trait App { // every call increases counter value by 1 // counter can be reset to initial value with `clear` flag def getCount(request: CounterRequest): CounterResponse + // provide countdown started from `initValue` with auto-decrease value + // has to be initiated with `CountdownClearRequest(initValue)` + // otherwise it has "Done" state by default + // when countdown reaches 0 it stops decrease value and returns 0 + // can be reset with `CountdownClearRequest(initValue)` + // `initValue` less than 2 leads to set the countdown to "Done" state + // default `initValue` is 1 + def countdown(request: CountdownRequest): CountdownResponse // reply on requested value with the same value // echo value can be multiplied up to 5 times with `repeatNum` value - // no multiply answer by default + // no multiply answer by default (`repeatNum` is 1) + // proper values return answer with `EchoAnswerResponse` + // bad values return error with `EchoErrorResponse` def echo(request: EchoRequest): EchoResponse // provide value by requested key // in current implementation keys are one, two, three @@ -82,11 +107,17 @@ object App { def apply(): App = new AppImpl(Config()) private class AppImpl(config: Config) extends App { - def getCount(request: CounterRequest): CounterResponse = config.counting.getCount(request) - def echo(request: EchoRequest): EchoResponse = config.echoing.echo(request) - def get(request: StorageRequest): Option[StorageResponse] = config.getting.get(request) - def greet(request: GreetRequest): GreetResponse = config.greeting.greet(request) - def reverse(request: ReverseRequest): ReverseResponse = config.reversing.reverse(request) - def sum(request: SumRequest): SumResponse = config.summing.sum(request) + def countdown(request: CountdownRequest): CountdownResponse = + config.countdowning.countdown(request) + def getCount(request: CounterRequest): CounterResponse = + config.counting.getCount(request) + def echo(request: EchoRequest): EchoResponse = config.echoing.echo(request) + def get(request: StorageRequest): Option[StorageResponse] = + config.getting.get(request) + def greet(request: GreetRequest): GreetResponse = + config.greeting.greet(request) + def reverse(request: ReverseRequest): ReverseResponse = + config.reversing.reverse(request) + def sum(request: SumRequest): SumResponse = config.summing.sum(request) } } diff --git a/src/main/scala/ru/otus/sc/countdown/dao/CountdownDao.scala b/src/main/scala/ru/otus/sc/countdown/dao/CountdownDao.scala new file mode 100644 index 0000000..cd071e7 --- /dev/null +++ b/src/main/scala/ru/otus/sc/countdown/dao/CountdownDao.scala @@ -0,0 +1,6 @@ +package ru.otus.sc.countdown.dao + +trait CountdownDao { + def getCountdown: String + def clearCountdown(initValue: Long): String +} diff --git a/src/main/scala/ru/otus/sc/countdown/dao/impl/CountdownDaoImpl.scala b/src/main/scala/ru/otus/sc/countdown/dao/impl/CountdownDaoImpl.scala new file mode 100644 index 0000000..42eebe1 --- /dev/null +++ b/src/main/scala/ru/otus/sc/countdown/dao/impl/CountdownDaoImpl.scala @@ -0,0 +1,18 @@ +package ru.otus.sc.countdown.dao.impl + +import ru.otus.sc.countdown.dao.CountdownDao + +class CountdownDaoImpl extends CountdownDao { + private var count: Long = 0 + + def clearCountdown(initValue: Long): String = { + if (initValue > 0) count = initValue + getCountdown + } + + def getCountdown: String = { + if (count > 0) count -= 1 + else count = 0 + count.toString + } +} diff --git a/src/main/scala/ru/otus/sc/countdown/model/CountdownRequest.scala b/src/main/scala/ru/otus/sc/countdown/model/CountdownRequest.scala new file mode 100644 index 0000000..7c88871 --- /dev/null +++ b/src/main/scala/ru/otus/sc/countdown/model/CountdownRequest.scala @@ -0,0 +1,6 @@ +package ru.otus.sc.countdown.model + +sealed trait CountdownRequest + +case class CountdownClearRequest(initValue: Long = 1) extends CountdownRequest +case class CountdownTickRequest() extends CountdownRequest diff --git a/src/main/scala/ru/otus/sc/countdown/model/CountdownResponse.scala b/src/main/scala/ru/otus/sc/countdown/model/CountdownResponse.scala new file mode 100644 index 0000000..5185ec5 --- /dev/null +++ b/src/main/scala/ru/otus/sc/countdown/model/CountdownResponse.scala @@ -0,0 +1,13 @@ +package ru.otus.sc.countdown.model + +sealed trait CountdownResponse { + val countValue: String + def isDone: Boolean +} + +case class CountdownCountResponse(countValue: String) extends CountdownResponse { + def isDone = false +} +case class CountdownDoneResponse(countValue: String) extends CountdownResponse { + def isDone = true +} diff --git a/src/main/scala/ru/otus/sc/countdown/service/CountdownService.scala b/src/main/scala/ru/otus/sc/countdown/service/CountdownService.scala new file mode 100644 index 0000000..ff7a2c3 --- /dev/null +++ b/src/main/scala/ru/otus/sc/countdown/service/CountdownService.scala @@ -0,0 +1,7 @@ +package ru.otus.sc.countdown.service + +import ru.otus.sc.countdown.model.{CountdownRequest, CountdownResponse} + +trait CountdownService { + def countdown(request: CountdownRequest): CountdownResponse +} diff --git a/src/main/scala/ru/otus/sc/countdown/service/impl/CountdownServiceImpl.scala b/src/main/scala/ru/otus/sc/countdown/service/impl/CountdownServiceImpl.scala new file mode 100644 index 0000000..8c8282e --- /dev/null +++ b/src/main/scala/ru/otus/sc/countdown/service/impl/CountdownServiceImpl.scala @@ -0,0 +1,20 @@ +package ru.otus.sc.countdown.service.impl + +import ru.otus.sc.countdown.dao.CountdownDao +import ru.otus.sc.countdown.model +import ru.otus.sc.countdown.model.{CountdownClearRequest, CountdownDoneResponse, CountdownRequest, CountdownResponse} +import ru.otus.sc.countdown.service.CountdownService + +class CountdownServiceImpl(dao: CountdownDao) extends CountdownService { + def countdown(request: CountdownRequest): CountdownResponse = { + // Can not use pattern matching here, so have to use ugly .isInstanceOf .asInstanceOf :( + if (request.isInstanceOf[CountdownClearRequest]) + makeAnswer(dao.clearCountdown(request.asInstanceOf[CountdownClearRequest].initValue)) + else makeAnswer(dao.getCountdown) + } + + private def makeAnswer(countdown: String): CountdownResponse = { + if (countdown.toLong <= 0) CountdownDoneResponse(countdown) + else model.CountdownCountResponse(countdown) + } +} diff --git a/src/main/scala/ru/otus/sc/echo/model/EchoResponse.scala b/src/main/scala/ru/otus/sc/echo/model/EchoResponse.scala index 25ad221..2fe893c 100644 --- a/src/main/scala/ru/otus/sc/echo/model/EchoResponse.scala +++ b/src/main/scala/ru/otus/sc/echo/model/EchoResponse.scala @@ -4,9 +4,9 @@ sealed trait EchoResponse { def isValid: Boolean } -case class EchoResponseAnswer(answer: String) extends EchoResponse { +case class EchoAnswerResponse(answer: String) extends EchoResponse { def isValid = true } -case class EchoResponseError(error: String) extends EchoResponse { +case class EchoErrorResponse(error: String) extends EchoResponse { def isValid = false } diff --git a/src/main/scala/ru/otus/sc/echo/service/impl/EchoServiceImpl.scala b/src/main/scala/ru/otus/sc/echo/service/impl/EchoServiceImpl.scala index 5642b54..4740824 100644 --- a/src/main/scala/ru/otus/sc/echo/service/impl/EchoServiceImpl.scala +++ b/src/main/scala/ru/otus/sc/echo/service/impl/EchoServiceImpl.scala @@ -1,13 +1,13 @@ package ru.otus.sc.echo.service.impl import ru.otus.sc.echo.dao.EchoDao -import ru.otus.sc.echo.model.{EchoRequest, EchoResponse, EchoResponseAnswer, EchoResponseError} +import ru.otus.sc.echo.model.{EchoAnswerResponse, EchoErrorResponse, EchoRequest, EchoResponse} import ru.otus.sc.echo.service.EchoService class EchoServiceImpl(dao: EchoDao) extends EchoService { def echo(request: EchoRequest): EchoResponse = if (request.repeatNum > 0 & request.repeatNum <= 5) - EchoResponseAnswer(s"${dao.getResponse(request.echoRequest, request.repeatNum)}") - else EchoResponseError("Number of replies has to be from 1 to 5") + EchoAnswerResponse(s"${dao.getResponse(request.echoRequest, request.repeatNum)}") + else EchoErrorResponse("Number of replies has to be from 1 to 5") } From 9bd0ddf282d4336e57a4175ea5984f26e3af2d4e Mon Sep 17 00:00:00 2001 From: Constantine Kormashev Date: Tue, 1 Sep 2020 10:01:32 +0300 Subject: [PATCH 3/3] CRUD implemented --- src/main/scala/ru/otus/sc/App.scala | 274 ++++++++++++------ src/main/scala/ru/otus/sc/Engine.scala | 80 +++++ .../otus/sc/countdown/dao/CountdownDao.scala | 13 +- .../countdown/dao/impl/CountdownDaoImpl.scala | 70 ++++- .../otus/sc/countdown/model/Countdown.scala | 17 ++ .../sc/countdown/model/CountdownRequest.scala | 6 - .../countdown/model/CountdownResponse.scala | 13 - .../sc/countdown/model/CreateCountdown.scala | 9 + .../sc/countdown/model/DeleteCountdown.scala | 11 + .../sc/countdown/model/FindCountdown.scala | 16 + .../sc/countdown/model/GetCountdown.scala | 11 + .../sc/countdown/model/UpdateCountdown.scala | 13 + .../countdown/service/CountdownService.scala | 8 +- .../service/impl/CountdownServiceImpl.scala | 60 +++- .../ru/otus/sc/counter/dao/CounterDao.scala | 12 +- .../sc/counter/dao/impl/CounterDaoImpl.scala | 50 +++- .../ru/otus/sc/counter/model/Counter.scala | 13 + .../sc/counter/model/CounterRequest.scala | 3 - .../sc/counter/model/CounterResponse.scala | 3 - .../otus/sc/counter/model/CreateCounter.scala | 9 + .../otus/sc/counter/model/DeleteCounter.scala | 11 + .../otus/sc/counter/model/FindCounters.scala | 17 ++ .../ru/otus/sc/counter/model/GetCounter.scala | 11 + .../otus/sc/counter/model/UpdateCounter.scala | 11 + .../sc/counter/service/CounterService.scala | 8 +- .../service/impl/CounterServiceImpl.scala | 38 ++- .../otus/sc/echo/dao/impl/EchoDaoImpl.scala | 14 +- .../ru/otus/sc/echo/model/EchoResponse.scala | 13 +- .../echo/service/impl/EchoServiceImpl.scala | 6 +- .../ru/otus/sc/greet/model/GreetRequest.scala | 4 +- .../sc/greet/service/GreetingService.scala | 4 +- .../service/impl/GreetingServiceImpl.scala | 6 +- .../sc/reverse/dao/impl/ReverseDaoImpl.scala | 15 +- .../ru/otus/sc/storage/dao/StorageDao.scala | 11 +- .../sc/storage/dao/impl/StorageDaoImpl.scala | 48 ++- .../otus/sc/storage/model/CreateStorage.scala | 9 + .../otus/sc/storage/model/DeleteStorage.scala | 9 + .../otus/sc/storage/model/FindStorages.scala | 12 + .../ru/otus/sc/storage/model/GetStorage.scala | 9 + .../otus/sc/storage/model/StorageEntry.scala | 3 + .../sc/storage/model/StorageRequest.scala | 3 - .../sc/storage/model/StorageResponse.scala | 3 - .../otus/sc/storage/model/UpdateStorage.scala | 9 + .../sc/storage/service/StorageService.scala | 10 +- .../service/impl/StorageServiceImpl.scala | 38 ++- .../scala/ru/otus/sc/user/dao/UserDao.scala | 16 + .../ru/otus/sc/user/dao/UserTagDao.scala | 17 ++ .../otus/sc/user/dao/impl/UserDaoImpl.scala | 63 ++++ .../sc/user/dao/impl/UserTagDaoImpl.scala | 72 +++++ .../sc/user/implicits/UserNameImplicits.scala | 16 + .../ru/otus/sc/user/model/CreateTag.scala | 9 + .../ru/otus/sc/user/model/CreateUser.scala | 9 + .../ru/otus/sc/user/model/DeleteTag.scala | 11 + .../ru/otus/sc/user/model/DeleteUser.scala | 11 + .../ru/otus/sc/user/model/FindTags.scala | 17 ++ .../ru/otus/sc/user/model/FindUsers.scala | 16 + .../scala/ru/otus/sc/user/model/GetTag.scala | 11 + .../scala/ru/otus/sc/user/model/GetUser.scala | 12 + .../ru/otus/sc/user/model/UpdateTag.scala | 12 + .../otus/sc/user/model/UpdateTagOnUsers.scala | 21 ++ .../ru/otus/sc/user/model/UpdateUser.scala | 13 + .../scala/ru/otus/sc/user/model/User.scala | 28 ++ .../scala/ru/otus/sc/user/model/UserTag.scala | 9 + .../ru/otus/sc/user/service/UserService.scala | 11 + .../otus/sc/user/service/UserTagService.scala | 13 + .../user/service/impl/UserServiceImpl.scala | 56 ++++ .../service/impl/UserTagServiceImpl.scala | 64 ++++ 67 files changed, 1294 insertions(+), 226 deletions(-) create mode 100644 src/main/scala/ru/otus/sc/Engine.scala create mode 100644 src/main/scala/ru/otus/sc/countdown/model/Countdown.scala delete mode 100644 src/main/scala/ru/otus/sc/countdown/model/CountdownRequest.scala delete mode 100644 src/main/scala/ru/otus/sc/countdown/model/CountdownResponse.scala create mode 100644 src/main/scala/ru/otus/sc/countdown/model/CreateCountdown.scala create mode 100644 src/main/scala/ru/otus/sc/countdown/model/DeleteCountdown.scala create mode 100644 src/main/scala/ru/otus/sc/countdown/model/FindCountdown.scala create mode 100644 src/main/scala/ru/otus/sc/countdown/model/GetCountdown.scala create mode 100644 src/main/scala/ru/otus/sc/countdown/model/UpdateCountdown.scala create mode 100644 src/main/scala/ru/otus/sc/counter/model/Counter.scala delete mode 100644 src/main/scala/ru/otus/sc/counter/model/CounterRequest.scala delete mode 100644 src/main/scala/ru/otus/sc/counter/model/CounterResponse.scala create mode 100644 src/main/scala/ru/otus/sc/counter/model/CreateCounter.scala create mode 100644 src/main/scala/ru/otus/sc/counter/model/DeleteCounter.scala create mode 100644 src/main/scala/ru/otus/sc/counter/model/FindCounters.scala create mode 100644 src/main/scala/ru/otus/sc/counter/model/GetCounter.scala create mode 100644 src/main/scala/ru/otus/sc/counter/model/UpdateCounter.scala create mode 100644 src/main/scala/ru/otus/sc/storage/model/CreateStorage.scala create mode 100644 src/main/scala/ru/otus/sc/storage/model/DeleteStorage.scala create mode 100644 src/main/scala/ru/otus/sc/storage/model/FindStorages.scala create mode 100644 src/main/scala/ru/otus/sc/storage/model/GetStorage.scala create mode 100644 src/main/scala/ru/otus/sc/storage/model/StorageEntry.scala delete mode 100644 src/main/scala/ru/otus/sc/storage/model/StorageRequest.scala delete mode 100644 src/main/scala/ru/otus/sc/storage/model/StorageResponse.scala create mode 100644 src/main/scala/ru/otus/sc/storage/model/UpdateStorage.scala create mode 100644 src/main/scala/ru/otus/sc/user/dao/UserDao.scala create mode 100644 src/main/scala/ru/otus/sc/user/dao/UserTagDao.scala create mode 100644 src/main/scala/ru/otus/sc/user/dao/impl/UserDaoImpl.scala create mode 100644 src/main/scala/ru/otus/sc/user/dao/impl/UserTagDaoImpl.scala create mode 100644 src/main/scala/ru/otus/sc/user/implicits/UserNameImplicits.scala create mode 100644 src/main/scala/ru/otus/sc/user/model/CreateTag.scala create mode 100644 src/main/scala/ru/otus/sc/user/model/CreateUser.scala create mode 100644 src/main/scala/ru/otus/sc/user/model/DeleteTag.scala create mode 100644 src/main/scala/ru/otus/sc/user/model/DeleteUser.scala create mode 100644 src/main/scala/ru/otus/sc/user/model/FindTags.scala create mode 100644 src/main/scala/ru/otus/sc/user/model/FindUsers.scala create mode 100644 src/main/scala/ru/otus/sc/user/model/GetTag.scala create mode 100644 src/main/scala/ru/otus/sc/user/model/GetUser.scala create mode 100644 src/main/scala/ru/otus/sc/user/model/UpdateTag.scala create mode 100644 src/main/scala/ru/otus/sc/user/model/UpdateTagOnUsers.scala create mode 100644 src/main/scala/ru/otus/sc/user/model/UpdateUser.scala create mode 100644 src/main/scala/ru/otus/sc/user/model/User.scala create mode 100644 src/main/scala/ru/otus/sc/user/model/UserTag.scala create mode 100644 src/main/scala/ru/otus/sc/user/service/UserService.scala create mode 100644 src/main/scala/ru/otus/sc/user/service/UserTagService.scala create mode 100644 src/main/scala/ru/otus/sc/user/service/impl/UserServiceImpl.scala create mode 100644 src/main/scala/ru/otus/sc/user/service/impl/UserTagServiceImpl.scala diff --git a/src/main/scala/ru/otus/sc/App.scala b/src/main/scala/ru/otus/sc/App.scala index 2f2ac73..549c676 100644 --- a/src/main/scala/ru/otus/sc/App.scala +++ b/src/main/scala/ru/otus/sc/App.scala @@ -1,99 +1,28 @@ package ru.otus.sc -import ru.otus.sc.countdown.dao.impl.CountdownDaoImpl -import ru.otus.sc.countdown.model.{CountdownRequest, CountdownResponse} -import ru.otus.sc.countdown.service.CountdownService -import ru.otus.sc.countdown.service.impl.CountdownServiceImpl -import ru.otus.sc.counter.dao.impl.CounterDaoImpl -import ru.otus.sc.counter.model.{CounterRequest, CounterResponse} -import ru.otus.sc.counter.service.CounterService -import ru.otus.sc.counter.service.impl.CounterServiceImpl -import ru.otus.sc.echo.dao.impl.EchoDaoImpl +import ru.otus.sc.Engine.{Greeted, StorageKey, StorageValue} +import ru.otus.sc.countdown.model._ +import ru.otus.sc.counter.model._ import ru.otus.sc.echo.model.{EchoRequest, EchoResponse} -import ru.otus.sc.echo.service.EchoService -import ru.otus.sc.echo.service.impl.EchoServiceImpl -import ru.otus.sc.greet.dao.impl.GreetingDaoImpl import ru.otus.sc.greet.model.{GreetRequest, GreetResponse} -import ru.otus.sc.greet.service.GreetingService -import ru.otus.sc.greet.service.impl.GreetingServiceImpl -import ru.otus.sc.reverse.dao.impl.ReverseDaoImpl import ru.otus.sc.reverse.model.{ReverseRequest, ReverseResponse} -import ru.otus.sc.reverse.service.ReverseService -import ru.otus.sc.reverse.service.impl.ReverseServiceImpl -import ru.otus.sc.storage.dao.impl.StorageDaoImpl -import ru.otus.sc.storage.model.{StorageRequest, StorageResponse} -import ru.otus.sc.storage.service.StorageService -import ru.otus.sc.storage.service.impl.StorageServiceImpl -import ru.otus.sc.sum.dao.impl.SumDaoImpl +import ru.otus.sc.storage.model._ import ru.otus.sc.sum.model.{SumRequest, SumResponse} -import ru.otus.sc.sum.service.SumService -import ru.otus.sc.sum.service.impl.SumServiceImpl - -// Helper class which aggregates service entries to single point -case class Config( - countdowning: CountdownService, - counting: CounterService, - echoing: EchoService, - getting: StorageService, - greeting: GreetingService, - reversing: ReverseService, - summing: SumService -) - -object Config { - def apply(): Config = { - val CountdownDao = new CountdownDaoImpl - val countdownService = new CountdownServiceImpl(CountdownDao) - val CounterDao = new CounterDaoImpl - val counterService = new CounterServiceImpl(CounterDao) - val echoDao = new EchoDaoImpl - val echoService = new EchoServiceImpl(echoDao) - val greetingDao = new GreetingDaoImpl - val greetingService = new GreetingServiceImpl(greetingDao) - val storageDao = new StorageDaoImpl - val storageService = new StorageServiceImpl(storageDao) - val reverseDao = new ReverseDaoImpl - val reverseService = new ReverseServiceImpl(reverseDao) - val sumDao = new SumDaoImpl - val sumService = new SumServiceImpl(sumDao) - - Config( - countdownService, - counterService, - echoService, - storageService, - greetingService, - reverseService, - sumService - ) - } -} +import ru.otus.sc.user.model._ trait App { - // provide counter started from 1 with auto-increase value - // every call increases counter value by 1 - // counter can be reset to initial value with `clear` flag - def getCount(request: CounterRequest): CounterResponse - // provide countdown started from `initValue` with auto-decrease value - // has to be initiated with `CountdownClearRequest(initValue)` - // otherwise it has "Done" state by default - // when countdown reaches 0 it stops decrease value and returns 0 - // can be reset with `CountdownClearRequest(initValue)` - // `initValue` less than 2 leads to set the countdown to "Done" state - // default `initValue` is 1 - def countdown(request: CountdownRequest): CountdownResponse // reply on requested value with the same value // echo value can be multiplied up to 5 times with `repeatNum` value // no multiply answer by default (`repeatNum` is 1) // proper values return answer with `EchoAnswerResponse` // bad values return error with `EchoErrorResponse` def echo(request: EchoRequest): EchoResponse - // provide value by requested key - // in current implementation keys are one, two, three - def get(request: StorageRequest): Option[StorageResponse] - // greet with provided name value + // greet with provided object // panic if `isHuman` flag is unset - def greet(request: GreetRequest): GreetResponse + // greeting request requires implicit parameter for extracting name from the object + // in current implementation object is User and implicit can be borrowed from + // `ru.otus.sc.user.implicits.UserNameImplicits._` + def greet(request: GreetRequest[Greeted]): GreetResponse // reverse given string from end to begin def reverse(request: ReverseRequest): ReverseResponse // sum 2 given values locally @@ -101,23 +30,180 @@ trait App { // in current implementation external computation is artificial delay // which was implemented with lazy value def sum(request: SumRequest): SumResponse + // manage users + // user is compound from: + // id, which is DB related stuff like pk + // unique id, UUID, this item is unique for whole system + // user name, which is compound from: first, last, middle, patronymic names and title + // age + // set of tags + // CRUD operations are available as well as search for first, last name and tag + def createUser(request: CreateUserRequest): CreateUserResponse + def getUser(request: GetUserRequest): GetUserResponse + def deleteUser(request: DeleteUserRequest): DeleteUserResponse + def updateUser(request: UpdateUserRequest): UpdateUserResponse + def findUsers(request: FindUsersRequest): FindUsersResponse + // manage user tags + // tags are bound with users and related only them + // tag is compound from: + // id, which is DB related stuff like pk + // name, for human + // CRUD operations are available as well as search for name + // also associated with certain tag users can be obtained in search request + // tags and users are totally independent + // tagging and untagging users do not allow manage tags on users directly + // and have to be performed as separate operations for users and tags + // e.g. tagging user example, + // imagine we already have app, user, tag and all operations are done successfully: + // val tagId = 42L + // val uniqueUserId = UUID.fromString("4a71a58b-4b39-44fc-ae52-76b9657be280") + // val tag = app.getUserTag(GetUserTagRequest(tagId)) + // val user = app.getUser(GetUserRequest(uniqueUserId)) + // val updatedUser = user.copy(tags = (user.tags + tag)) + // app.tagUser(UpdateTagUserRequest(tagId, uniqueUserId)) + // app.updateUser(UpdateUserRequest(updatedUser)) + def createUserTag(request: CreateUserTagRequest): CreateUserTagResponse + def getUserTag(request: GetUserTagRequest): GetUserTagResponse + def deleteUserTag(request: DeleteUserTagRequest): DeleteUserTagResponse + def updateUserTag(request: UpdateUserTagRequest): UpdateUserTagResponse + def findUserTags(request: FindUserTagsRequest): FindUserTagsResponse + def tagUser(request: UpdateTagUserRequest): UpdateTagUserResponse + def untagUser(request: UpdateUntagUserRequest): UpdateUntagUserResponse + // manage counters + // counter is auto-increment item with initial `value`, default value is 1 + // update increments counter by 1 + // counter value can not be updated itself, it can be only incremented + // timestamp is updated on every counter update + // counter is compound from: + // id, UUID, this item is unique for whole system + // timestamp of update + // value of counter + // CRUD operations are available as well as search for values or timestamps + // searching requires predicate (t: T, i: T) => Boolean for comparing items + // where `t` is target and `i` is item in DB + def createCounter(request: CreateCounterRequest): CreateCounterResponse + def deleteCounter(request: DeleteCounterRequest): DeleteCounterResponse + def updateCounter(request: UpdateCounterRequest): UpdateCounterResponse + def getCounter(request: GetCounterRequest): GetCounterResponse + def findCounters(request: FindCountersRequest): FindCountersResponse + // manage countdowns + // countdown is auto-decrement item with initial `value`, default value is 1 + // update auto-decreases value by 1 + // when countdown reaches 0 it stops decrease value and swaps state from Tick to Done + // Countdown.Done is composed from id, UUID, this item is unique for whole system + // State above can not be updated anymore, also it can be created `CountdownDone` + // Countdown.Tick is composed from: + // id, UUID, this item is unique for whole system + // updater, UUID this is id of something that create or update countdown + // value of countdown + // CRUD operations are available as well as search for: + // values, updaters, Done and NonDone states + // searching for values requires predicate (t: T, i: T) => Boolean for comparing items + // where `t` is target and `i` is item in DB + def createCountdown(request: CreateCountdownRequest): CreateCountdownResponse + def deleteCountdown(request: DeleteCountdownRequest): DeleteCountdownResponse + def updateCountdown(request: UpdateCountdownRequest): UpdateCountdownResponse + def getCountdown(request: GetCountdownRequest): GetCountdownResponse + def findCountdowns(request: FindCountdownsRequest): FindCountdownsResponse + // manage storage + // provide value by requested key + // StorageEntry is composed from key and value of defined types + // in current implementation keys and values are String + // CRUD operations are available as well as search for value itself or predicate on value + // searching with predicate requires predicate i: V => Boolean for comparing items + // where `i` is item in DB + def createStorage( + request: CreateStorageRequest[StorageKey, StorageValue] + ): CreateStorageResponse[StorageKey, StorageValue] + def getStorage( + request: GetStorageRequest[StorageKey, StorageValue] + ): GetStorageResponse[StorageKey, StorageValue] + def deleteStorage( + request: DeleteStorageRequest[StorageKey, StorageValue] + ): DeleteStorageResponse[StorageKey, StorageValue] + def updateStorage( + request: UpdateStorageRequest[StorageKey, StorageValue] + ): UpdateStorageResponse[StorageKey, StorageValue] + def findStorages( + request: FindStoragesRequest[StorageKey, StorageValue] + ): FindStoragesResponse[StorageKey, StorageValue] } object App { - def apply(): App = new AppImpl(Config()) + def apply(): App = new AppImpl(Engine()) - private class AppImpl(config: Config) extends App { - def countdown(request: CountdownRequest): CountdownResponse = - config.countdowning.countdown(request) - def getCount(request: CounterRequest): CounterResponse = - config.counting.getCount(request) - def echo(request: EchoRequest): EchoResponse = config.echoing.echo(request) - def get(request: StorageRequest): Option[StorageResponse] = - config.getting.get(request) - def greet(request: GreetRequest): GreetResponse = - config.greeting.greet(request) + private class AppImpl(engine: Engine) extends App { + def echo(request: EchoRequest): EchoResponse = + engine.echoing.echo(request) + def greet(request: GreetRequest[Greeted]): GreetResponse = + engine.greeting.greet(request) def reverse(request: ReverseRequest): ReverseResponse = - config.reversing.reverse(request) - def sum(request: SumRequest): SumResponse = config.summing.sum(request) + engine.reversing.reverse(request) + def sum(request: SumRequest): SumResponse = + engine.summing.sum(request) + def createUser(request: CreateUserRequest): CreateUserResponse = + engine.usering.createUser(request) + def getUser(request: GetUserRequest): GetUserResponse = + engine.usering.getUser(request) + def deleteUser(request: DeleteUserRequest): DeleteUserResponse = + engine.usering.deleteUser(request) + def updateUser(request: UpdateUserRequest): UpdateUserResponse = + engine.usering.updateUser(request) + def findUsers(request: FindUsersRequest): FindUsersResponse = + engine.usering.findUsers(request) + def createUserTag(request: CreateUserTagRequest): CreateUserTagResponse = + engine.userTagging.createUserTag(request) + def getUserTag(request: GetUserTagRequest): GetUserTagResponse = + engine.userTagging.getUserTag(request) + def deleteUserTag(request: DeleteUserTagRequest): DeleteUserTagResponse = + engine.userTagging.deleteUserTag(request) + def updateUserTag(request: UpdateUserTagRequest): UpdateUserTagResponse = + engine.userTagging.updateUserTag(request) + def findUserTags(request: FindUserTagsRequest): FindUserTagsResponse = + engine.userTagging.findUserTags(request) + def tagUser(request: UpdateTagUserRequest): UpdateTagUserResponse = + engine.userTagging.tagUser(request) + def untagUser(request: UpdateUntagUserRequest): UpdateUntagUserResponse = + engine.userTagging.untagUser(request) + def createCounter(request: CreateCounterRequest): CreateCounterResponse = + engine.counting.createCounter(request) + def deleteCounter(request: DeleteCounterRequest): DeleteCounterResponse = + engine.counting.deleteCounter(request) + def updateCounter(request: UpdateCounterRequest): UpdateCounterResponse = + engine.counting.updateCounter(request) + def getCounter(request: GetCounterRequest): GetCounterResponse = + engine.counting.getCounter(request) + def findCounters(request: FindCountersRequest): FindCountersResponse = + engine.counting.findCounters(request) + def createCountdown(request: CreateCountdownRequest): CreateCountdownResponse = + engine.countdowning.createCountdown(request) + def deleteCountdown(request: DeleteCountdownRequest): DeleteCountdownResponse = + engine.countdowning.deleteCountdown(request) + def updateCountdown(request: UpdateCountdownRequest): UpdateCountdownResponse = + engine.countdowning.updateCountdown(request) + def getCountdown(request: GetCountdownRequest): GetCountdownResponse = + engine.countdowning.getCountdown(request) + def findCountdowns(request: FindCountdownsRequest): FindCountdownsResponse = + engine.countdowning.findCountdowns(request) + def createStorage( + request: CreateStorageRequest[StorageKey, StorageValue] + ): CreateStorageResponse[StorageKey, StorageValue] = + engine.getting.createStorage(request) + def deleteStorage( + request: DeleteStorageRequest[StorageKey, StorageValue] + ): DeleteStorageResponse[StorageKey, StorageValue] = + engine.getting.deleteStorage(request) + def updateStorage( + request: UpdateStorageRequest[StorageKey, StorageValue] + ): UpdateStorageResponse[StorageKey, StorageValue] = + engine.getting.updateStorage(request) + def getStorage( + request: GetStorageRequest[StorageKey, StorageValue] + ): GetStorageResponse[StorageKey, StorageValue] = + engine.getting.getStorage(request) + def findStorages( + request: FindStoragesRequest[StorageKey, StorageValue] + ): FindStoragesResponse[StorageKey, StorageValue] = + engine.getting.findStorages(request) } } diff --git a/src/main/scala/ru/otus/sc/Engine.scala b/src/main/scala/ru/otus/sc/Engine.scala new file mode 100644 index 0000000..6cde543 --- /dev/null +++ b/src/main/scala/ru/otus/sc/Engine.scala @@ -0,0 +1,80 @@ +package ru.otus.sc + +import ru.otus.sc.Engine.{Greeted, StorageKey, StorageValue} +import ru.otus.sc.countdown.dao.impl.CountdownDaoImpl +import ru.otus.sc.countdown.service.CountdownService +import ru.otus.sc.countdown.service.impl.CountdownServiceImpl +import ru.otus.sc.counter.dao.impl.CounterDaoImpl +import ru.otus.sc.counter.service.CounterService +import ru.otus.sc.counter.service.impl.CounterServiceImpl +import ru.otus.sc.echo.dao.impl.EchoDaoImpl +import ru.otus.sc.echo.service.EchoService +import ru.otus.sc.echo.service.impl.EchoServiceImpl +import ru.otus.sc.greet.dao.impl.GreetingDaoImpl +import ru.otus.sc.greet.service.GreetingService +import ru.otus.sc.greet.service.impl.GreetingServiceImpl +import ru.otus.sc.reverse.dao.impl.ReverseDaoImpl +import ru.otus.sc.reverse.service.ReverseService +import ru.otus.sc.reverse.service.impl.ReverseServiceImpl +import ru.otus.sc.storage.dao.impl.StorageDaoImpl +import ru.otus.sc.storage.service.StorageService +import ru.otus.sc.storage.service.impl.StorageServiceImpl +import ru.otus.sc.sum.dao.impl.SumDaoImpl +import ru.otus.sc.sum.service.SumService +import ru.otus.sc.sum.service.impl.SumServiceImpl +import ru.otus.sc.user.dao.impl.{UserDaoImpl, UserTagDaoImpl} +import ru.otus.sc.user.model.User +import ru.otus.sc.user.service.impl.{UserServiceImpl, UserTagServiceImpl} +import ru.otus.sc.user.service.{UserService, UserTagService} + +// Helper class which aggregates service entries to single point +case class Engine( + countdowning: CountdownService, + counting: CounterService, + echoing: EchoService, + getting: StorageService[StorageKey, StorageValue], + greeting: GreetingService[Greeted], + reversing: ReverseService, + summing: SumService, + usering: UserService, + userTagging: UserTagService +) + +object Engine { + type StorageKey = String + type StorageValue = String + type Greeted = User + + def apply(): Engine = { + val CountdownDao = new CountdownDaoImpl + val countdownService = new CountdownServiceImpl(CountdownDao) + val CounterDao = new CounterDaoImpl + val counterService = new CounterServiceImpl(CounterDao) + val echoDao = new EchoDaoImpl + val echoService = new EchoServiceImpl(echoDao) + val greetingDao = new GreetingDaoImpl + val greetingService = new GreetingServiceImpl[Greeted](greetingDao) + val storageDao = new StorageDaoImpl[StorageKey, StorageValue] + val storageService = new StorageServiceImpl[StorageKey, StorageValue](storageDao) + val reverseDao = new ReverseDaoImpl + val reverseService = new ReverseServiceImpl(reverseDao) + val sumDao = new SumDaoImpl + val sumService = new SumServiceImpl(sumDao) + val userDao = new UserDaoImpl + val userService = new UserServiceImpl(userDao) + val userTagDao = new UserTagDaoImpl + val userTagService = new UserTagServiceImpl(userTagDao) + + Engine( + countdownService, + counterService, + echoService, + storageService, + greetingService, + reverseService, + sumService, + userService, + userTagService + ) + } +} diff --git a/src/main/scala/ru/otus/sc/countdown/dao/CountdownDao.scala b/src/main/scala/ru/otus/sc/countdown/dao/CountdownDao.scala index cd071e7..9da76a3 100644 --- a/src/main/scala/ru/otus/sc/countdown/dao/CountdownDao.scala +++ b/src/main/scala/ru/otus/sc/countdown/dao/CountdownDao.scala @@ -1,6 +1,15 @@ package ru.otus.sc.countdown.dao +import ru.otus.sc.countdown.model.Countdown +import ru.otus.sc.countdown.model.Countdown.{CompareValues, CountdownId, CountdownValue, UpdaterId} + trait CountdownDao { - def getCountdown: String - def clearCountdown(initValue: Long): String + def createCountdown(countdown: Countdown): Countdown + def deleteCountdown(id: CountdownId): Option[Countdown] + def getCountdown(id: CountdownId): Option[Countdown] + def updateCountdown(countdown: Countdown): Option[Countdown] + def findCountdownsByValue(value: CountdownValue, predicate: CompareValues): Seq[Countdown] + def findCountdownsByUpdater(updater: UpdaterId): Seq[Countdown] + def findAllDone: Seq[Countdown] + def findAllNonDone: Seq[Countdown] } diff --git a/src/main/scala/ru/otus/sc/countdown/dao/impl/CountdownDaoImpl.scala b/src/main/scala/ru/otus/sc/countdown/dao/impl/CountdownDaoImpl.scala index 42eebe1..697167e 100644 --- a/src/main/scala/ru/otus/sc/countdown/dao/impl/CountdownDaoImpl.scala +++ b/src/main/scala/ru/otus/sc/countdown/dao/impl/CountdownDaoImpl.scala @@ -1,18 +1,72 @@ package ru.otus.sc.countdown.dao.impl +import java.util.UUID + import ru.otus.sc.countdown.dao.CountdownDao +import ru.otus.sc.countdown.model.Countdown +import ru.otus.sc.countdown.model.Countdown.{CompareValues, CountdownId, CountdownValue, UpdaterId} class CountdownDaoImpl extends CountdownDao { - private var count: Long = 0 + private var countdowns = Map[CountdownId, Countdown]() + + def createCountdown(countdown: Countdown): Countdown = { - def clearCountdown(initValue: Long): String = { - if (initValue > 0) count = initValue - getCountdown + val id = UUID.randomUUID() + val newCountdown = countdown match { + case Countdown.Done(_) => Countdown.Done(Some(id)) + case Countdown.Tick(_, updater, value) => Countdown.Tick(Some(id), updater, value) + } + countdowns += (id -> newCountdown) + newCountdown } - def getCountdown: String = { - if (count > 0) count -= 1 - else count = 0 - count.toString + def deleteCountdown(id: CountdownId): Option[Countdown] = + for { + deletedCountdown <- countdowns.get(id) + } yield { + countdowns -= id + deletedCountdown + } + + def getCountdown(id: CountdownId): Option[Countdown] = countdowns.get(id) + + def updateCountdown(countdown: Countdown): Option[Countdown] = { + // only CountdownTick can be updated + val (countdownId, updaterId) = countdown match { + case Countdown.Tick(id, updater, _) => (id, Some(updater)) + case Countdown.Done(_) => (None, None) + } + + for { + id <- countdownId + updater <- updaterId + currentCountdown <- countdowns.get(id) + } yield { + val newCountdown = currentCountdown match { + case alreadyDone @ Countdown.Done(_) => alreadyDone + case Countdown.Tick(id, _, value) => + val newValue = value - 1L + if (newValue <= 0) Countdown.Done(id) + else Countdown.Tick(id, updater, newValue) + } + countdowns += (id -> newCountdown) + newCountdown + } } + + def findCountdownsByValue(value: CountdownValue, predicate: CompareValues): Seq[Countdown] = + countdowns.values.collect { + case x @ Countdown.Tick(_, _, currentValue) if predicate(value, currentValue) => x + }.toVector + + def findCountdownsByUpdater(updater: UpdaterId): Seq[Countdown] = + countdowns.values.collect { + case x @ Countdown.Tick(_, updaterId, _) if updaterId == updater => x + }.toVector + + def findAllDone: Seq[Countdown] = + countdowns.values.collect { case x @ Countdown.Done(_) => x }.toVector + + def findAllNonDone: Seq[Countdown] = + countdowns.values.collect { case x @ Countdown.Tick(_, _, _) => x }.toVector } diff --git a/src/main/scala/ru/otus/sc/countdown/model/Countdown.scala b/src/main/scala/ru/otus/sc/countdown/model/Countdown.scala new file mode 100644 index 0000000..e2f7bf6 --- /dev/null +++ b/src/main/scala/ru/otus/sc/countdown/model/Countdown.scala @@ -0,0 +1,17 @@ +package ru.otus.sc.countdown.model + +import java.util.UUID + +sealed trait Countdown + +object Countdown { + type CountdownId = UUID + type UpdaterId = UUID + type CountdownValue = Long + type CompareValues = (CountdownValue, CountdownValue) => Boolean + + case class Tick(id: Option[CountdownId], updater: UpdaterId, value: CountdownValue = 1L) + extends Countdown + case class Done(id: Option[CountdownId]) extends Countdown + +} diff --git a/src/main/scala/ru/otus/sc/countdown/model/CountdownRequest.scala b/src/main/scala/ru/otus/sc/countdown/model/CountdownRequest.scala deleted file mode 100644 index 7c88871..0000000 --- a/src/main/scala/ru/otus/sc/countdown/model/CountdownRequest.scala +++ /dev/null @@ -1,6 +0,0 @@ -package ru.otus.sc.countdown.model - -sealed trait CountdownRequest - -case class CountdownClearRequest(initValue: Long = 1) extends CountdownRequest -case class CountdownTickRequest() extends CountdownRequest diff --git a/src/main/scala/ru/otus/sc/countdown/model/CountdownResponse.scala b/src/main/scala/ru/otus/sc/countdown/model/CountdownResponse.scala deleted file mode 100644 index 5185ec5..0000000 --- a/src/main/scala/ru/otus/sc/countdown/model/CountdownResponse.scala +++ /dev/null @@ -1,13 +0,0 @@ -package ru.otus.sc.countdown.model - -sealed trait CountdownResponse { - val countValue: String - def isDone: Boolean -} - -case class CountdownCountResponse(countValue: String) extends CountdownResponse { - def isDone = false -} -case class CountdownDoneResponse(countValue: String) extends CountdownResponse { - def isDone = true -} diff --git a/src/main/scala/ru/otus/sc/countdown/model/CreateCountdown.scala b/src/main/scala/ru/otus/sc/countdown/model/CreateCountdown.scala new file mode 100644 index 0000000..3b1b404 --- /dev/null +++ b/src/main/scala/ru/otus/sc/countdown/model/CreateCountdown.scala @@ -0,0 +1,9 @@ +package ru.otus.sc.countdown.model + +case class CreateCountdownRequest(countdown: Countdown) + +sealed trait CreateCountdownResponse +object CreateCountdownResponse { + case class Created(countdown: Countdown) extends CreateCountdownResponse + case class Error(error: String) extends CreateCountdownResponse +} diff --git a/src/main/scala/ru/otus/sc/countdown/model/DeleteCountdown.scala b/src/main/scala/ru/otus/sc/countdown/model/DeleteCountdown.scala new file mode 100644 index 0000000..59dd8e7 --- /dev/null +++ b/src/main/scala/ru/otus/sc/countdown/model/DeleteCountdown.scala @@ -0,0 +1,11 @@ +package ru.otus.sc.countdown.model + +import ru.otus.sc.countdown.model.Countdown.CountdownId + +case class DeleteCountdownRequest(id: CountdownId) + +sealed trait DeleteCountdownResponse +object DeleteCountdownResponse { + case class Deleted(countdown: Countdown) extends DeleteCountdownResponse + case class NotFound(id: CountdownId) extends DeleteCountdownResponse +} diff --git a/src/main/scala/ru/otus/sc/countdown/model/FindCountdown.scala b/src/main/scala/ru/otus/sc/countdown/model/FindCountdown.scala new file mode 100644 index 0000000..ab9bbfa --- /dev/null +++ b/src/main/scala/ru/otus/sc/countdown/model/FindCountdown.scala @@ -0,0 +1,16 @@ +package ru.otus.sc.countdown.model + +import ru.otus.sc.countdown.model.Countdown.{CompareValues, CountdownValue, UpdaterId} + +sealed trait FindCountdownsRequest +object FindCountdownsRequest { + case class ByValue(value: CountdownValue, predicate: CompareValues) extends FindCountdownsRequest + case class ByUpdater(updater: UpdaterId) extends FindCountdownsRequest + case class AllDone() extends FindCountdownsRequest + case class AllNonDone() extends FindCountdownsRequest +} + +sealed trait FindCountdownsResponse +object FindCountdownsResponse { + case class Result(countdowns: Seq[Countdown]) extends FindCountdownsResponse +} diff --git a/src/main/scala/ru/otus/sc/countdown/model/GetCountdown.scala b/src/main/scala/ru/otus/sc/countdown/model/GetCountdown.scala new file mode 100644 index 0000000..5d67825 --- /dev/null +++ b/src/main/scala/ru/otus/sc/countdown/model/GetCountdown.scala @@ -0,0 +1,11 @@ +package ru.otus.sc.countdown.model + +import ru.otus.sc.countdown.model.Countdown.CountdownId + +case class GetCountdownRequest(id: CountdownId) + +sealed trait GetCountdownResponse +object GetCountdownResponse { + case class Found(countdown: Countdown) extends GetCountdownResponse + case class NotFound(id: CountdownId) extends GetCountdownResponse +} diff --git a/src/main/scala/ru/otus/sc/countdown/model/UpdateCountdown.scala b/src/main/scala/ru/otus/sc/countdown/model/UpdateCountdown.scala new file mode 100644 index 0000000..078c378 --- /dev/null +++ b/src/main/scala/ru/otus/sc/countdown/model/UpdateCountdown.scala @@ -0,0 +1,13 @@ +package ru.otus.sc.countdown.model + +import ru.otus.sc.countdown.model.Countdown.CountdownId + +case class UpdateCountdownRequest(countdown: Countdown) + +sealed trait UpdateCountdownResponse +object UpdateCountdownResponse { + case class Updated(countdown: Countdown) extends UpdateCountdownResponse + case class NotFound(id: CountdownId) extends UpdateCountdownResponse + case object CanNotUpdateDone extends UpdateCountdownResponse + case object ErrorWrongId extends UpdateCountdownResponse +} diff --git a/src/main/scala/ru/otus/sc/countdown/service/CountdownService.scala b/src/main/scala/ru/otus/sc/countdown/service/CountdownService.scala index ff7a2c3..8eb73a8 100644 --- a/src/main/scala/ru/otus/sc/countdown/service/CountdownService.scala +++ b/src/main/scala/ru/otus/sc/countdown/service/CountdownService.scala @@ -1,7 +1,11 @@ package ru.otus.sc.countdown.service -import ru.otus.sc.countdown.model.{CountdownRequest, CountdownResponse} +import ru.otus.sc.countdown.model._ trait CountdownService { - def countdown(request: CountdownRequest): CountdownResponse + def createCountdown(request: CreateCountdownRequest): CreateCountdownResponse + def deleteCountdown(request: DeleteCountdownRequest): DeleteCountdownResponse + def updateCountdown(request: UpdateCountdownRequest): UpdateCountdownResponse + def getCountdown(request: GetCountdownRequest): GetCountdownResponse + def findCountdowns(request: FindCountdownsRequest): FindCountdownsResponse } diff --git a/src/main/scala/ru/otus/sc/countdown/service/impl/CountdownServiceImpl.scala b/src/main/scala/ru/otus/sc/countdown/service/impl/CountdownServiceImpl.scala index 8c8282e..b150dbf 100644 --- a/src/main/scala/ru/otus/sc/countdown/service/impl/CountdownServiceImpl.scala +++ b/src/main/scala/ru/otus/sc/countdown/service/impl/CountdownServiceImpl.scala @@ -1,20 +1,58 @@ package ru.otus.sc.countdown.service.impl import ru.otus.sc.countdown.dao.CountdownDao -import ru.otus.sc.countdown.model -import ru.otus.sc.countdown.model.{CountdownClearRequest, CountdownDoneResponse, CountdownRequest, CountdownResponse} +import ru.otus.sc.countdown.model.{Countdown, CreateCountdownResponse, _} import ru.otus.sc.countdown.service.CountdownService class CountdownServiceImpl(dao: CountdownDao) extends CountdownService { - def countdown(request: CountdownRequest): CountdownResponse = { - // Can not use pattern matching here, so have to use ugly .isInstanceOf .asInstanceOf :( - if (request.isInstanceOf[CountdownClearRequest]) - makeAnswer(dao.clearCountdown(request.asInstanceOf[CountdownClearRequest].initValue)) - else makeAnswer(dao.getCountdown) - } + def createCountdown(request: CreateCountdownRequest): CreateCountdownResponse = + request.countdown match { + case countdown @ Countdown.Done(_) => + CreateCountdownResponse.Created(dao.createCountdown(countdown)) + case countdown @ Countdown.Tick(_, _, value) => + // it can return CreateCountdownResponse.Error + // but in current CountdownDao implementation there is not option for this + // so emulate it with countdown.value set less than 1 + if (value < 1L) + CreateCountdownResponse.Error("Countdown initial value has to be more or equal 1") + else + CreateCountdownResponse.Created(dao.createCountdown(countdown)) + } + def deleteCountdown(request: DeleteCountdownRequest): DeleteCountdownResponse = + dao + .deleteCountdown(request.id) + .map(DeleteCountdownResponse.Deleted) + .getOrElse(DeleteCountdownResponse.NotFound(request.id)) + + def updateCountdown(request: UpdateCountdownRequest): UpdateCountdownResponse = + request.countdown match { + // Done can not be updated anymore + case Countdown.Done(_) => UpdateCountdownResponse.CanNotUpdateDone + case countdown @ Countdown.Tick(Some(id), _, _) => + dao + .updateCountdown(countdown) + .map(UpdateCountdownResponse.Updated) + .getOrElse(UpdateCountdownResponse.NotFound(id)) + case Countdown.Tick(None, _, _) => UpdateCountdownResponse.ErrorWrongId + } + + def getCountdown(request: GetCountdownRequest): GetCountdownResponse = + dao + .getCountdown(request.id) + .map(GetCountdownResponse.Found) + .getOrElse(GetCountdownResponse.NotFound(request.id)) - private def makeAnswer(countdown: String): CountdownResponse = { - if (countdown.toLong <= 0) CountdownDoneResponse(countdown) - else model.CountdownCountResponse(countdown) + def findCountdowns(request: FindCountdownsRequest): FindCountdownsResponse = { + val found = request match { + case FindCountdownsRequest.ByValue(value, predicate) => + dao.findCountdownsByValue(value, predicate) + case FindCountdownsRequest.ByUpdater(updater) => + dao.findCountdownsByUpdater(updater) + case FindCountdownsRequest.AllDone() => + dao.findAllDone + case FindCountdownsRequest.AllNonDone() => + dao.findAllNonDone + } + FindCountdownsResponse.Result(found) } } diff --git a/src/main/scala/ru/otus/sc/counter/dao/CounterDao.scala b/src/main/scala/ru/otus/sc/counter/dao/CounterDao.scala index e88d142..422782e 100644 --- a/src/main/scala/ru/otus/sc/counter/dao/CounterDao.scala +++ b/src/main/scala/ru/otus/sc/counter/dao/CounterDao.scala @@ -1,6 +1,14 @@ package ru.otus.sc.counter.dao +import ru.otus.sc.counter.model.Counter +import ru.otus.sc.counter.model.Counter.{CounterId, CounterTime, CounterValue} +import ru.otus.sc.counter.model.FindCountersRequest.{CompareTimes, CompareValues} + trait CounterDao { - def getCount: String - def clearCount: String + def createCounter(counter: Counter): Counter + def deleteCounter(id: CounterId): Option[Counter] + def getCounter(id: CounterId): Option[Counter] + def updateCounter(id: CounterId): Option[Counter] + def findCountersByValue(value: CounterValue, predicate: CompareValues): Seq[Counter] + def findCountersByTimestamp(timestamp: CounterTime, predicate: CompareTimes): Seq[Counter] } diff --git a/src/main/scala/ru/otus/sc/counter/dao/impl/CounterDaoImpl.scala b/src/main/scala/ru/otus/sc/counter/dao/impl/CounterDaoImpl.scala index b6d1419..7fa7ff6 100644 --- a/src/main/scala/ru/otus/sc/counter/dao/impl/CounterDaoImpl.scala +++ b/src/main/scala/ru/otus/sc/counter/dao/impl/CounterDaoImpl.scala @@ -1,17 +1,51 @@ package ru.otus.sc.counter.dao.impl +import java.time.LocalDateTime +import java.util.UUID + import ru.otus.sc.counter.dao.CounterDao +import ru.otus.sc.counter.model.Counter +import ru.otus.sc.counter.model.Counter.{CounterId, CounterTime, CounterValue} +import ru.otus.sc.counter.model.FindCountersRequest.{CompareTimes, CompareValues} class CounterDaoImpl extends CounterDao { - private var count: Long = 0 + private var counters = Map[CounterId, Counter]() - def clearCount: String = { - count = 0 - getCount + def createCounter(counter: Counter): Counter = { + val newId = UUID.randomUUID() + val newTime = LocalDateTime.now() + val newCounter = counter.copy(id = Some(newId), timestamp = Some(newTime)) + counters += (newId -> newCounter) + newCounter } - def getCount: String = { - count += 1 - count.toString - } + def deleteCounter(id: CounterId): Option[Counter] = + counters.get(id) match { + case deletedCounter @ Some(_) => + counters -= id + deletedCounter + case None => None + } + + def getCounter(id: CounterId): Option[Counter] = counters.get(id) + + def updateCounter(id: CounterId): Option[Counter] = + counters.get(id) match { + case Some(counter) => + val newTime = LocalDateTime.now() + val newCounter = counter.copy(timestamp = Some(newTime), value = counter.value + 1L) + counters += (id -> newCounter) + Some(newCounter) + case None => None + } + + def findCountersByValue(value: CounterValue, predicate: CompareValues): Seq[Counter] = + counters.values.filter(counter => predicate(value, counter.value)).toVector + + def findCountersByTimestamp(timestamp: CounterTime, predicate: CompareTimes): Seq[Counter] = + counters.values.collect { counter => + counter.timestamp match { + case Some(time) if predicate(timestamp, time) => counter + } + }.toVector } diff --git a/src/main/scala/ru/otus/sc/counter/model/Counter.scala b/src/main/scala/ru/otus/sc/counter/model/Counter.scala new file mode 100644 index 0000000..e07d44e --- /dev/null +++ b/src/main/scala/ru/otus/sc/counter/model/Counter.scala @@ -0,0 +1,13 @@ +package ru.otus.sc.counter.model + +import java.time.LocalDateTime +import java.util.UUID + +import ru.otus.sc.counter.model.Counter.{CounterId, CounterTime, CounterValue} + +case class Counter(id: Option[CounterId], timestamp: Option[CounterTime], value: CounterValue = 1L) +object Counter { + type CounterId = UUID + type CounterValue = Long + type CounterTime = LocalDateTime +} diff --git a/src/main/scala/ru/otus/sc/counter/model/CounterRequest.scala b/src/main/scala/ru/otus/sc/counter/model/CounterRequest.scala deleted file mode 100644 index 291ff23..0000000 --- a/src/main/scala/ru/otus/sc/counter/model/CounterRequest.scala +++ /dev/null @@ -1,3 +0,0 @@ -package ru.otus.sc.counter.model - -case class CounterRequest(clear: Boolean = false, initialValue: Long = 0) diff --git a/src/main/scala/ru/otus/sc/counter/model/CounterResponse.scala b/src/main/scala/ru/otus/sc/counter/model/CounterResponse.scala deleted file mode 100644 index 0ced945..0000000 --- a/src/main/scala/ru/otus/sc/counter/model/CounterResponse.scala +++ /dev/null @@ -1,3 +0,0 @@ -package ru.otus.sc.counter.model - -case class CounterResponse(count: String) diff --git a/src/main/scala/ru/otus/sc/counter/model/CreateCounter.scala b/src/main/scala/ru/otus/sc/counter/model/CreateCounter.scala new file mode 100644 index 0000000..220709f --- /dev/null +++ b/src/main/scala/ru/otus/sc/counter/model/CreateCounter.scala @@ -0,0 +1,9 @@ +package ru.otus.sc.counter.model + +case class CreateCounterRequest(counter: Counter) + +sealed trait CreateCounterResponse +object CreateCounterResponse { + case class Created(counter: Counter) extends CreateCounterResponse + case class Error(error: String) extends CreateCounterResponse +} diff --git a/src/main/scala/ru/otus/sc/counter/model/DeleteCounter.scala b/src/main/scala/ru/otus/sc/counter/model/DeleteCounter.scala new file mode 100644 index 0000000..e9d8997 --- /dev/null +++ b/src/main/scala/ru/otus/sc/counter/model/DeleteCounter.scala @@ -0,0 +1,11 @@ +package ru.otus.sc.counter.model + +import ru.otus.sc.counter.model.Counter.CounterId + +case class DeleteCounterRequest(id: CounterId) + +sealed trait DeleteCounterResponse +object DeleteCounterResponse { + case class Deleted(counter: Counter) extends DeleteCounterResponse + case class NotFound(id: CounterId) extends DeleteCounterResponse +} diff --git a/src/main/scala/ru/otus/sc/counter/model/FindCounters.scala b/src/main/scala/ru/otus/sc/counter/model/FindCounters.scala new file mode 100644 index 0000000..193a6ff --- /dev/null +++ b/src/main/scala/ru/otus/sc/counter/model/FindCounters.scala @@ -0,0 +1,17 @@ +package ru.otus.sc.counter.model + +import ru.otus.sc.counter.model.Counter.{CounterTime, CounterValue} + +sealed trait FindCountersRequest +object FindCountersRequest { + type CompareValues = (CounterValue, CounterValue) => Boolean + type CompareTimes = (CounterTime, CounterTime) => Boolean + case class ByValue(value: CounterValue, predicate: CompareValues) extends FindCountersRequest + case class ByTimestamp(timestamp: CounterTime, predicate: CompareTimes) + extends FindCountersRequest +} + +sealed trait FindCountersResponse +object FindCountersResponse { + case class Result(counters: Seq[Counter]) extends FindCountersResponse +} diff --git a/src/main/scala/ru/otus/sc/counter/model/GetCounter.scala b/src/main/scala/ru/otus/sc/counter/model/GetCounter.scala new file mode 100644 index 0000000..608c58a --- /dev/null +++ b/src/main/scala/ru/otus/sc/counter/model/GetCounter.scala @@ -0,0 +1,11 @@ +package ru.otus.sc.counter.model + +import ru.otus.sc.counter.model.Counter.CounterId + +case class GetCounterRequest(id: CounterId) + +sealed trait GetCounterResponse +object GetCounterResponse { + case class Found(counter: Counter) extends GetCounterResponse + case class NotFound(id: CounterId) extends GetCounterResponse +} diff --git a/src/main/scala/ru/otus/sc/counter/model/UpdateCounter.scala b/src/main/scala/ru/otus/sc/counter/model/UpdateCounter.scala new file mode 100644 index 0000000..6afd138 --- /dev/null +++ b/src/main/scala/ru/otus/sc/counter/model/UpdateCounter.scala @@ -0,0 +1,11 @@ +package ru.otus.sc.counter.model + +import ru.otus.sc.counter.model.Counter.CounterId + +case class UpdateCounterRequest(id: CounterId) + +sealed trait UpdateCounterResponse +object UpdateCounterResponse { + case class Updated(counter: Counter) extends UpdateCounterResponse + case class NotFound(id: CounterId) extends UpdateCounterResponse +} diff --git a/src/main/scala/ru/otus/sc/counter/service/CounterService.scala b/src/main/scala/ru/otus/sc/counter/service/CounterService.scala index a9cf3f1..e41bed5 100644 --- a/src/main/scala/ru/otus/sc/counter/service/CounterService.scala +++ b/src/main/scala/ru/otus/sc/counter/service/CounterService.scala @@ -1,7 +1,11 @@ package ru.otus.sc.counter.service -import ru.otus.sc.counter.model.{CounterRequest, CounterResponse} +import ru.otus.sc.counter.model._ trait CounterService { - def getCount(request: CounterRequest): CounterResponse + def createCounter(request: CreateCounterRequest): CreateCounterResponse + def deleteCounter(request: DeleteCounterRequest): DeleteCounterResponse + def updateCounter(request: UpdateCounterRequest): UpdateCounterResponse + def getCounter(request: GetCounterRequest): GetCounterResponse + def findCounters(request: FindCountersRequest): FindCountersResponse } diff --git a/src/main/scala/ru/otus/sc/counter/service/impl/CounterServiceImpl.scala b/src/main/scala/ru/otus/sc/counter/service/impl/CounterServiceImpl.scala index d36dbab..af6630a 100644 --- a/src/main/scala/ru/otus/sc/counter/service/impl/CounterServiceImpl.scala +++ b/src/main/scala/ru/otus/sc/counter/service/impl/CounterServiceImpl.scala @@ -1,13 +1,43 @@ package ru.otus.sc.counter.service.impl import ru.otus.sc.counter.dao.CounterDao -import ru.otus.sc.counter.model.{CounterRequest, CounterResponse} +import ru.otus.sc.counter.model._ import ru.otus.sc.counter.service.CounterService class CounterServiceImpl(dao: CounterDao) extends CounterService { - def getCount(request: CounterRequest): CounterResponse = { - if (!request.clear) CounterResponse(dao.getCount) - else CounterResponse(dao.clearCount) + def createCounter(request: CreateCounterRequest): CreateCounterResponse = { + // it can return CreateCounterResponse.Error + // but in current CounterDao implementation there is not option for this + // so emulate it with counter.value set less than 1 + if (request.counter.value < 1L) + CreateCounterResponse.Error("Counter initial value has to be more or equal 1") + CreateCounterResponse.Created(dao.createCounter(request.counter)) } + def deleteCounter(request: DeleteCounterRequest): DeleteCounterResponse = + dao + .deleteCounter(request.id) + .map(DeleteCounterResponse.Deleted) + .getOrElse(DeleteCounterResponse.NotFound(request.id)) + def updateCounter(request: UpdateCounterRequest): UpdateCounterResponse = + dao + .updateCounter(request.id) + .map(UpdateCounterResponse.Updated) + .getOrElse(UpdateCounterResponse.NotFound(request.id)) + + def getCounter(request: GetCounterRequest): GetCounterResponse = + dao + .getCounter(request.id) + .map(GetCounterResponse.Found) + .getOrElse(GetCounterResponse.NotFound(request.id)) + + def findCounters(request: FindCountersRequest): FindCountersResponse = { + val found = request match { + case FindCountersRequest.ByValue(value, predicate) => + dao.findCountersByValue(value, predicate) + case FindCountersRequest.ByTimestamp(value, predicate) => + dao.findCountersByTimestamp(value, predicate) + } + FindCountersResponse.Result(found) + } } diff --git a/src/main/scala/ru/otus/sc/echo/dao/impl/EchoDaoImpl.scala b/src/main/scala/ru/otus/sc/echo/dao/impl/EchoDaoImpl.scala index b70820a..ccfdc9e 100644 --- a/src/main/scala/ru/otus/sc/echo/dao/impl/EchoDaoImpl.scala +++ b/src/main/scala/ru/otus/sc/echo/dao/impl/EchoDaoImpl.scala @@ -2,19 +2,11 @@ package ru.otus.sc.echo.dao.impl import ru.otus.sc.echo.dao.EchoDao -import scala.annotation.tailrec - class EchoDaoImpl extends EchoDao { - val echoPrefix: String = "Echo reply:" + val echoPrefix: String = "Echo reply: " def getResponse(request: String, repeatNum: Int): String = { - s"$echoPrefix ${multiplyResponse(request)()(repeatNum)}" - } - - // Can not use collections, so use recursion - @tailrec - private def multiplyResponse(response: String)(acc: String = "")(numResponse: Int): String = { - if (numResponse <= 0) acc.trim - else multiplyResponse(response)(s"$response $acc")(numResponse - 1) + // use collection for making request + List.fill(repeatNum)(request).mkString(echoPrefix, " ", "") } } diff --git a/src/main/scala/ru/otus/sc/echo/model/EchoResponse.scala b/src/main/scala/ru/otus/sc/echo/model/EchoResponse.scala index 2fe893c..0d36726 100644 --- a/src/main/scala/ru/otus/sc/echo/model/EchoResponse.scala +++ b/src/main/scala/ru/otus/sc/echo/model/EchoResponse.scala @@ -1,12 +1,7 @@ package ru.otus.sc.echo.model -sealed trait EchoResponse { - def isValid: Boolean -} - -case class EchoAnswerResponse(answer: String) extends EchoResponse { - def isValid = true -} -case class EchoErrorResponse(error: String) extends EchoResponse { - def isValid = false +sealed trait EchoResponse +object EchoResponse { + case class Answer(answer: String) extends EchoResponse + case class Error(error: String) extends EchoResponse } diff --git a/src/main/scala/ru/otus/sc/echo/service/impl/EchoServiceImpl.scala b/src/main/scala/ru/otus/sc/echo/service/impl/EchoServiceImpl.scala index 4740824..e1607e8 100644 --- a/src/main/scala/ru/otus/sc/echo/service/impl/EchoServiceImpl.scala +++ b/src/main/scala/ru/otus/sc/echo/service/impl/EchoServiceImpl.scala @@ -1,13 +1,13 @@ package ru.otus.sc.echo.service.impl import ru.otus.sc.echo.dao.EchoDao -import ru.otus.sc.echo.model.{EchoAnswerResponse, EchoErrorResponse, EchoRequest, EchoResponse} +import ru.otus.sc.echo.model.{EchoRequest, EchoResponse} import ru.otus.sc.echo.service.EchoService class EchoServiceImpl(dao: EchoDao) extends EchoService { def echo(request: EchoRequest): EchoResponse = if (request.repeatNum > 0 & request.repeatNum <= 5) - EchoAnswerResponse(s"${dao.getResponse(request.echoRequest, request.repeatNum)}") - else EchoErrorResponse("Number of replies has to be from 1 to 5") + EchoResponse.Answer(s"${dao.getResponse(request.echoRequest, request.repeatNum)}") + else EchoResponse.Error("Number of replies has to be from 1 to 5") } diff --git a/src/main/scala/ru/otus/sc/greet/model/GreetRequest.scala b/src/main/scala/ru/otus/sc/greet/model/GreetRequest.scala index 3115535..83f7f4e 100644 --- a/src/main/scala/ru/otus/sc/greet/model/GreetRequest.scala +++ b/src/main/scala/ru/otus/sc/greet/model/GreetRequest.scala @@ -1,3 +1,5 @@ package ru.otus.sc.greet.model -case class GreetRequest(name: String, isHuman: Boolean = true) +case class GreetRequest[T](name: T, isHuman: Boolean = true)(implicit conv: T => String) { + def extractName(): String = conv(name) +} diff --git a/src/main/scala/ru/otus/sc/greet/service/GreetingService.scala b/src/main/scala/ru/otus/sc/greet/service/GreetingService.scala index 2975165..675f4ac 100644 --- a/src/main/scala/ru/otus/sc/greet/service/GreetingService.scala +++ b/src/main/scala/ru/otus/sc/greet/service/GreetingService.scala @@ -2,6 +2,6 @@ package ru.otus.sc.greet.service import ru.otus.sc.greet.model.{GreetRequest, GreetResponse} -trait GreetingService { - def greet(request: GreetRequest): GreetResponse +trait GreetingService[T] { + def greet(request: GreetRequest[T]): GreetResponse } diff --git a/src/main/scala/ru/otus/sc/greet/service/impl/GreetingServiceImpl.scala b/src/main/scala/ru/otus/sc/greet/service/impl/GreetingServiceImpl.scala index fe63a3c..f39c540 100644 --- a/src/main/scala/ru/otus/sc/greet/service/impl/GreetingServiceImpl.scala +++ b/src/main/scala/ru/otus/sc/greet/service/impl/GreetingServiceImpl.scala @@ -4,9 +4,9 @@ import ru.otus.sc.greet.dao.GreetingDao import ru.otus.sc.greet.model.{GreetRequest, GreetResponse} import ru.otus.sc.greet.service.GreetingService -class GreetingServiceImpl(dao: GreetingDao) extends GreetingService { - def greet(request: GreetRequest): GreetResponse = +class GreetingServiceImpl[T](dao: GreetingDao) extends GreetingService[T] { + def greet(request: GreetRequest[T]): GreetResponse = if (request.isHuman) - GreetResponse(s"${dao.greetingPrefix} ${request.name} ${dao.greetingPostfix}") + GreetResponse(s"${dao.greetingPrefix} ${request.extractName()} ${dao.greetingPostfix}") else GreetResponse("AAAAAAAAAA!!!!!!") } diff --git a/src/main/scala/ru/otus/sc/reverse/dao/impl/ReverseDaoImpl.scala b/src/main/scala/ru/otus/sc/reverse/dao/impl/ReverseDaoImpl.scala index 5c1befa..f5d479a 100644 --- a/src/main/scala/ru/otus/sc/reverse/dao/impl/ReverseDaoImpl.scala +++ b/src/main/scala/ru/otus/sc/reverse/dao/impl/ReverseDaoImpl.scala @@ -2,18 +2,7 @@ package ru.otus.sc.reverse.dao.impl import ru.otus.sc.reverse.dao.ReverseDao -import scala.annotation.tailrec - class ReverseDaoImpl extends ReverseDao { - def reverse(word: String): String = { - if (word.length < 2) word - else reverseWord(word)()(word.length - 1) - } - - // Can not use .reverse, so use recursion - @tailrec - private def reverseWord(source: String)(acc: String = "")(position: Int): String = { - if (position < 0) acc - else reverseWord(source)(s"$acc${source.charAt(position)}")(position - 1) - } + // use string collection method + def reverse(word: String): String = word.reverse } diff --git a/src/main/scala/ru/otus/sc/storage/dao/StorageDao.scala b/src/main/scala/ru/otus/sc/storage/dao/StorageDao.scala index 2f30c99..d0cf3e3 100644 --- a/src/main/scala/ru/otus/sc/storage/dao/StorageDao.scala +++ b/src/main/scala/ru/otus/sc/storage/dao/StorageDao.scala @@ -1,5 +1,12 @@ package ru.otus.sc.storage.dao -trait StorageDao { - def get(key: String): Option[String] +import ru.otus.sc.storage.model.StorageEntry + +trait StorageDao[K, V] { + def createStorage(entry: StorageEntry[K, V]): Option[StorageEntry[K, V]] + def getStorage(key: K): Option[StorageEntry[K, V]] + def deleteStorage(key: K): Option[StorageEntry[K, V]] + def updateStorage(entry: StorageEntry[K, V]): Option[StorageEntry[K, V]] + def findByValue(value: V): Seq[StorageEntry[K, V]] + def findValuesByPredicate(predicate: V => Boolean): Seq[StorageEntry[K, V]] } diff --git a/src/main/scala/ru/otus/sc/storage/dao/impl/StorageDaoImpl.scala b/src/main/scala/ru/otus/sc/storage/dao/impl/StorageDaoImpl.scala index b6d3354..7ab3c53 100644 --- a/src/main/scala/ru/otus/sc/storage/dao/impl/StorageDaoImpl.scala +++ b/src/main/scala/ru/otus/sc/storage/dao/impl/StorageDaoImpl.scala @@ -1,13 +1,45 @@ package ru.otus.sc.storage.dao.impl import ru.otus.sc.storage.dao.StorageDao +import ru.otus.sc.storage.model.StorageEntry -class StorageDaoImpl extends StorageDao { - // Can not use collections, so use if/else - def get(key: String): Option[String] = { - if (key == "one") Some("1") - else if (key == "two") Some("2") - else if (key == "three") Some("3") - else None - } +class StorageDaoImpl[K, V] extends StorageDao[K, V] { + private var storages: Map[K, V] = Map.empty + + def createStorage(entry: StorageEntry[K, V]): Option[StorageEntry[K, V]] = + storages.get(entry.key) match { + case Some(_) => None + case None => + storages += (entry.key -> entry.value) + Some(entry) + } + + def getStorage(key: K): Option[StorageEntry[K, V]] = + storages.get(key).map(value => StorageEntry(key, value)) + + def deleteStorage(key: K): Option[StorageEntry[K, V]] = + for { + value <- storages.get(key) + } yield { + storages -= key + StorageEntry(key, value) + } + + def updateStorage(entry: StorageEntry[K, V]): Option[StorageEntry[K, V]] = + for { + _ <- storages.get(entry.key) + } yield { + storages += (entry.key -> entry.value) + entry + } + + def findByValue(value: V): Seq[StorageEntry[K, V]] = + storages.collect { + case (k, v) if v == value => StorageEntry[K, V](k, v) + }.toVector + + def findValuesByPredicate(predicate: V => Boolean): Seq[StorageEntry[K, V]] = + storages.collect { + case (k, v) if predicate(v) => StorageEntry[K, V](k, v) + }.toVector } diff --git a/src/main/scala/ru/otus/sc/storage/model/CreateStorage.scala b/src/main/scala/ru/otus/sc/storage/model/CreateStorage.scala new file mode 100644 index 0000000..e010e82 --- /dev/null +++ b/src/main/scala/ru/otus/sc/storage/model/CreateStorage.scala @@ -0,0 +1,9 @@ +package ru.otus.sc.storage.model + +case class CreateStorageRequest[K, V](entry: StorageEntry[K, V]) + +trait CreateStorageResponse[K, V] +object CreateStorageResponse { + case class Created[K, V](entry: StorageEntry[K, V]) extends CreateStorageResponse[K, V] + case class ErrorKeyExists[K, V](key: K) extends CreateStorageResponse[K, V] +} diff --git a/src/main/scala/ru/otus/sc/storage/model/DeleteStorage.scala b/src/main/scala/ru/otus/sc/storage/model/DeleteStorage.scala new file mode 100644 index 0000000..3b93119 --- /dev/null +++ b/src/main/scala/ru/otus/sc/storage/model/DeleteStorage.scala @@ -0,0 +1,9 @@ +package ru.otus.sc.storage.model + +case class DeleteStorageRequest[K, _](key: K) + +trait DeleteStorageResponse[K, V] +object DeleteStorageResponse { + case class Deleted[K, V](entry: StorageEntry[K, V]) extends DeleteStorageResponse[K, V] + case class NotFound[K, V](key: K) extends DeleteStorageResponse[K, V] +} diff --git a/src/main/scala/ru/otus/sc/storage/model/FindStorages.scala b/src/main/scala/ru/otus/sc/storage/model/FindStorages.scala new file mode 100644 index 0000000..d556924 --- /dev/null +++ b/src/main/scala/ru/otus/sc/storage/model/FindStorages.scala @@ -0,0 +1,12 @@ +package ru.otus.sc.storage.model + +trait FindStoragesRequest[K, V] +object FindStoragesRequest { + case class ByValue[K, V](value: V) extends FindStoragesRequest[K, V] + case class ValuesByPredicate[K, V](predicate: V => Boolean) extends FindStoragesRequest[K, V] +} + +trait FindStoragesResponse[K, V] +object FindStoragesResponse { + case class Result[K, V](entries: Seq[StorageEntry[K, V]]) extends FindStoragesResponse[K, V] +} diff --git a/src/main/scala/ru/otus/sc/storage/model/GetStorage.scala b/src/main/scala/ru/otus/sc/storage/model/GetStorage.scala new file mode 100644 index 0000000..4dfbc11 --- /dev/null +++ b/src/main/scala/ru/otus/sc/storage/model/GetStorage.scala @@ -0,0 +1,9 @@ +package ru.otus.sc.storage.model + +case class GetStorageRequest[K, V](key: K) + +trait GetStorageResponse[K, V] +object GetStorageResponse { + case class Found[K, V](entry: StorageEntry[K, V]) extends GetStorageResponse[K, V] + case class NotFound[K, V](key: K) extends GetStorageResponse[K, V] +} diff --git a/src/main/scala/ru/otus/sc/storage/model/StorageEntry.scala b/src/main/scala/ru/otus/sc/storage/model/StorageEntry.scala new file mode 100644 index 0000000..52f8528 --- /dev/null +++ b/src/main/scala/ru/otus/sc/storage/model/StorageEntry.scala @@ -0,0 +1,3 @@ +package ru.otus.sc.storage.model + +case class StorageEntry[K, V](key: K, value: V) diff --git a/src/main/scala/ru/otus/sc/storage/model/StorageRequest.scala b/src/main/scala/ru/otus/sc/storage/model/StorageRequest.scala deleted file mode 100644 index d67619a..0000000 --- a/src/main/scala/ru/otus/sc/storage/model/StorageRequest.scala +++ /dev/null @@ -1,3 +0,0 @@ -package ru.otus.sc.storage.model - -case class StorageRequest(storageKey: String) diff --git a/src/main/scala/ru/otus/sc/storage/model/StorageResponse.scala b/src/main/scala/ru/otus/sc/storage/model/StorageResponse.scala deleted file mode 100644 index b272d2d..0000000 --- a/src/main/scala/ru/otus/sc/storage/model/StorageResponse.scala +++ /dev/null @@ -1,3 +0,0 @@ -package ru.otus.sc.storage.model - -case class StorageResponse(storageValue: String) diff --git a/src/main/scala/ru/otus/sc/storage/model/UpdateStorage.scala b/src/main/scala/ru/otus/sc/storage/model/UpdateStorage.scala new file mode 100644 index 0000000..1072bd8 --- /dev/null +++ b/src/main/scala/ru/otus/sc/storage/model/UpdateStorage.scala @@ -0,0 +1,9 @@ +package ru.otus.sc.storage.model + +case class UpdateStorageRequest[K, V](entry: StorageEntry[K, V]) + +trait UpdateStorageResponse[K, V] +object UpdateStorageResponse { + case class Updated[K, V](entry: StorageEntry[K, V]) extends UpdateStorageResponse[K, V] + case class NotFound[K, V](key: K) extends UpdateStorageResponse[K, V] +} diff --git a/src/main/scala/ru/otus/sc/storage/service/StorageService.scala b/src/main/scala/ru/otus/sc/storage/service/StorageService.scala index 2e0d1e5..d33d3f1 100644 --- a/src/main/scala/ru/otus/sc/storage/service/StorageService.scala +++ b/src/main/scala/ru/otus/sc/storage/service/StorageService.scala @@ -1,7 +1,11 @@ package ru.otus.sc.storage.service -import ru.otus.sc.storage.model.{StorageRequest, StorageResponse} +import ru.otus.sc.storage.model._ -trait StorageService { - def get(request: StorageRequest): Option[StorageResponse] +trait StorageService[K, V] { + def createStorage(request: CreateStorageRequest[K, V]): CreateStorageResponse[K, V] + def getStorage(request: GetStorageRequest[K, V]): GetStorageResponse[K, V] + def deleteStorage(request: DeleteStorageRequest[K, V]): DeleteStorageResponse[K, V] + def updateStorage(request: UpdateStorageRequest[K, V]): UpdateStorageResponse[K, V] + def findStorages(request: FindStoragesRequest[K, V]): FindStoragesResponse[K, V] } diff --git a/src/main/scala/ru/otus/sc/storage/service/impl/StorageServiceImpl.scala b/src/main/scala/ru/otus/sc/storage/service/impl/StorageServiceImpl.scala index 318bb26..61d5809 100644 --- a/src/main/scala/ru/otus/sc/storage/service/impl/StorageServiceImpl.scala +++ b/src/main/scala/ru/otus/sc/storage/service/impl/StorageServiceImpl.scala @@ -1,15 +1,39 @@ package ru.otus.sc.storage.service.impl import ru.otus.sc.storage.dao.StorageDao -import ru.otus.sc.storage.model.{StorageRequest, StorageResponse} +import ru.otus.sc.storage.model._ import ru.otus.sc.storage.service.StorageService -class StorageServiceImpl(dao: StorageDao) extends StorageService { +class StorageServiceImpl[K, V](dao: StorageDao[K, V]) extends StorageService[K, V] { + def createStorage(request: CreateStorageRequest[K, V]): CreateStorageResponse[K, V] = + dao + .createStorage(request.entry) + .map(CreateStorageResponse.Created[K, V]) + .getOrElse(CreateStorageResponse.ErrorKeyExists[K, V](request.entry.key)) - def get(request: StorageRequest): Option[StorageResponse] = { - val value = dao.get(request.storageKey) - // Can not use pattern matching or .map, so use if/else - if (value.nonEmpty) Some(StorageResponse(value.get)) - else None + def getStorage(request: GetStorageRequest[K, V]): GetStorageResponse[K, V] = + dao + .getStorage(request.key) + .map(GetStorageResponse.Found[K, V]) + .getOrElse(GetStorageResponse.NotFound[K, V](request.key)) + + def deleteStorage(request: DeleteStorageRequest[K, V]): DeleteStorageResponse[K, V] = + dao + .deleteStorage(request.key) + .map(DeleteStorageResponse.Deleted[K, V]) + .getOrElse(DeleteStorageResponse.NotFound[K, V](request.key)) + + def updateStorage(request: UpdateStorageRequest[K, V]): UpdateStorageResponse[K, V] = + dao + .updateStorage(request.entry) + .map(UpdateStorageResponse.Updated[K, V]) + .getOrElse(UpdateStorageResponse.NotFound[K, V](request.entry.key)) + + def findStorages(request: FindStoragesRequest[K, V]): FindStoragesResponse[K, V] = { + val found = request match { + case FindStoragesRequest.ByValue(value) => dao.findByValue(value) + case FindStoragesRequest.ValuesByPredicate(predicate) => dao.findValuesByPredicate(predicate) + } + FindStoragesResponse.Result[K, V](found) } } diff --git a/src/main/scala/ru/otus/sc/user/dao/UserDao.scala b/src/main/scala/ru/otus/sc/user/dao/UserDao.scala new file mode 100644 index 0000000..48a3c30 --- /dev/null +++ b/src/main/scala/ru/otus/sc/user/dao/UserDao.scala @@ -0,0 +1,16 @@ +package ru.otus.sc.user.dao + +import ru.otus.sc.user.model.User +import ru.otus.sc.user.model.User.UniqueUserId +import ru.otus.sc.user.model.UserTag.UserTagId + +trait UserDao { + def createUser(user: User): User + def getUser(uniqueUserId: UniqueUserId): Option[User] + def updateUser(user: User): Option[User] + def deleteUser(uniqueUserId: UniqueUserId): Option[User] + def findByFirstName(firstName: String): Seq[User] + def findByLastName(lastName: String): Seq[User] + def findByTag(tag: UserTagId): Seq[User] + def getAllUsers: Seq[User] +} diff --git a/src/main/scala/ru/otus/sc/user/dao/UserTagDao.scala b/src/main/scala/ru/otus/sc/user/dao/UserTagDao.scala new file mode 100644 index 0000000..606b9bc --- /dev/null +++ b/src/main/scala/ru/otus/sc/user/dao/UserTagDao.scala @@ -0,0 +1,17 @@ +package ru.otus.sc.user.dao + +import ru.otus.sc.user.model.User.UniqueUserId +import ru.otus.sc.user.model.UserTag +import ru.otus.sc.user.model.UserTag.UserTagId + +trait UserTagDao { + def createTag(tag: UserTag): UserTag + def getTag(tagId: UserTagId): Option[UserTag] + def updateTag(tag: UserTag): Option[UserTag] + def deleteTag(tagId: UserTagId): Option[UserTag] + def findByName(tagName: String): Seq[UserTag] + def getUsersByTag(tagId: UserTagId): Seq[UniqueUserId] + def getAllTags: Seq[UserTag] + def tagUser(tagId: UserTagId, uniqueUserId: UniqueUserId): Option[(UserTagId, UniqueUserId)] + def untagUser(tagId: UserTagId, uniqueUserId: UniqueUserId): Option[(UserTagId, UniqueUserId)] +} diff --git a/src/main/scala/ru/otus/sc/user/dao/impl/UserDaoImpl.scala b/src/main/scala/ru/otus/sc/user/dao/impl/UserDaoImpl.scala new file mode 100644 index 0000000..d3165d0 --- /dev/null +++ b/src/main/scala/ru/otus/sc/user/dao/impl/UserDaoImpl.scala @@ -0,0 +1,63 @@ +package ru.otus.sc.user.dao.impl + +import java.util.UUID + +import ru.otus.sc.user.dao.UserDao +import ru.otus.sc.user.model.User +import ru.otus.sc.user.model.User.UniqueUserId +import ru.otus.sc.user.model.UserTag.UserTagId + +class UserDaoImpl extends UserDao { + + private var users: Map[UniqueUserId, User] = Map.empty + // emulate pk for users + private var id: Long = 1 + + def createUser(user: User): User = { + val newUniqueId = UUID.randomUUID + val newUser = user.copy(id = Some(id), uniqueId = Some(newUniqueId)) + id += 1 + users += (newUniqueId -> newUser) + newUser + } + + def getUser(uniqueUserId: UniqueUserId): Option[User] = users.get(uniqueUserId) + + def updateUser(user: User): Option[User] = + for { + uniqueId <- user.uniqueId + existUser <- users.get(uniqueId) + // only uniqueId is required for user, id might not exist + // if id exists then it has to match existing user id + if user.id.isEmpty || existUser.id == user.id + } yield { + val newUser = if (user.id.isEmpty) user.copy(id = existUser.id) else user + users += (uniqueId -> newUser) + newUser + } + + def deleteUser(uniqueUserId: UniqueUserId): Option[User] = + users.get(uniqueUserId) match { + case deletedUser @ Some(_) => + users -= uniqueUserId + deletedUser + case _ => None + } + + def findByFirstName(firstName: String): Seq[User] = + users.values.filter(_.name.firstName == firstName).toVector + + def findByLastName(lastName: String): Seq[User] = + users.values + .collect(user => + user.name.lastName match { + case Some(userLastName) if userLastName == lastName => user + } + ) + .toVector + + def findByTag(tag: UserTagId): Seq[User] = + users.values.filter(_.tags.contains(tag)).toVector + + def getAllUsers: Seq[User] = users.values.toVector +} diff --git a/src/main/scala/ru/otus/sc/user/dao/impl/UserTagDaoImpl.scala b/src/main/scala/ru/otus/sc/user/dao/impl/UserTagDaoImpl.scala new file mode 100644 index 0000000..d1eb990 --- /dev/null +++ b/src/main/scala/ru/otus/sc/user/dao/impl/UserTagDaoImpl.scala @@ -0,0 +1,72 @@ +package ru.otus.sc.user.dao.impl + +import ru.otus.sc.user.dao.UserTagDao +import ru.otus.sc.user.model.User.UniqueUserId +import ru.otus.sc.user.model.UserTag +import ru.otus.sc.user.model.UserTag.UserTagId + +class UserTagDaoImpl extends UserTagDao { + + private var tags: Map[UserTagId, UserTag] = Map.empty + // tag -> users mapping, due to tag does not keep associated users itself + private var taggedUsers: Map[UserTagId, Set[UniqueUserId]] = Map.empty + // emulate pk for tags + private var id: Long = 1 + + override def createTag(tag: UserTag): UserTag = { + val newUserTag = tag.copy(id = Some(id)) + tags += (id -> newUserTag) + taggedUsers += (id -> Set.empty) + id += 1 + newUserTag + } + + override def getTag(tagId: UserTagId): Option[UserTag] = tags.get(tagId) + + override def updateTag(tag: UserTag): Option[UserTag] = + for { + tagId <- tag.id + _ <- tags.get(tagId) + } yield { + tags += (tagId -> tag) + tag + } + + override def deleteTag(tagId: UserTagId): Option[UserTag] = + tags.get(tagId) match { + case deletedTag @ Some(_) => + tags -= tagId + deletedTag + case None => None + } + + override def findByName(tagName: String): Seq[UserTag] = + tags.values.filter(_.tagName == tagName).toVector + + override def getUsersByTag(tagId: UserTagId): Seq[UniqueUserId] = + taggedUsers.getOrElse(tagId, Set[UniqueUserId]()).toVector + + override def getAllTags: Seq[UserTag] = tags.values.toVector + + override def tagUser( + tagId: UserTagId, + uniqueUserId: UniqueUserId + ): Option[(UserTagId, UniqueUserId)] = + taggedUsers.get(tagId) match { + case Some(users) => + taggedUsers += (tagId -> (users + uniqueUserId)) + Some((tagId, uniqueUserId)) + case None => None + } + + override def untagUser( + tagId: UserTagId, + uniqueUserId: UniqueUserId + ): Option[(UserTagId, UniqueUserId)] = + taggedUsers.get(tagId) match { + case Some(users) => + taggedUsers += (tagId -> (users - uniqueUserId)) + Some((tagId, uniqueUserId)) + case None => None + } +} diff --git a/src/main/scala/ru/otus/sc/user/implicits/UserNameImplicits.scala b/src/main/scala/ru/otus/sc/user/implicits/UserNameImplicits.scala new file mode 100644 index 0000000..b817d05 --- /dev/null +++ b/src/main/scala/ru/otus/sc/user/implicits/UserNameImplicits.scala @@ -0,0 +1,16 @@ +package ru.otus.sc.user.implicits + +import ru.otus.sc.user.model.User + +object UserNameImplicits { + implicit def userNameToString(user: User): String = + List( + user.name.title, + Some(user.name.firstName), + user.name.middleName, + user.name.patronymicName, + user.name.lastName + ) + .collect { case Some(name) => name } + .mkString(" ") +} diff --git a/src/main/scala/ru/otus/sc/user/model/CreateTag.scala b/src/main/scala/ru/otus/sc/user/model/CreateTag.scala new file mode 100644 index 0000000..61a407e --- /dev/null +++ b/src/main/scala/ru/otus/sc/user/model/CreateTag.scala @@ -0,0 +1,9 @@ +package ru.otus.sc.user.model + +case class CreateUserTagRequest(tag: UserTag) + +sealed trait CreateUserTagResponse +object CreateUserTagResponse { + case class Created(tag: UserTag) extends CreateUserTagResponse + case class Error(error: String) extends CreateUserTagResponse +} diff --git a/src/main/scala/ru/otus/sc/user/model/CreateUser.scala b/src/main/scala/ru/otus/sc/user/model/CreateUser.scala new file mode 100644 index 0000000..e21327b --- /dev/null +++ b/src/main/scala/ru/otus/sc/user/model/CreateUser.scala @@ -0,0 +1,9 @@ +package ru.otus.sc.user.model + +case class CreateUserRequest(user: User) + +sealed trait CreateUserResponse +object CreateUserResponse { + case class Created(user: User) extends CreateUserResponse + case class Error(error: String) extends CreateUserResponse +} diff --git a/src/main/scala/ru/otus/sc/user/model/DeleteTag.scala b/src/main/scala/ru/otus/sc/user/model/DeleteTag.scala new file mode 100644 index 0000000..ff40123 --- /dev/null +++ b/src/main/scala/ru/otus/sc/user/model/DeleteTag.scala @@ -0,0 +1,11 @@ +package ru.otus.sc.user.model + +import ru.otus.sc.user.model.UserTag.UserTagId + +case class DeleteUserTagRequest(tagId: UserTagId) + +sealed trait DeleteUserTagResponse +object DeleteUserTagResponse { + case class Deleted(tag: UserTag) extends DeleteUserTagResponse + case class NotFound(tagId: UserTagId) extends DeleteUserTagResponse +} diff --git a/src/main/scala/ru/otus/sc/user/model/DeleteUser.scala b/src/main/scala/ru/otus/sc/user/model/DeleteUser.scala new file mode 100644 index 0000000..1be5da1 --- /dev/null +++ b/src/main/scala/ru/otus/sc/user/model/DeleteUser.scala @@ -0,0 +1,11 @@ +package ru.otus.sc.user.model + +import ru.otus.sc.user.model.User.UniqueUserId + +case class DeleteUserRequest(uniqueId: UniqueUserId) + +sealed trait DeleteUserResponse +object DeleteUserResponse { + case class Deleted(user: User) extends DeleteUserResponse + case class NotFound(uniqueId: UniqueUserId) extends DeleteUserResponse +} diff --git a/src/main/scala/ru/otus/sc/user/model/FindTags.scala b/src/main/scala/ru/otus/sc/user/model/FindTags.scala new file mode 100644 index 0000000..56d322b --- /dev/null +++ b/src/main/scala/ru/otus/sc/user/model/FindTags.scala @@ -0,0 +1,17 @@ +package ru.otus.sc.user.model + +import ru.otus.sc.user.model.User.UniqueUserId +import ru.otus.sc.user.model.UserTag.UserTagId + +sealed trait FindUserTagsRequest +object FindUserTagsRequest { + case class ByName(tagName: String) extends FindUserTagsRequest + case class UsersByTag(tag: UserTagId) extends FindUserTagsRequest + case class GetAll() extends FindUserTagsRequest +} + +sealed trait FindUserTagsResponse +object FindUserTagsResponse { + case class TagsResult(tags: Seq[UserTag]) extends FindUserTagsResponse + case class UsersResult(users: Seq[UniqueUserId]) extends FindUserTagsResponse +} diff --git a/src/main/scala/ru/otus/sc/user/model/FindUsers.scala b/src/main/scala/ru/otus/sc/user/model/FindUsers.scala new file mode 100644 index 0000000..71efc78 --- /dev/null +++ b/src/main/scala/ru/otus/sc/user/model/FindUsers.scala @@ -0,0 +1,16 @@ +package ru.otus.sc.user.model + +import ru.otus.sc.user.model.UserTag.UserTagId + +sealed trait FindUsersRequest +object FindUsersRequest { + case class ByFirstName(firstName: String) extends FindUsersRequest + case class ByLastName(lastName: String) extends FindUsersRequest + case class ByTag(tag: UserTagId) extends FindUsersRequest + case class GetAll() extends FindUsersRequest +} + +sealed trait FindUsersResponse +object FindUsersResponse { + case class Result(users: Seq[User]) extends FindUsersResponse +} diff --git a/src/main/scala/ru/otus/sc/user/model/GetTag.scala b/src/main/scala/ru/otus/sc/user/model/GetTag.scala new file mode 100644 index 0000000..266da46 --- /dev/null +++ b/src/main/scala/ru/otus/sc/user/model/GetTag.scala @@ -0,0 +1,11 @@ +package ru.otus.sc.user.model + +import ru.otus.sc.user.model.UserTag.UserTagId + +case class GetUserTagRequest(tagId: UserTagId) + +sealed trait GetUserTagResponse +object GetUserTagResponse { + case class Found(tag: UserTag) extends GetUserTagResponse + case class NotFound(tagId: UserTagId) extends GetUserTagResponse +} diff --git a/src/main/scala/ru/otus/sc/user/model/GetUser.scala b/src/main/scala/ru/otus/sc/user/model/GetUser.scala new file mode 100644 index 0000000..045ee00 --- /dev/null +++ b/src/main/scala/ru/otus/sc/user/model/GetUser.scala @@ -0,0 +1,12 @@ +package ru.otus.sc.user.model + +import ru.otus.sc.user.model.User.UniqueUserId + +case class GetUserRequest(uniqueId: UniqueUserId) + +sealed trait GetUserResponse +object GetUserResponse { + case class Found(user: User) extends GetUserResponse + case class NotFound(uniqueId: UniqueUserId) extends GetUserResponse + +} diff --git a/src/main/scala/ru/otus/sc/user/model/UpdateTag.scala b/src/main/scala/ru/otus/sc/user/model/UpdateTag.scala new file mode 100644 index 0000000..716131b --- /dev/null +++ b/src/main/scala/ru/otus/sc/user/model/UpdateTag.scala @@ -0,0 +1,12 @@ +package ru.otus.sc.user.model + +import ru.otus.sc.user.model.UserTag.UserTagId + +case class UpdateUserTagRequest(tag: UserTag) + +sealed trait UpdateUserTagResponse +object UpdateUserTagResponse { + case class Updated(tag: UserTag) extends UpdateUserTagResponse + case class NotFound(tagId: UserTagId) extends UpdateUserTagResponse + case object ErrorNoTagId extends UpdateUserTagResponse +} diff --git a/src/main/scala/ru/otus/sc/user/model/UpdateTagOnUsers.scala b/src/main/scala/ru/otus/sc/user/model/UpdateTagOnUsers.scala new file mode 100644 index 0000000..3bfb28c --- /dev/null +++ b/src/main/scala/ru/otus/sc/user/model/UpdateTagOnUsers.scala @@ -0,0 +1,21 @@ +package ru.otus.sc.user.model + +import ru.otus.sc.user.model.User.UniqueUserId +import ru.otus.sc.user.model.UserTag.UserTagId + +case class UpdateTagUserRequest(tagId: UserTagId, uniqueUserId: UniqueUserId) + +sealed trait UpdateTagUserResponse +object UpdateTagUserResponse { + case class TaggedUser(tagId: UserTagId, uniqueUserId: UniqueUserId) extends UpdateTagUserResponse + case class NotFoundTag(tagId: UserTagId) extends UpdateTagUserResponse +} + +case class UpdateUntagUserRequest(tagId: UserTagId, uniqueUserId: UniqueUserId) + +sealed trait UpdateUntagUserResponse +object UpdateUntagUserResponse { + case class UntaggedUser(tagId: UserTagId, uniqueUserId: UniqueUserId) + extends UpdateUntagUserResponse + case class NotFoundTag(tagId: UserTagId) extends UpdateUntagUserResponse +} diff --git a/src/main/scala/ru/otus/sc/user/model/UpdateUser.scala b/src/main/scala/ru/otus/sc/user/model/UpdateUser.scala new file mode 100644 index 0000000..fca1437 --- /dev/null +++ b/src/main/scala/ru/otus/sc/user/model/UpdateUser.scala @@ -0,0 +1,13 @@ +package ru.otus.sc.user.model + +import ru.otus.sc.user.model.User.UniqueUserId + +case class UpdateUserRequest(user: User) + +sealed trait UpdateUserResponse +object UpdateUserResponse { + case class Updated(user: User) extends UpdateUserResponse + case class NotFound(uniqueId: UniqueUserId) extends UpdateUserResponse + case object ErrorNoUniqueId extends UpdateUserResponse + case object ErrorWrongId extends UpdateUserResponse +} diff --git a/src/main/scala/ru/otus/sc/user/model/User.scala b/src/main/scala/ru/otus/sc/user/model/User.scala new file mode 100644 index 0000000..f2aaef9 --- /dev/null +++ b/src/main/scala/ru/otus/sc/user/model/User.scala @@ -0,0 +1,28 @@ +package ru.otus.sc.user.model + +import java.util.UUID + +import ru.otus.sc.user.model.User.UniqueUserId +import ru.otus.sc.user.model.UserTag.UserTagId + +case class UserName( + firstName: String, + lastName: Option[String], + middleName: Option[String], + patronymicName: Option[String], + title: Option[String] +) + +case class User( + // id related to DB table + id: Option[Long], + // unique id for whole system + uniqueId: Option[UniqueUserId], + name: UserName, + age: Option[Int], + tags: Set[UserTagId] +) + +object User { + type UniqueUserId = UUID +} diff --git a/src/main/scala/ru/otus/sc/user/model/UserTag.scala b/src/main/scala/ru/otus/sc/user/model/UserTag.scala new file mode 100644 index 0000000..70ee550 --- /dev/null +++ b/src/main/scala/ru/otus/sc/user/model/UserTag.scala @@ -0,0 +1,9 @@ +package ru.otus.sc.user.model + +import ru.otus.sc.user.model.UserTag.UserTagId + +case class UserTag(id: Option[UserTagId], tagName: String) + +object UserTag { + type UserTagId = Long +} diff --git a/src/main/scala/ru/otus/sc/user/service/UserService.scala b/src/main/scala/ru/otus/sc/user/service/UserService.scala new file mode 100644 index 0000000..de1f57d --- /dev/null +++ b/src/main/scala/ru/otus/sc/user/service/UserService.scala @@ -0,0 +1,11 @@ +package ru.otus.sc.user.service + +import ru.otus.sc.user.model._ + +trait UserService { + def createUser(request: CreateUserRequest): CreateUserResponse + def getUser(request: GetUserRequest): GetUserResponse + def updateUser(request: UpdateUserRequest): UpdateUserResponse + def deleteUser(request: DeleteUserRequest): DeleteUserResponse + def findUsers(request: FindUsersRequest): FindUsersResponse +} diff --git a/src/main/scala/ru/otus/sc/user/service/UserTagService.scala b/src/main/scala/ru/otus/sc/user/service/UserTagService.scala new file mode 100644 index 0000000..2f85871 --- /dev/null +++ b/src/main/scala/ru/otus/sc/user/service/UserTagService.scala @@ -0,0 +1,13 @@ +package ru.otus.sc.user.service + +import ru.otus.sc.user.model._ + +trait UserTagService { + def createUserTag(request: CreateUserTagRequest): CreateUserTagResponse + def getUserTag(request: GetUserTagRequest): GetUserTagResponse + def updateUserTag(request: UpdateUserTagRequest): UpdateUserTagResponse + def deleteUserTag(request: DeleteUserTagRequest): DeleteUserTagResponse + def findUserTags(request: FindUserTagsRequest): FindUserTagsResponse + def tagUser(request: UpdateTagUserRequest): UpdateTagUserResponse + def untagUser(request: UpdateUntagUserRequest): UpdateUntagUserResponse +} diff --git a/src/main/scala/ru/otus/sc/user/service/impl/UserServiceImpl.scala b/src/main/scala/ru/otus/sc/user/service/impl/UserServiceImpl.scala new file mode 100644 index 0000000..fbcaa27 --- /dev/null +++ b/src/main/scala/ru/otus/sc/user/service/impl/UserServiceImpl.scala @@ -0,0 +1,56 @@ +package ru.otus.sc.user.service.impl + +import ru.otus.sc.user.dao.UserDao +import ru.otus.sc.user.model._ +import ru.otus.sc.user.service.UserService + +class UserServiceImpl(dao: UserDao) extends UserService { + def createUser(request: CreateUserRequest): CreateUserResponse = { + // it can return CreateUserResponse.Error + // but in current UserDao implementation there is not option for this + // so emulate it with palindrome or empty name + val userName = request.user.name.firstName + if (userName == userName.reverse) + CreateUserResponse.Error("palindrome or empty name is not allowed in current DB version") + else + CreateUserResponse.Created(dao.createUser(request.user)) + } + + def getUser(request: GetUserRequest): GetUserResponse = + dao.getUser(request.uniqueId) match { + case Some(user) => GetUserResponse.Found(user) + case None => GetUserResponse.NotFound(request.uniqueId) + } + + def updateUser(request: UpdateUserRequest): UpdateUserResponse = + request.user.uniqueId match { + case Some(uniqueId) => + dao.updateUser(request.user) match { + case Some(user) => UpdateUserResponse.Updated(user) + case None => UpdateUserResponse.NotFound(uniqueId) + } + case None => + if (request.user.id.isEmpty) + UpdateUserResponse.ErrorWrongId + else + UpdateUserResponse.ErrorNoUniqueId + } + + def deleteUser(request: DeleteUserRequest): DeleteUserResponse = + dao + .deleteUser(request.uniqueId) + .map(DeleteUserResponse.Deleted) + .getOrElse(DeleteUserResponse.NotFound(request.uniqueId)) + + def findUsers(request: FindUsersRequest): FindUsersResponse = + request match { + case FindUsersRequest.ByFirstName(firstName) => + FindUsersResponse.Result(dao.findByFirstName(firstName)) + case FindUsersRequest.ByLastName(lastName) => + FindUsersResponse.Result(dao.findByLastName(lastName)) + case FindUsersRequest.ByTag(tagId) => + FindUsersResponse.Result(dao.findByTag(tagId)) + case FindUsersRequest.GetAll() => + FindUsersResponse.Result(dao.getAllUsers) + } +} diff --git a/src/main/scala/ru/otus/sc/user/service/impl/UserTagServiceImpl.scala b/src/main/scala/ru/otus/sc/user/service/impl/UserTagServiceImpl.scala new file mode 100644 index 0000000..9091c19 --- /dev/null +++ b/src/main/scala/ru/otus/sc/user/service/impl/UserTagServiceImpl.scala @@ -0,0 +1,64 @@ +package ru.otus.sc.user.service.impl + +import ru.otus.sc.user.dao.UserTagDao +import ru.otus.sc.user.model._ +import ru.otus.sc.user.service.UserTagService + +class UserTagServiceImpl(dao: UserTagDao) extends UserTagService { + def createUserTag(request: CreateUserTagRequest): CreateUserTagResponse = { + // it can return CreateUserTagResponse.Error + // but in current UserTagDao implementation there is not option for this + // so emulate it with palindrome or empty tag name + val tagName = request.tag.tagName + if (tagName == tagName.reverse) + CreateUserTagResponse.Error( + "palindrome or empty tag name is not allowed in current DB version" + ) + else + CreateUserTagResponse.Created(dao.createTag(request.tag)) + } + + def getUserTag(request: GetUserTagRequest): GetUserTagResponse = + dao.getTag(request.tagId) match { + case Some(user) => GetUserTagResponse.Found(user) + case None => GetUserTagResponse.NotFound(request.tagId) + } + + def updateUserTag(request: UpdateUserTagRequest): UpdateUserTagResponse = + request.tag.id match { + case Some(tagId) => + dao.updateTag(request.tag) match { + case Some(tag) => UpdateUserTagResponse.Updated(tag) + case None => UpdateUserTagResponse.NotFound(tagId) + } + case None => UpdateUserTagResponse.ErrorNoTagId + } + + def deleteUserTag(request: DeleteUserTagRequest): DeleteUserTagResponse = + dao + .deleteTag(request.tagId) + .map(DeleteUserTagResponse.Deleted) + .getOrElse(DeleteUserTagResponse.NotFound(request.tagId)) + + def findUserTags(request: FindUserTagsRequest): FindUserTagsResponse = + request match { + case FindUserTagsRequest.ByName(tagName) => + FindUserTagsResponse.TagsResult(dao.findByName(tagName)) + case FindUserTagsRequest.UsersByTag(tagId) => + FindUserTagsResponse.UsersResult(dao.getUsersByTag(tagId)) + case FindUserTagsRequest.GetAll() => + FindUserTagsResponse.TagsResult(dao.getAllTags) + } + + override def tagUser(request: UpdateTagUserRequest): UpdateTagUserResponse = + dao + .tagUser(request.tagId, request.uniqueUserId) + .map(result => UpdateTagUserResponse.TaggedUser(result._1, result._2)) + .getOrElse(UpdateTagUserResponse.NotFoundTag(request.tagId)) + + def untagUser(request: UpdateUntagUserRequest): UpdateUntagUserResponse = + dao + .untagUser(request.tagId, request.uniqueUserId) + .map(result => UpdateUntagUserResponse.UntaggedUser(result._1, result._2)) + .getOrElse(UpdateUntagUserResponse.NotFoundTag(request.tagId)) +}