From f9ac0adf5c3bcfcde03bd3ea2bc2471b0d0f99fe Mon Sep 17 00:00:00 2001 From: Jakob Odersky Date: Wed, 5 Jul 2017 19:02:13 -0700 Subject: Implement REST services for trial curation --- build.sbt | 29 +++-- project/build.properties | 2 +- project/plugins.sbt | 2 +- .../auth/AuthenticatedRequestContext.scala | 5 +- .../BridgeUploadQueueRepositoryAdapter.scala | 4 +- .../xyz/driver/pdsuicommon/concurrent/Cron.scala | 6 +- .../concurrent/SafeBridgeUploadQueue.scala | 2 +- .../xyz/driver/pdsuicommon/db/MySqlContext.scala | 26 ++-- .../xyz/driver/pdsuicommon/db/Pagination.scala | 2 +- .../xyz/driver/pdsuicommon/db/QueryBuilder.scala | 31 ++--- .../driver/pdsuicommon/db/SearchFilterExpr.scala | 6 +- .../scala/xyz/driver/pdsuicommon/db/Sorting.scala | 4 +- .../scala/xyz/driver/pdsuicommon/domain/Id.scala | 8 +- .../scala/xyz/driver/pdsuicommon/domain/User.scala | 14 +-- .../xyz/driver/pdsuicommon/json/JsResultOps.scala | 2 +- .../serialization/PlayJsonSupport.scala | 34 +++++ .../driver/pdsuidomain/entities/DirectReport.scala | 14 +-- .../pdsuidomain/entities/LinkedPatient.scala | 2 +- .../pdsuidomain/entities/MedicalRecord.scala | 48 +++---- .../xyz/driver/pdsuidomain/entities/Message.scala | 6 +- .../pdsuidomain/entities/RawPatientDocument.scala | 20 +-- .../pdsuidomain/entities/RawTrialLabel.scala | 26 ++-- .../xyz/driver/pdsuidomain/entities/Trial.scala | 24 ++-- .../pdsuidomain/formats/json/ListResponse.scala | 4 +- .../pdsuidomain/formats/json/arm/ApiArm.scala | 15 ++- .../json/intervention/ApiIntervention.scala | 39 +++++- .../formats/json/message/ApiMessage.scala | 22 +++- .../pdsuidomain/formats/json/trial/ApiTrial.scala | 58 +++++++-- .../formats/json/user/ApiPartialUser.scala | 10 +- .../driver/pdsuidomain/services/ArmService.scala | 10 +- .../pdsuidomain/services/CategoryService.scala | 27 ---- .../pdsuidomain/services/CriterionService.scala | 18 +-- .../pdsuidomain/services/DocumentService.scala | 19 +-- .../pdsuidomain/services/DocumentTypeService.scala | 2 +- .../pdsuidomain/services/ExportService.scala | 6 +- .../services/ExtractedDataService.scala | 18 +-- .../pdsuidomain/services/HypothesisService.scala | 2 +- .../pdsuidomain/services/InterventionService.scala | 10 +- .../services/InterventionTypeService.scala | 2 +- .../pdsuidomain/services/KeywordService.scala | 28 ----- .../driver/pdsuidomain/services/LabelService.scala | 27 ---- .../services/MedicalRecordService.scala | 18 +-- .../pdsuidomain/services/MessageService.scala | 21 ++-- .../services/PatientCriterionService.scala | 22 ++-- .../services/PatientEligibleTrialService.scala | 21 ++-- .../services/PatientHypothesisService.scala | 12 +- .../services/PatientLabelEvidenceService.scala | 6 +- .../pdsuidomain/services/PatientLabelService.scala | 16 +-- .../pdsuidomain/services/PatientService.scala | 11 +- .../pdsuidomain/services/ProviderTypeService.scala | 2 +- .../pdsuidomain/services/QueueUploadService.scala | 20 +-- .../services/ScrapedTrialsService.scala | 8 +- .../pdsuidomain/services/StudyDesignService.scala | 2 +- .../pdsuidomain/services/TrialHistoryService.scala | 4 +- .../pdsuidomain/services/TrialIssueService.scala | 20 +-- .../driver/pdsuidomain/services/TrialService.scala | 16 +-- .../pdsuidomain/services/UserHistoryService.scala | 2 +- .../driver/pdsuidomain/services/UserService.scala | 34 ++--- .../pdsuidomain/services/rest/RestArmService.scala | 99 +++++++++++++++ .../services/rest/RestCriterionService.scala | 108 ++++++++++++++++ .../pdsuidomain/services/rest/RestHelper.scala | 139 +++++++++++++++++++++ .../services/rest/RestHypothesisService.scala | 39 ++++++ .../services/rest/RestInterventionService.scala | 78 ++++++++++++ .../rest/RestInterventionTypeService.scala | 40 ++++++ .../services/rest/RestMessageService.scala | 103 +++++++++++++++ .../services/rest/RestStudyDesignService.scala | 37 ++++++ .../services/rest/RestTrialService.scala | 117 +++++++++++++++++ 67 files changed, 1226 insertions(+), 403 deletions(-) create mode 100644 src/main/scala/xyz/driver/pdsuicommon/serialization/PlayJsonSupport.scala delete mode 100644 src/main/scala/xyz/driver/pdsuidomain/services/CategoryService.scala delete mode 100644 src/main/scala/xyz/driver/pdsuidomain/services/KeywordService.scala delete mode 100644 src/main/scala/xyz/driver/pdsuidomain/services/LabelService.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/services/rest/RestArmService.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/services/rest/RestCriterionService.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/services/rest/RestHelper.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/services/rest/RestHypothesisService.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/services/rest/RestInterventionService.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/services/rest/RestInterventionTypeService.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/services/rest/RestMessageService.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/services/rest/RestStudyDesignService.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/services/rest/RestTrialService.scala diff --git a/build.sbt b/build.sbt index 7772f5b..82a3249 100644 --- a/build.sbt +++ b/build.sbt @@ -3,26 +3,31 @@ import Keys._ lazy val core = (project in file(".")) .driverLibrary("pds-ui-common") - .settings(scalastyleSettings ++ /* wartRemoverSettings ++ */ formatSettings) + .settings(scalastyleSettings ++ wartRemoverSettings ++ formatSettings) + .settings(wartremoverErrors in (Compile, compile) --= Seq( + Wart.ImplicitConversion, Wart.MutableDataStructures, Wart.TraversableOps, Wart.OptionPartial)) .settings(sources in (Compile, doc) := Seq.empty, publishArtifact in (Compile, packageDoc) := false) .settings(libraryDependencies ++= Seq( - "ch.qos.logback" % "logback-classic" % "1.1.7", - "org.slf4j" % "slf4j-api" % "1.7.21", - "com.typesafe.scala-logging" %% "scala-logging" % "3.5.0", - "com.typesafe" % "config" % "1.3.0", "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.8.3", - "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.8.4", + "com.github.pureconfig" %% "pureconfig" % "0.7.2", + "com.typesafe.akka" %% "akka-http" % "10.0.9", "com.typesafe.play" %% "play" % "2.5.15", - "org.davidbild" %% "tristate-core" % "0.2.0", - "org.davidbild" %% "tristate-play" % "0.2.0" exclude ("com.typesafe.play", "play-json"), - "org.asynchttpclient" % "async-http-client" % "2.0.24", + "com.typesafe.scala-logging" %% "scala-logging" % "3.5.0", "io.getquill" %% "quill-jdbc" % "1.2.1", "io.github.cloudify" %% "spdf" % "1.4.0", - "com.sendgrid" % "sendgrid-java" % "3.1.0" exclude ("org.mockito", "mockito-core"), + "org.davidbild" %% "tristate-core" % "0.2.0", + "org.davidbild" %% "tristate-play" % "0.2.0" exclude ("com.typesafe.play", "play-json"), + "xyz.driver" %% "core" % "0.13.15", + "xyz.driver" %% "domain-model" % "0.10.40", + "ch.qos.logback" % "logback-classic" % "1.1.7", + "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.8.4", "com.github.spullara.mustache.java" % "scala-extensions-2.11" % "0.9.4", - "de.svenkubiak" % "jBCrypt" % "0.4.1", "com.google.cloud" % "google-cloud-storage" % "0.9.4-beta", - "com.github.pureconfig" %% "pureconfig" % "0.7.2", + "com.sendgrid" % "sendgrid-java" % "3.1.0" exclude ("org.mockito", "mockito-core"), + "com.typesafe" % "config" % "1.3.0", + "de.svenkubiak" % "jBCrypt" % "0.4.1", + "org.asynchttpclient" % "async-http-client" % "2.0.24", + "org.slf4j" % "slf4j-api" % "1.7.21", "ai.x" %% "diff" % "1.2.0-get-simple-name-fix" % "test", "org.scalatest" %% "scalatest" % "3.0.0" % "test" )) diff --git a/project/build.properties b/project/build.properties index 4c003f6..6561361 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1,4 +1,4 @@ #Activator-generated Properties #Wed Jul 06 16:08:49 PDT 2016 template.uuid=a675a7df-bee3-48df-9eaa-688d99e5814e -sbt.version=0.13.8 +sbt.version=0.13.15 diff --git a/project/plugins.sbt b/project/plugins.sbt index a5ce3ec..83fdd9a 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,4 @@ resolvers += "releases" at "https://drivergrp.jfrog.io/drivergrp/releases" credentials += Credentials("Artifactory Realm", "drivergrp.jfrog.io", "sbt-publisher", "ANC-d8X-Whm-USS") -addSbtPlugin("xyz.driver" % "sbt-settings" % "0.7.34") +addSbtPlugin("xyz.driver" % "sbt-settings" % "0.7.39") diff --git a/src/main/scala/xyz/driver/pdsuicommon/auth/AuthenticatedRequestContext.scala b/src/main/scala/xyz/driver/pdsuicommon/auth/AuthenticatedRequestContext.scala index a1f93cd..e9da132 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/auth/AuthenticatedRequestContext.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/auth/AuthenticatedRequestContext.scala @@ -3,7 +3,7 @@ package xyz.driver.pdsuicommon.auth import xyz.driver.pdsuicommon.logging._ import xyz.driver.pdsuicommon.domain.User -class AuthenticatedRequestContext(val executor: User, override val requestId: RequestId) +class AuthenticatedRequestContext(val executor: User, override val requestId: RequestId, val authToken: String = "") extends AnonymousRequestContext(requestId) { override def equals(that: Any): Boolean = { @@ -22,9 +22,10 @@ class AuthenticatedRequestContext(val executor: User, override val requestId: Re object AuthenticatedRequestContext { - def apply(executor: User) = new AuthenticatedRequestContext(executor, RequestId()) + def apply(executor: User, authToken: String) = new AuthenticatedRequestContext(executor, RequestId(), authToken) implicit def toPhiString(x: AuthenticatedRequestContext): PhiString = { phi"AuthenticatedRequestContext(executor=${x.executor}, requestId=${x.requestId})" } + } diff --git a/src/main/scala/xyz/driver/pdsuicommon/concurrent/BridgeUploadQueueRepositoryAdapter.scala b/src/main/scala/xyz/driver/pdsuicommon/concurrent/BridgeUploadQueueRepositoryAdapter.scala index 48c81c2..3bf9192 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/concurrent/BridgeUploadQueueRepositoryAdapter.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/concurrent/BridgeUploadQueueRepositoryAdapter.scala @@ -77,8 +77,8 @@ object BridgeUploadQueueRepositoryAdapter { sealed trait OnAttempt object OnAttempt { - case object Complete extends OnAttempt - case class Continue(interval: Duration) extends OnAttempt + case object Complete extends OnAttempt + final case class Continue(interval: Duration) extends OnAttempt implicit def toPhiString(x: OnAttempt): PhiString = Unsafe(x.toString) } diff --git a/src/main/scala/xyz/driver/pdsuicommon/concurrent/Cron.scala b/src/main/scala/xyz/driver/pdsuicommon/concurrent/Cron.scala index dd84beb..6659088 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/concurrent/Cron.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/concurrent/Cron.scala @@ -45,9 +45,9 @@ class Cron(settings: Cron.Settings) extends Closeable with StrictLogging { * Checks unused jobs */ def verify(): Unit = { - import scala.collection.JavaConversions.asScalaSet + import scala.collection.JavaConverters._ - val unusedJobs = settings.intervals.keySet -- jobs.toSet + val unusedJobs = settings.intervals.keySet -- jobs.asScala.toSet unusedJobs.foreach { job => logger.warn(s"The job '$job' is listed, but not registered or ignored") } @@ -60,7 +60,7 @@ class Cron(settings: Cron.Settings) extends Closeable with StrictLogging { object Cron { - case class Settings(disable: String, intervals: Map[String, FiniteDuration]) + final case class Settings(disable: String, intervals: Map[String, FiniteDuration]) private class SingletonTask(taskName: String, job: () => Future[Unit])(implicit ec: ExecutionContext) extends TimerTask with StrictLogging { diff --git a/src/main/scala/xyz/driver/pdsuicommon/concurrent/SafeBridgeUploadQueue.scala b/src/main/scala/xyz/driver/pdsuicommon/concurrent/SafeBridgeUploadQueue.scala index 0bc8220..2f7fe6c 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/concurrent/SafeBridgeUploadQueue.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/concurrent/SafeBridgeUploadQueue.scala @@ -11,7 +11,7 @@ object SafeBridgeUploadQueue { trait Tag extends Product with Serializable - case class SafeTask[T <: Tag](tag: T, private[SafeBridgeUploadQueue] val queueItem: BridgeUploadQueue.Item) + final case class SafeTask[T <: Tag](tag: T, private[SafeBridgeUploadQueue] val queueItem: BridgeUploadQueue.Item) object SafeTask { implicit def toPhiString[T <: Tag](x: SafeTask[T]): PhiString = { diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/MySqlContext.scala b/src/main/scala/xyz/driver/pdsuicommon/db/MySqlContext.scala index f804e87..c547bf4 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/db/MySqlContext.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/db/MySqlContext.scala @@ -18,20 +18,20 @@ import scala.util.{Failure, Success, Try} object MySqlContext extends PhiLogging { - case class DbCredentials(user: String, - password: String, - host: String, - port: Int, - dbName: String, - dbCreateFlag: Boolean, - dbContext: String, - connectionParams: String, - url: String) + final case class DbCredentials(user: String, + password: String, + host: String, + port: Int, + dbName: String, + dbCreateFlag: Boolean, + dbContext: String, + connectionParams: String, + url: String) - case class Settings(credentials: DbCredentials, - connection: Config, - connectionAttemptsOnStartup: Int, - threadPoolSize: Int) + final case class Settings(credentials: DbCredentials, + connection: Config, + connectionAttemptsOnStartup: Int, + threadPoolSize: Int) def apply(settings: Settings): MySqlContext = { // Prevent leaking credentials to a log diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/Pagination.scala b/src/main/scala/xyz/driver/pdsuicommon/db/Pagination.scala index e72b5c2..92689dd 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/db/Pagination.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/db/Pagination.scala @@ -5,7 +5,7 @@ import xyz.driver.pdsuicommon.logging._ /** * @param pageNumber Starts with 1 */ -case class Pagination(pageSize: Int, pageNumber: Int) +final case class Pagination(pageSize: Int, pageNumber: Int) object Pagination { diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/QueryBuilder.scala b/src/main/scala/xyz/driver/pdsuicommon/db/QueryBuilder.scala index f941627..aa32166 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/db/QueryBuilder.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/db/QueryBuilder.scala @@ -23,15 +23,15 @@ object QueryBuilder { */ type Binder = PreparedStatement => PreparedStatement - case class TableData(tableName: String, - lastUpdateFieldName: Option[String] = None, - nullableFields: Set[String] = Set.empty) + final case class TableData(tableName: String, + lastUpdateFieldName: Option[String] = None, + nullableFields: Set[String] = Set.empty) val AllFields = Set("*") } -case class TableLink(keyColumnName: String, foreignTableName: String, foreignKeyColumnName: String) +final case class TableLink(keyColumnName: String, foreignTableName: String, foreignKeyColumnName: String) object QueryBuilderParameters { val AllFields = Set("*") @@ -57,10 +57,11 @@ sealed trait QueryBuilderParameters { def toSql(countQuery: Boolean, fields: Set[String], namingStrategy: NamingStrategy): (String, QueryBuilder.Binder) = { val escapedTableName = namingStrategy.table(tableData.tableName) val fieldsSql: String = if (countQuery) { - "count(*)" + (tableData.lastUpdateFieldName match { + val suffix: String = (tableData.lastUpdateFieldName match { case Some(lastUpdateField) => s", max($escapedTableName.${namingStrategy.column(lastUpdateField)})" case None => "" }) + "count(*)" + suffix } else { if (fields == QueryBuilderParameters.AllFields) { s"$escapedTableName.*" @@ -260,11 +261,11 @@ sealed trait QueryBuilderParameters { } -case class PostgresQueryBuilderParameters(tableData: QueryBuilder.TableData, - links: Map[String, TableLink] = Map.empty, - filter: SearchFilterExpr = SearchFilterExpr.Empty, - sorting: Sorting = Sorting.Empty, - pagination: Option[Pagination] = None) +final case class PostgresQueryBuilderParameters(tableData: QueryBuilder.TableData, + links: Map[String, TableLink] = Map.empty, + filter: SearchFilterExpr = SearchFilterExpr.Empty, + sorting: Sorting = Sorting.Empty, + pagination: Option[Pagination] = None) extends QueryBuilderParameters { def limitToSql(): String = { @@ -279,11 +280,11 @@ case class PostgresQueryBuilderParameters(tableData: QueryBuilder.TableData, /** * @param links Links to another tables grouped by foreignTableName */ -case class MysqlQueryBuilderParameters(tableData: QueryBuilder.TableData, - links: Map[String, TableLink] = Map.empty, - filter: SearchFilterExpr = SearchFilterExpr.Empty, - sorting: Sorting = Sorting.Empty, - pagination: Option[Pagination] = None) +final case class MysqlQueryBuilderParameters(tableData: QueryBuilder.TableData, + links: Map[String, TableLink] = Map.empty, + filter: SearchFilterExpr = SearchFilterExpr.Empty, + sorting: Sorting = Sorting.Empty, + pagination: Option[Pagination] = None) extends QueryBuilderParameters { def limitToSql(): String = diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/SearchFilterExpr.scala b/src/main/scala/xyz/driver/pdsuicommon/db/SearchFilterExpr.scala index 4b66f22..0577921 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/db/SearchFilterExpr.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/db/SearchFilterExpr.scala @@ -16,7 +16,7 @@ object SearchFilterExpr { value = "false" ) - case class Dimension(tableName: Option[String], name: String) { + final case class Dimension(tableName: Option[String], name: String) { def isForeign: Boolean = tableName.isDefined } @@ -33,13 +33,13 @@ object SearchFilterExpr { } object Atom { - case class Binary(dimension: Dimension, op: SearchFilterBinaryOperation, value: AnyRef) extends Atom + final case class Binary(dimension: Dimension, op: SearchFilterBinaryOperation, value: AnyRef) extends Atom object Binary { def apply(field: String, op: SearchFilterBinaryOperation, value: AnyRef): Binary = Binary(Dimension(None, field), op, value) } - case class NAry(dimension: Dimension, op: SearchFilterNAryOperation, values: Seq[AnyRef]) extends Atom + final case class NAry(dimension: Dimension, op: SearchFilterNAryOperation, values: Seq[AnyRef]) extends Atom object NAry { def apply(field: String, op: SearchFilterNAryOperation, values: Seq[AnyRef]): NAry = NAry(Dimension(None, field), op, values) diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/Sorting.scala b/src/main/scala/xyz/driver/pdsuicommon/db/Sorting.scala index b796b83..a2c5a75 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/db/Sorting.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/db/Sorting.scala @@ -23,11 +23,11 @@ object Sorting { * @param name Dimension name * @param order Order */ - case class Dimension(tableName: Option[String], name: String, order: SortingOrder) extends Sorting { + final case class Dimension(tableName: Option[String], name: String, order: SortingOrder) extends Sorting { def isForeign: Boolean = tableName.isDefined } - case class Sequential(sorting: Seq[Dimension]) extends Sorting { + final case class Sequential(sorting: Seq[Dimension]) extends Sorting { override def toString: String = if (isEmpty(this)) "Empty" else super.toString } diff --git a/src/main/scala/xyz/driver/pdsuicommon/domain/Id.scala b/src/main/scala/xyz/driver/pdsuicommon/domain/Id.scala index 1bb70f8..e238245 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/domain/Id.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/domain/Id.scala @@ -6,9 +6,9 @@ import xyz.driver.pdsuicommon.logging._ sealed trait Id[+T] -case class CompoundId[Id1 <: Id[_], Id2 <: Id[_]](part1: Id1, part2: Id2) extends Id[(Id1, Id2)] +final case class CompoundId[Id1 <: Id[_], Id2 <: Id[_]](part1: Id1, part2: Id2) extends Id[(Id1, Id2)] -case class LongId[+T](id: Long) extends Id[T] { +final case class LongId[+T](id: Long) extends Id[T] { override def toString: String = id.toString def is(longId: Long): Boolean = { @@ -20,7 +20,7 @@ object LongId { implicit def toPhiString[T](x: LongId[T]): PhiString = Unsafe(s"LongId(${x.id})") } -case class StringId[+T](id: String) extends Id[T] { +final case class StringId[+T](id: String) extends Id[T] { override def toString: String = id def is(stringId: String): Boolean = { @@ -32,7 +32,7 @@ object StringId { implicit def toPhiString[T](x: StringId[T]): PhiString = Unsafe(s"StringId(${x.id})") } -case class UuidId[+T](id: UUID) extends Id[T] { +final case class UuidId[+T](id: UUID) extends Id[T] { override def toString: String = id.toString } diff --git a/src/main/scala/xyz/driver/pdsuicommon/domain/User.scala b/src/main/scala/xyz/driver/pdsuicommon/domain/User.scala index 8d2d86d..4920176 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/domain/User.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/domain/User.scala @@ -8,13 +8,13 @@ import xyz.driver.pdsuicommon.logging._ import xyz.driver.pdsuicommon.domain.User.Role import xyz.driver.pdsuicommon.utils.Utils -case class User(id: StringId[User], - email: Email, - name: String, - role: Role, - passwordHash: PasswordHash, - latestActivity: Option[LocalDateTime], - deleted: Option[LocalDateTime]) +final case class User(id: StringId[User], + email: Email, + name: String, + role: Role, + passwordHash: PasswordHash, + latestActivity: Option[LocalDateTime], + deleted: Option[LocalDateTime]) object User { diff --git a/src/main/scala/xyz/driver/pdsuicommon/json/JsResultOps.scala b/src/main/scala/xyz/driver/pdsuicommon/json/JsResultOps.scala index 07dfefc..4ff4034 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/json/JsResultOps.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/json/JsResultOps.scala @@ -7,7 +7,7 @@ import scala.util.{Failure, Success, Try} final class JsResultOps[T](val self: JsResult[T]) extends AnyVal { def toTry: Try[T] = { - self.fold( + self.fold[Try[T]]( errors => Failure(new JsonValidationException(errors)), Success(_) ) diff --git a/src/main/scala/xyz/driver/pdsuicommon/serialization/PlayJsonSupport.scala b/src/main/scala/xyz/driver/pdsuicommon/serialization/PlayJsonSupport.scala new file mode 100644 index 0000000..5158dab --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuicommon/serialization/PlayJsonSupport.scala @@ -0,0 +1,34 @@ +package xyz.driver.pdsuicommon.serialization + +import akka.http.scaladsl.server.{RejectionError, ValidationRejection} +import akka.http.scaladsl.unmarshalling.Unmarshaller +import play.api.libs.json.{Reads, Writes} +import play.api.libs.json.Json +import akka.http.scaladsl.marshalling.ToEntityMarshaller +import akka.http.scaladsl.unmarshalling.FromEntityUnmarshaller +import akka.http.scaladsl.model.MediaTypes.`application/json` + +trait PlayJsonSupport { + import akka.http.scaladsl.marshalling.Marshaller + + implicit def playJsonUnmarshaller[A: Reads]: FromEntityUnmarshaller[A] = { + val reads = implicitly[Reads[A]] + Unmarshaller.stringUnmarshaller + .forContentTypes(`application/json`) + .map(Json.parse) + .map(reads.reads) + .map(_.recoverTotal { error => + throw RejectionError(ValidationRejection(s"Error reading JSON response as ${reads}.")) + }) + } + + implicit def playJsonMarshaller[A: Writes]: ToEntityMarshaller[A] = { + Marshaller + .stringMarshaller(`application/json`) + .compose(Json.prettyPrint) + .compose(implicitly[Writes[A]].writes) + } + +} + +object PlayJsonSupport extends PlayJsonSupport diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/DirectReport.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/DirectReport.scala index c09a1d8..42ac55b 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/entities/DirectReport.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/entities/DirectReport.scala @@ -32,10 +32,10 @@ object DirectReport { } } -case class DirectReport(id: UuidId[DirectReport], - patientId: UuidId[Patient], - reportType: ReportType, - date: LocalDate, - documentType: String, - providerType: String, - providerName: String) +final case class DirectReport(id: UuidId[DirectReport], + patientId: UuidId[Patient], + reportType: ReportType, + date: LocalDate, + documentType: String, + providerType: String, + providerName: String) diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/LinkedPatient.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/LinkedPatient.scala index 28f279f..d10310a 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/entities/LinkedPatient.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/entities/LinkedPatient.scala @@ -11,4 +11,4 @@ object LinkedPatient { } } -case class LinkedPatient(userId: StringId[User], patientId: UuidId[Patient], trialId: StringId[Trial]) +final case class LinkedPatient(userId: StringId[User], patientId: UuidId[Patient], trialId: StringId[Trial]) diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/MedicalRecord.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/MedicalRecord.scala index 9f38283..3b53945 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/entities/MedicalRecord.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/entities/MedicalRecord.scala @@ -32,11 +32,11 @@ object MedicalRecord { object Meta { - case class Duplicate(predicted: Option[Boolean], - startPage: Double, - endPage: Double, - startOriginalPage: Double, - endOriginalPage: Option[Double]) + final case class Duplicate(predicted: Option[Boolean], + startPage: Double, + endPage: Double, + startOriginalPage: Double, + endOriginalPage: Option[Double]) extends Meta { override val metaType = "duplicate" override def confirmed: Duplicate = copy(predicted = predicted.map(_ => false)) @@ -50,7 +50,7 @@ object MedicalRecord { } } - case class Reorder(predicted: Option[Boolean], items: Seq[Int]) extends Meta { + final case class Reorder(predicted: Option[Boolean], items: Seq[Int]) extends Meta { override val metaType = "reorder" override def confirmed: Reorder = copy(predicted = predicted.map(_ => false)) } @@ -62,7 +62,7 @@ object MedicalRecord { } } - case class Rotation(predicted: Option[Boolean], items: Map[String, Int]) extends Meta { + final case class Rotation(predicted: Option[Boolean], items: Map[String, Int]) extends Meta { override val metaType = "rotation" override def confirmed: Rotation = copy(predicted = predicted.map(_ => false)) } @@ -129,7 +129,7 @@ object MedicalRecord { case object Empty extends PdfSource /** @param createResource Constructor of the resource which is represents the file */ - case class Channel(createResource: () => ReadableByteChannel) extends PdfSource + final case class Channel(createResource: () => ReadableByteChannel) extends PdfSource } implicit def toPhiString(x: MedicalRecord): PhiString = { @@ -139,22 +139,22 @@ object MedicalRecord { } } -case class MedicalRecord(id: LongId[MedicalRecord], - status: MedicalRecord.Status, - previousStatus: Option[MedicalRecord.Status], - assignee: Option[StringId[User]], - previousAssignee: Option[StringId[User]], - lastActiveUserId: Option[StringId[User]], - patientId: UuidId[Patient], - requestId: RecordRequestId, - disease: String, - caseId: Option[CaseId], - physician: Option[String], - sourceName: String, - meta: Option[TextJson[List[Meta]]], - predictedMeta: Option[TextJson[List[Meta]]], - predictedDocuments: Option[TextJson[List[Document]]], - lastUpdate: LocalDateTime) { +final case class MedicalRecord(id: LongId[MedicalRecord], + status: MedicalRecord.Status, + previousStatus: Option[MedicalRecord.Status], + assignee: Option[StringId[User]], + previousAssignee: Option[StringId[User]], + lastActiveUserId: Option[StringId[User]], + patientId: UuidId[Patient], + requestId: RecordRequestId, + disease: String, + caseId: Option[CaseId], + physician: Option[String], + sourceName: String, + meta: Option[TextJson[List[Meta]]], + predictedMeta: Option[TextJson[List[Meta]]], + predictedDocuments: Option[TextJson[List[Document]]], + lastUpdate: LocalDateTime) { import MedicalRecord.Status._ diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/Message.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/Message.scala index f02f481..3bdbac8 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/entities/Message.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/entities/Message.scala @@ -24,10 +24,10 @@ object Message { implicit def toPhiString(x: Message): PhiString = { import x._ - val entityId = recordId - .orElse(documentId) - .orElse(patientId) + val entityId: Option[String] = recordId .map(_.toString) + .orElse(documentId.map(_.toString)) + .orElse(patientId.map(_.toString)) phi"Message(id=$id, userId=$userId, isDraft=$isDraft, entityId=${Unsafe(entityId)}" } diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/RawPatientDocument.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/RawPatientDocument.scala index 8e72023..b66483c 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/entities/RawPatientDocument.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/entities/RawPatientDocument.scala @@ -5,16 +5,16 @@ import java.time.LocalDate import xyz.driver.pdsuicommon.domain.{LongId, UuidId} import xyz.driver.pdsuicommon.logging._ -case class RawPatientDocument(disease: String, - patientId: UuidId[Patient], - requestId: RecordRequestId, - recordId: LongId[MedicalRecord], - recordStatus: MedicalRecord.Status, - documentId: LongId[Document], - documentType: String, - documentProviderType: String, - documentStartDate: LocalDate, - documentStatus: Document.Status) +final case class RawPatientDocument(disease: String, + patientId: UuidId[Patient], + requestId: RecordRequestId, + recordId: LongId[MedicalRecord], + recordStatus: MedicalRecord.Status, + documentId: LongId[Document], + documentType: String, + documentProviderType: String, + documentStartDate: LocalDate, + documentStatus: Document.Status) object RawPatientDocument { diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/RawTrialLabel.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/RawTrialLabel.scala index 82204c2..9e69c87 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/entities/RawTrialLabel.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/entities/RawTrialLabel.scala @@ -5,19 +5,19 @@ import java.time.LocalDateTime import xyz.driver.pdsuicommon.domain.{LongId, StringId, UuidId} import xyz.driver.pdsuicommon.logging._ -case class RawTrialLabel(nctId: StringId[Trial], - trialId: UuidId[Trial], - condition: String, - lastReviewed: LocalDateTime, - armName: String, - armId: LongId[Arm], - labelId: LongId[Label], - value: Option[Boolean], - criterionId: LongId[Criterion], - criteria: String, - criterionArmId: LongId[Arm], - isCompound: Boolean, - isDefining: Boolean) +final case class RawTrialLabel(nctId: StringId[Trial], + trialId: UuidId[Trial], + condition: String, + lastReviewed: LocalDateTime, + armName: String, + armId: LongId[Arm], + labelId: LongId[Label], + value: Option[Boolean], + criterionId: LongId[Criterion], + criteria: String, + criterionArmId: LongId[Arm], + isCompound: Boolean, + isDefining: Boolean) object RawTrialLabel { diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/Trial.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/Trial.scala index 3fe8176..f5c6974 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/entities/Trial.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/entities/Trial.scala @@ -52,16 +52,15 @@ object Trial { implicit def toPhiString(x: Status): PhiString = Unsafe(Utils.getClassSimpleName(x.getClass)) } - case class PdfSource(path: Path) extends AnyVal + final case class PdfSource(path: Path) extends AnyVal implicit def toPhiString(x: Trial): PhiString = { import x._ phi"Trial(id=$id, externalId=$externalId, status=$status, previousStatus=$previousStatus, " + - phi"lastActiveUserId=$lastActiveUserId, assignee=$assignee, previousAssignee=$previousAssignee, " + - phi"isSummaryReviewed=$isSummaryReviewed, isCriteriaReviewed=$isCriteriaReviewed)" + phi"lastActiveUserId=$lastActiveUserId, assignee=$assignee, previousAssignee=$previousAssignee, " } - case class Locations(locations: List[String]) + final case class Locations(locations: List[String]) sealed trait Condition @@ -71,7 +70,13 @@ object Trial { case object Lung extends Condition case object Prostate extends Condition - val All = Set(Breast, Lung, Prostate) + def fromString(condition: String): Condition = condition match { + case "Breast" => Breast + case "Lung" => Lung + case "Prostate" => Prostate + } + + val All: Set[Condition] = Set(Breast, Lung, Prostate) } } @@ -93,14 +98,7 @@ final case class Trial(id: StringId[Trial], overviewTemplate: String, isUpdated: Boolean, title: String, - originalTitle: String, - isSummaryReviewed: Boolean, - isCriteriaReviewed: Boolean, - eligibilityCriteriaChecksum: String, - briefSummaryChecksum: String, - detailedDescriptionChecksum: String, - armDescriptionChecksum: String) { - + originalTitle: String) { import Trial.Status._ if (previousStatus.nonEmpty) { diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/ListResponse.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/ListResponse.scala index c823df5..3fbeac8 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/ListResponse.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/ListResponse.scala @@ -7,11 +7,11 @@ import xyz.driver.pdsuicommon.json.Serialization.seqJsonFormat import play.api.libs.functional.syntax._ import play.api.libs.json._ -case class ListResponse[+T](items: Seq[T], meta: ListResponse.Meta) +final case class ListResponse[+T](items: Seq[T], meta: ListResponse.Meta) object ListResponse { - case class Meta(itemsCount: Int, pageNumber: Int, pageSize: Int, lastUpdate: Option[LocalDateTime]) + final case class Meta(itemsCount: Int, pageNumber: Int, pageSize: Int, lastUpdate: Option[LocalDateTime]) object Meta { def apply(itemsCount: Int, pagination: Pagination, lastUpdate: Option[LocalDateTime]): Meta = { diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/arm/ApiArm.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/arm/ApiArm.scala index 2185e3a..32e2b54 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/arm/ApiArm.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/arm/ApiArm.scala @@ -1,22 +1,35 @@ package xyz.driver.pdsuidomain.formats.json.arm +import xyz.driver.pdsuicommon.domain.{LongId, StringId} import xyz.driver.pdsuidomain.entities.Arm import play.api.libs.functional.syntax._ import play.api.libs.json._ -final case class ApiArm(id: Long, name: String, trialId: String) +final case class ApiArm(id: Long, name: String, originalName: String, trialId: String) { + + def toDomain: Arm = Arm( + id = LongId(this.id), + name = this.name, + originalName = this.originalName, + trialId = StringId(this.trialId), + deleted = None // if we have an ApiArm object, the Arm itself has not been deleted + ) + +} object ApiArm { implicit val format: Format[ApiArm] = ( (JsPath \ "id").format[Long] and (JsPath \ "name").format[String] and + (JsPath \ "originalName").format[String] and (JsPath \ "trialId").format[String] )(ApiArm.apply, unlift(ApiArm.unapply)) def fromDomain(arm: Arm): ApiArm = ApiArm( id = arm.id.id, name = arm.name, + originalName = arm.originalName, trialId = arm.trialId.id ) } diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiIntervention.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiIntervention.scala index 39e0000..39acbde 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiIntervention.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiIntervention.scala @@ -1,6 +1,7 @@ package xyz.driver.pdsuidomain.formats.json.intervention -import xyz.driver.pdsuidomain.entities.InterventionWithArms +import xyz.driver.pdsuicommon.domain.{LongId, StringId} +import xyz.driver.pdsuidomain.entities.{Intervention, InterventionArm, InterventionWithArms} import play.api.libs.functional.syntax._ import play.api.libs.json.{Format, JsPath} @@ -10,7 +11,31 @@ final case class ApiIntervention(id: Long, description: String, isActive: Boolean, arms: List[Long], - trialId: String) + trialId: String, + originalName: String, + originalDescription: String, + originalType: Option[String]) { + + def toDomain = { + val intervention = Intervention( + id = LongId(this.id), + trialId = StringId(this.trialId), + name = this.name, + originalName = this.originalName, + typeId = this.typeId.map(id => LongId(id)), + originalType = this.originalType.map(id => id.toString), + description = this.description, + originalDescription = this.originalDescription, + isActive = this.isActive + ) + + InterventionWithArms(intervention, this.arms.map { armId => + InterventionArm(LongId(armId), intervention.id) + }) + + } + +} object ApiIntervention { @@ -21,7 +46,10 @@ object ApiIntervention { (JsPath \ "description").format[String] and (JsPath \ "isActive").format[Boolean] and (JsPath \ "arms").format[List[Long]] and - (JsPath \ "trialId").format[String] + (JsPath \ "trialId").format[String] and + (JsPath \ "originalName").format[String] and + (JsPath \ "originalDescription").format[String] and + (JsPath \ "originalType").formatNullable[String] )(ApiIntervention.apply, unlift(ApiIntervention.unapply)) def fromDomain(interventionWithArms: InterventionWithArms): ApiIntervention = { @@ -35,7 +63,10 @@ object ApiIntervention { description = intervention.description, isActive = intervention.isActive, arms = arms.map(_.armId.id), - trialId = intervention.trialId.id + trialId = intervention.trialId.id, + originalName = intervention.originalName, + originalDescription = intervention.originalDescription, + originalType = intervention.originalType ) } } diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/message/ApiMessage.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/message/ApiMessage.scala index 2591509..425eeac 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/message/ApiMessage.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/message/ApiMessage.scala @@ -4,6 +4,7 @@ import java.time.{ZoneId, ZonedDateTime} import play.api.libs.functional.syntax._ import play.api.libs.json.{Format, JsPath} +import xyz.driver.pdsuicommon.domain.{LongId, StringId, UuidId} import xyz.driver.pdsuidomain.entities.Message final case class ApiMessage(id: Long, @@ -19,7 +20,26 @@ final case class ApiMessage(id: Long, endPage: Option[Double], evidence: Option[String], archiveRequired: Option[Boolean], - meta: Option[String]) + meta: Option[String]) { + + def toDomain = Message( + id = LongId(this.id), + text = this.text, + lastUpdate = this.lastUpdate.toLocalDateTime(), + userId = StringId(this.userId), + isDraft = this.isDraft, + recordId = this.recordId.map(id => LongId(id)), + documentId = this.documentId.map(id => LongId(id)), + patientId = this.patientId.map(id => UuidId(id)), + trialId = this.trialId.map(id => StringId(id)), + startPage = this.startPage, + endPage = this.endPage, + evidence = this.evidence, + archiveRequired = this.archiveRequired, + meta = this.meta + ) + +} object ApiMessage { diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/trial/ApiTrial.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/trial/ApiTrial.scala index 940f1f0..2556feb 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/trial/ApiTrial.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/trial/ApiTrial.scala @@ -2,65 +2,99 @@ package xyz.driver.pdsuidomain.formats.json.trial import java.time.{ZoneId, ZonedDateTime} import java.util.UUID +import xyz.driver.pdsuicommon.domain.{LongId, StringId, UuidId} import xyz.driver.pdsuidomain.entities.Trial import play.api.libs.functional.syntax._ import play.api.libs.json._ final case class ApiTrial(id: String, - lastUpdate: Option[ZonedDateTime], + externalId: UUID, + lastUpdate: ZonedDateTime, status: String, assignee: Option[String], previousStatus: Option[String], previousAssignee: Option[String], lastActiveUser: Option[String], - condition: Option[String], - phase: Option[String], + condition: String, + phase: String, hypothesisId: Option[UUID], studyDesignId: Option[Long], + originalStudyDesign: Option[String], isPartner: Boolean, overview: Option[String], overviewTemplate: String, isUpdated: Boolean, - title: String) + title: String, + originalTitle: String) { + + def toDomain = Trial( + id = StringId(this.id), + externalId = UuidId(this.externalId), + status = TrialStatus.statusFromString(this.status), + assignee = this.assignee.map(id => StringId(id)), + previousStatus = this.previousStatus.map(s => TrialStatus.statusFromString(s)), + previousAssignee = this.previousAssignee.map(id => StringId(id)), + lastActiveUserId = this.lastActiveUser.map(id => StringId(id)), + lastUpdate = this.lastUpdate.toLocalDateTime(), + condition = Trial.Condition.fromString(this.condition), + phase = this.phase, + hypothesisId = this.hypothesisId.map(id => UuidId(id)), + studyDesignId = this.studyDesignId.map(id => LongId(id)), + originalStudyDesign = this.originalStudyDesign, + isPartner = this.isPartner, + overview = this.overview, + overviewTemplate = this.overviewTemplate, + isUpdated = this.isUpdated, + title = this.title, + originalTitle = this.originalTitle + ) + +} object ApiTrial { implicit val format: Format[ApiTrial] = ( (JsPath \ "id").format[String] and - (JsPath \ "lastUpdate").formatNullable[ZonedDateTime] and + (JsPath \ "externalid").format[UUID] and + (JsPath \ "lastUpdate").format[ZonedDateTime] and (JsPath \ "status").format[String] and (JsPath \ "assignee").formatNullable[String] and (JsPath \ "previousStatus").formatNullable[String] and (JsPath \ "previousAssignee").formatNullable[String] and (JsPath \ "lastActiveUser").formatNullable[String] and - (JsPath \ "condition").formatNullable[String] and - (JsPath \ "phase").formatNullable[String] and + (JsPath \ "condition").format[String] and + (JsPath \ "phase").format[String] and (JsPath \ "hypothesisId").formatNullable[UUID] and (JsPath \ "studyDesignId").formatNullable[Long] and + (JsPath \ "originalStudyDesignId").formatNullable[String] and (JsPath \ "isPartner").format[Boolean] and (JsPath \ "overview").formatNullable[String] and (JsPath \ "overviewTemplate").format[String] and (JsPath \ "isUpdated").format[Boolean] and - (JsPath \ "title").format[String] + (JsPath \ "title").format[String] and + (JsPath \ "otiginalTitle").format[String] )(ApiTrial.apply, unlift(ApiTrial.unapply)) def fromDomain(trial: Trial): ApiTrial = ApiTrial( id = trial.id.id, + externalId = trial.externalId.id, status = TrialStatus.statusToString(trial.status), assignee = trial.assignee.map(_.id), previousStatus = trial.previousStatus.map(TrialStatus.statusToString), previousAssignee = trial.previousAssignee.map(_.id), lastActiveUser = trial.lastActiveUserId.map(_.id), - lastUpdate = Option(ZonedDateTime.of(trial.lastUpdate, ZoneId.of("Z"))), - condition = Option(trial.condition.toString), - phase = Option(trial.phase), + lastUpdate = ZonedDateTime.of(trial.lastUpdate, ZoneId.of("Z")), + condition = trial.condition.toString, + phase = trial.phase, hypothesisId = trial.hypothesisId.map(_.id), studyDesignId = trial.studyDesignId.map(_.id), + originalStudyDesign = trial.originalStudyDesign, isPartner = trial.isPartner, overview = trial.overview, overviewTemplate = trial.overviewTemplate, isUpdated = trial.isUpdated, - title = trial.title + title = trial.title, + originalTitle = trial.originalTitle ) } diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiPartialUser.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiPartialUser.scala index f31efb3..5fad653 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiPartialUser.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiPartialUser.scala @@ -63,10 +63,12 @@ object ApiPartialUser { implicit val format: Format[ApiPartialUser] = ( (JsPath \ "email").formatNullable[String](Format(Reads.email, Writes.StringWrites)) and - (JsPath \ "name").formatNullable[String](Format( - Reads.filterNot[String](ValidationError("Username is too long (max length is 255 chars)", 255))(_.length > 255), - Writes.StringWrites - )) and + (JsPath \ "name").formatNullable[String]( + Format( + Reads.filterNot[String](ValidationError("Username is too long (max length is 255 chars)", 255))( + _.length > 255), + Writes.StringWrites + )) and (JsPath \ "roleId").formatNullable[String]( Format(Reads .of[String] diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/ArmService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/ArmService.scala index 8c89505..1f907c8 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/ArmService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/ArmService.scala @@ -22,7 +22,7 @@ object ArmService { sealed trait GetByIdReply object GetByIdReply { - case class Entity(x: Arm) extends GetByIdReply + final case class Entity(x: Arm) extends GetByIdReply type Error = GetByIdReply with DomainError @@ -31,7 +31,7 @@ object ArmService { case object AuthorizationError extends GetByIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError - case class CommonError(userMessage: String)(implicit requestContext: AuthenticatedRequestContext) + final case class CommonError(userMessage: String)(implicit requestContext: AuthenticatedRequestContext) extends GetByIdReply with DomainError } @@ -39,7 +39,7 @@ object ArmService { object GetListReply { type Error = GetListReply with DomainError - case class EntityList(xs: Seq[Arm], totalFound: Int) extends GetListReply + final case class EntityList(xs: Seq[Arm], totalFound: Int) extends GetListReply case object AuthorizationError extends GetListReply with DomainError.AuthorizationError with DefaultAccessDeniedError @@ -71,7 +71,7 @@ object ArmService { sealed trait CreateReply object CreateReply { - case class Created(x: Arm) extends CreateReply + final case class Created(x: Arm) extends CreateReply type Error = CreateReply with DomainError @@ -101,7 +101,7 @@ object ArmService { case object AuthorizationError extends DeleteReply with DefaultAccessDeniedError with DomainError.AuthorizationError - case class CommonError(userMessage: String) extends DeleteReply with DomainError + final case class CommonError(userMessage: String) extends DeleteReply with DomainError } } diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/CategoryService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/CategoryService.scala deleted file mode 100644 index f8d820b..0000000 --- a/src/main/scala/xyz/driver/pdsuidomain/services/CategoryService.scala +++ /dev/null @@ -1,27 +0,0 @@ -package xyz.driver.pdsuidomain.services - -import xyz.driver.pdsuicommon.auth.AuthenticatedRequestContext -import xyz.driver.pdsuicommon.db.Sorting -import xyz.driver.pdsuicommon.error.DomainError -import xyz.driver.pdsuidomain.entities.CategoryWithLabels - -import scala.concurrent.Future - -object CategoryService { - sealed trait GetListReply - object GetListReply { - case class EntityList(xs: Seq[CategoryWithLabels], totalFound: Int) extends GetListReply - - case object AuthorizationError extends GetListReply with DomainError.AuthorizationError { - def userMessage: String = "Access denied" - } - } -} - -trait CategoryService { - - import CategoryService._ - - def getAll(sorting: Option[Sorting] = None)( - implicit requestContext: AuthenticatedRequestContext): Future[GetListReply] -} diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/CriterionService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/CriterionService.scala index ffb7843..fd9268b 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/CriterionService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/CriterionService.scala @@ -21,7 +21,7 @@ object CriterionService { def userMessage: String = "Access denied" } - case class RichCriterion(criterion: Criterion, armIds: Seq[LongId[Arm]], labels: Seq[CriterionLabel]) + final case class RichCriterion(criterion: Criterion, armIds: Seq[LongId[Arm]], labels: Seq[CriterionLabel]) object RichCriterion { implicit def toPhiString(x: RichCriterion): PhiString = { import x._ @@ -33,17 +33,17 @@ object CriterionService { object CreateReply { type Error = CreateReply with DomainError - case class Created(x: RichCriterion) extends CreateReply + final case class Created(x: RichCriterion) extends CreateReply case object AuthorizationError extends CreateReply with DomainError.AuthorizationError with DefaultAccessDeniedError - case class CommonError(userMessage: String) extends CreateReply with DomainError + final case class CommonError(userMessage: String) extends CreateReply with DomainError } sealed trait GetByIdReply object GetByIdReply { - case class Entity(x: RichCriterion) extends GetByIdReply + final case class Entity(x: RichCriterion) extends GetByIdReply type Error = GetByIdReply with DomainError @@ -52,7 +52,7 @@ object CriterionService { case object AuthorizationError extends GetByIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError - case class CommonError(userMessage: String)(implicit requestContext: AuthenticatedRequestContext) + final case class CommonError(userMessage: String)(implicit requestContext: AuthenticatedRequestContext) extends GetByIdReply with DomainError implicit def toPhiString(reply: GetByIdReply): PhiString = reply match { @@ -63,7 +63,7 @@ object CriterionService { sealed trait GetListReply object GetListReply { - case class EntityList(xs: Seq[RichCriterion], totalFound: Int, lastUpdate: Option[LocalDateTime]) + final case class EntityList(xs: Seq[RichCriterion], totalFound: Int, lastUpdate: Option[LocalDateTime]) extends GetListReply case object AuthorizationError @@ -74,14 +74,14 @@ object CriterionService { object UpdateReply { type Error = UpdateReply with DomainError - case class Updated(updated: RichCriterion) extends UpdateReply + final case class Updated(updated: RichCriterion) extends UpdateReply case object NotFoundError extends UpdateReply with DefaultNotFoundError with DomainError.NotFoundError case object AuthorizationError extends UpdateReply with DefaultAccessDeniedError with DomainError.AuthorizationError - case class CommonError(userMessage: String) extends UpdateReply with DomainError + final case class CommonError(userMessage: String) extends UpdateReply with DomainError } sealed trait DeleteReply @@ -95,7 +95,7 @@ object CriterionService { case object AuthorizationError extends DeleteReply with DefaultAccessDeniedError with DomainError.AuthorizationError - case class CommonError(userMessage: String) extends DeleteReply with DomainError + final case class CommonError(userMessage: String) extends DeleteReply with DomainError } } diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/DocumentService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/DocumentService.scala index 2c01a7e..9338a59 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/DocumentService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/DocumentService.scala @@ -23,7 +23,7 @@ object DocumentService { sealed trait GetByIdReply object GetByIdReply { - case class Entity(x: Document) extends GetByIdReply + final case class Entity(x: Document) extends GetByIdReply type Error = GetByIdReply with DomainError @@ -32,7 +32,7 @@ object DocumentService { case object AuthorizationError extends GetByIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError - case class CommonError(userMessage: String)(implicit requestContext: AuthenticatedRequestContext) + final case class CommonError(userMessage: String)(implicit requestContext: AuthenticatedRequestContext) extends GetByIdReply with DomainError implicit def toPhiString(reply: GetByIdReply): PhiString = reply match { @@ -43,19 +43,20 @@ object DocumentService { sealed trait GetListReply object GetListReply { - case class EntityList(xs: Seq[Document], totalFound: Int, lastUpdate: Option[LocalDateTime]) extends GetListReply + final case class EntityList(xs: Seq[Document], totalFound: Int, lastUpdate: Option[LocalDateTime]) + extends GetListReply type Error = GetListReply with DomainError case object AuthorizationError extends GetListReply with DefaultAccessDeniedError with DomainError.AuthorizationError - case class CommonError(userMessage: String) extends GetListReply with DomainError + final case class CommonError(userMessage: String) extends GetListReply with DomainError } sealed trait CreateReply object CreateReply { - case class Created(x: Document) extends CreateReply + final case class Created(x: Document) extends CreateReply type Error = CreateReply with DomainError @@ -64,12 +65,12 @@ object DocumentService { case object AuthorizationError extends CreateReply with DefaultAccessDeniedError with DomainError.AuthorizationError - case class CommonError(userMessage: String) extends CreateReply with DomainError + final case class CommonError(userMessage: String) extends CreateReply with DomainError } sealed trait UpdateReply object UpdateReply { - case class Updated(updated: Document) extends UpdateReply + final case class Updated(updated: Document) extends UpdateReply type Error = UpdateReply with DomainError @@ -78,7 +79,7 @@ object DocumentService { case object AuthorizationError extends UpdateReply with DefaultAccessDeniedError with DomainError.AuthorizationError - case class CommonError(userMessage: String) extends UpdateReply with DomainError + final case class CommonError(userMessage: String) extends UpdateReply with DomainError implicit def toPhiString(reply: UpdateReply): PhiString = reply match { case Updated(x) => phi"Updated($x)" @@ -97,7 +98,7 @@ object DocumentService { case object AuthorizationError extends DeleteReply with DefaultAccessDeniedError with DomainError.AuthorizationError - case class CommonError(userMessage: String) extends DeleteReply with DomainError + final case class CommonError(userMessage: String) extends DeleteReply with DomainError } } diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/DocumentTypeService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/DocumentTypeService.scala index 1268cbf..0f1bf76 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/DocumentTypeService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/DocumentTypeService.scala @@ -11,7 +11,7 @@ import scala.concurrent.Future object DocumentTypeService { sealed trait GetListReply object GetListReply { - case class EntityList(xs: Seq[DocumentType], totalFound: Int) extends GetListReply + final case class EntityList(xs: Seq[DocumentType], totalFound: Int) extends GetListReply case object AuthorizationError extends GetListReply with DomainError.AuthorizationError { def userMessage: String = "Access denied" } diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/ExportService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/ExportService.scala index ddc927d..a313703 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/ExportService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/ExportService.scala @@ -18,7 +18,7 @@ object ExportService { object GetPatientReply { type Error = GetPatientReply with DomainError - case class Entity(x: ExportPatientWithLabels) extends GetPatientReply + final case class Entity(x: ExportPatientWithLabels) extends GetPatientReply case object NotFoundError extends GetPatientReply with DomainError.NotFoundError { def userMessage: String = "Patient not found" @@ -27,7 +27,7 @@ object ExportService { sealed trait GetTrialListReply object GetTrialListReply { - case class EntityList(xs: Seq[ExportTrial], totalFound: Int, lastUpdate: Option[LocalDateTime]) + final case class EntityList(xs: Seq[ExportTrial], totalFound: Int, lastUpdate: Option[LocalDateTime]) extends GetTrialListReply } @@ -35,7 +35,7 @@ object ExportService { object GetTrialReply { type Error = GetTrialReply with DomainError - case class Entity(x: ExportTrialWithLabels) extends GetTrialReply + final case class Entity(x: ExportTrialWithLabels) extends GetTrialReply case object NotFoundError extends GetTrialReply with DomainError.NotFoundError { def userMessage: String = "Trial not found" diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/ExtractedDataService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/ExtractedDataService.scala index 93cb3ea..afb994e 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/ExtractedDataService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/ExtractedDataService.scala @@ -19,7 +19,7 @@ object ExtractedDataService { def userMessage: String = "Access denied" } - case class RichExtractedData(extractedData: ExtractedData, labels: List[ExtractedDataLabel]) + final case class RichExtractedData(extractedData: ExtractedData, labels: List[ExtractedDataLabel]) object RichExtractedData { implicit def toPhiString(x: RichExtractedData): PhiString = { @@ -31,19 +31,19 @@ object ExtractedDataService { sealed trait GetByIdReply object GetByIdReply { type Error = GetByIdReply with DomainError - case class Entity(x: RichExtractedData) extends GetByIdReply + final case class Entity(x: RichExtractedData) extends GetByIdReply case object AuthorizationError extends GetByIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError case object NotFoundError extends GetByIdReply with DefaultNotFoundError with DomainError.NotFoundError - case class CommonError(userMessage: String) extends GetByIdReply with DomainError + final case class CommonError(userMessage: String) extends GetByIdReply with DomainError } sealed trait GetListReply object GetListReply { - case class EntityList(xs: Seq[RichExtractedData], totalFound: Int) extends GetListReply + final case class EntityList(xs: Seq[RichExtractedData], totalFound: Int) extends GetListReply case object AuthorizationError extends GetListReply with DomainError.AuthorizationError with DefaultAccessDeniedError @@ -52,25 +52,25 @@ object ExtractedDataService { sealed trait CreateReply object CreateReply { type Error = CreateReply with DomainError - case class Created(x: RichExtractedData) extends CreateReply + final case class Created(x: RichExtractedData) extends CreateReply case object AuthorizationError extends CreateReply with DomainError.AuthorizationError with DefaultAccessDeniedError - case class CommonError(userMessage: String) extends CreateReply with DomainError + final case class CommonError(userMessage: String) extends CreateReply with DomainError } sealed trait UpdateReply object UpdateReply { type Error = UpdateReply with DomainError - case class Updated(updated: RichExtractedData) extends UpdateReply + final case class Updated(updated: RichExtractedData) extends UpdateReply case object AuthorizationError extends UpdateReply with DefaultAccessDeniedError with DomainError.AuthorizationError case object NotFoundError extends UpdateReply with DefaultNotFoundError with DomainError.NotFoundError - case class CommonError(userMessage: String) extends UpdateReply with DomainError + final case class CommonError(userMessage: String) extends UpdateReply with DomainError } sealed trait DeleteReply @@ -83,7 +83,7 @@ object ExtractedDataService { case object NotFoundError extends DeleteReply with DefaultNotFoundError with DomainError.NotFoundError - case class CommonError(userMessage: String) extends DeleteReply with DomainError + final case class CommonError(userMessage: String) extends DeleteReply with DomainError } } diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/HypothesisService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/HypothesisService.scala index 7fccf7f..52cd6c8 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/HypothesisService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/HypothesisService.scala @@ -10,7 +10,7 @@ import scala.concurrent.Future object HypothesisService { sealed trait GetListReply object GetListReply { - case class EntityList(xs: Seq[Hypothesis], totalFound: Int) extends GetListReply + final case class EntityList(xs: Seq[Hypothesis], totalFound: Int) extends GetListReply case object AuthorizationError extends GetListReply with DomainError.AuthorizationError { def userMessage: String = "Access denied" diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/InterventionService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/InterventionService.scala index a4b380f..439e456 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/InterventionService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/InterventionService.scala @@ -21,7 +21,7 @@ object InterventionService { sealed trait GetListReply object GetListReply { - case class EntityList(xs: Seq[InterventionWithArms], totalFound: Int) extends GetListReply + final case class EntityList(xs: Seq[InterventionWithArms], totalFound: Int) extends GetListReply case object AuthorizationError extends GetListReply with DomainError.AuthorizationError with DefaultAccessDeniedError @@ -29,7 +29,7 @@ object InterventionService { sealed trait GetByIdReply object GetByIdReply { - case class Entity(x: InterventionWithArms) extends GetByIdReply + final case class Entity(x: InterventionWithArms) extends GetByIdReply type Error = GetByIdReply with DomainError @@ -38,7 +38,7 @@ object InterventionService { case object AuthorizationError extends GetByIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError - case class CommonError(userMessage: String)(implicit requestContext: AuthenticatedRequestContext) + final case class CommonError(userMessage: String)(implicit requestContext: AuthenticatedRequestContext) extends GetByIdReply with DomainError implicit def toPhiString(reply: GetByIdReply): PhiString = reply match { @@ -51,14 +51,14 @@ object InterventionService { object UpdateReply { type Error = UpdateReply with DomainError - case class Updated(updated: InterventionWithArms) extends UpdateReply + final case class Updated(updated: InterventionWithArms) extends UpdateReply case object NotFoundError extends UpdateReply with DefaultNotFoundError with DomainError.NotFoundError case object AuthorizationError extends UpdateReply with DefaultAccessDeniedError with DomainError.AuthorizationError - case class CommonError(userMessage: String) extends UpdateReply with DomainError + final case class CommonError(userMessage: String) extends UpdateReply with DomainError } } diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/InterventionTypeService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/InterventionTypeService.scala index 5805655..298a92d 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/InterventionTypeService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/InterventionTypeService.scala @@ -10,7 +10,7 @@ import scala.concurrent.Future object InterventionTypeService { sealed trait GetListReply object GetListReply { - case class EntityList(xs: Seq[InterventionType], totalFound: Int) extends GetListReply + final case class EntityList(xs: Seq[InterventionType], totalFound: Int) extends GetListReply case object AuthorizationError extends GetListReply with DomainError.AuthorizationError { def userMessage: String = "Access denied" diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/KeywordService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/KeywordService.scala deleted file mode 100644 index 8824c41..0000000 --- a/src/main/scala/xyz/driver/pdsuidomain/services/KeywordService.scala +++ /dev/null @@ -1,28 +0,0 @@ -package xyz.driver.pdsuidomain.services - -import xyz.driver.pdsuicommon.auth.AuthenticatedRequestContext -import xyz.driver.pdsuicommon.db.Sorting -import xyz.driver.pdsuicommon.error.DomainError -import xyz.driver.pdsuidomain.entities.KeywordWithLabels - -import scala.concurrent.Future - -object KeywordService { - - sealed trait GetListReply - object GetListReply { - case class EntityList(xs: Seq[KeywordWithLabels], totalFound: Int) extends GetListReply - - case object AuthorizationError extends GetListReply with DomainError.AuthorizationError { - def userMessage: String = "Access denied" - } - } -} - -trait KeywordService { - - import KeywordService._ - - def getAll(sorting: Option[Sorting] = None)( - implicit requestContext: AuthenticatedRequestContext): Future[GetListReply] -} diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/LabelService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/LabelService.scala deleted file mode 100644 index aadc5fb..0000000 --- a/src/main/scala/xyz/driver/pdsuidomain/services/LabelService.scala +++ /dev/null @@ -1,27 +0,0 @@ -package xyz.driver.pdsuidomain.services - -import xyz.driver.pdsuicommon.auth.AuthenticatedRequestContext -import xyz.driver.pdsuicommon.db.Sorting -import xyz.driver.pdsuicommon.error.DomainError -import xyz.driver.pdsuidomain.entities.Label - -import scala.concurrent.Future - -object LabelService { - - sealed trait GetListReply - object GetListReply { - case class EntityList(xs: Seq[Label], totalFound: Int) extends GetListReply - - case object AuthorizationError extends GetListReply with DomainError.AuthorizationError { - def userMessage: String = "Access denied" - } - } -} - -trait LabelService { - import LabelService._ - - def getAll(sorting: Option[Sorting] = None)( - implicit requestContext: AuthenticatedRequestContext): Future[GetListReply] -} diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/MedicalRecordService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/MedicalRecordService.scala index f077d82..b2fc9f9 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/MedicalRecordService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/MedicalRecordService.scala @@ -25,13 +25,13 @@ object MedicalRecordService { sealed trait GetByIdReply object GetByIdReply { - case class Entity(x: MedicalRecord) extends GetByIdReply + final case class Entity(x: MedicalRecord) extends GetByIdReply type Error = GetByIdReply with DomainError case object NotFoundError extends GetByIdReply with DomainError.NotFoundError with DefaultNotFoundError - case class CommonError(userMessage: String) extends GetByIdReply with DomainError + final case class CommonError(userMessage: String) extends GetByIdReply with DomainError case object AuthorizationError extends GetByIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError @@ -41,7 +41,7 @@ object MedicalRecordService { object GetPdfSourceReply { type Error = GetPdfSourceReply with DomainError - case class Entity(x: PdfSource.Channel) extends GetPdfSourceReply + final case class Entity(x: PdfSource.Channel) extends GetPdfSourceReply case object AuthorizationError extends GetPdfSourceReply with DomainError.AuthorizationError with DefaultAccessDeniedError @@ -52,12 +52,12 @@ object MedicalRecordService { case object RecordNotFoundError extends GetPdfSourceReply with DomainError.NotFoundError with DefaultNotFoundError - case class CommonError(userMessage: String) extends GetPdfSourceReply with DomainError + final case class CommonError(userMessage: String) extends GetPdfSourceReply with DomainError } sealed trait GetListReply object GetListReply { - case class EntityList(xs: Seq[MedicalRecord], totalFound: Int, lastUpdate: Option[LocalDateTime]) + final case class EntityList(xs: Seq[MedicalRecord], totalFound: Int, lastUpdate: Option[LocalDateTime]) extends GetListReply case object AuthorizationError @@ -66,24 +66,24 @@ object MedicalRecordService { sealed trait CreateReply object CreateReply { - case class Created(x: MedicalRecord) extends CreateReply + final case class Created(x: MedicalRecord) extends CreateReply } sealed trait UpdateReply object UpdateReply { type Error = UpdateReply with DomainError - case class Updated(updated: MedicalRecord) extends UpdateReply + final case class Updated(updated: MedicalRecord) extends UpdateReply case object NotFoundError extends UpdateReply with DefaultNotFoundError with DomainError.NotFoundError case object AuthorizationError extends UpdateReply with DefaultAccessDeniedError with DomainError.AuthorizationError - case class CommonError(userMessage: String) extends UpdateReply with DomainError + final case class CommonError(userMessage: String) extends UpdateReply with DomainError } - case class Settings(pdfSourceBucket: String) + final case class Settings(pdfSourceBucket: String) } trait MedicalRecordService { diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/MessageService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/MessageService.scala index df57e17..6718f26 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/MessageService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/MessageService.scala @@ -23,18 +23,18 @@ object MessageService { sealed trait CreateReply object CreateReply { type Error = CreateReply with DomainError - case class Created(x: Message) extends CreateReply + final case class Created(x: Message) extends CreateReply case object AuthorizationError extends CreateReply with DomainError.AuthorizationError with DefaultAccessDeniedError - case class CommonError(userMessage: String) extends CreateReply with DomainError + final case class CommonError(userMessage: String) extends CreateReply with DomainError } sealed trait GetByIdReply object GetByIdReply { type Error = GetByIdReply with DomainError - case class Entity(x: Message) extends GetByIdReply - case object NotFoundError extends GetByIdReply with DomainError.NotFoundError with DefaultNotFoundError - case class CommonError(userMessage: String) extends GetByIdReply with DomainError + final case class Entity(x: Message) extends GetByIdReply + case object NotFoundError extends GetByIdReply with DomainError.NotFoundError with DefaultNotFoundError + final case class CommonError(userMessage: String) extends GetByIdReply with DomainError case object AuthorizationError extends GetByIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError } @@ -42,7 +42,8 @@ object MessageService { sealed trait GetListReply object GetListReply { type Error = GetListReply with DomainError - case class EntityList(xs: Seq[Message], totalFound: Int, lastUpdate: Option[LocalDateTime]) extends GetListReply + final case class EntityList(xs: Seq[Message], totalFound: Int, lastUpdate: Option[LocalDateTime]) + extends GetListReply case object AuthorizationError extends GetListReply with DomainError.AuthorizationError with DefaultAccessDeniedError } @@ -50,10 +51,10 @@ object MessageService { sealed trait UpdateReply object UpdateReply { type Error = UpdateReply with DomainError - case class Updated(updated: Message) extends UpdateReply + final case class Updated(updated: Message) extends UpdateReply case object AuthorizationError extends UpdateReply with DomainError.AuthorizationError with DefaultAccessDeniedError - case class CommonError(userMessage: String) extends UpdateReply with DomainError + final case class CommonError(userMessage: String) extends UpdateReply with DomainError } sealed trait DeleteReply @@ -62,8 +63,8 @@ object MessageService { case object Deleted extends DeleteReply case object AuthorizationError extends DeleteReply with DomainError.AuthorizationError with DefaultAccessDeniedError - case object NotFoundError extends DeleteReply with DomainError.NotFoundError with DefaultNotFoundError - case class CommonError(userMessage: String) extends DeleteReply with DomainError + case object NotFoundError extends DeleteReply with DomainError.NotFoundError with DefaultNotFoundError + final case class CommonError(userMessage: String) extends DeleteReply with DomainError } } diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/PatientCriterionService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/PatientCriterionService.scala index c2f7b50..9ff3879 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/PatientCriterionService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/PatientCriterionService.scala @@ -13,9 +13,9 @@ import scala.concurrent.Future object PatientCriterionService { - case class DraftPatientCriterion(id: LongId[PatientCriterion], - eligibilityStatus: Option[FuzzyValue], - isVerified: Option[Boolean]) { + final case class DraftPatientCriterion(id: LongId[PatientCriterion], + eligibilityStatus: Option[FuzzyValue], + isVerified: Option[Boolean]) { def applyTo(orig: PatientCriterion) = { orig.copy( eligibilityStatus = eligibilityStatus.orElse(orig.eligibilityStatus), @@ -40,9 +40,9 @@ object PatientCriterionService { object GetListReply { type Error = GetListReply with DomainError - case class EntityList(xs: Seq[(PatientCriterion, LongId[Label], List[PatientCriterionArm])], - totalFound: Int, - lastUpdate: Option[LocalDateTime]) + final case class EntityList(xs: Seq[(PatientCriterion, LongId[Label], List[PatientCriterionArm])], + totalFound: Int, + lastUpdate: Option[LocalDateTime]) extends GetListReply case object AuthorizationError @@ -51,7 +51,7 @@ object PatientCriterionService { case object PatientNotFoundError extends GetListReply with DomainError.NotFoundError with DefaultPatientNotFoundError - case class CommonError(userMessage: String) extends GetListReply with DomainError + final case class CommonError(userMessage: String) extends GetListReply with DomainError } @@ -59,7 +59,7 @@ object PatientCriterionService { object GetByIdReply { type Error = GetByIdReply with DomainError - case class Entity(x: PatientCriterion, labelId: LongId[Label], armList: List[PatientCriterionArm]) + final case class Entity(x: PatientCriterion, labelId: LongId[Label], armList: List[PatientCriterionArm]) extends GetByIdReply case object AuthorizationError @@ -70,7 +70,7 @@ object PatientCriterionService { case object PatientNotFoundError extends GetByIdReply with DomainError.NotFoundError with DefaultPatientNotFoundError - case class CommonError(userMessage: String) extends GetByIdReply with DomainError + final case class CommonError(userMessage: String) extends GetByIdReply with DomainError implicit def toPhiString(reply: GetByIdReply): PhiString = reply match { case x: DomainError => phi"GetByIdReply.Error($x)" @@ -82,7 +82,7 @@ object PatientCriterionService { object UpdateReply { type Error = UpdateReply with DomainError - case class Updated(x: PatientCriterion, labelId: LongId[Label], armList: List[PatientCriterionArm]) + final case class Updated(x: PatientCriterion, labelId: LongId[Label], armList: List[PatientCriterionArm]) extends UpdateReply case object UpdatedList extends UpdateReply @@ -93,7 +93,7 @@ object PatientCriterionService { case object PatientNotFoundError extends UpdateReply with DomainError.NotFoundError with DefaultPatientNotFoundError - case class CommonError(userMessage: String) extends UpdateReply with DomainError + final case class CommonError(userMessage: String) extends UpdateReply with DomainError } } diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/PatientEligibleTrialService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/PatientEligibleTrialService.scala index 12cd4f3..2c17f74 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/PatientEligibleTrialService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/PatientEligibleTrialService.scala @@ -23,7 +23,10 @@ object PatientEligibleTrialService { def userMessage: String = "Access denied" } - case class RichPatientEligibleTrial(trial: Trial, group: PatientTrialArmGroupView, arms: List[PatientCriterionArm]) + final case class RichPatientEligibleTrial(trial: Trial, + group: PatientTrialArmGroupView, + arms: List[PatientCriterionArm]) + object RichPatientEligibleTrial { implicit def toPhiString(x: RichPatientEligibleTrial): PhiString = { phi"RichPatientEligibleTrial(group=${x.group}, trial=${x.trial}, arms=${x.arms})" @@ -32,7 +35,7 @@ object PatientEligibleTrialService { sealed trait GetListReply object GetListReply { - case class EntityList(xs: Seq[RichPatientEligibleTrial], totalFound: Int) extends GetListReply + final case class EntityList(xs: Seq[RichPatientEligibleTrial], totalFound: Int) extends GetListReply case object AuthorizationError extends GetListReply with DomainError.AuthorizationError with DefaultAccessDeniedError @@ -40,12 +43,12 @@ object PatientEligibleTrialService { case object PatientNotFoundError extends GetListReply with DefaultPatientNotFoundError with DomainError.NotFoundError - case class CommonError(userMessage: String) extends GetListReply with DomainError + final case class CommonError(userMessage: String) extends GetListReply with DomainError } sealed trait GetByIdReply object GetByIdReply { - case class Entity(x: RichPatientEligibleTrial) extends GetByIdReply + final case class Entity(x: RichPatientEligibleTrial) extends GetByIdReply type Error = GetByIdReply with DomainError @@ -57,7 +60,7 @@ object PatientEligibleTrialService { case object AuthorizationError extends GetByIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError - case class CommonError(userMessage: String) extends GetByIdReply with DomainError + final case class CommonError(userMessage: String) extends GetByIdReply with DomainError implicit def toPhiString(reply: GetByIdReply): PhiString = reply match { case x: DomainError => phi"GetByIdReply.Error($x)" @@ -67,7 +70,7 @@ object PatientEligibleTrialService { sealed trait GetCriterionListOfGroupReply object GetCriterionListOfGroupReply { - case class EntityList(xs: Seq[(PatientCriterion, LongId[Label], List[PatientCriterionArm])], totalFound: Int) + final case class EntityList(xs: Seq[(PatientCriterion, LongId[Label], List[PatientCriterionArm])], totalFound: Int) extends GetCriterionListOfGroupReply type Error = GetCriterionListOfGroupReply with DomainError @@ -81,14 +84,14 @@ object PatientEligibleTrialService { case object PatientNotFoundError extends GetCriterionListOfGroupReply with DefaultPatientNotFoundError with DomainError.NotFoundError - case class CommonError(userMessage: String) extends GetCriterionListOfGroupReply with DomainError + final case class CommonError(userMessage: String) extends GetCriterionListOfGroupReply with DomainError } sealed trait UpdateReply object UpdateReply { type Error = UpdateReply with DomainError - case class Updated(updated: RichPatientEligibleTrial) extends UpdateReply + final case class Updated(updated: RichPatientEligibleTrial) extends UpdateReply case object NotFoundError extends UpdateReply with DefaultNotFoundError with DomainError.NotFoundError @@ -98,7 +101,7 @@ object PatientEligibleTrialService { case object AuthorizationError extends UpdateReply with DomainError.AuthorizationError with DefaultAccessDeniedError - case class CommonError(userMessage: String) extends UpdateReply with DomainError + final case class CommonError(userMessage: String) extends UpdateReply with DomainError implicit def toPhiString(reply: UpdateReply): PhiString = reply match { case Updated(x) => phi"Updated($x)" diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/PatientHypothesisService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/PatientHypothesisService.scala index a15e11f..f782cab 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/PatientHypothesisService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/PatientHypothesisService.scala @@ -25,7 +25,7 @@ object PatientHypothesisService { sealed trait GetListReply object GetListReply { - case class EntityList(xs: Seq[(PatientHypothesis, Boolean)], totalFound: Int) extends GetListReply + final case class EntityList(xs: Seq[(PatientHypothesis, Boolean)], totalFound: Int) extends GetListReply case object AuthorizationError extends GetListReply with DomainError.AuthorizationError with DefaultAccessDeniedError @@ -33,12 +33,12 @@ object PatientHypothesisService { case object PatientNotFoundError extends GetListReply with DefaultPatientNotFoundError with DomainError.NotFoundError - case class CommonError(userMessage: String) extends GetListReply with DomainError + final case class CommonError(userMessage: String) extends GetListReply with DomainError } sealed trait GetByIdReply object GetByIdReply { - case class Entity(x: PatientHypothesis, isRequired: Boolean) extends GetByIdReply + final case class Entity(x: PatientHypothesis, isRequired: Boolean) extends GetByIdReply type Error = GetByIdReply with DomainError @@ -50,7 +50,7 @@ object PatientHypothesisService { case object AuthorizationError extends GetByIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError - case class CommonError(userMessage: String) extends GetByIdReply with DomainError + final case class CommonError(userMessage: String) extends GetByIdReply with DomainError implicit def toPhiString(reply: GetByIdReply): PhiString = reply match { case x: DomainError => phi"GetByIdReply.Error($x)" @@ -62,7 +62,7 @@ object PatientHypothesisService { object UpdateReply { type Error = UpdateReply with DomainError - case class Updated(updated: PatientHypothesis) extends UpdateReply + final case class Updated(updated: PatientHypothesis) extends UpdateReply case object NotFoundError extends UpdateReply with DefaultNotFoundError with DomainError.NotFoundError @@ -72,7 +72,7 @@ object PatientHypothesisService { case object AuthorizationError extends UpdateReply with DomainError.AuthorizationError with DefaultAccessDeniedError - case class CommonError(userMessage: String) extends UpdateReply with DomainError + final case class CommonError(userMessage: String) extends UpdateReply with DomainError implicit def toPhiString(reply: UpdateReply): PhiString = reply match { case Updated(x) => phi"Updated($x)" diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/PatientLabelEvidenceService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/PatientLabelEvidenceService.scala index 2586798..56e2e3d 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/PatientLabelEvidenceService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/PatientLabelEvidenceService.scala @@ -20,7 +20,7 @@ object PatientLabelEvidenceService { sealed trait GetByIdReply object GetByIdReply { - case class Entity(x: PatientLabelEvidenceView) extends GetByIdReply + final case class Entity(x: PatientLabelEvidenceView) extends GetByIdReply type Error = GetByIdReply with DomainError @@ -34,7 +34,7 @@ object PatientLabelEvidenceService { sealed trait GetListReply object GetListReply { - case class EntityList(xs: Seq[PatientLabelEvidenceView], totalFound: Int) extends GetListReply + final case class EntityList(xs: Seq[PatientLabelEvidenceView], totalFound: Int) extends GetListReply type Error = GetListReply with DomainError @@ -44,7 +44,7 @@ object PatientLabelEvidenceService { case object AuthorizationError extends GetListReply with DomainError.AuthorizationError with DefaultAccessDeniedError - case class CommonError(userMessage: String) extends GetListReply with DomainError + final case class CommonError(userMessage: String) extends GetListReply with DomainError } } diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/PatientLabelService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/PatientLabelService.scala index 5fa2a4d..71b8bd4 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/PatientLabelService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/PatientLabelService.scala @@ -25,7 +25,7 @@ object PatientLabelService { sealed trait GetListReply object GetListReply { - case class EntityList(xs: Seq[(PatientLabel, Boolean)], totalFound: Int) extends GetListReply + final case class EntityList(xs: Seq[(PatientLabel, Boolean)], totalFound: Int) extends GetListReply case object AuthorizationError extends GetListReply with DomainError.AuthorizationError with DefaultAccessDeniedError @@ -33,12 +33,12 @@ object PatientLabelService { case object PatientNotFoundError extends GetListReply with DefaultPatientNotFoundError with DomainError.NotFoundError - case class CommonError(userMessage: String) extends GetListReply with DomainError + final case class CommonError(userMessage: String) extends GetListReply with DomainError } sealed trait GetDefiningCriteriaListReply object GetDefiningCriteriaListReply { - case class EntityList(xs: Seq[PatientLabel], totalFound: Int) extends GetDefiningCriteriaListReply + final case class EntityList(xs: Seq[PatientLabel], totalFound: Int) extends GetDefiningCriteriaListReply case object AuthorizationError extends GetDefiningCriteriaListReply with DomainError.AuthorizationError with DefaultAccessDeniedError @@ -46,12 +46,12 @@ object PatientLabelService { case object PatientNotFoundError extends GetDefiningCriteriaListReply with DefaultPatientNotFoundError with DomainError.NotFoundError - case class CommonError(userMessage: String) extends GetDefiningCriteriaListReply with DomainError + final case class CommonError(userMessage: String) extends GetDefiningCriteriaListReply with DomainError } sealed trait GetByLabelIdReply object GetByLabelIdReply { - case class Entity(x: PatientLabel, isVerified: Boolean) extends GetByLabelIdReply + final case class Entity(x: PatientLabel, isVerified: Boolean) extends GetByLabelIdReply type Error = GetByLabelIdReply with DomainError @@ -63,7 +63,7 @@ object PatientLabelService { case object AuthorizationError extends GetByLabelIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError - case class CommonError(userMessage: String) extends GetByLabelIdReply with DomainError + final case class CommonError(userMessage: String) extends GetByLabelIdReply with DomainError implicit def toPhiString(reply: GetByLabelIdReply): PhiString = reply match { case x: DomainError => phi"GetByIdReply.Error($x)" @@ -75,7 +75,7 @@ object PatientLabelService { object UpdateReply { type Error = UpdateReply with DomainError - case class Updated(updated: PatientLabel, isVerified: Boolean) extends UpdateReply + final case class Updated(updated: PatientLabel, isVerified: Boolean) extends UpdateReply case object NotFoundError extends UpdateReply with DefaultNotFoundError with DomainError.NotFoundError @@ -85,7 +85,7 @@ object PatientLabelService { case object AuthorizationError extends UpdateReply with DomainError.AuthorizationError with DefaultAccessDeniedError - case class CommonError(userMessage: String) extends UpdateReply with DomainError + final case class CommonError(userMessage: String) extends UpdateReply with DomainError implicit def toPhiString(reply: UpdateReply): PhiString = reply match { case Updated(x, y) => phi"Updated($x, $y)" diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/PatientService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/PatientService.scala index 4619011..ce70934 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/PatientService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/PatientService.scala @@ -23,7 +23,8 @@ object PatientService { sealed trait GetListReply object GetListReply { - case class EntityList(xs: Seq[Patient], totalFound: Int, lastUpdate: Option[LocalDateTime]) extends GetListReply + final case class EntityList(xs: Seq[Patient], totalFound: Int, lastUpdate: Option[LocalDateTime]) + extends GetListReply case object AuthorizationError extends GetListReply with DomainError.AuthorizationError with DefaultAccessDeniedError @@ -31,7 +32,7 @@ object PatientService { sealed trait GetByIdReply object GetByIdReply { - case class Entity(x: Patient) extends GetByIdReply + final case class Entity(x: Patient) extends GetByIdReply type Error = GetByIdReply with DomainError @@ -40,7 +41,7 @@ object PatientService { case object AuthorizationError extends GetByIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError - case class CommonError(userMessage: String)(implicit requestContext: AuthenticatedRequestContext) + final case class CommonError(userMessage: String)(implicit requestContext: AuthenticatedRequestContext) extends GetByIdReply with DomainError implicit def toPhiString(reply: GetByIdReply): PhiString = reply match { @@ -53,14 +54,14 @@ object PatientService { object UpdateReply { type Error = UpdateReply with DomainError - case class Updated(updated: Patient) extends UpdateReply + final case class Updated(updated: Patient) extends UpdateReply case object NotFoundError extends UpdateReply with DefaultNotFoundError with DomainError.NotFoundError case object AuthorizationError extends UpdateReply with DefaultAccessDeniedError with DomainError.AuthorizationError - case class CommonError(userMessage: String) extends UpdateReply with DomainError + final case class CommonError(userMessage: String) extends UpdateReply with DomainError implicit def toPhiString(reply: UpdateReply): PhiString = reply match { case Updated(x) => phi"Updated($x)" diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/ProviderTypeService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/ProviderTypeService.scala index ef48aa0..cad17a4 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/ProviderTypeService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/ProviderTypeService.scala @@ -10,7 +10,7 @@ import scala.concurrent.Future object ProviderTypeService { sealed trait GetListReply object GetListReply { - case class EntityList(xs: Seq[ProviderType], totalFound: Int) extends GetListReply + final case class EntityList(xs: Seq[ProviderType], totalFound: Int) extends GetListReply case object AuthorizationError extends GetListReply with DomainError.AuthorizationError { def userMessage: String = "Access denied" } diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/QueueUploadService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/QueueUploadService.scala index f201fd9..340733d 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/QueueUploadService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/QueueUploadService.scala @@ -20,28 +20,28 @@ object QueueUploadService { object CreateReply { type Error = CreateReply with DomainError - case class Created(x: BridgeUploadQueue.Item) extends CreateReply + final case class Created(x: BridgeUploadQueue.Item) extends CreateReply case object AuthorizationError extends CreateReply with DomainError.AuthorizationError with DefaultAccessDeniedError - case class CommonError(userMessage: String) extends CreateReply with DomainError + final case class CommonError(userMessage: String) extends CreateReply with DomainError } sealed trait GetByIdReply object GetByIdReply { type Error = GetByIdReply with DomainError - case class Entity(x: BridgeUploadQueue.Item) extends GetByIdReply + final case class Entity(x: BridgeUploadQueue.Item) extends GetByIdReply case object AuthorizationError extends GetByIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError - case object NotFoundError extends GetByIdReply with DomainError.NotFoundError with DefaultNotFoundError - case class CommonError(userMessage: String) extends GetByIdReply with DomainError + case object NotFoundError extends GetByIdReply with DomainError.NotFoundError with DefaultNotFoundError + final case class CommonError(userMessage: String) extends GetByIdReply with DomainError } sealed trait GetListReply object GetListReply { type Error = GetListReply with DomainError - case class EntityList(xs: Seq[BridgeUploadQueue.Item], totalFound: Int) extends GetListReply + final case class EntityList(xs: Seq[BridgeUploadQueue.Item], totalFound: Int) extends GetListReply case object AuthorizationError extends GetListReply with DomainError.AuthorizationError with DefaultAccessDeniedError @@ -51,10 +51,10 @@ object QueueUploadService { object ResetReply { type Error = ResetReply with DomainError - case class Updated(updated: BridgeUploadQueue.Item) extends ResetReply - case object AuthorizationError extends ResetReply with DomainError.AuthorizationError with DefaultAccessDeniedError - case object NotFoundError extends ResetReply with DefaultNotFoundError with DomainError.NotFoundError - case class CommonError(userMessage: String) extends ResetReply with DomainError + final case class Updated(updated: BridgeUploadQueue.Item) extends ResetReply + case object AuthorizationError extends ResetReply with DomainError.AuthorizationError with DefaultAccessDeniedError + case object NotFoundError extends ResetReply with DefaultNotFoundError with DomainError.NotFoundError + final case class CommonError(userMessage: String) extends ResetReply with DomainError } } diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/ScrapedTrialsService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/ScrapedTrialsService.scala index b595584..233495e 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/ScrapedTrialsService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/ScrapedTrialsService.scala @@ -11,7 +11,7 @@ object ScrapedTrialsService { object GetRawTrialReply { type Error = GetRawTrialReply with DomainError - case class TrialRawEntity(rawTrial: ScrapedTrial) extends GetRawTrialReply + final case class TrialRawEntity(rawTrial: ScrapedTrial) extends GetRawTrialReply case object NotFoundError extends GetRawTrialReply with DomainError.NotFoundError { override def userMessage: String = "Raw clinical trial not found" @@ -20,12 +20,12 @@ object ScrapedTrialsService { sealed trait GetRawTrialOptReply object GetRawTrialOptReply { - case class TrialRawEntity(rawTrial: Option[ScrapedTrial]) extends GetRawTrialOptReply + final case class TrialRawEntity(rawTrial: Option[ScrapedTrial]) extends GetRawTrialOptReply } sealed trait GetAllRawTrialsExceptReply object GetAllRawTrialsExceptReply { - case class MultipleRawTrials(rawTrials: Seq[ScrapedTrial]) extends GetAllRawTrialsExceptReply + final case class MultipleRawTrials(rawTrials: Seq[ScrapedTrial]) extends GetAllRawTrialsExceptReply } sealed trait GetHtmlForReply @@ -35,7 +35,7 @@ object ScrapedTrialsService { /** * @param trialHtmlMap nctId -> html */ - case class HtmlMap(trialHtmlMap: TrialHtmlMap) extends GetHtmlForReply + final case class HtmlMap(trialHtmlMap: TrialHtmlMap) extends GetHtmlForReply } } diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/StudyDesignService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/StudyDesignService.scala index a1ce613..e98c1ec 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/StudyDesignService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/StudyDesignService.scala @@ -10,7 +10,7 @@ import scala.concurrent.Future object StudyDesignService { sealed trait GetListReply object GetListReply { - case class EntityList(xs: Seq[StudyDesign], totalFound: Int) extends GetListReply + final case class EntityList(xs: Seq[StudyDesign], totalFound: Int) extends GetListReply case object AuthorizationError extends GetListReply with DomainError.AuthorizationError { def userMessage: String = "Access denied" diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/TrialHistoryService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/TrialHistoryService.scala index 9ad9fc1..bcf1c53 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/TrialHistoryService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/TrialHistoryService.scala @@ -22,10 +22,10 @@ object TrialHistoryService { sealed trait GetListReply object GetListReply { - case class EntityList(xs: Seq[TrialHistory], totalFound: Int, lastUpdate: Option[LocalDateTime]) + final case class EntityList(xs: Seq[TrialHistory], totalFound: Int, lastUpdate: Option[LocalDateTime]) extends GetListReply - case object AuthorizationError + final case object AuthorizationError extends GetListReply with DomainError.AuthorizationError with DefaultAccessDeniedError } diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/TrialIssueService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/TrialIssueService.scala index df231ac..726feda 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/TrialIssueService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/TrialIssueService.scala @@ -23,18 +23,18 @@ object TrialIssueService { sealed trait CreateReply object CreateReply { type Error = CreateReply with DomainError - case class Created(x: TrialIssue) extends CreateReply + final case class Created(x: TrialIssue) extends CreateReply case object AuthorizationError extends CreateReply with DomainError.AuthorizationError with DefaultAccessDeniedError - case class CommonError(userMessage: String) extends CreateReply with DomainError + final case class CommonError(userMessage: String) extends CreateReply with DomainError } sealed trait GetByIdReply object GetByIdReply { type Error = GetByIdReply with DomainError - case class Entity(x: TrialIssue) extends GetByIdReply - case object NotFoundError extends GetByIdReply with DomainError.NotFoundError with DefaultNotFoundError - case class CommonError(userMessage: String) extends GetByIdReply with DomainError + final case class Entity(x: TrialIssue) extends GetByIdReply + case object NotFoundError extends GetByIdReply with DomainError.NotFoundError with DefaultNotFoundError + final case class CommonError(userMessage: String) extends GetByIdReply with DomainError case object AuthorizationError extends GetByIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError } @@ -42,7 +42,7 @@ object TrialIssueService { sealed trait GetListByTrialIdReply object GetListByTrialIdReply { type Error = GetListByTrialIdReply with DomainError - case class EntityList(xs: Seq[TrialIssue], totalFound: Int, lastUpdate: Option[LocalDateTime]) + final case class EntityList(xs: Seq[TrialIssue], totalFound: Int, lastUpdate: Option[LocalDateTime]) extends GetListByTrialIdReply case object NotFoundError extends GetListByTrialIdReply with DomainError.NotFoundError with DefaultNotFoundError case object AuthorizationError @@ -52,10 +52,10 @@ object TrialIssueService { sealed trait UpdateReply object UpdateReply { type Error = UpdateReply with DomainError - case class Updated(updated: TrialIssue) extends UpdateReply + final case class Updated(updated: TrialIssue) extends UpdateReply case object AuthorizationError extends UpdateReply with DomainError.AuthorizationError with DefaultAccessDeniedError - case class CommonError(userMessage: String) extends UpdateReply with DomainError + final case class CommonError(userMessage: String) extends UpdateReply with DomainError } sealed trait DeleteReply @@ -64,8 +64,8 @@ object TrialIssueService { case object Deleted extends DeleteReply case object AuthorizationError extends DeleteReply with DomainError.AuthorizationError with DefaultAccessDeniedError - case object NotFoundError extends DeleteReply with DomainError.NotFoundError with DefaultNotFoundError - case class CommonError(userMessage: String) extends DeleteReply with DomainError + case object NotFoundError extends DeleteReply with DomainError.NotFoundError with DefaultNotFoundError + final case class CommonError(userMessage: String) extends DeleteReply with DomainError } } diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/TrialService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/TrialService.scala index 47f76f2..3148842 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/TrialService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/TrialService.scala @@ -24,7 +24,8 @@ object TrialService { sealed trait GetListReply object GetListReply { - case class EntityList(xs: Seq[Trial], totalFound: Int, lastUpdate: Option[LocalDateTime]) extends GetListReply + final case class EntityList(xs: Seq[Trial], totalFound: Int, lastUpdate: Option[LocalDateTime]) + extends GetListReply case object AuthorizationError extends GetListReply with DomainError.AuthorizationError with DefaultAccessDeniedError @@ -32,7 +33,7 @@ object TrialService { sealed trait GetByIdReply object GetByIdReply { - case class Entity(x: Trial) extends GetByIdReply + final case class Entity(x: Trial) extends GetByIdReply type Error = GetByIdReply with DomainError @@ -41,7 +42,7 @@ object TrialService { case object AuthorizationError extends GetByIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError - case class CommonError(userMessage: String)(implicit requestContext: AuthenticatedRequestContext) + final case class CommonError(userMessage: String)(implicit requestContext: AuthenticatedRequestContext) extends GetByIdReply with DomainError implicit def toPhiString(reply: GetByIdReply): PhiString = reply match { @@ -54,7 +55,7 @@ object TrialService { object GetPdfSourceReply { type Error = GetPdfSourceReply with DomainError - case class Entity(x: PdfSource) extends GetPdfSourceReply + final case class Entity(x: PdfSource) extends GetPdfSourceReply case object AuthorizationError extends GetPdfSourceReply with DomainError.AuthorizationError with DefaultAccessDeniedError @@ -65,21 +66,21 @@ object TrialService { case object TrialNotFoundError extends GetPdfSourceReply with DomainError.NotFoundError with DefaultNotFoundError - case class CommonError(userMessage: String) extends GetPdfSourceReply with DomainError + final case class CommonError(userMessage: String) extends GetPdfSourceReply with DomainError } sealed trait UpdateReply object UpdateReply { type Error = UpdateReply with DomainError - case class Updated(updated: Trial) extends UpdateReply + final case class Updated(updated: Trial) extends UpdateReply case object NotFoundError extends UpdateReply with DefaultNotFoundError with DomainError.NotFoundError case object AuthorizationError extends UpdateReply with DefaultAccessDeniedError with DomainError.AuthorizationError - case class CommonError(userMessage: String) extends UpdateReply with DomainError + final case class CommonError(userMessage: String) extends UpdateReply with DomainError implicit def toPhiString(reply: UpdateReply): PhiString = reply match { case Updated(x) => phi"Updated($x)" @@ -119,5 +120,4 @@ trait TrialService { def unassign(origTrial: Trial)(implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply] - def removeTrialDetails(trialId: StringId[Trial]): Unit } diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/UserHistoryService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/UserHistoryService.scala index e342813..a29e041 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/UserHistoryService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/UserHistoryService.scala @@ -11,7 +11,7 @@ object UserHistoryService { sealed trait GetListReply object GetListReply { - case class EntityList(xs: Seq[UserHistory], totalFound: Int) extends GetListReply + final case class EntityList(xs: Seq[UserHistory], totalFound: Int) extends GetListReply case object AuthorizationError extends GetListReply with DomainError.AuthorizationError { def userMessage: String = "Access denied" diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/UserService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/UserService.scala index b54b6a9..85b93ed 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/UserService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/UserService.scala @@ -24,7 +24,7 @@ object UserService { sealed trait ActivateExecutorReply object ActivateExecutorReply { type Error = ActivateExecutorReply with DomainError - case class Entity(x: User) extends ActivateExecutorReply + final case class Entity(x: User) extends ActivateExecutorReply case object NotFoundError extends ActivateExecutorReply with DomainError.NotFoundError { val userMessage = "Info about you is not found on the server" } @@ -33,16 +33,16 @@ object UserService { sealed trait GetByIdReply object GetByIdReply { type Error = GetByIdReply with DomainError - case class Entity(x: User) extends GetByIdReply + final case class Entity(x: User) extends GetByIdReply case object AuthorizationError extends GetByIdReply with DomainError.AuthorizationError with DefaultAccessDeniedError - case object NotFoundError extends GetByIdReply with DomainError.NotFoundError with DefaultNotFoundError - case class CommonError(userMessage: String) extends GetByIdReply with DomainError + case object NotFoundError extends GetByIdReply with DomainError.NotFoundError with DefaultNotFoundError + final case class CommonError(userMessage: String) extends GetByIdReply with DomainError } sealed trait GetByEmailReply object GetByEmailReply { - case class Entity(x: User) extends GetByEmailReply + final case class Entity(x: User) extends GetByEmailReply case object NotFoundError extends GetByEmailReply with DefaultNotFoundError with DomainError.NotFoundError { override def userMessage: String = "Incorrect email. Try again." } @@ -50,7 +50,7 @@ object UserService { sealed trait GetByCredentialsReply object GetByCredentialsReply { - case class Entity(x: User) extends GetByCredentialsReply + final case class Entity(x: User) extends GetByCredentialsReply case object AuthenticationError extends GetByCredentialsReply with DefaultCredentialsError with DomainError.AuthenticationError case object NotFoundError extends GetByCredentialsReply with DomainError.NotFoundError with DefaultNotFoundError @@ -58,41 +58,41 @@ object UserService { sealed trait GetListReply object GetListReply { - case class EntityList(xs: Seq[User], totalFound: Int) extends GetListReply - case object AuthorizationError extends GetListReply with DomainError.AuthorizationError with DefaultNotFoundError + final case class EntityList(xs: Seq[User], totalFound: Int) extends GetListReply + case object AuthorizationError extends GetListReply with DomainError.AuthorizationError with DefaultNotFoundError } sealed trait CreateReply object CreateReply { type Error = CreateReply with DomainError - case class Created(x: User) extends CreateReply - case object AuthorizationError extends CreateReply with DefaultNotFoundError with DomainError.AuthorizationError - case class UserAlreadyExistsError(email: Email) extends CreateReply with DomainError { + final case class Created(x: User) extends CreateReply + case object AuthorizationError extends CreateReply with DefaultNotFoundError with DomainError.AuthorizationError + final case class UserAlreadyExistsError(email: Email) extends CreateReply with DomainError { val userMessage = s"The user with this email already exists." } - case class CommonError(userMessage: String) extends CreateReply with DomainError + final case class CommonError(userMessage: String) extends CreateReply with DomainError } sealed trait UpdateReply object UpdateReply { type Error = UpdateReply with DomainError - case class Updated(updated: User) extends UpdateReply + final case class Updated(updated: User) extends UpdateReply case object AuthorizationError extends UpdateReply with DefaultAccessDeniedError with DomainError.AuthorizationError - case class CommonError(userMessage: String) extends UpdateReply with DomainError + final case class CommonError(userMessage: String) extends UpdateReply with DomainError } sealed trait DeleteReply object DeleteReply { type Error = DeleteReply with DomainError case object Deleted extends DeleteReply - case class AuthorizationError(user: User) + final case class AuthorizationError(user: User) extends DeleteReply with DefaultAccessDeniedError with DomainError.AuthorizationError case object AssignedToRecordAndDocumentError extends DeleteReply with DomainError { val userMessage = "User is can not be deleted because he has record and document in work" } - case object NotFoundError extends DeleteReply with DefaultNotFoundError with DomainError.NotFoundError - case class CommonError(userMessage: String) extends DeleteReply with DomainError + case object NotFoundError extends DeleteReply with DefaultNotFoundError with DomainError.NotFoundError + final case class CommonError(userMessage: String) extends DeleteReply with DomainError } } 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") + +} -- cgit v1.2.3