diff --git a/api/src/main/scala/com/lbs/api/LuxmedApi.scala b/api/src/main/scala/com/lbs/api/LuxmedApi.scala index 235dd9f..689c3cc 100644 --- a/api/src/main/scala/com/lbs/api/LuxmedApi.scala +++ b/api/src/main/scala/com/lbs/api/LuxmedApi.scala @@ -4,18 +4,21 @@ package com.lbs.api import java.time.ZonedDateTime import java.time.format.DateTimeFormatter +import cats.implicits.toFunctorOps import com.lbs.api.ApiResponseMutators._ import com.lbs.api.http._ import com.lbs.api.http.headers._ import com.lbs.api.json.JsonSerializer.extensions._ -import com.lbs.api.json.model._ +import com.lbs.api.json.model.{AvailableTermsResponse, ReservationFilterResponse, ReservedVisitsResponse, VisitsHistoryResponse, _} import scalaj.http.{HttpRequest, HttpResponse} -object LuxmedApi extends ApiBase { +import scala.language.higherKinds + +class LuxmedApi[F[_] : ThrowableMonad] extends ApiBase { private val dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") - def login(username: String, password: String, clientId: String = "iPhone"): Either[Throwable, LoginResponse] = { + def login(username: String, password: String, clientId: String = "iPhone"): F[LoginResponse] = { val request = http("token"). header(`Content-Type`, "application/x-www-form-urlencoded"). header(`x-api-client-identifier`, clientId). @@ -26,7 +29,7 @@ object LuxmedApi extends ApiBase { post[LoginResponse](request) } - def refreshToken(refreshToken: String, clientId: String = "iPhone"): Either[Throwable, LoginResponse] = { + def refreshToken(refreshToken: String, clientId: String = "iPhone"): F[LoginResponse] = { val request = http("token"). header(`Content-Type`, "application/x-www-form-urlencoded"). header(`x-api-client-identifier`, clientId). @@ -37,7 +40,7 @@ object LuxmedApi extends ApiBase { } def reservedVisits(accessToken: String, tokenType: String, fromDate: ZonedDateTime = ZonedDateTime.now(), - toDate: ZonedDateTime = ZonedDateTime.now().plusMonths(3)): Either[Throwable, ReservedVisitsResponse] = { + toDate: ZonedDateTime = ZonedDateTime.now().plusMonths(3)): F[ReservedVisitsResponse] = { val request = http("visits/reserved"). header(`Content-Type`, "application/json"). header(Authorization, s"$tokenType $accessToken"). @@ -47,7 +50,7 @@ object LuxmedApi extends ApiBase { } def visitsHistory(accessToken: String, tokenType: String, fromDate: ZonedDateTime = ZonedDateTime.now().minusYears(1), - toDate: ZonedDateTime = ZonedDateTime.now(), page: Int = 1, pageSize: Int = 100): Either[Throwable, VisitsHistoryResponse] = { + toDate: ZonedDateTime = ZonedDateTime.now(), page: Int = 1, pageSize: Int = 100): F[VisitsHistoryResponse] = { val request = http("visits/history"). header(`Content-Type`, "application/json"). header(Authorization, s"$tokenType $accessToken"). @@ -60,7 +63,7 @@ object LuxmedApi extends ApiBase { def reservationFilter(accessToken: String, tokenType: String, fromDate: ZonedDateTime = ZonedDateTime.now(), toDate: Option[ZonedDateTime] = None, cityId: Option[Long] = None, clinicId: Option[Long] = None, - serviceId: Option[Long] = None): Either[Throwable, ReservationFilterResponse] = { + serviceId: Option[Long] = None): F[ReservationFilterResponse] = { val request = http("visits/available-terms/reservation-filter"). header(`Content-Type`, "application/json"). header(Authorization, s"$tokenType $accessToken"). @@ -74,7 +77,7 @@ object LuxmedApi extends ApiBase { def availableTerms(accessToken: String, tokenType: String, payerId: Long, cityId: Long, clinicId: Option[Long], serviceId: Long, doctorId: Option[Long], fromDate: ZonedDateTime = ZonedDateTime.now(), toDate: Option[ZonedDateTime] = None, timeOfDay: Int = 0, - languageId: Long = 10, findFirstFreeTerm: Boolean = false): Either[Throwable, AvailableTermsResponse] = { + languageId: Long = 10, findFirstFreeTerm: Boolean = false): F[AvailableTermsResponse] = { val request = http("visits/available-terms"). header(`Content-Type`, "application/json"). header(Authorization, s"$tokenType $accessToken"). @@ -91,35 +94,35 @@ object LuxmedApi extends ApiBase { get[AvailableTermsResponse](request).mutate } - def temporaryReservation(accessToken: String, tokenType: String, temporaryReservationRequest: TemporaryReservationRequest): Either[Throwable, TemporaryReservationResponse] = { + def temporaryReservation(accessToken: String, tokenType: String, temporaryReservationRequest: TemporaryReservationRequest): F[TemporaryReservationResponse] = { val request = http("visits/temporary-reservation"). header(`Content-Type`, "application/json"). header(Authorization, s"$tokenType $accessToken") post[TemporaryReservationResponse](request, bodyOpt = Some(temporaryReservationRequest)) } - def deleteTemporaryReservation(accessToken: String, tokenType: String, temporaryReservationId: Long): Either[Throwable, HttpResponse[String]] = { + def deleteTemporaryReservation(accessToken: String, tokenType: String, temporaryReservationId: Long): F[HttpResponse[String]] = { val request = http(s"visits/temporary-reservation/$temporaryReservationId"). header(`Content-Type`, "application/json"). header(Authorization, s"$tokenType $accessToken") delete(request) } - def valuations(accessToken: String, tokenType: String, valuationsRequest: ValuationsRequest): Either[Throwable, ValuationsResponse] = { + def valuations(accessToken: String, tokenType: String, valuationsRequest: ValuationsRequest): F[ValuationsResponse] = { val request = http("visits/available-terms/valuations"). header(`Content-Type`, "application/json"). header(Authorization, s"$tokenType $accessToken") post[ValuationsResponse](request, bodyOpt = Some(valuationsRequest)) } - def reservation(accessToken: String, tokenType: String, reservationRequest: ReservationRequest): Either[Throwable, ReservationResponse] = { + def reservation(accessToken: String, tokenType: String, reservationRequest: ReservationRequest): F[ReservationResponse] = { val request = http("visits/reserved"). header(`Content-Type`, "application/json"). header(Authorization, s"$tokenType $accessToken") post[ReservationResponse](request, bodyOpt = Some(reservationRequest)) } - def deleteReservation(accessToken: String, tokenType: String, reservationId: Long): Either[Throwable, HttpResponse[String]] = { + def deleteReservation(accessToken: String, tokenType: String, reservationId: Long): F[HttpResponse[String]] = { val request = http(s"visits/reserved/$reservationId"). header(`Content-Type`, "application/json"). header(Authorization, s"$tokenType $accessToken") @@ -127,63 +130,63 @@ object LuxmedApi extends ApiBase { } //204 means OK? - def canTermBeChanged(accessToken: String, tokenType: String, reservationId: Long): Either[Throwable, HttpResponse[String]] = { + def canTermBeChanged(accessToken: String, tokenType: String, reservationId: Long): F[HttpResponse[String]] = { val request = http(s"visits/reserved/$reservationId/can-term-be-changed"). header(`Content-Type`, "application/json"). header(Authorization, s"$tokenType $accessToken") - request.toEither + request.invoke } - def detailToChangeTerm(accessToken: String, tokenType: String, reservationId: Long): Either[Throwable, ChangeTermDetailsResponse] = { + def detailToChangeTerm(accessToken: String, tokenType: String, reservationId: Long): F[ChangeTermDetailsResponse] = { val request = http(s"visits/reserved/$reservationId/details-to-change-term"). header(`Content-Type`, "application/json"). header(Authorization, s"$tokenType $accessToken") get[ChangeTermDetailsResponse](request) } - def temporaryReservationToChangeTerm(accessToken: String, tokenType: String, reservationId: Long, temporaryReservationRequest: TemporaryReservationRequest): Either[Throwable, TemporaryReservationResponse] = { + def temporaryReservationToChangeTerm(accessToken: String, tokenType: String, reservationId: Long, temporaryReservationRequest: TemporaryReservationRequest): F[TemporaryReservationResponse] = { val request = http(s"visits/reserved/$reservationId/temporary-reservation-to-change-term"). header(`Content-Type`, "application/json"). header(Authorization, s"$tokenType $accessToken") post[TemporaryReservationResponse](request, bodyOpt = Some(temporaryReservationRequest)) } - def valuationToChangeTerm(accessToken: String, tokenType: String, reservationId: Long, valuationsRequest: ValuationsRequest): Either[Throwable, ValuationsResponse] = { + def valuationToChangeTerm(accessToken: String, tokenType: String, reservationId: Long, valuationsRequest: ValuationsRequest): F[ValuationsResponse] = { val request = http(s"visits/reserved/$reservationId/valuations-to-change-term"). header(`Content-Type`, "application/json"). header(Authorization, s"$tokenType $accessToken") post[ValuationsResponse](request, bodyOpt = Some(valuationsRequest)) } - def changeTerm(accessToken: String, tokenType: String, reservationId: Long, reservationRequest: ReservationRequest): Either[Throwable, ChangeTermResponse] = { + def changeTerm(accessToken: String, tokenType: String, reservationId: Long, reservationRequest: ReservationRequest): F[ChangeTermResponse] = { val request = http(s"visits/reserved/$reservationId/term"). header(`Content-Type`, "application/json"). header(Authorization, s"$tokenType $accessToken") put[ChangeTermResponse](request, bodyOpt = Some(reservationRequest)) } - private def get[T <: SerializableJsonObject](request: HttpRequest)(implicit mf: scala.reflect.Manifest[T]): Either[Throwable, T] = { - request.toEither.map(_.body.as[T]) + private def get[T <: SerializableJsonObject](request: HttpRequest)(implicit mf: scala.reflect.Manifest[T]): F[T] = { + request.invoke.map(_.body.as[T]) } - private def post[T <: SerializableJsonObject](request: HttpRequest, bodyOpt: Option[SerializableJsonObject] = None)(implicit mf: scala.reflect.Manifest[T]): Either[Throwable, T] = { + private def post[T <: SerializableJsonObject](request: HttpRequest, bodyOpt: Option[SerializableJsonObject] = None)(implicit mf: scala.reflect.Manifest[T]): F[T] = { val postRequest = bodyOpt match { case Some(body) => request.postData(body.asJson) case None => request.postForm } - postRequest.toEither.map(_.body.as[T]) + postRequest.invoke.map(_.body.as[T]) } - private def put[T <: SerializableJsonObject](request: HttpRequest, bodyOpt: Option[SerializableJsonObject] = None)(implicit mf: scala.reflect.Manifest[T]): Either[Throwable, T] = { + private def put[T <: SerializableJsonObject](request: HttpRequest, bodyOpt: Option[SerializableJsonObject] = None)(implicit mf: scala.reflect.Manifest[T]): F[T] = { val putRequest = bodyOpt match { case Some(body) => request.put(body.asJson) case None => request.method("PUT") } - putRequest.toEither.map(_.body.as[T]) + putRequest.invoke.map(_.body.as[T]) } - private def delete(request: HttpRequest): Either[Throwable, HttpResponse[String]] = { - request.postForm.method("DELETE").toEither + private def delete(request: HttpRequest): F[HttpResponse[String]] = { + request.postForm.method("DELETE").invoke } } diff --git a/api/src/main/scala/com/lbs/api/exception/GenericException.scala b/api/src/main/scala/com/lbs/api/exception/GenericException.scala index 9038211..457c075 100644 --- a/api/src/main/scala/com/lbs/api/exception/GenericException.scala +++ b/api/src/main/scala/com/lbs/api/exception/GenericException.scala @@ -1,6 +1,6 @@ package com.lbs.api.exception -class GenericException(val code: Int, val status: String, val message: String) extends ApiException(message) { - override def toString: String = s"Code: $code, status: $status, message: $message" +case class GenericException(code: Int, message: String) extends ApiException(message) { + override def toString: String = s"Code: $code, message: $message" } diff --git a/api/src/main/scala/com/lbs/api/http/package.scala b/api/src/main/scala/com/lbs/api/http/package.scala index 8d716e1..f699491 100644 --- a/api/src/main/scala/com/lbs/api/http/package.scala +++ b/api/src/main/scala/com/lbs/api/http/package.scala @@ -1,14 +1,16 @@ package com.lbs.api -import com.lbs.api.exception.{ApiException, GenericException, InvalidLoginOrPasswordException, ServiceIsAlreadyBookedException, SessionExpiredException} +import cats.MonadError +import cats.implicits._ +import com.lbs.api.exception._ import com.lbs.api.json.JsonSerializer.extensions._ import com.lbs.api.json.model._ import com.lbs.common.Logger import scalaj.http.{HttpRequest, HttpResponse} -import scala.concurrent.{ExecutionContext, Future} -import scala.util.{Failure, Try} +import scala.language.higherKinds +import scala.util.{Failure, Success, Try} package object http extends Logger { @@ -29,25 +31,25 @@ package object http extends Logger { def asEntity[T <: SerializableJsonObject](implicit mf: scala.reflect.Manifest[T]): HttpResponse[T] = { httpResponse.copy(body = httpResponse.body.as[T]) } - - def asEntityAsync[T <: SerializableJsonObject](implicit mf: scala.reflect.Manifest[T], ec: ExecutionContext): Future[HttpResponse[T]] = { - Future(asEntity[T]) - } } - implicit class ExtendedHttpRequest(httpRequest: HttpRequest) { - - def toEither: Either[Throwable, HttpResponse[String]] = { - toTry.toEither - } - - def toTry: Try[HttpResponse[String]] = { + implicit class ExtendedHttpRequest[F[_] : ThrowableMonad](httpRequest: HttpRequest) { + def invoke: F[HttpResponse[String]] = { + val me = MonadError[F, Throwable] debug(s"Sending request:\n${hidePasswords(httpRequest)}") - val httpResponse = Try(httpRequest.asString) + val httpResponse = me.pure(httpRequest.asString) debug(s"Received response:\n$httpResponse") - extractLuxmedError(httpResponse) match { - case Some(error) => Failure(error) - case None => httpResponse.map(_.throwError) + + httpResponse.flatMap { response => + val errorMaybe = extractLuxmedError(response) + errorMaybe match { + case Some(error) => me.raiseError(error) + case None => + Try(response.throwError) match { + case Failure(error) => me.raiseError(error) + case Success(value) => me.pure(value) + } + } } } @@ -55,8 +57,8 @@ package object http extends Logger { value.map(v => httpRequest.param(key, v)).getOrElse(httpRequest) } - private def luxmedErrorToApiException[T <: LuxmedBaseError](ler: HttpResponse[T]): ApiException = { - val message = ler.body.message + private def luxmedErrorToApiException[T <: LuxmedBaseError](code: Int, error: T): ApiException = { + val message = error.message val errorMessage = message.toLowerCase if (errorMessage.contains("invalid login or password")) new InvalidLoginOrPasswordException @@ -65,16 +67,17 @@ package object http extends Logger { else if (errorMessage.contains("session has expired")) new SessionExpiredException else - new GenericException(ler.code, ler.statusLine, message) + new GenericException(code, message) } - private def extractLuxmedError(httpResponse: Try[HttpResponse[String]]) = { - httpResponse.flatMap { response => - Try(response.asEntity[LuxmedErrorsMap]) - .orElse(Try(response.asEntity[LuxmedErrorsList])) - .orElse(Try(response.asEntity[LuxmedError])) - .map(e => luxmedErrorToApiException(e.asInstanceOf[HttpResponse[LuxmedBaseError]])) - }.toOption + private def extractLuxmedError(httpResponse: HttpResponse[String]) = { + val body = httpResponse.body + val code = httpResponse.code + Try(body.as[LuxmedErrorsMap]) + .orElse(Try(body.as[LuxmedErrorsList])) + .orElse(Try(body.as[LuxmedError])) + .map(error => luxmedErrorToApiException(code, error)) + .toOption } private def hidePasswords(httpRequest: HttpRequest) = { diff --git a/api/src/main/scala/com/lbs/api/json/model/LuxmedErrorsMap.scala b/api/src/main/scala/com/lbs/api/json/model/LuxmedErrorsMap.scala index 9cd779a..22c5bc3 100644 --- a/api/src/main/scala/com/lbs/api/json/model/LuxmedErrorsMap.scala +++ b/api/src/main/scala/com/lbs/api/json/model/LuxmedErrorsMap.scala @@ -2,5 +2,5 @@ package com.lbs.api.json.model case class LuxmedErrorsMap(errors: Map[String, List[String]]) extends SerializableJsonObject with LuxmedBaseError { - override def message: String = errors.values.mkString("; ") + override def message: String = errors.values.map(_.mkString("; ")).mkString("; ") } \ No newline at end of file diff --git a/api/src/main/scala/com/lbs/api/package.scala b/api/src/main/scala/com/lbs/api/package.scala index f1a0281..2892963 100644 --- a/api/src/main/scala/com/lbs/api/package.scala +++ b/api/src/main/scala/com/lbs/api/package.scala @@ -1,13 +1,18 @@ package com.lbs +import cats.MonadError +import cats.implicits._ import com.lbs.api.json.model.{AvailableTermsResponse, ReservationFilterResponse, ReservedVisitsResponse, VisitsHistoryResponse} import com.softwaremill.quicklens._ +import scala.language.higherKinds import scala.util.matching.Regex package object api { + type ThrowableMonad[F[_]] = MonadError[F, Throwable] + object ApiResponseMutators { private val DoctorPrefixes: Regex = """\s*(dr\s*n.\s*med.|dr\s*hab.\s*n.\s*med|lek.\s*med.|lek.\s*stom.)\s*""".r @@ -17,8 +22,8 @@ package object api { def mutate(response: T): T } - implicit class ResponseOps[T: ResponseMutator](response: Either[Throwable, T]) { - def mutate: Either[Throwable, T] = { + implicit class ResponseOps[T: ResponseMutator, F[_] : ThrowableMonad](response: F[T]) { + def mutate: F[T] = { val mutator = implicitly[ResponseMutator[T]] response.map(mutator.mutate) } diff --git a/api/src/test/scala/com/lbs/api/http/ExtendedHttpRequestSpec.scala b/api/src/test/scala/com/lbs/api/http/ExtendedHttpRequestSpec.scala new file mode 100644 index 0000000..8b8ab04 --- /dev/null +++ b/api/src/test/scala/com/lbs/api/http/ExtendedHttpRequestSpec.scala @@ -0,0 +1,38 @@ +package com.lbs.api.http + +import cats.instances.either._ +import com.lbs.api.exception.GenericException +import org.mockito.Mockito._ +import org.scalatest.mockito.MockitoSugar +import org.scalatest.{BeforeAndAfterEach, FunSuiteLike, Matchers} +import scalaj.http.{HttpRequest, HttpResponse} + +class ExtendedHttpRequestSpec extends FunSuiteLike with Matchers with MockitoSugar with BeforeAndAfterEach { + + private val request = mock[HttpRequest] + private type ThrowableOr[T] = Either[Throwable, T] + + override protected def beforeEach(): Unit = { + reset(request) + when(request.params).thenReturn(Seq()) + } + + test("ok response") { + val okResponse = HttpResponse("ok", 200, Map()) + when(request.asString).thenReturn(okResponse) + + assert(invoke(request) == Right(okResponse)) + } + + test("error response") { + val errorResponse = HttpResponse("""{"Errors":{"ToDate.Date":["'To Date. Date' must be greater than or equal to '06/04/2018 00:00:00'."]}}""", 200, Map()) + when(request.asString).thenReturn(errorResponse) + val result = invoke(request) + + assert(result == Left(GenericException(200, "'To Date. Date' must be greater than or equal to '06/04/2018 00:00:00'."))) + } + + private def invoke(request: HttpRequest) = { + ExtendedHttpRequest[ThrowableOr](request).invoke + } +} diff --git a/common/build.gradle b/common/build.gradle index bdf3ccd..adfb9e2 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -2,4 +2,5 @@ dependencies { compile group: "org.slf4j", name: "slf4j-api", version: "1.7.25" compile group: "ch.qos.logback", name: "logback-classic", version: "1.2.3" compile group: "ch.qos.logback", name: "logback-core", version: "1.2.3" + compile("org.typelevel:cats-core_2.12:2.0.0-M1") } diff --git a/server/src/main/scala/com/lbs/server/conversation/StaticData.scala b/server/src/main/scala/com/lbs/server/conversation/StaticData.scala index bd24f9d..36251af 100644 --- a/server/src/main/scala/com/lbs/server/conversation/StaticData.scala +++ b/server/src/main/scala/com/lbs/server/conversation/StaticData.scala @@ -5,6 +5,7 @@ import akka.actor.ActorSystem import com.lbs.api.json.model.IdName import com.lbs.bot.model.{Button, Command, TaggedButton} import com.lbs.bot.{Bot, _} +import com.lbs.server.ThrowableOr import com.lbs.server.conversation.Login.UserId import com.lbs.server.conversation.StaticData._ import com.lbs.server.conversation.base.{Conversation, Interactional} @@ -78,6 +79,6 @@ object StaticData { case class FindOptions(searchText: String) - case class FoundOptions(option: Either[Throwable, List[IdName]]) + case class FoundOptions(option: ThrowableOr[List[IdName]]) } \ No newline at end of file diff --git a/server/src/main/scala/com/lbs/server/conversation/StaticDataForBooking.scala b/server/src/main/scala/com/lbs/server/conversation/StaticDataForBooking.scala index eb00860..07a18c9 100644 --- a/server/src/main/scala/com/lbs/server/conversation/StaticDataForBooking.scala +++ b/server/src/main/scala/com/lbs/server/conversation/StaticDataForBooking.scala @@ -6,12 +6,13 @@ import com.lbs.bot.model.Command import com.lbs.server.conversation.Book.BookingData import com.lbs.server.conversation.StaticData.{FindOptions, FoundOptions, LatestOptions, StaticDataConfig} import com.lbs.server.conversation.base.Conversation +import com.lbs.server.ThrowableOr trait StaticDataForBooking extends Conversation[BookingData] { private[conversation] def staticData: StaticData - protected def withFunctions(latestOptions: => Seq[IdName], staticOptions: => Either[Throwable, List[IdName]], applyId: IdName => BookingData): Step => MessageProcessorFn = { + protected def withFunctions(latestOptions: => Seq[IdName], staticOptions: => ThrowableOr[List[IdName]], applyId: IdName => BookingData): Step => MessageProcessorFn = { nextStep: Step => { case Msg(cmd: Command, _) => staticData ! cmd @@ -38,7 +39,7 @@ trait StaticDataForBooking extends Conversation[BookingData] { } } - private def filterOptions(options: Either[Throwable, List[IdName]], searchText: String) = { + private def filterOptions(options: ThrowableOr[List[IdName]], searchText: String) = { options.map(opt => opt.filter(c => c.name.toLowerCase.contains(searchText))) } } diff --git a/server/src/main/scala/com/lbs/server/package.scala b/server/src/main/scala/com/lbs/server/package.scala new file mode 100644 index 0000000..87ece19 --- /dev/null +++ b/server/src/main/scala/com/lbs/server/package.scala @@ -0,0 +1,5 @@ +package com.lbs + +package object server { + type ThrowableOr[T] = Either[Throwable, T] +} diff --git a/server/src/main/scala/com/lbs/server/service/ApiService.scala b/server/src/main/scala/com/lbs/server/service/ApiService.scala index 0924f05..4dd3e3b 100644 --- a/server/src/main/scala/com/lbs/server/service/ApiService.scala +++ b/server/src/main/scala/com/lbs/server/service/ApiService.scala @@ -3,8 +3,10 @@ package com.lbs.server.service import java.time.{LocalTime, ZonedDateTime} +import cats.instances.either._ import com.lbs.api.LuxmedApi import com.lbs.api.json.model._ +import com.lbs.server.ThrowableOr import com.lbs.server.util.ServerModelConverters._ import org.jasypt.util.text.TextEncryptor import org.springframework.beans.factory.annotation.Autowired @@ -19,34 +21,36 @@ class ApiService extends SessionSupport { @Autowired private var textEncryptor: TextEncryptor = _ - def getAllCities(accountId: Long): Either[Throwable, List[IdName]] = + private val luxmedApi = new LuxmedApi[ThrowableOr] + + def getAllCities(accountId: Long): ThrowableOr[List[IdName]] = withSession(accountId) { session => - LuxmedApi.reservationFilter(session.accessToken, session.tokenType).map(_.cities) + luxmedApi.reservationFilter(session.accessToken, session.tokenType).map(_.cities) } - def getAllClinics(accountId: Long, cityId: Long): Either[Throwable, List[IdName]] = + def getAllClinics(accountId: Long, cityId: Long): ThrowableOr[List[IdName]] = withSession(accountId) { session => - LuxmedApi.reservationFilter(session.accessToken, + luxmedApi.reservationFilter(session.accessToken, session.tokenType, cityId = Some(cityId)).map(_.clinics) } - def getAllServices(accountId: Long, cityId: Long, clinicId: Option[Long]): Either[Throwable, List[IdName]] = + def getAllServices(accountId: Long, cityId: Long, clinicId: Option[Long]): ThrowableOr[List[IdName]] = withSession(accountId) { session => - LuxmedApi.reservationFilter(session.accessToken, + luxmedApi.reservationFilter(session.accessToken, session.tokenType, cityId = Some(cityId), clinicId = clinicId).map(_.services) } - def getAllDoctors(accountId: Long, cityId: Long, clinicId: Option[Long], serviceId: Long): Either[Throwable, List[IdName]] = + def getAllDoctors(accountId: Long, cityId: Long, clinicId: Option[Long], serviceId: Long): ThrowableOr[List[IdName]] = withSession(accountId) { session => - LuxmedApi.reservationFilter(session.accessToken, + luxmedApi.reservationFilter(session.accessToken, session.tokenType, cityId = Some(cityId), clinicId = clinicId, serviceId = Some(serviceId)).map(_.doctors) } - def getPayers(accountId: Long, cityId: Long, clinicId: Option[Long], serviceId: Long): Either[Throwable, (Option[IdName], Seq[IdName])] = + def getPayers(accountId: Long, cityId: Long, clinicId: Option[Long], serviceId: Long): ThrowableOr[(Option[IdName], Seq[IdName])] = withSession(accountId) { session => - val reservationFilterResponse = LuxmedApi.reservationFilter(session.accessToken, + val reservationFilterResponse = luxmedApi.reservationFilter(session.accessToken, session.tokenType, cityId = Some(cityId), clinicId = clinicId, serviceId = Some(serviceId)) reservationFilterResponse.map { response => @@ -56,9 +60,9 @@ class ApiService extends SessionSupport { def getAvailableTerms(accountId: Long, payerId: Long, cityId: Long, clinicId: Option[Long], serviceId: Long, doctorId: Option[Long], fromDate: ZonedDateTime = ZonedDateTime.now(), toDate: Option[ZonedDateTime] = None, timeFrom: LocalTime, timeTo: LocalTime, - languageId: Long = 10, findFirstFreeTerm: Boolean = false): Either[Throwable, List[AvailableVisitsTermPresentation]] = + languageId: Long = 10, findFirstFreeTerm: Boolean = false): ThrowableOr[List[AvailableVisitsTermPresentation]] = withSession(accountId) { session => - val termsEither = LuxmedApi.availableTerms(session.accessToken, session.tokenType, payerId, cityId, clinicId, serviceId, doctorId, + val termsEither = luxmedApi.availableTerms(session.accessToken, session.tokenType, payerId, cityId, clinicId, serviceId, doctorId, fromDate, toDate, languageId = languageId, findFirstFreeTerm = findFirstFreeTerm).map(_.availableVisitsTermPresentation) termsEither.map { terms => terms.filter { term => @@ -68,25 +72,25 @@ class ApiService extends SessionSupport { } } - def temporaryReservation(accountId: Long, temporaryReservationRequest: TemporaryReservationRequest, valuationsRequest: ValuationsRequest): Either[Throwable, (TemporaryReservationResponse, ValuationsResponse)] = + def temporaryReservation(accountId: Long, temporaryReservationRequest: TemporaryReservationRequest, valuationsRequest: ValuationsRequest): ThrowableOr[(TemporaryReservationResponse, ValuationsResponse)] = withSession(accountId) { session => for { - temporaryReservation <- LuxmedApi.temporaryReservation(session.accessToken, session.tokenType, temporaryReservationRequest) - valuationsResponse <- LuxmedApi.valuations(session.accessToken, session.tokenType, valuationsRequest) + temporaryReservation <- luxmedApi.temporaryReservation(session.accessToken, session.tokenType, temporaryReservationRequest) + valuationsResponse <- luxmedApi.valuations(session.accessToken, session.tokenType, valuationsRequest) } yield temporaryReservation -> valuationsResponse } - def deleteTemporaryReservation(accountId: Long, temporaryReservationId: Long): Either[Throwable, HttpResponse[String]] = + def deleteTemporaryReservation(accountId: Long, temporaryReservationId: Long): ThrowableOr[HttpResponse[String]] = withSession(accountId) { session => - LuxmedApi.deleteTemporaryReservation(session.accessToken, session.tokenType, temporaryReservationId) + luxmedApi.deleteTemporaryReservation(session.accessToken, session.tokenType, temporaryReservationId) } - def reservation(accountId: Long, reservationRequest: ReservationRequest): Either[Throwable, ReservationResponse] = + def reservation(accountId: Long, reservationRequest: ReservationRequest): ThrowableOr[ReservationResponse] = withSession(accountId) { session => - LuxmedApi.reservation(session.accessToken, session.tokenType, reservationRequest) + luxmedApi.reservation(session.accessToken, session.tokenType, reservationRequest) } - def reserveVisit(accountId: Long, term: AvailableVisitsTermPresentation): Either[Throwable, ReservationResponse] = { + def reserveVisit(accountId: Long, term: AvailableVisitsTermPresentation): ThrowableOr[ReservationResponse] = { val temporaryReservationRequest = term.mapTo[TemporaryReservationRequest] val valuationsRequest = term.mapTo[ValuationsRequest] for { @@ -99,38 +103,38 @@ class ApiService extends SessionSupport { } yield reservation } - def canTermBeChanged(accountId: Long, reservationId: Long): Either[Throwable, HttpResponse[String]] = + def canTermBeChanged(accountId: Long, reservationId: Long): ThrowableOr[HttpResponse[String]] = withSession(accountId) { session => - LuxmedApi.canTermBeChanged(session.accessToken, session.tokenType, reservationId) + luxmedApi.canTermBeChanged(session.accessToken, session.tokenType, reservationId) } - def detailToChangeTerm(accountId: Long, reservationId: Long): Either[Throwable, ChangeTermDetailsResponse] = + def detailToChangeTerm(accountId: Long, reservationId: Long): ThrowableOr[ChangeTermDetailsResponse] = withSession(accountId) { session => - LuxmedApi.detailToChangeTerm(session.accessToken, session.tokenType, reservationId) + luxmedApi.detailToChangeTerm(session.accessToken, session.tokenType, reservationId) } - def temporaryReservationToChangeTerm(accountId: Long, reservationId: Long, temporaryReservationRequest: TemporaryReservationRequest, valuationsRequest: ValuationsRequest): Either[Throwable, (TemporaryReservationResponse, ValuationsResponse)] = + def temporaryReservationToChangeTerm(accountId: Long, reservationId: Long, temporaryReservationRequest: TemporaryReservationRequest, valuationsRequest: ValuationsRequest): ThrowableOr[(TemporaryReservationResponse, ValuationsResponse)] = withSession(accountId) { session => for { - temporaryReservation <- LuxmedApi.temporaryReservationToChangeTerm(session.accessToken, session.tokenType, reservationId, temporaryReservationRequest) - valuationsResponse <- LuxmedApi.valuationToChangeTerm(session.accessToken, session.tokenType, reservationId, valuationsRequest) + temporaryReservation <- luxmedApi.temporaryReservationToChangeTerm(session.accessToken, session.tokenType, reservationId, temporaryReservationRequest) + valuationsResponse <- luxmedApi.valuationToChangeTerm(session.accessToken, session.tokenType, reservationId, valuationsRequest) } yield temporaryReservation -> valuationsResponse } - def valuationToChangeTerm(accountId: Long, reservationId: Long, valuationsRequest: ValuationsRequest): Either[Throwable, ValuationsResponse] = + def valuationToChangeTerm(accountId: Long, reservationId: Long, valuationsRequest: ValuationsRequest): ThrowableOr[ValuationsResponse] = withSession(accountId) { session => - LuxmedApi.valuationToChangeTerm(session.accessToken, session.tokenType, reservationId, valuationsRequest) + luxmedApi.valuationToChangeTerm(session.accessToken, session.tokenType, reservationId, valuationsRequest) } - def changeTerm(accountId: Long, reservationId: Long, reservationRequest: ReservationRequest): Either[Throwable, ChangeTermResponse] = + def changeTerm(accountId: Long, reservationId: Long, reservationRequest: ReservationRequest): ThrowableOr[ChangeTermResponse] = withSession(accountId) { session => - LuxmedApi.changeTerm(session.accessToken, session.tokenType, reservationId, reservationRequest) + luxmedApi.changeTerm(session.accessToken, session.tokenType, reservationId, reservationRequest) } - def updateReservedVisit(accountId: Long, term: AvailableVisitsTermPresentation): Either[Throwable, ChangeTermResponse] = { - val reservedVisitEither = reservedVisits(accountId, toDate = ZonedDateTime.now().plusMonths(6)).map(_.find(_.service.id == term.serviceId)) - reservedVisitEither match { + def updateReservedVisit(accountId: Long, term: AvailableVisitsTermPresentation): ThrowableOr[ChangeTermResponse] = { + val reservedVisitMaybe = reservedVisits(accountId, toDate = ZonedDateTime.now().plusMonths(6)).map(_.find(_.service.id == term.serviceId)) + reservedVisitMaybe match { case Right(Some(reservedVisit: ReservedVisit)) => val reservationId = reservedVisit.reservationId val temporaryReservationRequest = term.mapTo[TemporaryReservationRequest] @@ -154,24 +158,24 @@ class ApiService extends SessionSupport { } def visitsHistory(accountId: Long, fromDate: ZonedDateTime = ZonedDateTime.now().minusYears(1), - toDate: ZonedDateTime = ZonedDateTime.now(), page: Int = 1, pageSize: Int = 100): Either[Throwable, List[HistoricVisit]] = + toDate: ZonedDateTime = ZonedDateTime.now(), page: Int = 1, pageSize: Int = 100): ThrowableOr[List[HistoricVisit]] = withSession(accountId) { session => - LuxmedApi.visitsHistory(session.accessToken, session.tokenType, fromDate, toDate, page, pageSize).map(_.historicVisits) + luxmedApi.visitsHistory(session.accessToken, session.tokenType, fromDate, toDate, page, pageSize).map(_.historicVisits) } def reservedVisits(accountId: Long, fromDate: ZonedDateTime = ZonedDateTime.now(), - toDate: ZonedDateTime = ZonedDateTime.now().plusMonths(3)): Either[Throwable, List[ReservedVisit]] = + toDate: ZonedDateTime = ZonedDateTime.now().plusMonths(3)): ThrowableOr[List[ReservedVisit]] = withSession(accountId) { session => - LuxmedApi.reservedVisits(session.accessToken, session.tokenType, fromDate, toDate).map(_.reservedVisits) + luxmedApi.reservedVisits(session.accessToken, session.tokenType, fromDate, toDate).map(_.reservedVisits) } - def deleteReservation(accountId: Long, reservationId: Long): Either[Throwable, HttpResponse[String]] = + def deleteReservation(accountId: Long, reservationId: Long): ThrowableOr[HttpResponse[String]] = withSession(accountId) { session => - LuxmedApi.deleteReservation(session.accessToken, session.tokenType, reservationId) + luxmedApi.deleteReservation(session.accessToken, session.tokenType, reservationId) } - def login(username: String, password: String): Either[Throwable, LoginResponse] = { - LuxmedApi.login(username, textEncryptor.decrypt(password)) + def login(username: String, password: String): ThrowableOr[LoginResponse] = { + luxmedApi.login(username, textEncryptor.decrypt(password)) } private def left(msg: String) = Left(new RuntimeException(msg)) diff --git a/server/src/main/scala/com/lbs/server/service/SessionSupport.scala b/server/src/main/scala/com/lbs/server/service/SessionSupport.scala index a3b210c..0fd84bc 100644 --- a/server/src/main/scala/com/lbs/server/service/SessionSupport.scala +++ b/server/src/main/scala/com/lbs/server/service/SessionSupport.scala @@ -4,6 +4,7 @@ package com.lbs.server.service import com.lbs.api.exception.SessionExpiredException import com.lbs.api.json.model.LoginResponse import com.lbs.common.{Logger, ParametrizedLock} +import com.lbs.server.ThrowableOr import com.lbs.server.exception.UserNotFoundException import scala.collection.mutable @@ -12,7 +13,7 @@ trait SessionSupport extends Logger { case class Session(accessToken: String, tokenType: String) - def login(username: String, password: String): Either[Throwable, LoginResponse] + def login(username: String, password: String): ThrowableOr[LoginResponse] protected def dataService: DataService @@ -20,10 +21,10 @@ trait SessionSupport extends Logger { private val lock = new ParametrizedLock[Long] - protected def withSession[T](accountId: Long)(fn: Session => Either[Throwable, T]): Either[Throwable, T] = + protected def withSession[T](accountId: Long)(fn: Session => ThrowableOr[T]): ThrowableOr[T] = lock.obtainLock(accountId).synchronized { - def auth: Either[Throwable, Session] = { + def auth: ThrowableOr[Session] = { val credentialsMaybe = dataService.getCredentials(accountId) credentialsMaybe match { case Some(credentials) => @@ -33,7 +34,7 @@ trait SessionSupport extends Logger { } } - def getSession: Either[Throwable, Session] = { + def getSession: ThrowableOr[Session] = { sessions.get(accountId) match { case Some(sess) => Right(sess) case None =>