diff options
Diffstat (limited to 'src/main/scala/xyz/driver/pdsuidomain/services/rest')
9 files changed, 760 insertions, 0 deletions
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestArmService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestArmService.scala new file mode 100644 index 0000000..299e6f8 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestArmService.scala @@ -0,0 +1,99 @@ +package xyz.driver.pdsuidomain.services.rest + +import akka.http.scaladsl.marshalling.Marshal +import scala.concurrent.{ExecutionContext, Future} + +import akka.http.scaladsl.model._ +import akka.stream.ActorMaterializer +import xyz.driver.core.rest._ +import xyz.driver.pdsuicommon.auth._ +import xyz.driver.pdsuicommon.db._ +import xyz.driver.pdsuicommon.domain._ +import xyz.driver.pdsuidomain.entities._ +import xyz.driver.pdsuidomain.formats.json.ListResponse +import xyz.driver.pdsuidomain.formats.json.arm.ApiArm +import xyz.driver.pdsuidomain.services.ArmService + +class RestArmService(transport: ServiceTransport, baseUri: Uri)(implicit protected val materializer: ActorMaterializer, + protected val exec: ExecutionContext) + extends ArmService with RestHelper { + + import xyz.driver.pdsuidomain.services.ArmService._ + import xyz.driver.pdsuicommon.serialization.PlayJsonSupport._ + + // GET /v1/arm xyz.driver.server.controllers.ArmController.getList + // GET /v1/arm/:id xyz.driver.server.controllers.ArmController.getById(id: Long) + // POST /v1/arm xyz.driver.server.controllers.ArmController.create + // PATCH /v1/arm/:id xyz.driver.server.controllers.ArmController.update(id: Long) + // DELETE /v1/arm/:id xyz.driver.server.controllers.ArmController.delete(id: Long) + + def getAll(filter: SearchFilterExpr = SearchFilterExpr.Empty, + sorting: Option[Sorting] = None, + pagination: Option[Pagination] = None)( + implicit requestContext: AuthenticatedRequestContext): Future[GetListReply] = { + + val request = HttpRequest( + HttpMethods.GET, + endpointUri(baseUri, "/v1/arm", filterQuery(filter) ++ sortingQuery(sorting) ++ paginationQuery(pagination))) + + for { + response <- transport.sendRequestGetResponse(requestContext)(request) + reply <- apiResponse[ListResponse[ApiArm], GetListReply](response) { api => + GetListReply.EntityList(api.items.map(_.toDomain), api.meta.itemsCount) + }() + } yield { + reply + } + } + + def getById(armId: LongId[Arm])(implicit requestContext: AuthenticatedRequestContext): Future[GetByIdReply] = { + val request = HttpRequest(HttpMethods.GET, endpointUri(baseUri, s"/v1/arm/$armId")) + for { + response <- transport.sendRequestGetResponse(requestContext)(request) + reply <- apiResponse[ApiArm, GetByIdReply](response) { api => + GetByIdReply.Entity(api.toDomain) + }() + } yield { + reply + } + } + + def create(draftArm: Arm)(implicit requestContext: AuthenticatedRequestContext): Future[CreateReply] = { + for { + entity <- Marshal(ApiArm.fromDomain(draftArm)).to[RequestEntity] + request = HttpRequest(HttpMethods.POST, endpointUri(baseUri, "/v1/arm")).withEntity(entity) + response <- transport.sendRequestGetResponse(requestContext)(request) + reply <- apiResponse[ApiArm, CreateReply](response) { api => + CreateReply.Created(api.toDomain) + }() + } yield { + reply + } + } + + def update(origArm: Arm, draftArm: Arm)(implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply] = { + val id = origArm.id + val request = HttpRequest(HttpMethods.PATCH, endpointUri(baseUri, s"/v1/arm/$id")) + for { + response <- transport.sendRequestGetResponse(requestContext)(request) + reply <- apiResponse[ApiArm, UpdateReply](response) { api => + UpdateReply.Updated(api.toDomain) + }() + } yield { + reply + } + } + + def delete(id: LongId[Arm])(implicit requestContext: AuthenticatedRequestContext): Future[DeleteReply] = { + val request = HttpRequest(HttpMethods.DELETE, endpointUri(baseUri, s"/v1/arm/$id")) + for { + response <- transport.sendRequestGetResponse(requestContext)(request) + reply <- apiResponse[ApiArm, DeleteReply](response) { _ => + DeleteReply.Deleted + }() + } yield { + reply + } + } + +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestCriterionService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestCriterionService.scala new file mode 100644 index 0000000..b27ce3d --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestCriterionService.scala @@ -0,0 +1,108 @@ +package xyz.driver.pdsuidomain.services.rest + +import scala.concurrent.{ExecutionContext, Future} + +import akka.http.scaladsl.marshalling.Marshal +import akka.http.scaladsl.model._ +import akka.stream.ActorMaterializer +import xyz.driver.core.rest._ +import xyz.driver.pdsuicommon.auth._ +import xyz.driver.pdsuicommon.db._ +import xyz.driver.pdsuicommon.domain._ +import xyz.driver.pdsuidomain.entities._ +import xyz.driver.pdsuidomain.formats.json.ListResponse +import xyz.driver.pdsuidomain.services.CriterionService + +class RestCriterionService(transport: ServiceTransport, baseUri: Uri)( + implicit protected val materializer: ActorMaterializer, + protected val exec: ExecutionContext) + extends CriterionService with RestHelper { + + import xyz.driver.pdsuicommon.serialization.PlayJsonSupport._ + import xyz.driver.pdsuidomain.formats.json.criterion.ApiCriterion + import xyz.driver.pdsuidomain.services.CriterionService._ + + // GET /v1/criterion xyz.driver.server.controllers.CriterionController.getList + // GET /v1/criterion/:id xyz.driver.server.controllers.CriterionController.getById(id: Long) + // PATCH /v1/criterion/:id xyz.driver.server.controllers.CriterionController.update(id: Long) + // POST /v1/criterion xyz.driver.server.controllers.CriterionController.create + // DELETE /v1/criterion/:id xyz.driver.server.controllers.CriterionController.delete(id: Long) + + def create(draftRichCriterion: RichCriterion)( + implicit requestContext: AuthenticatedRequestContext): Future[CreateReply] = { + for { + entity <- Marshal(ApiCriterion.fromDomain(draftRichCriterion)).to[RequestEntity] + request = HttpRequest(HttpMethods.POST, endpointUri(baseUri, "/v1/criterion")).withEntity(entity) + response <- transport.sendRequestGetResponse(requestContext)(request) + reply <- apiResponse[ApiCriterion, CreateReply](response) { api => + CreateReply.Created(api.toDomain) + }() + } yield { + reply + } + } + + def getById(id: LongId[Criterion])(implicit requestContext: AuthenticatedRequestContext): Future[GetByIdReply] = { + val request = HttpRequest(HttpMethods.GET, endpointUri(baseUri, s"/v1/criterion/$id")) + for { + response <- transport.sendRequestGetResponse(requestContext)(request) + reply <- apiResponse[ApiCriterion, GetByIdReply](response) { api => + GetByIdReply.Entity(api.toDomain) + }() + } yield { + reply + } + } + + def getAll(filter: SearchFilterExpr = SearchFilterExpr.Empty, + sorting: Option[Sorting] = None, + pagination: Option[Pagination] = None)( + implicit requestContext: AuthenticatedRequestContext): Future[GetListReply] = { + + val request = HttpRequest(HttpMethods.GET, + endpointUri(baseUri, + s"/v1/criterion", + filterQuery(filter) ++ sortingQuery(sorting) ++ paginationQuery(pagination))) + + for { + response <- transport.sendRequestGetResponse(requestContext)(request) + reply <- apiResponse[ListResponse[ApiCriterion], GetListReply](response) { api => + GetListReply.EntityList( + api.items.map(_.toDomain), + api.meta.itemsCount, + api.meta.lastUpdate + ) + }() + } yield { + reply + } + } + + def update(origRichCriterion: RichCriterion, draftRichCriterion: RichCriterion)( + implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply] = { + val id = origRichCriterion.criterion.id + for { + entity <- Marshal(ApiCriterion.fromDomain(draftRichCriterion)).to[RequestEntity] + request = HttpRequest(HttpMethods.PATCH, endpointUri(baseUri, s"/v1/criterion/$id")).withEntity(entity) + response <- transport.sendRequestGetResponse(requestContext)(request) + reply <- apiResponse[ApiCriterion, UpdateReply](response) { api => + UpdateReply.Updated(api.toDomain) + }() + } yield { + reply + } + } + + def delete(id: LongId[Criterion])(implicit requestContext: AuthenticatedRequestContext): Future[DeleteReply] = { + val request = HttpRequest(HttpMethods.DELETE, endpointUri(baseUri, s"/v1/criterion/$id")) + for { + response <- transport.sendRequestGetResponse(requestContext)(request) + reply <- apiResponse[ApiCriterion, DeleteReply](response) { _ => + DeleteReply.Deleted + }() + } yield { + reply + } + } + +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestHelper.scala b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestHelper.scala new file mode 100644 index 0000000..7d2838b --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestHelper.scala @@ -0,0 +1,139 @@ +package xyz.driver.pdsuidomain.services.rest + +import java.lang.RuntimeException + +import scala.concurrent.{ExecutionContext, Future} + +import akka.http.scaladsl.model.{HttpMethods, HttpRequest, HttpResponse, ResponseEntity, StatusCode, StatusCodes, Uri} +import akka.http.scaladsl.unmarshalling.{Unmarshal, Unmarshaller} +import akka.stream.Materializer +import xyz.driver.core.rest.ServiceRequestContext +import xyz.driver.pdsuicommon.auth.{AnonymousRequestContext, AuthenticatedRequestContext} +import xyz.driver.pdsuicommon.db.{ + Pagination, + SearchFilterBinaryOperation, + SearchFilterExpr, + SearchFilterNAryOperation, + Sorting, + SortingOrder +} +import xyz.driver.pdsuicommon.error.DomainError + +trait RestHelper { + + implicit protected val materializer: Materializer + implicit protected val exec: ExecutionContext + + protected def endpointUri(baseUri: Uri, path: String) = + baseUri.withPath(Uri.Path(path)) + + protected def endpointUri(baseUri: Uri, path: String, query: Seq[(String, String)]) = + baseUri.withPath(Uri.Path(path)).withQuery(Uri.Query(query: _*)) + + def get(baseUri: Uri, path: String, query: Seq[(String, String)] = Seq.empty): HttpRequest = + HttpRequest(HttpMethods.GET, endpointUri(baseUri, path, query)) + + def sortingQuery(sorting: Option[Sorting]): Seq[(String, String)] = { + def dimensionQuery(dimension: Sorting.Dimension) = { + val ord = dimension.order match { + case SortingOrder.Ascending => "" + case SortingOrder.Descending => "-" + } + s"$ord${dimension.name}" + } + + sorting match { + case None => Seq.empty + case Some(dimension: Sorting.Dimension) => Seq("sort" -> dimensionQuery(dimension)) + case Some(Sorting.Sequential(dimensions)) => Seq("sort" -> dimensions.map(dimensionQuery).mkString(",")) + } + } + + def filterQuery(expr: SearchFilterExpr): Seq[(String, String)] = { + def opToString(op: SearchFilterBinaryOperation) = op match { + case SearchFilterBinaryOperation.Eq => "eq" + case SearchFilterBinaryOperation.NotEq => "ne" + case SearchFilterBinaryOperation.Like => "like" + case SearchFilterBinaryOperation.Gt => "gt" + case SearchFilterBinaryOperation.GtEq => "ge" + case SearchFilterBinaryOperation.Lt => "lt" + case SearchFilterBinaryOperation.LtEq => "le" + } + + def exprToQuery(expr: SearchFilterExpr): Seq[(String, String)] = expr match { + case SearchFilterExpr.Empty => Seq.empty + case SearchFilterExpr.Atom.Binary(dimension, op, value) => + Seq("filters" -> s"${dimension.name} ${opToString(op)} $value") + case SearchFilterExpr.Atom.NAry(dimension, SearchFilterNAryOperation.In, values) => + Seq("filters" -> s"${dimension.name} in ${values.mkString(",")}") + case SearchFilterExpr.Intersection(ops) => + ops.flatMap(op => exprToQuery(op)) + case expr => sys.error(s"No parser available for filter expression $expr.") + } + + exprToQuery(expr) + } + + def paginationQuery(pagination: Option[Pagination]): Seq[(String, String)] = pagination match { + case None => Seq.empty + case Some(pp) => + Seq( + "pageNumber" -> pp.pageNumber.toString, + "pageSize" -> pp.pageSize.toHexString + ) + } + + /** Utility method to parse responses that encode success and errors as subtypes + * of a common reply type. + * + * @tparam ApiReply The type of the serialized reply object, contained in the HTTP entity + * @tparam DomainReply The type of the domain object that will be created from a successful reply. + * + * @param response The HTTP response to parse. + * @param successMapper Transformation function from a deserialized api entity to a domain object. + * @param errorMapper Transformation function from general domain errors to + * specialized errors of the given DomainReply. Note that if a domain error + * is not explicitly handled, it will be encoded as a failure in the returned future. + * @param unmarshaller An unmarshaller that converts a successful response to an api reply. + */ + def apiResponse[ApiReply, DomainReply](response: HttpResponse)(successMapper: ApiReply => DomainReply)( + errorMapper: PartialFunction[DomainError, DomainReply] = PartialFunction.empty)( + implicit unmarshaller: Unmarshaller[ResponseEntity, ApiReply]): Future[DomainReply] = { + + val domainErrors: Map[StatusCode, DomainError] = Map( + StatusCodes.Unauthorized -> new DomainError.AuthenticationError { + override protected def userMessage: String = "unauthorized" + }, // 401 + StatusCodes.Forbidden -> new DomainError.AuthorizationError { + override protected def userMessage: String = "forbidden" + }, // 403 + StatusCodes.NotFound -> new DomainError.NotFoundError { + override protected def userMessage: String = "not found" + } // 404 + ) + + if (response.status.isSuccess) { + val reply = Unmarshal(response.entity).to[ApiReply] + reply.map(successMapper) + } else { + val domainError = domainErrors.get(response.status) + domainError.flatMap(errorMapper.lift) match { + case Some(error) => Future.successful(error) + case None => + Future.failed( + new RuntimeException( + s"Unhandled domain error for HTTP status ${response.status}. Message ${response.entity}") + ) + } + } + } + + implicit def toServiceRequestContext(requestContext: AnonymousRequestContext): ServiceRequestContext = { + val auth: Map[String, String] = requestContext match { + case ctx: AuthenticatedRequestContext => Map("Auth-token" -> ctx.authToken) + case _ => Map() + } + new ServiceRequestContext(contextHeaders = auth) + } + +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestHypothesisService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestHypothesisService.scala new file mode 100644 index 0000000..ff9d490 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestHypothesisService.scala @@ -0,0 +1,39 @@ +package xyz.driver.pdsuidomain.services.rest + +import scala.concurrent.{ExecutionContext, Future} + +import akka.http.scaladsl.model._ +import akka.stream.ActorMaterializer +import xyz.driver.core.rest._ +import xyz.driver.pdsuicommon.auth._ +import xyz.driver.pdsuicommon.db._ +import xyz.driver.pdsuidomain.formats.json.ListResponse +import xyz.driver.pdsuidomain.formats.json.hypothesis.ApiHypothesis +import xyz.driver.pdsuidomain.services.HypothesisService + +class RestHypothesisService(transport: ServiceTransport, baseUri: Uri)( + implicit protected val materializer: ActorMaterializer, + protected val exec: ExecutionContext) + extends HypothesisService with RestHelper { + + import xyz.driver.pdsuicommon.serialization.PlayJsonSupport._ + import xyz.driver.pdsuidomain.services.HypothesisService._ + + // GET /v1/hypothesis xyz.driver.server.controllers.HypothesisController.getList + + def getAll(sorting: Option[Sorting] = None)( + implicit requestContext: AuthenticatedRequestContext): Future[GetListReply] = { + + val request = HttpRequest(HttpMethods.GET, endpointUri(baseUri, "/v1/hypothesis", sortingQuery(sorting))) + + for { + response <- transport.sendRequestGetResponse(requestContext)(request) + reply <- apiResponse[ListResponse[ApiHypothesis], GetListReply](response) { api => + GetListReply.EntityList(api.items.map(_.toDomain), api.meta.itemsCount) + }() + } yield { + reply + } + } + +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestInterventionService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestInterventionService.scala new file mode 100644 index 0000000..810a9d6 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestInterventionService.scala @@ -0,0 +1,78 @@ +package xyz.driver.pdsuidomain.services.rest + +import akka.http.scaladsl.marshalling.Marshal +import scala.concurrent.{ExecutionContext, Future} + +import akka.http.scaladsl.model._ +import akka.stream.ActorMaterializer +import xyz.driver.core.rest._ +import xyz.driver.pdsuicommon.auth._ +import xyz.driver.pdsuicommon.db._ +import xyz.driver.pdsuicommon.domain._ +import xyz.driver.pdsuidomain.entities._ +import xyz.driver.pdsuidomain.formats.json.ListResponse +import xyz.driver.pdsuidomain.formats.json.intervention.ApiIntervention +import xyz.driver.pdsuidomain.services.InterventionService + +class RestInterventionService(transport: ServiceTransport, baseUri: Uri)( + implicit protected val materializer: ActorMaterializer, + protected val exec: ExecutionContext) + extends InterventionService with RestHelper { + + import xyz.driver.pdsuidomain.services.InterventionService._ + import xyz.driver.pdsuicommon.serialization.PlayJsonSupport._ + + // GET /v1/intervention xyz.driver.server.controllers.InterventionController.getList + // GET /v1/intervention/:id xyz.driver.server.controllers.InterventionController.getById(id: Long) + // PATCH /v1/intervention/:id xyz.driver.server.controllers.InterventionController.update(id: Long) + + def getAll(filter: SearchFilterExpr = SearchFilterExpr.Empty, + sorting: Option[Sorting] = None, + pagination: Option[Pagination] = None)( + implicit requestContext: AuthenticatedRequestContext): Future[GetListReply] = { + + val request = HttpRequest(HttpMethods.GET, + endpointUri(baseUri, + "/v1/intervention", + filterQuery(filter) ++ sortingQuery(sorting) ++ paginationQuery(pagination))) + + for { + response <- transport.sendRequestGetResponse(requestContext)(request) + reply <- apiResponse[ListResponse[ApiIntervention], GetListReply](response) { api => + GetListReply.EntityList(api.items.map(_.toDomain), api.meta.itemsCount) + }() + } yield { + reply + } + } + + def getById(id: LongId[Intervention])(implicit requestContext: AuthenticatedRequestContext): Future[GetByIdReply] = { + val request = HttpRequest(HttpMethods.GET, endpointUri(baseUri, s"/v1/intervention/$id")) + + for { + response <- transport.sendRequestGetResponse(requestContext)(request) + reply <- apiResponse[ApiIntervention, GetByIdReply](response) { api => + GetByIdReply.Entity(api.toDomain) + }() + } yield { + reply + } + } + + def update(origIntervention: InterventionWithArms, draftIntervention: InterventionWithArms)( + implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply] = { + val id = origIntervention.intervention.id + + for { + entity <- Marshal(ApiIntervention.fromDomain(draftIntervention)).to[RequestEntity] + request = HttpRequest(HttpMethods.PATCH, endpointUri(baseUri, s"/v1/intervention/$id")).withEntity(entity) + response <- transport.sendRequestGetResponse(requestContext)(request) + reply <- apiResponse[ApiIntervention, UpdateReply](response) { api => + UpdateReply.Updated(api.toDomain) + }() + } yield { + reply + } + } + +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestInterventionTypeService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestInterventionTypeService.scala new file mode 100644 index 0000000..1243500 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestInterventionTypeService.scala @@ -0,0 +1,40 @@ +package xyz.driver.pdsuidomain.services.rest + +import scala.concurrent.{ExecutionContext, Future} + +import akka.http.scaladsl.model._ +import akka.stream.ActorMaterializer +import xyz.driver.core.rest._ +import xyz.driver.pdsuicommon.auth._ +import xyz.driver.pdsuicommon.db._ +import xyz.driver.pdsuicommon.error.DomainError +import xyz.driver.pdsuidomain.formats.json.intervention.ApiInterventionType +import xyz.driver.pdsuidomain.services.InterventionTypeService + +class RestInterventionTypeService(transport: ServiceTransport, baseUri: Uri)( + implicit protected val materializer: ActorMaterializer, + protected val exec: ExecutionContext) + extends InterventionTypeService with RestHelper { + + import xyz.driver.pdsuicommon.serialization.PlayJsonSupport._ + import xyz.driver.pdsuidomain.formats.json.ListResponse + import xyz.driver.pdsuidomain.services.InterventionTypeService._ + + def getAll(sorting: Option[Sorting] = None)( + implicit requestContext: AuthenticatedRequestContext): Future[GetListReply] = { + + for { + response <- transport.sendRequestGetResponse(requestContext)( + get(baseUri, "/v1/intervention-type", query = sortingQuery(sorting))) + reply <- apiResponse[ListResponse[ApiInterventionType], GetListReply](response) { list => + val domain = list.items.map(_.toDomain) + GetListReply.EntityList(domain.toList, list.meta.itemsCount) + } { + case _: DomainError.AuthorizationError => GetListReply.AuthorizationError + } + } yield { + reply + } + } + +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestMessageService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestMessageService.scala new file mode 100644 index 0000000..61d2050 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestMessageService.scala @@ -0,0 +1,103 @@ +package xyz.driver.pdsuidomain.services.rest + +import akka.http.scaladsl.marshalling.Marshal +import scala.concurrent.{ExecutionContext, Future} + +import akka.http.scaladsl.model._ +import akka.stream.ActorMaterializer +import xyz.driver.core.rest._ +import xyz.driver.pdsuicommon.auth._ +import xyz.driver.pdsuicommon.db._ +import xyz.driver.pdsuicommon.domain._ +import xyz.driver.pdsuidomain.entities._ +import xyz.driver.pdsuidomain.formats.json.ListResponse +import xyz.driver.pdsuidomain.formats.json.message.ApiMessage +import xyz.driver.pdsuidomain.services.MessageService + +class RestMessageService(transport: ServiceTransport, baseUri: Uri)( + implicit protected val materializer: ActorMaterializer, + protected val exec: ExecutionContext) + extends MessageService with RestHelper { + + import xyz.driver.pdsuicommon.serialization.PlayJsonSupport._ + import xyz.driver.pdsuidomain.services.MessageService._ + + // GET /v1/message xyz.driver.server.controllers.MessageController.getList + // POST /v1/message xyz.driver.server.controllers.MessageController.create + // PATCH /v1/message/:id xyz.driver.server.controllers.MessageController.update(id: Long) + // DELETE /v1/message/:id xyz.driver.server.controllers.MessageController.delete(id: Long) + + def create(draftMessage: Message)(implicit requestContext: AuthenticatedRequestContext): Future[CreateReply] = { + for { + entity <- Marshal(ApiMessage.fromDomain(draftMessage)).to[RequestEntity] + request = HttpRequest(HttpMethods.POST, endpointUri(baseUri, "/v1/message")).withEntity(entity) + response <- transport.sendRequestGetResponse(requestContext)(request) + reply <- apiResponse[ApiMessage, CreateReply](response) { api => + CreateReply.Created(api.toDomain) + }() + } yield { + reply + } + } + + def getById(messageId: LongId[Message])(implicit requestContext: AuthenticatedRequestContext): Future[GetByIdReply] = { + import SearchFilterExpr._ + import SearchFilterBinaryOperation._ + val filter = Atom.Binary("id", Eq, messageId) + getAll(filter).map { + case GetListReply.EntityList(messages, _, _) if messages.isEmpty => + GetByIdReply.NotFoundError + case GetListReply.EntityList(messages, _, _) => + GetByIdReply.Entity(messages.head) + case GetListReply.AuthorizationError => + GetByIdReply.AuthorizationError + } + } + def getAll(filter: SearchFilterExpr = SearchFilterExpr.Empty, + sorting: Option[Sorting] = None, + pagination: Option[Pagination] = None)( + implicit requestContext: AuthenticatedRequestContext): Future[GetListReply] = { + + val request = HttpRequest( + HttpMethods.GET, + endpointUri(baseUri, "/v1/message", filterQuery(filter) ++ sortingQuery(sorting) ++ paginationQuery(pagination))) + + for { + response <- transport.sendRequestGetResponse(requestContext)(request) + reply <- apiResponse[ListResponse[ApiMessage], GetListReply](response) { api => + GetListReply.EntityList(api.items.map(_.toDomain), api.meta.itemsCount, api.meta.lastUpdate) + }() + } yield { + reply + } + } + + def update(origMessage: Message, draftMessage: Message)( + implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply] = { + for { + entity <- Marshal(ApiMessage.fromDomain(draftMessage)).to[RequestEntity] + id = origMessage.id.id + request = HttpRequest(HttpMethods.PATCH, endpointUri(baseUri, s"/v1/message/$id")).withEntity(entity) + response <- transport.sendRequestGetResponse(requestContext)(request) + reply <- apiResponse[ApiMessage, UpdateReply](response) { api => + UpdateReply.Updated(api.toDomain) + }() + } yield { + reply + } + } + + def delete(messageId: LongId[Message])(implicit requestContext: AuthenticatedRequestContext): Future[DeleteReply] = { + val request = HttpRequest(HttpMethods.GET, endpointUri(baseUri, s"/v1/message/${messageId.id}")) + + for { + response <- transport.sendRequestGetResponse(requestContext)(request) + reply <- apiResponse[ApiMessage, DeleteReply](response) { _ => + DeleteReply.Deleted + }() + } yield { + reply + } + } + +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestStudyDesignService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestStudyDesignService.scala new file mode 100644 index 0000000..66f7a78 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestStudyDesignService.scala @@ -0,0 +1,37 @@ +package xyz.driver.pdsuidomain.services.rest + +import scala.concurrent.{ExecutionContext, Future} + +import akka.http.scaladsl.model._ +import akka.stream.ActorMaterializer +import xyz.driver.core.rest._ +import xyz.driver.pdsuicommon.auth._ +import xyz.driver.pdsuicommon.db._ +import xyz.driver.pdsuidomain.formats.json.ListResponse +import xyz.driver.pdsuidomain.formats.json.studydesign.ApiStudyDesign +import xyz.driver.pdsuidomain.services.StudyDesignService + +class RestStudyDesignService(transport: ServiceTransport, baseUri: Uri)( + implicit protected val materializer: ActorMaterializer, + protected val exec: ExecutionContext) + extends StudyDesignService with RestHelper { + + import xyz.driver.pdsuicommon.serialization.PlayJsonSupport._ + import xyz.driver.pdsuidomain.services.StudyDesignService._ + + // GET /v1/study-design xyz.driver.server.controllers.StudyDesignController.getList + + def getAll(sorting: Option[Sorting] = None)( + implicit requestContext: AuthenticatedRequestContext): Future[GetListReply] = { + val request = HttpRequest(HttpMethods.GET, endpointUri(baseUri, "/v1/study-design", sortingQuery(sorting))) + for { + response <- transport.sendRequestGetResponse(requestContext)(request) + reply <- apiResponse[ListResponse[ApiStudyDesign], GetListReply](response) { api => + GetListReply.EntityList(api.items.map(_.toDomain), api.meta.itemsCount) + }() + } yield { + reply + } + } + +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestTrialService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestTrialService.scala new file mode 100644 index 0000000..9b4b576 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestTrialService.scala @@ -0,0 +1,117 @@ +package xyz.driver.pdsuidomain.services.rest + +import akka.http.scaladsl.marshalling.Marshal +import scala.NotImplementedError +import scala.concurrent.{ExecutionContext, Future} + +import akka.http.scaladsl.model._ +import akka.stream.ActorMaterializer +import xyz.driver.core.rest._ +import xyz.driver.pdsuicommon.auth._ +import xyz.driver.pdsuicommon.db._ +import xyz.driver.pdsuicommon.domain._ +import xyz.driver.pdsuidomain.entities._ +import xyz.driver.pdsuidomain.formats.json.trial.ApiTrial +import xyz.driver.pdsuidomain.services.TrialService +import xyz.driver.pdsuidomain.formats.json.ListResponse + +class RestTrialService(transport: ServiceTransport, baseUri: Uri)( + implicit protected val materializer: ActorMaterializer, + protected val exec: ExecutionContext) + extends TrialService with RestHelper { + + import xyz.driver.pdsuicommon.serialization.PlayJsonSupport._ + import xyz.driver.pdsuidomain.services.TrialService._ + + // GET /v1/trial xyz.driver.server.controllers.TrialController.getList + // GET /v1/trial/:id xyz.driver.server.controllers.TrialController.getById(id: String) + // GET /v1/trial/:id/source xyz.driver.server.controllers.TrialController.getSource(id: String) + // PATCH /v1/trial/:id xyz.driver.server.controllers.TrialController.update(id: String) + // POST /v1/trial/:id/start xyz.driver.server.controllers.TrialController.start(id: String) + // POST /v1/trial/:id/submit xyz.driver.server.controllers.TrialController.submit(id: String) + // POST /v1/trial/:id/restart xyz.driver.server.controllers.TrialController.restart(id: String) + // POST /v1/trial/:id/flag xyz.driver.server.controllers.TrialController.flag(id: String) + // POST /v1/trial/:id/resolve xyz.driver.server.controllers.TrialController.resolve(id: String) + // POST /v1/trial/:id/archive xyz.driver.server.controllers.TrialController.archive(id: String) + // POST /v1/trial/:id/unassign xyz.driver.server.controllers.TrialController.unassign(id: String) + + def getById(id: StringId[Trial])(implicit requestContext: AuthenticatedRequestContext): Future[GetByIdReply] = { + val request = HttpRequest(HttpMethods.GET, endpointUri(baseUri, s"/v1/trial/$id")) + for { + response <- transport.sendRequestGetResponse(requestContext)(request) + reply <- apiResponse[ApiTrial, GetByIdReply](response) { api => + GetByIdReply.Entity(api.toDomain) + }() + } yield { + reply + } + } + + def getPdfSource(trialId: StringId[Trial])( + implicit requestContext: AuthenticatedRequestContext): Future[GetPdfSourceReply] = Future.failed( + new NotImplementedError("Streaming PDF over network is not supported.") + ) + + def getAll(filter: SearchFilterExpr = SearchFilterExpr.Empty, + sorting: Option[Sorting] = None, + pagination: Option[Pagination] = None)( + implicit requestContext: AuthenticatedRequestContext): Future[GetListReply] = { + + val request = HttpRequest( + HttpMethods.GET, + endpointUri(baseUri, "/v1/trial", filterQuery(filter) ++ sortingQuery(sorting) ++ paginationQuery(pagination))) + for { + response <- transport.sendRequestGetResponse(requestContext)(request) + reply <- apiResponse[ListResponse[ApiTrial], GetListReply](response) { api => + GetListReply.EntityList(api.items.map(_.toDomain), api.meta.itemsCount, api.meta.lastUpdate) + }() + } yield { + reply + } + } + + def update(origTrial: Trial, draftTrial: Trial)( + implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply] = { + val id = origTrial.id.id + for { + entity <- Marshal(ApiTrial.fromDomain(draftTrial)).to[RequestEntity] + request = HttpRequest(HttpMethods.PATCH, endpointUri(baseUri, s"/v1/trial/$id")).withEntity(entity) + response <- transport.sendRequestGetResponse(requestContext)(request) + reply <- apiResponse[ApiTrial, UpdateReply](response) { api => + UpdateReply.Updated(api.toDomain) + }() + } yield { + reply + } + } + + private def singleAction(origTrial: Trial, action: String)( + implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply] = { + val id = origTrial.id.id + val request = HttpRequest(HttpMethods.GET, endpointUri(baseUri, s"/v1/trial/$id/$action")) + for { + response <- transport.sendRequestGetResponse(requestContext)(request) + reply <- apiResponse[ApiTrial, UpdateReply](response) { api => + UpdateReply.Updated(api.toDomain) + }() + } yield { + reply + } + } + + def start(origTrial: Trial)(implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply] = + singleAction(origTrial, "start") + def submit(origTrial: Trial)(implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply] = + singleAction(origTrial, "submit") + def restart(origTrial: Trial)(implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply] = + singleAction(origTrial, "restart") + def flag(origTrial: Trial)(implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply] = + singleAction(origTrial, "flag") + def resolve(origTrial: Trial)(implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply] = + singleAction(origTrial, "resolve") + def archive(origTrial: Trial)(implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply] = + singleAction(origTrial, "archive") + def unassign(origTrial: Trial)(implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply] = + singleAction(origTrial, "unassign") + +} |