diff --git a/bot/src/main/resources/application.conf b/bot/src/main/resources/application.conf new file mode 100644 index 0000000..ba776fa --- /dev/null +++ b/bot/src/main/resources/application.conf @@ -0,0 +1,4 @@ +ofs { + base-url = "http://localhost:8080" + request-timeout = 20s +} diff --git a/bot/src/main/scala/com/github/mmvpm/bot/Config.scala b/bot/src/main/scala/com/github/mmvpm/bot/Config.scala new file mode 100644 index 0000000..ed415cc --- /dev/null +++ b/bot/src/main/scala/com/github/mmvpm/bot/Config.scala @@ -0,0 +1,7 @@ +package com.github.mmvpm.bot + +import scala.concurrent.duration.FiniteDuration + +case class Config(ofs: OfsConfig) + +case class OfsConfig(baseUrl: String, requestTimeout: FiniteDuration) diff --git a/bot/src/main/scala/com/github/mmvpm/bot/Main.scala b/bot/src/main/scala/com/github/mmvpm/bot/Main.scala index aabf3ad..32f261e 100644 --- a/bot/src/main/scala/com/github/mmvpm/bot/Main.scala +++ b/bot/src/main/scala/com/github/mmvpm/bot/Main.scala @@ -1,26 +1,41 @@ package com.github.mmvpm.bot +import cats.effect.std.Random import cats.effect.{IO, IOApp} +import com.github.mmvpm.bot.client.ofs.{OfsClient, OfsClientSttp} +import com.github.mmvpm.bot.manager.ofs.{OfsManager, OfsManagerImpl} import com.github.mmvpm.bot.model.MessageID -import com.github.mmvpm.bot.render.RendererImpl -import com.github.mmvpm.bot.state.{State, StateManagerImpl, StorageImpl} +import com.github.mmvpm.bot.render.{Renderer, RendererImpl} +import com.github.mmvpm.bot.state.{State, StateManager, StateManagerImpl, StorageImpl} import com.github.mmvpm.bot.util.ResourceUtils +import com.github.mmvpm.model.Session import org.asynchttpclient.Dsl.asyncHttpClient +import pureconfig.ConfigSource +import pureconfig.generic.auto._ import sttp.client3.asynchttpclient.cats.AsyncHttpClientCatsBackend object Main extends IOApp.Simple { override def run: IO[Unit] = for { - _ <- IO.println("Starting telegram bot...") + random <- Random.scalaUtilRandom[IO] token = ResourceUtils.readTelegramToken() + config = ConfigSource.default.loadOrThrow[Config] + sttpBackend = AsyncHttpClientCatsBackend.usingClient[IO](asyncHttpClient) - renderer = new RendererImpl - manager = new StateManagerImpl[IO] + stateStorage = new StorageImpl[State](State.Started) + sessionStorage = new StorageImpl[Option[Session]](None) lastMessageStorage = new StorageImpl[Option[MessageID]](None) - bot = new OfferServiceBot[IO](token, sttpBackend, renderer, manager, stateStorage, lastMessageStorage) + + ofsClient: OfsClient[IO] = new OfsClientSttp[IO](config.ofs, sttpBackend) + ofsManager: OfsManager[IO] = new OfsManagerImpl[IO](ofsClient, sessionStorage, random) + + renderer: Renderer = new RendererImpl + manager: StateManager[IO] = new StateManagerImpl[IO](ofsManager) + + bot = new OfferServiceBot[IO](token, sttpBackend, renderer, manager, stateStorage, lastMessageStorage, ofsManager) _ <- bot.startPolling() } yield () diff --git a/bot/src/main/scala/com/github/mmvpm/bot/OfferServiceBot.scala b/bot/src/main/scala/com/github/mmvpm/bot/OfferServiceBot.scala index 31e2f35..9cf607e 100644 --- a/bot/src/main/scala/com/github/mmvpm/bot/OfferServiceBot.scala +++ b/bot/src/main/scala/com/github/mmvpm/bot/OfferServiceBot.scala @@ -7,6 +7,9 @@ import com.bot4s.telegram.api.declarative.{Callbacks, Command, Commands} import com.bot4s.telegram.cats.{Polling, TelegramBot} import com.bot4s.telegram.methods.{EditMessageText, SendDice, SendMessage} import com.bot4s.telegram.models._ +import com.github.mmvpm.bot.manager.ofs.OfsManager +import com.github.mmvpm.bot.manager.ofs.error.OfsError.InvalidSession +import com.github.mmvpm.bot.manager.ofs.response.LoginOrRegisterResponse import com.github.mmvpm.bot.model.MessageID import com.github.mmvpm.bot.render.Renderer import com.github.mmvpm.bot.state.{State, StateManager, Storage} @@ -17,9 +20,10 @@ class OfferServiceBot[F[_]: Concurrent]( token: String, sttpBackend: SttpBackend[F, Any], renderer: Renderer, - manager: StateManager[F], + stateManager: StateManager[F], stateStorage: Storage[State], - lastMessageStorage: Storage[Option[MessageID]] + lastMessageStorage: Storage[Option[MessageID]], + ofsManager: OfsManager[F] ) extends TelegramBot[F](token, sttpBackend) with Polling[F] with Commands[F] @@ -50,11 +54,25 @@ class OfferServiceBot[F[_]: Concurrent]( request(SendDice(message.chat.id)).void private def start(implicit message: Message): F[Unit] = - requestLogged(renderer.render(stateStorage.get, lastMessageStorage.get)).void + ofsManager.loginOrRegister.value.flatMap { + case Left(InvalidSession) => + val state: State = EnterPassword + stateStorage.set(state) + requestLogged(renderer.render(state, None)) + case Left(error) => + reply(s"Ошибка: ${error.details}. Попробуйте команду /start ещё раз").void + case Right(response) => + val state = response match { + case LoginOrRegisterResponse.LoggedIn(name) => LoggedIn(name) + case LoginOrRegisterResponse.Registered(password) => Registered(password) + } + val saveMessageId = !state.isInstanceOf[Registered] // to save password in the chat + requestLogged(renderer.render(state, None), saveMessageId) + } private def replyResolved(tag: String)(implicit message: Message): F[Unit] = for { - nextState <- manager.getNextState(tag, stateStorage.get) + nextState <- stateManager.getNextState(tag, stateStorage.get) _ = stateStorage.set(withoutError(nextState)) reply = renderer.render(nextState, lastMessageStorage.get) _ <- requestLogged(reply) @@ -71,7 +89,7 @@ class OfferServiceBot[F[_]: Concurrent]( case _ => state } - private def requestLogged(req: Either[EditMessageText, SendMessage]): F[Unit] = + private def requestLogged(req: Either[EditMessageText, SendMessage], saveMessageId: Boolean = true): F[Unit] = req match { case Left(toEdit) => for { @@ -81,7 +99,7 @@ class OfferServiceBot[F[_]: Concurrent]( case Right(toSend) => for { sent <- request(toSend) - _ = lastMessageStorage.set(Some(sent.messageId))(sent) + _ = if (saveMessageId) lastMessageStorage.set(Some(sent.messageId))(sent) _ = println(s"Sent $sent") } yield () } diff --git a/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/OfsClient.scala b/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/OfsClient.scala new file mode 100644 index 0000000..ed9b238 --- /dev/null +++ b/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/OfsClient.scala @@ -0,0 +1,15 @@ +package com.github.mmvpm.bot.client.ofs + +import cats.data.EitherT +import com.github.mmvpm.model.{OfferDescription, Session} +import com.github.mmvpm.bot.client.ofs.error.OfsClientError +import com.github.mmvpm.bot.client.ofs.response.{CreateOfferResponse, SignInResponse, SignUpResponse, UserIdResponse} + +trait OfsClient[F[_]] { + + def signUp(name: String, login: String, password: String): EitherT[F, OfsClientError, SignUpResponse] + def signIn(login: String, password: String): EitherT[F, OfsClientError, SignInResponse] + def whoami(session: Session): EitherT[F, OfsClientError, UserIdResponse] + + def createOffer(session: Session, description: OfferDescription): EitherT[F, OfsClientError, CreateOfferResponse] +} diff --git a/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/OfsClientSttp.scala b/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/OfsClientSttp.scala new file mode 100644 index 0000000..5cbab5b --- /dev/null +++ b/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/OfsClientSttp.scala @@ -0,0 +1,86 @@ +package com.github.mmvpm.bot.client.ofs + +import cats.data.EitherT +import cats.MonadThrow +import cats.implicits.{toBifunctorOps, toFunctorOps} +import com.github.mmvpm.model.{OfferDescription, Session} +import com.github.mmvpm.bot.OfsConfig +import com.github.mmvpm.bot.client.ofs.request._ +import com.github.mmvpm.bot.client.ofs.response._ +import com.github.mmvpm.bot.client.ofs.error._ +import io.circe.generic.auto._ +import io.circe.Error +import sttp.client3._ +import sttp.client3.circe._ + +class OfsClientSttp[F[_]: MonadThrow](ofsConfig: OfsConfig, sttpBackend: SttpBackend[F, Any]) extends OfsClient[F] { + + def signUp(name: String, login: String, password: String): EitherT[F, OfsClientError, SignUpResponse] = { + val requestUri = uri"${ofsConfig.baseUrl}/api/v1/auth/sign-up" + val request = SignUpRequest(name, login, password) + + val response = basicRequest + .post(requestUri) + .body(request) + .response(asJsonEither[OfsApiClientError, SignUpResponse]) + .readTimeout(ofsConfig.requestTimeout) + .send(sttpBackend) + .map(_.body.leftMap(parseFailure)) + + EitherT(response) + } + + override def signIn(login: String, password: String): EitherT[F, OfsClientError, SignInResponse] = { + val requestUri = uri"${ofsConfig.baseUrl}/api/v1/auth/sign-in" + + val response = basicRequest + .post(requestUri) + .auth + .basic(login, password) + .response(asJsonEither[OfsApiClientError, SignInResponse]) + .readTimeout(ofsConfig.requestTimeout) + .send(sttpBackend) + .map(_.body.leftMap(parseFailure)) + + EitherT(response) + } + + override def createOffer( + session: Session, + description: OfferDescription + ): EitherT[F, OfsClientError, CreateOfferResponse] = { + val requestUri = uri"${ofsConfig.baseUrl}/api/v1/offer" + val request = CreateOfferRequest(description) + + val response = basicRequest + .post(requestUri) + .body(request) + .header(SessionHeaderName, session.toString) + .response(asJsonEither[OfsApiClientError, CreateOfferResponse]) + .readTimeout(ofsConfig.requestTimeout) + .send(sttpBackend) + .map(_.body.leftMap(parseFailure)) + + EitherT(response) + } + + def whoami(session: Session): EitherT[F, OfsClientError, UserIdResponse] = { + val requestUri = uri"${ofsConfig.baseUrl}/api/v1/auth/whoami/$session" + + val response = basicRequest + .get(requestUri) + .response(asJsonEither[OfsApiClientError, UserIdResponse]) + .readTimeout(ofsConfig.requestTimeout) + .send(sttpBackend) + .map(_.body.leftMap(parseFailure)) + + EitherT(response) + } + + // internal + + private def parseFailure: ResponseException[OfsApiClientError, Error] => OfsClientError = { + case HttpError(body, _) => body + case error => OfsUnknownClientError(error.getMessage) + } +} diff --git a/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/error/package.scala b/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/error/package.scala new file mode 100644 index 0000000..8a1d659 --- /dev/null +++ b/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/error/package.scala @@ -0,0 +1,12 @@ +package com.github.mmvpm.bot.client.ofs + +package object error { + + trait OfsClientError { + def details: String + } + + case class OfsApiClientError(id: String, code: Int, details: String) extends OfsClientError + + case class OfsUnknownClientError(details: String) extends OfsClientError +} diff --git a/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/ofs.scala b/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/ofs.scala new file mode 100644 index 0000000..ad33a68 --- /dev/null +++ b/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/ofs.scala @@ -0,0 +1,5 @@ +package com.github.mmvpm.bot.client + +package object ofs { + val SessionHeaderName = "X-Auth-Session" +} diff --git a/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/request/CreateOfferRequest.scala b/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/request/CreateOfferRequest.scala new file mode 100644 index 0000000..fc070d0 --- /dev/null +++ b/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/request/CreateOfferRequest.scala @@ -0,0 +1,5 @@ +package com.github.mmvpm.bot.client.ofs.request + +import com.github.mmvpm.model.OfferDescription + +case class CreateOfferRequest(description: OfferDescription) diff --git a/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/request/SignUpRequest.scala b/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/request/SignUpRequest.scala new file mode 100644 index 0000000..8f80de8 --- /dev/null +++ b/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/request/SignUpRequest.scala @@ -0,0 +1,3 @@ +package com.github.mmvpm.bot.client.ofs.request + +case class SignUpRequest(name: String, login: String, password: String) diff --git a/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/request/WhoamiRequest.scala b/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/request/WhoamiRequest.scala new file mode 100644 index 0000000..ec08fd6 --- /dev/null +++ b/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/request/WhoamiRequest.scala @@ -0,0 +1,5 @@ +package com.github.mmvpm.bot.client.ofs.request + +import com.github.mmvpm.model.Session + +case class WhoamiRequest(session: Session) diff --git a/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/response/CreateOfferResponse.scala b/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/response/CreateOfferResponse.scala new file mode 100644 index 0000000..5653e42 --- /dev/null +++ b/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/response/CreateOfferResponse.scala @@ -0,0 +1,7 @@ +package com.github.mmvpm.bot.client.ofs.response + +import com.github.mmvpm.model.OfferID + +case class CreateOfferResponse(offer: OfsOffer) + +case class OfsOffer(id: OfferID) // it's not necessary to copy all fields diff --git a/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/response/SignInResponse.scala b/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/response/SignInResponse.scala new file mode 100644 index 0000000..43bcab1 --- /dev/null +++ b/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/response/SignInResponse.scala @@ -0,0 +1,5 @@ +package com.github.mmvpm.bot.client.ofs.response + +import com.github.mmvpm.model.Session + +case class SignInResponse(session: Session) diff --git a/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/response/SignUpResponse.scala b/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/response/SignUpResponse.scala new file mode 100644 index 0000000..0000569 --- /dev/null +++ b/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/response/SignUpResponse.scala @@ -0,0 +1,5 @@ +package com.github.mmvpm.bot.client.ofs.response + +case class SignUpResponse(user: OfsUser) + +case class OfsUser(id: String) // not necessary to copy all fields diff --git a/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/response/UserIdResponse.scala b/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/response/UserIdResponse.scala new file mode 100644 index 0000000..18fb0ad --- /dev/null +++ b/bot/src/main/scala/com/github/mmvpm/bot/client/ofs/response/UserIdResponse.scala @@ -0,0 +1,5 @@ +package com.github.mmvpm.bot.client.ofs.response + +import com.github.mmvpm.model.UserID + +case class UserIdResponse(userId: UserID) diff --git a/bot/src/main/scala/com/github/mmvpm/bot/manager/ofs/OfsManager.scala b/bot/src/main/scala/com/github/mmvpm/bot/manager/ofs/OfsManager.scala new file mode 100644 index 0000000..6607664 --- /dev/null +++ b/bot/src/main/scala/com/github/mmvpm/bot/manager/ofs/OfsManager.scala @@ -0,0 +1,13 @@ +package com.github.mmvpm.bot.manager.ofs + +import cats.data.EitherT +import com.bot4s.telegram.models.{Chat, Message} +import com.github.mmvpm.bot.manager.ofs.error.OfsError +import com.github.mmvpm.bot.manager.ofs.response.LoginOrRegisterResponse +import com.github.mmvpm.model.OfferDescription + +trait OfsManager[F[_]] { + def login(implicit message: Message): EitherT[F, OfsError, LoginOrRegisterResponse.LoggedIn] + def loginOrRegister(implicit message: Message): EitherT[F, OfsError, LoginOrRegisterResponse] + def createOffer(description: OfferDescription)(implicit message: Message): EitherT[F, OfsError, Unit] +} diff --git a/bot/src/main/scala/com/github/mmvpm/bot/manager/ofs/OfsManagerImpl.scala b/bot/src/main/scala/com/github/mmvpm/bot/manager/ofs/OfsManagerImpl.scala new file mode 100644 index 0000000..43b009c --- /dev/null +++ b/bot/src/main/scala/com/github/mmvpm/bot/manager/ofs/OfsManagerImpl.scala @@ -0,0 +1,92 @@ +package com.github.mmvpm.bot.manager.ofs + +import cats.Monad +import cats.data.EitherT +import cats.effect.std.Random +import cats.implicits.{toFunctorOps, toTraverseOps} +import com.bot4s.telegram.models.Message +import com.github.mmvpm.bot.client.ofs.{OfsClient, error} +import com.github.mmvpm.bot.client.ofs.error.OfsApiClientError +import com.github.mmvpm.bot.client.ofs.response.{CreateOfferResponse, UserIdResponse} +import com.github.mmvpm.bot.manager.ofs.OfsManagerImpl._ +import com.github.mmvpm.bot.manager.ofs.error.OfsError +import com.github.mmvpm.bot.manager.ofs.error.OfsError._ +import com.github.mmvpm.bot.manager.ofs.response.LoginOrRegisterResponse +import com.github.mmvpm.bot.manager.ofs.response.LoginOrRegisterResponse._ +import com.github.mmvpm.bot.state.Storage +import com.github.mmvpm.model.{OfferDescription, Session} +import sttp.model.StatusCode + +class OfsManagerImpl[F[_]: Monad](ofsClient: OfsClient[F], sessionStorage: Storage[Option[Session]], random: Random[F]) + extends OfsManager[F] { + + override def login(implicit message: Message): EitherT[F, OfsError, LoggedIn] = + ofsClient + .signIn(getLogin, message.text.get) + .map { response => + sessionStorage.set(Some(response.session)) + LoggedIn(getName) + } + .leftMap(error => OfsSomeError(error.details)) + + override def loginOrRegister(implicit message: Message): EitherT[F, OfsError, LoginOrRegisterResponse] = + sessionStorage.get match { + case None => registerAndSaveSession + case Some(session) => checkSession(session).map(_ => LoggedIn(getName)) + } + + override def createOffer(description: OfferDescription)(implicit message: Message): EitherT[F, OfsError, Unit] = + sessionStorage.get match { + case None => EitherT.leftT(InvalidSession) + case Some(session) => createOffer(session, description).void + } + + // internal + + private def getLogin(implicit message: Message): String = + message.chat.username.getOrElse("@noname") + + private def getName(implicit message: Message): String = + (for { + firstName <- message.chat.firstName + lastName <- message.chat.lastName + name = s"$firstName $lastName" + } yield name).getOrElse("No Name") + + private def generatePassword: F[String] = + (0 until PasswordLength).toList + .traverse(_ => random.nextAlphaNumeric) + .map(_.mkString) + + private def checkSession(session: Session): EitherT[F, OfsError, UserIdResponse] = + ofsClient + .whoami(session) + .leftMap { + case OfsApiClientError(_, _, _) => InvalidSession: OfsError + case error => OfsSomeError(error.details) + } + + private def registerAndSaveSession(implicit message: Message): EitherT[F, OfsError, LoginOrRegisterResponse] = + (for { + password <- EitherT.liftF(generatePassword) + _ <- ofsClient.signUp(getName, getLogin, password) + session <- ofsClient.signIn(getLogin, password).map(_.session) + _ = sessionStorage.set(Some(session)) + } yield Registered(password): LoginOrRegisterResponse) + .leftMap { + case OfsApiClientError(_, _, _) => InvalidSession: OfsError + case error => OfsSomeError(error.details) + } + + private def createOffer(session: Session, description: OfferDescription): EitherT[F, OfsError, CreateOfferResponse] = + ofsClient + .createOffer(session, description) + .leftMap { + case OfsApiClientError(_, StatusCode.Unauthorized.code, _) => InvalidSession: OfsError + case error => OfsSomeError(error.details) + } +} + +object OfsManagerImpl { + private val PasswordLength = 10 +} diff --git a/bot/src/main/scala/com/github/mmvpm/bot/manager/ofs/error/OfsError.scala b/bot/src/main/scala/com/github/mmvpm/bot/manager/ofs/error/OfsError.scala new file mode 100644 index 0000000..94e34a3 --- /dev/null +++ b/bot/src/main/scala/com/github/mmvpm/bot/manager/ofs/error/OfsError.scala @@ -0,0 +1,14 @@ +package com.github.mmvpm.bot.manager.ofs.error + +sealed trait OfsError { + def details: String +} + +object OfsError { + + case object InvalidSession extends OfsError { + override def details: String = "Session is invalid or expired" + } + + case class OfsSomeError(details: String) extends OfsError +} diff --git a/bot/src/main/scala/com/github/mmvpm/bot/manager/ofs/response/LoginOrRegisterResponse.scala b/bot/src/main/scala/com/github/mmvpm/bot/manager/ofs/response/LoginOrRegisterResponse.scala new file mode 100644 index 0000000..539d8ad --- /dev/null +++ b/bot/src/main/scala/com/github/mmvpm/bot/manager/ofs/response/LoginOrRegisterResponse.scala @@ -0,0 +1,8 @@ +package com.github.mmvpm.bot.manager.ofs.response + +sealed trait LoginOrRegisterResponse + +object LoginOrRegisterResponse { + case class LoggedIn(name: String) extends LoginOrRegisterResponse + case class Registered(password: String) extends LoginOrRegisterResponse +} diff --git a/bot/src/main/scala/com/github/mmvpm/bot/model/Draft.scala b/bot/src/main/scala/com/github/mmvpm/bot/model/Draft.scala index e08e219..a635282 100644 --- a/bot/src/main/scala/com/github/mmvpm/bot/model/Draft.scala +++ b/bot/src/main/scala/com/github/mmvpm/bot/model/Draft.scala @@ -1,8 +1,18 @@ package com.github.mmvpm.bot.model +import com.github.mmvpm.model.OfferDescription + case class Draft( name: Option[String] = None, price: Option[Long] = None, description: Option[String] = None, photos: Seq[String] = Seq.empty -) +) { + + def toOfferDescription: Option[OfferDescription] = + for { + name <- name + price <- price + description <- description + } yield OfferDescription(name, price.toInt, description) +} diff --git a/bot/src/main/scala/com/github/mmvpm/bot/state/State.scala b/bot/src/main/scala/com/github/mmvpm/bot/state/State.scala index 54fef24..08331dd 100644 --- a/bot/src/main/scala/com/github/mmvpm/bot/state/State.scala +++ b/bot/src/main/scala/com/github/mmvpm/bot/state/State.scala @@ -14,6 +14,9 @@ object State { // tags + val RegisteredTag = "registered" + val LoggedInTag = "legged-in" + val EnterPasswordTag = "enter-password" val SearchTag: Tag = "search" val ListingTag: Tag = "listing" val OneOfferTag: Tag = "one-offer" @@ -69,6 +72,7 @@ object State { case EditOfferPrice(_) => UpdatedOfferTag case EditOfferDescription(_) => UpdatedOfferTag case AddOfferPhoto(_) => UpdatedOfferTag + case EnterPassword => LoggedInTag case _ => UnknownTag } @@ -82,12 +86,37 @@ object State { val optPrevious: Option[State] = Some(self.previous) } + // sign-in & sign-up + + case class Registered(password: String) extends State with NoPrevious { + override def tag: Tag = RegisteredTag + override def next: Seq[Tag] = Seq(StartedTag) + override def text: String = + s""" + |Вы успешно зарегистрированы на платформе! + | + |Ваш пароль: `$password` + |""".stripMargin + } + + case class LoggedIn(name: String) extends State with NoPrevious { + override def tag: Tag = LoggedInTag + override def next: Seq[Tag] = Seq(StartedTag) + override def text: String = s"Добро пожаловать, $name!" + } + + case object EnterPassword extends State with NoPrevious { + override def tag: Tag = EnterPasswordTag + override def next: Seq[Tag] = Seq.empty + override def text: String = "Пожалуйста, введите пароль, чтобы войти в свой профиль:" + } + // beginning case object Started extends State with NoPrevious { val tag: Tag = StartedTag val next: Seq[Tag] = Seq(SearchTag, CreateOfferNameTag, MyOffersTag) - val text: String = "Как я могу вам помочь?" + val text: String = "Чем я могу вам помочь?" } // search diff --git a/bot/src/main/scala/com/github/mmvpm/bot/state/StateManagerImpl.scala b/bot/src/main/scala/com/github/mmvpm/bot/state/StateManagerImpl.scala index d168ffb..3ef84e0 100644 --- a/bot/src/main/scala/com/github/mmvpm/bot/state/StateManagerImpl.scala +++ b/bot/src/main/scala/com/github/mmvpm/bot/state/StateManagerImpl.scala @@ -1,7 +1,10 @@ package com.github.mmvpm.bot.state import cats.Monad +import cats.implicits.toFlatMapOps import com.bot4s.telegram.models.Message +import com.github.mmvpm.bot.manager.ofs.OfsManager +import com.github.mmvpm.bot.manager.ofs.error.OfsError.InvalidSession import com.github.mmvpm.bot.model.Draft import com.github.mmvpm.bot.state.State.{Listing, _} import com.github.mmvpm.bot.util.StateUtils.StateSyntax @@ -10,7 +13,7 @@ import com.github.mmvpm.model.{Offer, OfferDescription, OfferStatus} import java.util.UUID import scala.util.Random -class StateManagerImpl[F[_]: Monad] extends StateManager[F] { +class StateManagerImpl[F[_]: Monad](ofsManager: OfsManager[F]) extends StateManager[F] { override def getNextState(tag: String, current: State)(implicit message: Message): F[State] = tag match { @@ -32,6 +35,7 @@ class StateManagerImpl[F[_]: Monad] extends StateManager[F] { case DeleteOfferPhotosTag => toDeleteOfferPhotos(current) case UpdatedOfferTag => toUpdatedOffer(current) case DeletedOfferTag => toDeleteOffer(current) + case LoggedInTag => toLoggedIn(current) case BackTag => toBack(current) case StartedTag => toStarted(current) } @@ -110,8 +114,14 @@ class StateManagerImpl[F[_]: Monad] extends StateManager[F] { private def toCreatedOffer(current: State)(implicit message: Message): F[State] = current match { - case CreateOfferPhoto(_, draft) => CreatedOffer(current, draft).pure - case _ => Error(current, "Произошла ошибка! Попробуйте ещё раз").pure + case CreateOfferPhoto(_, draft) if draft.toOfferDescription.nonEmpty => + ofsManager.createOffer(draft.toOfferDescription.get).value.flatMap { + case Right(_) => CreatedOffer(current, draft).pure + case Left(InvalidSession) => EnterPassword.pure + case Left(error) => Error(current, s"Произошла ошибка: ${error.details}. Попробуйте ещё раз").pure + } + case _ => + Error(current, "Произошла ошибка! Попробуйте ещё раз").pure } private def toMyOffers(current: State)(implicit message: Message): F[State] = @@ -193,6 +203,17 @@ class StateManagerImpl[F[_]: Monad] extends StateManager[F] { private def toDeleteOffer(current: State)(implicit message: Message): F[State] = DeletedOffer(current).pure + private def toLoggedIn(current: State)(implicit message: Message): F[State] = + message.text match { + case Some(_) => + ofsManager.login.value.flatMap { + case Left(error) => Error(current, s"Ошибка: ${error.details}").pure + case Right(loggedIn) => LoggedIn(loggedIn.name).pure + } + case None => + Error(current, s"Пожалуйста, введите пароль").pure + } + private def toBack(current: State)(implicit message: Message): F[State] = current match { case DeletedOffer(previous) => diff --git a/build.sbt b/build.sbt index 0fadc74..a0e58c8 100644 --- a/build.sbt +++ b/build.sbt @@ -129,7 +129,7 @@ lazy val common = (project in file("common")) libraryDependencies ++= Seq(cats, logback, apacheCommons).flatten ) -lazy val stub = (project in file("service")) +lazy val service = (project in file("service")) .dependsOn(common) .settings( name := "service", @@ -148,7 +148,8 @@ lazy val bot = (project in file("bot")) name := "bot", libraryDependencies ++= Seq( bot4s, - sttpClient + sttpClient, + pureconfig ).flatten ) @@ -156,4 +157,4 @@ lazy val root = (project in file(".")) .settings( name := "OffersService" ) - .aggregate(common, stub, bot) + .aggregate(common, service, bot)