abstracted luxmed api over cats monad error

This commit is contained in:
Eugene Zadyra
2019-05-09 12:08:18 +02:00
parent 823b409c92
commit 84007fb140
12 changed files with 171 additions and 109 deletions

View File

@@ -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
}
}

View File

@@ -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"
}

View File

@@ -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) = {

View File

@@ -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("; ")
}

View File

@@ -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)
}

View File

@@ -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
}
}

View File

@@ -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")
}

View File

@@ -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]])
}

View File

@@ -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)))
}
}

View File

@@ -0,0 +1,5 @@
package com.lbs
package object server {
type ThrowableOr[T] = Either[Throwable, T]
}

View File

@@ -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))

View File

@@ -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 =>