From 5832f63b84d7388441d1200f2442dc1e9de0225c Mon Sep 17 00:00:00 2001 From: vlad Date: Tue, 27 Jun 2017 17:13:02 -0700 Subject: All PDS UI domain models, API case classes, service traits and necessary utils moved to pdsui-common --- .../pdsuidomain/formats/json/arm/ApiArm.scala | 22 ++++ .../formats/json/arm/ApiCreateArm.scala | 20 ++++ .../formats/json/arm/ApiPartialArm.scala | 14 +++ .../formats/json/category/ApiCategory.scala | 23 +++++ .../formats/json/criterion/ApiCriterion.scala | 40 ++++++++ .../formats/json/criterion/ApiNewCriterion.scala | 41 ++++++++ .../json/criterion/ApiUpdateCriterion.scala | 56 ++++++++++ .../formats/json/document/ApiDocument.scala | 71 +++++++++++++ .../formats/json/document/ApiDocumentType.scala | 20 ++++ .../formats/json/document/ApiPartialDocument.scala | 114 +++++++++++++++++++++ .../formats/json/document/ApiProviderType.scala | 20 ++++ .../formats/json/document/DocumentUtils.scala | 24 +++++ .../json/evidence/ApiPatientLabelEvidence.scala | 38 +++++++ .../json/extracteddata/ApiExtractedData.scala | 44 ++++++++ .../extracteddata/ApiPartialExtractedData.scala | 80 +++++++++++++++ .../formats/json/hypothesis/ApiHypothesis.scala | 26 +++++ .../json/intervention/ApiIntervention.scala | 41 ++++++++ .../json/intervention/ApiInterventionType.scala | 20 ++++ .../json/intervention/ApiPartialIntervention.scala | 44 ++++++++ .../formats/json/keyword/ApiKeyword.scala | 23 +++++ .../formats/json/label/ApiCriterionLabel.scala | 49 +++++++++ .../formats/json/label/ApiExtractedDataLabel.scala | 36 +++++++ .../pdsuidomain/formats/json/label/ApiLabel.scala | 22 ++++ .../formats/json/message/ApiMessage.scala | 59 +++++++++++ .../formats/json/message/ApiPartialMessage.scala | 84 +++++++++++++++ .../json/password/PasswordCreateRequest.scala | 9 ++ .../json/password/PasswordUpdateRequest.scala | 9 ++ .../formats/json/patient/ApiPatient.scala | 44 ++++++++ .../formats/json/patient/PatientStatus.scala | 24 +++++ .../eligible/ApiPartialPatientEligibleTrial.scala | 18 ++++ .../patient/eligible/ApiPatientEligibleTrial.scala | 46 +++++++++ .../hypothesis/ApiPartialPatientHypothesis.scala | 27 +++++ .../patient/hypothesis/ApiPatientHypothesis.scala | 32 ++++++ .../patient/label/ApiPartialPatientLabel.scala | 38 +++++++ .../json/patient/label/ApiPatientLabel.scala | 47 +++++++++ .../label/ApiPatientLabelDefiningCriteria.scala | 25 +++++ .../patient/trial/ApiPartialPatientCriterion.scala | 40 ++++++++ .../trial/ApiPartialPatientCriterionList.scala | 32 ++++++ .../json/patient/trial/ApiPatientCriterion.scala | 72 +++++++++++++ .../formats/json/record/ApiCreateRecord.scala | 37 +++++++ .../formats/json/record/ApiRecord.scala | 57 +++++++++++ .../formats/json/record/ApiUpdateRecord.scala | 47 +++++++++ .../formats/json/record/MedicalRecordStatus.scala | 34 ++++++ .../formats/json/session/NewSessionRequest.scala | 12 +++ .../formats/json/session/NewSessionResponse.scala | 11 ++ .../formats/json/studydesign/ApiStudyDesign.scala | 20 ++++ .../formats/json/trial/ApiPartialTrial.scala | 44 ++++++++ .../pdsuidomain/formats/json/trial/ApiTrial.scala | 63 ++++++++++++ .../formats/json/trial/TrialStatus.scala | 30 ++++++ .../formats/json/user/ApiPartialUser.scala | 83 +++++++++++++++ .../pdsuidomain/formats/json/user/ApiUser.scala | 32 ++++++ .../pdsuidomain/formats/json/user/UserRole.scala | 33 ++++++ 52 files changed, 1997 insertions(+) create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/arm/ApiArm.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/arm/ApiCreateArm.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/arm/ApiPartialArm.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/category/ApiCategory.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/criterion/ApiCriterion.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/criterion/ApiNewCriterion.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/criterion/ApiUpdateCriterion.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiDocument.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiDocumentType.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiPartialDocument.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiProviderType.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/document/DocumentUtils.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/evidence/ApiPatientLabelEvidence.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/extracteddata/ApiExtractedData.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/extracteddata/ApiPartialExtractedData.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/hypothesis/ApiHypothesis.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiIntervention.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiInterventionType.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiPartialIntervention.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/keyword/ApiKeyword.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/label/ApiCriterionLabel.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/label/ApiExtractedDataLabel.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/label/ApiLabel.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/message/ApiMessage.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/message/ApiPartialMessage.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/password/PasswordCreateRequest.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/password/PasswordUpdateRequest.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/ApiPatient.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/PatientStatus.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/eligible/ApiPartialPatientEligibleTrial.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/eligible/ApiPatientEligibleTrial.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/hypothesis/ApiPartialPatientHypothesis.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/hypothesis/ApiPatientHypothesis.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/label/ApiPartialPatientLabel.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/label/ApiPatientLabel.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/label/ApiPatientLabelDefiningCriteria.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/trial/ApiPartialPatientCriterion.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/trial/ApiPartialPatientCriterionList.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/trial/ApiPatientCriterion.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiCreateRecord.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiRecord.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiUpdateRecord.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/record/MedicalRecordStatus.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/session/NewSessionRequest.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/session/NewSessionResponse.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/studydesign/ApiStudyDesign.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/trial/ApiPartialTrial.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/trial/ApiTrial.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/trial/TrialStatus.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiPartialUser.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiUser.scala create mode 100644 src/main/scala/xyz/driver/pdsuidomain/formats/json/user/UserRole.scala (limited to 'src/main/scala/xyz/driver/pdsuidomain/formats') 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 new file mode 100644 index 0000000..509ea35 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/arm/ApiArm.scala @@ -0,0 +1,22 @@ +package xyz.driver.pdsuidomain.formats.json.arm + +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) + +object ApiArm { + + implicit val format: Format[ApiArm] = ( + (JsPath \ "id").format[Long] and + (JsPath \ "name").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, + trialId = arm.trialId.id + ) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/arm/ApiCreateArm.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/arm/ApiCreateArm.scala new file mode 100644 index 0000000..5168e94 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/arm/ApiCreateArm.scala @@ -0,0 +1,20 @@ +package xyz.driver.pdsuidomain.formats.json.arm + +import xyz.driver.pdsuicommon.domain.{LongId, StringId} +import xyz.driver.pdsuidomain.entities.Arm +import play.api.libs.json.{Format, Json} + +final case class ApiCreateArm(name: String, trialId: String) { + + def toDomain = Arm( + id = LongId(0), + name = name, + trialId = StringId(trialId), + originalName = name + ) +} + +object ApiCreateArm { + + implicit val format: Format[ApiCreateArm] = Json.format[ApiCreateArm] +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/arm/ApiPartialArm.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/arm/ApiPartialArm.scala new file mode 100644 index 0000000..f85d7ff --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/arm/ApiPartialArm.scala @@ -0,0 +1,14 @@ +package xyz.driver.pdsuidomain.formats.json.arm + +import xyz.driver.pdsuidomain.entities.Arm +import play.api.libs.json.{Format, Json} + +final case class ApiPartialArm(name: String) { + + def applyTo(arm: Arm): Arm = arm.copy(name = name) +} + +object ApiPartialArm { + + implicit val format: Format[ApiPartialArm] = Json.format +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/category/ApiCategory.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/category/ApiCategory.scala new file mode 100644 index 0000000..fabdaa2 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/category/ApiCategory.scala @@ -0,0 +1,23 @@ +package xyz.driver.pdsuidomain.formats.json.category + +import play.api.libs.functional.syntax._ +import play.api.libs.json.{Format, JsPath} +import xyz.driver.pdsuidomain.entities.CategoryWithLabels +import xyz.driver.pdsuidomain.formats.json.label.ApiLabel + +final case class ApiCategory(id: Long, name: String, labels: List[ApiLabel]) + +object ApiCategory { + + implicit val format: Format[ApiCategory] = ( + (JsPath \ "id").format[Long] and + (JsPath \ "name").format[String] and + (JsPath \ "labels").format[List[ApiLabel]] + ) (ApiCategory.apply, unlift(ApiCategory.unapply)) + + def fromDomain(categoryWithLabels: CategoryWithLabels) = ApiCategory( + id = categoryWithLabels.category.id.id, + name = categoryWithLabels.category.name, + labels = categoryWithLabels.labels.map(x => ApiLabel(x.id.id, x.name, x.categoryId.id)) + ) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/criterion/ApiCriterion.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/criterion/ApiCriterion.scala new file mode 100644 index 0000000..0f3b76e --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/criterion/ApiCriterion.scala @@ -0,0 +1,40 @@ +package xyz.driver.pdsuidomain.formats.json.criterion + +import xyz.driver.pdsuicommon.json.Serialization.seqJsonFormat +import play.api.libs.functional.syntax._ +import play.api.libs.json._ +import xyz.driver.pdsuidomain.formats.json.label.ApiCriterionLabel +import xyz.driver.pdsuidomain.services.CriterionService.RichCriterion + +final case class ApiCriterion(id: Long, + meta: Option[String], + arms: Seq[Long], + text: Option[String], + isCompound: Boolean, + labels: Seq[ApiCriterionLabel], + trialId: String) + +object ApiCriterion { + + implicit val format: Format[ApiCriterion] = ( + (JsPath \ "id").format[Long] and + (JsPath \ "meta").formatNullable(Format(Reads { x => + JsSuccess(Json.stringify(x)) + }, Writes[String](Json.parse))) and + (JsPath \ "arms").format(seqJsonFormat[Long]) and + (JsPath \ "text").formatNullable[String] and + (JsPath \ "isCompound").format[Boolean] and + (JsPath \ "labels").format(seqJsonFormat[ApiCriterionLabel]) and + (JsPath \ "trialId").format[String] + ) (ApiCriterion.apply, unlift(ApiCriterion.unapply)) + + def fromDomain(richCriterion: RichCriterion) = ApiCriterion( + id = richCriterion.criterion.id.id, + meta = Option(richCriterion.criterion.meta), + arms = richCriterion.armIds.map(_.id), + text = richCriterion.criterion.text, + isCompound = richCriterion.criterion.isCompound, + labels = richCriterion.labels.map(ApiCriterionLabel.fromDomain), + trialId = richCriterion.criterion.trialId.id + ) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/criterion/ApiNewCriterion.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/criterion/ApiNewCriterion.scala new file mode 100644 index 0000000..85c91d5 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/criterion/ApiNewCriterion.scala @@ -0,0 +1,41 @@ +package xyz.driver.pdsuidomain.formats.json.criterion + +import xyz.driver.pdsuicommon.domain.{LongId, StringId} +import xyz.driver.pdsuicommon.json.Serialization.seqJsonFormat +import xyz.driver.pdsuidomain.entities._ +import play.api.libs.functional.syntax._ +import play.api.libs.json._ +import xyz.driver.pdsuidomain.formats.json.label.ApiCriterionLabel +import xyz.driver.pdsuidomain.services.CriterionService.RichCriterion + +final case class ApiNewCriterion(meta: Option[String], + arms: Option[Seq[Long]], + text: Option[String], + labels: Seq[ApiCriterionLabel], + trialId: String) { + + def toDomain = RichCriterion( + criterion = Criterion( + id = LongId(0L), + meta = meta.getOrElse(""), + trialId = StringId(trialId), + isCompound = false, + text = text + ), + armIds = arms.getOrElse(Seq.empty).map(LongId[Arm]), + labels = labels.map(_.toDomain(LongId(Long.MaxValue))) // A developer should specify right criterionId himself + ) +} + +object ApiNewCriterion { + + implicit val format: Format[ApiNewCriterion] = ( + (JsPath \ "meta").formatNullable(Format(Reads { x => + JsSuccess(Json.stringify(x)) + }, Writes[String](Json.parse))) and + (JsPath \ "arms").formatNullable(seqJsonFormat[Long]) and + (JsPath \ "text").formatNullable[String] and + (JsPath \ "labels").format(seqJsonFormat[ApiCriterionLabel]) and + (JsPath \ "trialId").format[String] + ) (ApiNewCriterion.apply, unlift(ApiNewCriterion.unapply)) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/criterion/ApiUpdateCriterion.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/criterion/ApiUpdateCriterion.scala new file mode 100644 index 0000000..c73c3ef --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/criterion/ApiUpdateCriterion.scala @@ -0,0 +1,56 @@ +package xyz.driver.pdsuidomain.formats.json.criterion + +import xyz.driver.pdsuicommon.domain.LongId +import xyz.driver.pdsuicommon.json.Serialization.seqJsonFormat +import xyz.driver.pdsuidomain.entities.{Arm, Criterion} +import org.davidbild.tristate._ +import org.davidbild.tristate.contrib.play.ToJsPathOpsFromJsPath +import play.api.libs.functional.syntax._ +import play.api.libs.json._ +import xyz.driver.pdsuidomain.formats.json.label.ApiCriterionLabel +import xyz.driver.pdsuidomain.services.CriterionService.RichCriterion + +final case class ApiUpdateCriterion(meta: Tristate[String], + arms: Tristate[Seq[Long]], + text: Option[String], + isCompound: Option[Boolean], + labels: Tristate[Seq[ApiCriterionLabel]]) { + + def applyTo(orig: RichCriterion): RichCriterion = RichCriterion( + criterion = applyTo(orig.criterion), + armIds = arms.cata(_.map(LongId[Arm]), Seq.empty, orig.armIds), + labels = labels.cata(_.map(_.toDomain(orig.criterion.id)), Seq.empty, orig.labels) + ) + + private def applyTo(orig: Criterion): Criterion = Criterion( + id = orig.id, + meta = meta.cata(identity, "{}", orig.meta), + text = text.orElse(orig.text), + isCompound = isCompound.getOrElse(orig.isCompound), + trialId = orig.trialId + ) +} + +object ApiUpdateCriterion { + + private val reads: Reads[ApiUpdateCriterion] = ( + (JsPath \ "meta").readTristate(Reads { x => JsSuccess(Json.stringify(x)) }).map { + case Tristate.Present("{}") => Tristate.Absent + case x => x + } and + (JsPath \ "arms").readTristate(seqJsonFormat[Long]) and + (JsPath \ "text").readNullable[String] and + (JsPath \ "isCompound").readNullable[Boolean] and + (JsPath \ "labels").readTristate(seqJsonFormat[ApiCriterionLabel]) + ) (ApiUpdateCriterion.apply _) + + private val writes: Writes[ApiUpdateCriterion] = ( + (JsPath \ "meta").writeTristate(Writes[String](Json.parse)) and + (JsPath \ "arms").writeTristate(seqJsonFormat[Long]) and + (JsPath \ "text").writeNullable[String] and + (JsPath \ "isCompound").writeNullable[Boolean] and + (JsPath \ "labels").writeTristate(seqJsonFormat[ApiCriterionLabel]) + ) (unlift(ApiUpdateCriterion.unapply)) + + implicit val format: Format[ApiUpdateCriterion] = Format(reads, writes) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiDocument.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiDocument.scala new file mode 100644 index 0000000..be9c65b --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiDocument.scala @@ -0,0 +1,71 @@ +package xyz.driver.pdsuidomain.formats.json.document + +import java.time.{LocalDate, ZoneId, ZonedDateTime} + +import xyz.driver.pdsuidomain.entities._ +import play.api.data.validation.ValidationError +import play.api.libs.functional.syntax._ +import play.api.libs.json._ +import xyz.driver.pdsuicommon.json.JsonSerializer + +final case class ApiDocument(id: Long, + recordId: Long, + physician: Option[String], + lastUpdate: Option[ZonedDateTime], + typeId: Option[Long], + startDate: Option[LocalDate], + endDate: Option[LocalDate], + provider: Option[String], + providerTypeId: Option[Long], + status: Option[String], + previousStatus: Option[String], + assignee: Option[Long], + previousAssignee: Option[Long], + meta: Option[String]) + +object ApiDocument { + + private val statusFormat = Format( + Reads.StringReads.filter(ValidationError("unknown status")) { + case x if DocumentUtils.statusFromString.isDefinedAt(x) => true + case _ => false + }, + Writes.StringWrites + ) + + implicit val format: Format[ApiDocument] = ( + (JsPath \ "id").format[Long] and + (JsPath \ "recordId").format[Long] and + (JsPath \ "physician").formatNullable[String] and + (JsPath \ "lastUpdate").formatNullable[ZonedDateTime] and + (JsPath \ "typeId").formatNullable[Long] and + (JsPath \ "startDate").formatNullable[LocalDate] and + (JsPath \ "endDate").formatNullable[LocalDate] and + (JsPath \ "provider").formatNullable[String] and + (JsPath \ "providerTypeId").formatNullable[Long] and + (JsPath \ "status").formatNullable(statusFormat) and + (JsPath \ "previousStatus").formatNullable(statusFormat) and + (JsPath \ "assignee").formatNullable[Long] and + (JsPath \ "previousAssignee").formatNullable[Long] and + (JsPath \ "meta").formatNullable(Format(Reads { x => JsSuccess(Json.stringify(x)) }, Writes[String](Json.parse))) + ) (ApiDocument.apply, unlift(ApiDocument.unapply)) + + def fromDomain(document: Document): ApiDocument = { + ApiDocument( + id = document.id.id, + recordId = document.recordId.id, + physician = document.physician, + lastUpdate = Option(document.lastUpdate).map(ZonedDateTime.of(_, ZoneId.of("Z"))), + typeId = document.typeId.map(_.id), + startDate = document.startDate, + endDate = document.endDate, + provider = document.providerName, + providerTypeId = document.providerTypeId.map(_.id), + status = Option(DocumentUtils.statusToString(document.status)), + previousStatus = document.previousStatus.map(DocumentUtils.statusToString), + assignee = document.assignee.map(_.id), + previousAssignee = document.previousAssignee.map(_.id), + meta = document.meta.map(meta => JsonSerializer.serialize(meta.content)) + ) + } +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiDocumentType.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiDocumentType.scala new file mode 100644 index 0000000..e00da20 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiDocumentType.scala @@ -0,0 +1,20 @@ +package xyz.driver.pdsuidomain.formats.json.document + +import play.api.libs.functional.syntax._ +import play.api.libs.json.{Format, JsPath} +import xyz.driver.pdsuidomain.entities.DocumentType + +final case class ApiDocumentType(id: Long, name: String) + +object ApiDocumentType { + + implicit val format: Format[ApiDocumentType] = ( + (JsPath \ "id").format[Long] and + (JsPath \ "name").format[String] + ) (ApiDocumentType.apply, unlift(ApiDocumentType.unapply)) + + def fromDomain(documentType: DocumentType) = ApiDocumentType( + id = documentType.id.id, + name = documentType.name + ) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiPartialDocument.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiPartialDocument.scala new file mode 100644 index 0000000..7682bb5 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiPartialDocument.scala @@ -0,0 +1,114 @@ +package xyz.driver.pdsuidomain.formats.json.document + +import java.time.{LocalDate, LocalDateTime} + +import xyz.driver.pdsuicommon.domain.{LongId, TextJson} +import xyz.driver.pdsuidomain.entities.Document.Meta +import xyz.driver.pdsuidomain.entities._ +import org.davidbild.tristate.Tristate +import org.davidbild.tristate.contrib.play.ToJsPathOpsFromJsPath +import play.api.data.validation._ +import play.api.libs.functional.syntax._ +import play.api.libs.json._ +import xyz.driver.pdsuicommon.json.{JsonSerializer, JsonValidationException} +import xyz.driver.pdsuicommon.validation.{AdditionalConstraints, JsonValidationErrors} + +import scala.collection.breakOut +import scala.util.Try + +final case class ApiPartialDocument(recordId: Option[Long], + physician: Option[String], + typeId: Tristate[Long], + startDate: Tristate[LocalDate], + endDate: Tristate[LocalDate], + provider: Tristate[String], + providerTypeId: Tristate[Long], + status: Option[String], + assignee: Tristate[Long], + meta: Tristate[String]) { + + import xyz.driver.pdsuicommon.domain.User + + def applyTo(orig: Document): Document = Document( + id = orig.id, + status = status.map(DocumentUtils.statusFromString).getOrElse(orig.status), + previousStatus = orig.previousStatus, + assignee = assignee.map(LongId[User]).cata(Some(_), None, orig.assignee), + previousAssignee = orig.previousAssignee, + recordId = recordId.map(LongId[MedicalRecord]).getOrElse(orig.recordId), + physician = physician.orElse(orig.physician), + typeId = typeId.map(LongId[DocumentType]).cata(Some(_), None, orig.typeId), + providerName = provider.cata(Some(_), None, orig.providerName), + providerTypeId = providerTypeId.map(LongId[ProviderType]).cata(Some(_), None, orig.providerTypeId), + meta = meta.cata(x => Some(TextJson(JsonSerializer.deserialize[Meta](x))), None, orig.meta), + startDate = startDate.cata(Some(_), None, orig.startDate), + endDate = endDate.cata(Some(_), None, orig.endDate), + lastUpdate = LocalDateTime.MIN // Should update internally in a business logic module + ) + + def toDomain: Try[Document] = Try { + val validation = Map(JsPath \ "recordId" -> AdditionalConstraints.optionNonEmptyConstraint(recordId)) + + val validationErrors: JsonValidationErrors = validation.collect({ + case (fieldName, e: Invalid) => (fieldName, e.errors) + })(breakOut) + + if (validationErrors.isEmpty) { + Document( + id = LongId(0), + recordId = recordId.map(LongId[MedicalRecord]).get, + status = Document.Status.New, + physician = physician, + typeId = typeId.map(LongId[DocumentType]).toOption, + startDate = startDate.toOption, + endDate = endDate.toOption, + providerName = provider.toOption, + providerTypeId = providerTypeId.map(LongId[ProviderType]).toOption, + meta = meta.map(x => TextJson(JsonSerializer.deserialize[Meta](x))).toOption, + previousStatus = None, + assignee = None, + previousAssignee = None, + lastUpdate = LocalDateTime.MIN + ) + } else { + throw new JsonValidationException(validationErrors) + } + } +} + +object ApiPartialDocument { + + private val reads: Reads[ApiPartialDocument] = ( + (JsPath \ "recordId").readNullable[Long] and + (JsPath \ "physician").readNullable[String] and + (JsPath \ "typeId").readTristate[Long] and + (JsPath \ "startDate").readTristate[LocalDate] and + (JsPath \ "endDate").readTristate[LocalDate] and + (JsPath \ "provider").readTristate[String] and + (JsPath \ "providerTypeId").readTristate[Long] and + (JsPath \ "status").readNullable[String](Reads.of[String].filter(ValidationError("unknown status"))({ + case x if DocumentUtils.statusFromString.isDefinedAt(x) => true + case _ => false + })) and + (JsPath \ "assignee").readTristate[Long] and + (JsPath \ "meta").readTristate(Reads { x => JsSuccess(Json.stringify(x)) }).map { + case Tristate.Present("{}") => Tristate.Absent + case x => x + } + ) (ApiPartialDocument.apply _) + + private val writes: Writes[ApiPartialDocument] = ( + (JsPath \ "recordId").writeNullable[Long] and + (JsPath \ "physician").writeNullable[String] and + (JsPath \ "typeId").writeTristate[Long] and + (JsPath \ "startDate").writeTristate[LocalDate] and + (JsPath \ "endDate").writeTristate[LocalDate] and + (JsPath \ "provider").writeTristate[String] and + (JsPath \ "providerTypeId").writeTristate[Long] and + (JsPath \ "status").writeNullable[String] and + (JsPath \ "assignee").writeTristate[Long] and + (JsPath \ "meta").writeTristate(Writes[String](Json.parse)) + ) (unlift(ApiPartialDocument.unapply)) + + implicit val format: Format[ApiPartialDocument] = Format(reads, writes) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiProviderType.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiProviderType.scala new file mode 100644 index 0000000..7b370ba --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/ApiProviderType.scala @@ -0,0 +1,20 @@ +package xyz.driver.pdsuidomain.formats.json.document + +import play.api.libs.functional.syntax._ +import play.api.libs.json.{Format, JsPath} +import xyz.driver.pdsuidomain.entities.ProviderType + +final case class ApiProviderType(id: Long, name: String) + +object ApiProviderType { + + implicit val format: Format[ApiProviderType] = ( + (JsPath \ "id").format[Long] and + (JsPath \ "name").format[String] + ) (ApiProviderType.apply, unlift(ApiProviderType.unapply)) + + def fromDomain(providerType: ProviderType) = ApiProviderType( + id = providerType.id.id, + name = providerType.name + ) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/DocumentUtils.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/DocumentUtils.scala new file mode 100644 index 0000000..87e449f --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/document/DocumentUtils.scala @@ -0,0 +1,24 @@ +package xyz.driver.pdsuidomain.formats.json.document + +import xyz.driver.pdsuidomain.entities.Document.Status + +object DocumentUtils { + + val statusFromString: PartialFunction[String, Status] = { + case "New" => Status.New + case "Organized" => Status.Organized + case "Extracted" => Status.Extracted + case "Done" => Status.Done + case "Flagged" => Status.Flagged + case "Archived" => Status.Archived + } + + def statusToString(x: Status): String = x match { + case Status.New => "New" + case Status.Organized => "Organized" + case Status.Extracted => "Extracted" + case Status.Done => "Done" + case Status.Flagged => "Flagged" + case Status.Archived => "Archived" + } +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/evidence/ApiPatientLabelEvidence.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/evidence/ApiPatientLabelEvidence.scala new file mode 100644 index 0000000..2bb4945 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/evidence/ApiPatientLabelEvidence.scala @@ -0,0 +1,38 @@ +package xyz.driver.pdsuidomain.formats.json.evidence + +import java.time.LocalDate + +import xyz.driver.pdsuidomain.services.PatientLabelEvidenceService +import play.api.libs.json._ +import xyz.driver.pdsuicommon.domain.FuzzyValue + +final case class ApiPatientLabelEvidence(id: Long, + value: String, + evidenceText: String, + documentId: Option[Long], + evidenceId: Option[Long], + reportId: Option[String], + documentType: String, + date: LocalDate, + providerType: String) + +object ApiPatientLabelEvidence { + + implicit val format: Format[ApiPatientLabelEvidence] = Json.format + + def fromDomain(x: PatientLabelEvidenceService.Aggregated): ApiPatientLabelEvidence = { + import x._ + + ApiPatientLabelEvidence( + id = evidence.id.id, + value = FuzzyValue.valueToString(evidence.value), + evidenceText = evidence.evidenceText, + documentId = evidence.documentId.map(_.id), + evidenceId = evidence.evidenceId.map(_.id), + reportId = evidence.reportId.map(_.toString), + documentType = documentType, + date = date, + providerType = providerType + ) + } +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/extracteddata/ApiExtractedData.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/extracteddata/ApiExtractedData.scala new file mode 100644 index 0000000..dad7a1e --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/extracteddata/ApiExtractedData.scala @@ -0,0 +1,44 @@ +package xyz.driver.pdsuidomain.formats.json.extracteddata + +import xyz.driver.pdsuidomain.formats.json.label.ApiExtractedDataLabel +import play.api.libs.json._ +import play.api.data.validation._ +import play.api.libs.functional.syntax._ +import xyz.driver.pdsuicommon.json.JsonSerializer +import xyz.driver.pdsuidomain.services.ExtractedDataService.RichExtractedData + +// The specification: https://driverinc.atlassian.net/wiki/pages/viewpage.action?pageId=33423387 +// Note, that there is "Extracted data object or Temporary extracted data object" in specification +// ApiExtractedData represents both types +final case class ApiExtractedData(id: Long, + documentId: Long, + keywordId: Option[Long], + evidence: Option[String], + meta: Option[String], + // An empty list and no-existent list are different cases + labels: Option[List[ApiExtractedDataLabel]]) + +object ApiExtractedData { + + implicit val format: Format[ApiExtractedData] = ( + (JsPath \ "id").format[Long] and + (JsPath \ "documentId").format[Long] and + (JsPath \ "keywordId").formatNullable[Long] and + (JsPath \ "evidence").formatNullable[String] and + (JsPath \ "meta").formatNullable[String] and + (JsPath \ "labels").formatNullable[List[ApiExtractedDataLabel]](Format( + Reads.of[List[ApiExtractedDataLabel]].filter(ValidationError("empty labels"))({ + case x if x.nonEmpty => true + case _ => false + }), Writes.of[List[ApiExtractedDataLabel]])) + ) (ApiExtractedData.apply, unlift(ApiExtractedData.unapply)) + + def fromDomain(extractedDataWithLabels: RichExtractedData) = ApiExtractedData( + id = extractedDataWithLabels.extractedData.id.id, + documentId = extractedDataWithLabels.extractedData.documentId.id, + keywordId = extractedDataWithLabels.extractedData.keywordId.map(_.id), + evidence = extractedDataWithLabels.extractedData.evidenceText, + meta = extractedDataWithLabels.extractedData.meta.map(x => JsonSerializer.serialize(x.content)), + labels = Option(extractedDataWithLabels.labels.map(ApiExtractedDataLabel.fromDomain)) + ) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/extracteddata/ApiPartialExtractedData.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/extracteddata/ApiPartialExtractedData.scala new file mode 100644 index 0000000..69b5627 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/extracteddata/ApiPartialExtractedData.scala @@ -0,0 +1,80 @@ +package xyz.driver.pdsuidomain.formats.json.extracteddata + +import xyz.driver.pdsuicommon.domain.{LongId, TextJson} +import xyz.driver.pdsuidomain.entities.ExtractedData.Meta +import xyz.driver.pdsuidomain.entities._ +import org.davidbild.tristate.Tristate +import org.davidbild.tristate.contrib.play.ToJsPathOpsFromJsPath +import play.api.data.validation._ +import play.api.libs.functional.syntax._ +import play.api.libs.json._ +import xyz.driver.pdsuicommon.json.{JsonSerializer, JsonValidationException} +import xyz.driver.pdsuicommon.validation.{AdditionalConstraints, JsonValidationErrors} +import xyz.driver.pdsuidomain.formats.json.label.ApiExtractedDataLabel +import xyz.driver.pdsuidomain.services.ExtractedDataService.RichExtractedData + +import scala.collection._ + +final case class ApiPartialExtractedData(documentId: Option[Long], + keywordId: Option[Long], + evidence: Tristate[String], + meta: Tristate[String], + labels: Tristate[List[ApiExtractedDataLabel]]) { + + def applyTo(orig: RichExtractedData): RichExtractedData = RichExtractedData( + extractedData = applyTo(orig.extractedData), + labels = labels.cata(_.map(_.toDomain(orig.extractedData.id)), List.empty, orig.labels) + ) + + private def applyTo(orig: ExtractedData): ExtractedData = ExtractedData( + id = orig.id, + documentId = orig.documentId, + keywordId = keywordId.map(LongId[Keyword]).orElse(orig.keywordId), + evidenceText = evidence.cata(Some(_), None, orig.evidenceText), + meta = meta.map(x => TextJson(JsonSerializer.deserialize[Meta](x))).cata(Some(_), None, orig.meta) + ) + + def toDomain: RichExtractedData = { + val validation = Map( + JsPath \ "documentId" -> AdditionalConstraints.optionNonEmptyConstraint(documentId) + ) + + val validationErrors: JsonValidationErrors = validation.collect({ + case (fieldName, e: Invalid) => (fieldName, e.errors) + })(breakOut) + + if (validationErrors.isEmpty) { + val extractedData = ExtractedData( + documentId = documentId.map(LongId[Document]).get, + keywordId = keywordId.map(LongId[Keyword]), + evidenceText = evidence.toOption, + meta = meta.map(x => TextJson(JsonSerializer.deserialize[Meta](x))).toOption + ) + val labelList = labels.map(_.map(_.toDomain())) + RichExtractedData(extractedData, labelList.getOrElse(List.empty)) + } else { + throw new JsonValidationException(validationErrors) + } + } +} + +object ApiPartialExtractedData { + + private val reads: Reads[ApiPartialExtractedData] = ( + (JsPath \ "documentId").readNullable[Long] and + (JsPath \ "keywordId").readNullable[Long] and + (JsPath \ "evidence").readTristate[String] and + (JsPath \ "meta").readTristate[String] and + (JsPath \ "labels").readTristate[List[ApiExtractedDataLabel]] + ) (ApiPartialExtractedData.apply _) + + private val writes: Writes[ApiPartialExtractedData] = ( + (JsPath \ "documentId").writeNullable[Long] and + (JsPath \ "keywordId").writeNullable[Long] and + (JsPath \ "evidence").writeTristate[String] and + (JsPath \ "meta").writeTristate[String] and + (JsPath \ "labels").writeTristate[List[ApiExtractedDataLabel]] + ) (unlift(ApiPartialExtractedData.unapply)) + + implicit val format: Format[ApiPartialExtractedData] = Format(reads, writes) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/hypothesis/ApiHypothesis.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/hypothesis/ApiHypothesis.scala new file mode 100644 index 0000000..0d6763c --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/hypothesis/ApiHypothesis.scala @@ -0,0 +1,26 @@ +package xyz.driver.pdsuidomain.formats.json.hypothesis + +import java.util.UUID + +import xyz.driver.pdsuidomain.entities.Hypothesis +import play.api.libs.functional.syntax._ +import play.api.libs.json.{Format, JsPath} + +final case class ApiHypothesis(id: UUID, name: String, treatmentType: String, description: String) + +object ApiHypothesis { + + implicit val format: Format[ApiHypothesis] = ( + (JsPath \ "id").format[UUID] and + (JsPath \ "name").format[String] and + (JsPath \ "treatmentType").format[String] and + (JsPath \ "description").format[String] + ) (ApiHypothesis.apply, unlift(ApiHypothesis.unapply)) + + def fromDomain(hypothesis: Hypothesis) = ApiHypothesis( + id = hypothesis.id.id, + name = hypothesis.name, + treatmentType = hypothesis.treatmentType, + description = hypothesis.description + ) +} 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 new file mode 100644 index 0000000..37a9758 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiIntervention.scala @@ -0,0 +1,41 @@ +package xyz.driver.pdsuidomain.formats.json.intervention + +import xyz.driver.pdsuidomain.entities.InterventionWithArms +import play.api.libs.functional.syntax._ +import play.api.libs.json.{Format, JsPath} + +final case class ApiIntervention(id: Long, + name: String, + typeId: Option[Long], + description: String, + isActive: Boolean, + arms: List[Long], + trialId: String) + +object ApiIntervention { + + implicit val format: Format[ApiIntervention] = ( + (JsPath \ "id").format[Long] and + (JsPath \ "name").format[String] and + (JsPath \ "typeId").formatNullable[Long] and + (JsPath \ "description").format[String] and + (JsPath \ "isActive").format[Boolean] and + (JsPath \ "arms").format[List[Long]] and + (JsPath \ "trialId").format[String] + ) (ApiIntervention.apply, unlift(ApiIntervention.unapply)) + + def fromDomain(interventionWithArms: InterventionWithArms): ApiIntervention = { + import interventionWithArms.intervention + import interventionWithArms.arms + + ApiIntervention( + id = intervention.id.id, + name = intervention.name, + typeId = intervention.typeId.map(_.id), + description = intervention.description, + isActive = intervention.isActive, + arms = arms.map(_.armId.id), + trialId = intervention.trialId.id + ) + } +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiInterventionType.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiInterventionType.scala new file mode 100644 index 0000000..ca444eb --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiInterventionType.scala @@ -0,0 +1,20 @@ +package xyz.driver.pdsuidomain.formats.json.intervention + +import play.api.libs.functional.syntax._ +import play.api.libs.json.{Format, JsPath} +import xyz.driver.pdsuidomain.entities.InterventionType + +final case class ApiInterventionType(id: Long, name: String) + +object ApiInterventionType { + + implicit val format: Format[ApiInterventionType] = ( + (JsPath \ "id").format[Long] and + (JsPath \ "name").format[String] + ) (ApiInterventionType.apply, unlift(ApiInterventionType.unapply)) + + def fromDomain(interventionType: InterventionType) = ApiInterventionType( + id = interventionType.id.id, + name = interventionType.name + ) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiPartialIntervention.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiPartialIntervention.scala new file mode 100644 index 0000000..416237a --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiPartialIntervention.scala @@ -0,0 +1,44 @@ +package xyz.driver.pdsuidomain.formats.json.intervention + +import xyz.driver.pdsuicommon.domain.LongId +import xyz.driver.pdsuidomain.entities.{InterventionArm, InterventionWithArms} +import play.api.libs.functional.syntax._ +import play.api.libs.json._ + +final case class ApiPartialIntervention(typeId: Option[Long], + description: Option[String], + isActive: Option[Boolean], + arms: Option[List[Long]]) { + + def applyTo(orig: InterventionWithArms): InterventionWithArms = { + val origIntervention = orig.intervention + val draftArmList = arms.map(_.map(x => InterventionArm(LongId(x), orig.intervention.id))) + orig.copy( + intervention = origIntervention.copy( + typeId = typeId.map(LongId(_)).orElse(origIntervention.typeId), + description = description.getOrElse(origIntervention.description), + isActive = isActive.getOrElse(origIntervention.isActive) + ), + arms = draftArmList.getOrElse(orig.arms) + ) + } +} + +object ApiPartialIntervention { + + private val reads: Reads[ApiPartialIntervention] = ( + (JsPath \ "typeId").readNullable[Long] and + (JsPath \ "description").readNullable[String] and + (JsPath \ "isActive").readNullable[Boolean] and + (JsPath \ "arms").readNullable[List[Long]] + ) (ApiPartialIntervention.apply _) + + private val writes: Writes[ApiPartialIntervention] = ( + (JsPath \ "typeId").writeNullable[Long] and + (JsPath \ "description").writeNullable[String] and + (JsPath \ "isActive").writeNullable[Boolean] and + (JsPath \ "arms").writeNullable[List[Long]] + ) (unlift(ApiPartialIntervention.unapply)) + + implicit val format: Format[ApiPartialIntervention] = Format(reads, writes) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/keyword/ApiKeyword.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/keyword/ApiKeyword.scala new file mode 100644 index 0000000..afd012d --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/keyword/ApiKeyword.scala @@ -0,0 +1,23 @@ +package xyz.driver.pdsuidomain.formats.json.keyword + +import xyz.driver.pdsuidomain.entities.KeywordWithLabels +import play.api.libs.functional.syntax._ +import play.api.libs.json.{Format, JsPath} +import xyz.driver.pdsuidomain.formats.json.label.ApiLabel + +final case class ApiKeyword(id: Long, keyword: String, labels: List[ApiLabel]) + +object ApiKeyword { + + implicit val format: Format[ApiKeyword] = ( + (JsPath \ "id").format[Long] and + (JsPath \ "keyword").format[String] and + (JsPath \ "labels").format[List[ApiLabel]] + ) (ApiKeyword.apply, unlift(ApiKeyword.unapply)) + + def fromDomain(keywordWithLabels: KeywordWithLabels) = ApiKeyword( + id = keywordWithLabels.keyword.id.id, + keyword = keywordWithLabels.keyword.keyword, + labels = keywordWithLabels.labels.map(x => ApiLabel(x.id.id, x.name, x.categoryId.id)) + ) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/label/ApiCriterionLabel.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/label/ApiCriterionLabel.scala new file mode 100644 index 0000000..2788bf2 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/label/ApiCriterionLabel.scala @@ -0,0 +1,49 @@ +package xyz.driver.pdsuidomain.formats.json.label + +import xyz.driver.pdsuicommon.domain.{FuzzyValue, LongId} +import xyz.driver.pdsuidomain.entities.{Category, Criterion, CriterionLabel, Label} +import play.api.data.validation.ValidationError +import play.api.libs.functional.syntax._ +import play.api.libs.json._ + +/** + * @param value Yes|No + */ +final case class ApiCriterionLabel(labelId: Option[Long], + categoryId: Option[Long], + value: Option[String], + isDefining: Boolean) { + + def toDomain(criterionId: LongId[Criterion]) = CriterionLabel( + id = LongId(0L), + labelId = labelId.map(LongId[Label]), + criterionId = criterionId, + categoryId = categoryId.map(LongId[Category]), + value = value.map { + case "Yes" => true + case "No" => false + }, + isDefining = isDefining + ) +} + +object ApiCriterionLabel { + + def fromDomain(x: CriterionLabel) = ApiCriterionLabel( + labelId = x.labelId.map(_.id), + categoryId = x.categoryId.map(_.id), + value = x.value.map { x => + FuzzyValue.valueToString(FuzzyValue.fromBoolean(x)) + }, + isDefining = x.isDefining + ) + + implicit val format: Format[ApiCriterionLabel] = ( + (JsPath \ "labelId").formatNullable[Long] and + (JsPath \ "categoryId").formatNullable[Long] and + (JsPath \ "value").formatNullable[String](Format(Reads.of[String].filter(ValidationError("unknown value"))({ x => + x == "Yes" || x == "No" + }), Writes.of[String])) and + (JsPath \ "isDefining").format[Boolean] + ) (ApiCriterionLabel.apply, unlift(ApiCriterionLabel.unapply)) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/label/ApiExtractedDataLabel.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/label/ApiExtractedDataLabel.scala new file mode 100644 index 0000000..9159d27 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/label/ApiExtractedDataLabel.scala @@ -0,0 +1,36 @@ +package xyz.driver.pdsuidomain.formats.json.label + +import xyz.driver.pdsuicommon.domain.{FuzzyValue, LongId} +import xyz.driver.pdsuidomain.entities.{Category, ExtractedData, ExtractedDataLabel, Label} +import play.api.data.validation.ValidationError +import play.api.libs.functional.syntax._ +import play.api.libs.json._ + +final case class ApiExtractedDataLabel(id: Option[Long], categoryId: Option[Long], value: Option[String]) { + + def toDomain(dataId: LongId[ExtractedData] = LongId(0)) = ExtractedDataLabel( + id = LongId(0), + dataId = dataId, + labelId = id.map(LongId[Label]), + categoryId = categoryId.map(LongId[Category]), + value = value.map(FuzzyValue.fromString) + ) +} + +object ApiExtractedDataLabel { + + implicit val format: Format[ApiExtractedDataLabel] = ( + (JsPath \ "id").formatNullable[Long] and + (JsPath \ "categoryId").formatNullable[Long] and + (JsPath \ "value").formatNullable[String](Format(Reads.of[String].filter(ValidationError("unknown value"))({ + case x if FuzzyValue.fromString.isDefinedAt(x) => true + case _ => false + }), Writes.of[String])) + ) (ApiExtractedDataLabel.apply, unlift(ApiExtractedDataLabel.unapply)) + + def fromDomain(dataLabel: ExtractedDataLabel) = ApiExtractedDataLabel( + id = dataLabel.labelId.map(_.id), + categoryId = dataLabel.categoryId.map(_.id), + value = dataLabel.value.map(FuzzyValue.valueToString) + ) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/label/ApiLabel.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/label/ApiLabel.scala new file mode 100644 index 0000000..8c30f3a --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/label/ApiLabel.scala @@ -0,0 +1,22 @@ +package xyz.driver.pdsuidomain.formats.json.label + +import play.api.libs.functional.syntax._ +import play.api.libs.json.{Format, JsPath} +import xyz.driver.pdsuidomain.entities.Label + +final case class ApiLabel(id: Long, name: String, categoryId: Long) + +object ApiLabel { + + implicit val format: Format[ApiLabel] = ( + (JsPath \ "id").format[Long] and + (JsPath \ "name").format[String] and + (JsPath \ "categoryId").format[Long] + ) (ApiLabel.apply, unlift(ApiLabel.unapply)) + + def fromDomain(x: Label) = ApiLabel( + id = x.id.id, + name = x.name, + categoryId = x.categoryId.id + ) +} 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 new file mode 100644 index 0000000..20b2607 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/message/ApiMessage.scala @@ -0,0 +1,59 @@ +package xyz.driver.pdsuidomain.formats.json.message + +import java.time.{ZoneId, ZonedDateTime} + +import play.api.libs.functional.syntax._ +import play.api.libs.json.{Format, JsPath} +import xyz.driver.pdsuidomain.entities.Message + +final case class ApiMessage(id: Long, + text: String, + lastUpdate: ZonedDateTime, + userId: Long, + isDraft: Boolean, + recordId: Option[Long], + documentId: Option[Long], + patientId: Option[String], + trialId: Option[String], + startPage: Option[Double], + endPage: Option[Double], + evidence: Option[String], + archiveRequired: Option[Boolean], + meta: Option[String]) + +object ApiMessage { + + def fromDomain(domain: Message) = ApiMessage( + id = domain.id.id, + text = domain.text, + lastUpdate = ZonedDateTime.of(domain.lastUpdate, ZoneId.of("Z")), + userId = domain.userId.id, + isDraft = domain.isDraft, + recordId = domain.recordId.map(_.id), + documentId = domain.documentId.map(_.id), + patientId = domain.patientId.map(_.toString), + trialId = domain.trialId.map(_.toString), + startPage = domain.startPage, + endPage = domain.endPage, + evidence = domain.evidence, + archiveRequired = domain.archiveRequired, + meta = domain.meta + ) + + implicit val format: Format[ApiMessage] = ( + (JsPath \ "id").format[Long] and + (JsPath \ "text").format[String] and + (JsPath \ "lastUpdate").format[ZonedDateTime] and + (JsPath \ "userId").format[Long] and + (JsPath \ "isDraft").format[Boolean] and + (JsPath \ "recordId").formatNullable[Long] and + (JsPath \ "documentId").formatNullable[Long] and + (JsPath \ "patientId").formatNullable[String] and + (JsPath \ "trialId").formatNullable[String] and + (JsPath \ "startPage").formatNullable[Double] and + (JsPath \ "endPage").formatNullable[Double] and + (JsPath \ "evidence").formatNullable[String] and + (JsPath \ "archiveRequired").formatNullable[Boolean] and + (JsPath \ "meta").formatNullable[String] + ) (ApiMessage.apply, unlift(ApiMessage.unapply)) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/message/ApiPartialMessage.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/message/ApiPartialMessage.scala new file mode 100644 index 0000000..151234c --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/message/ApiPartialMessage.scala @@ -0,0 +1,84 @@ +package xyz.driver.pdsuidomain.formats.json.message + +import java.time.LocalDateTime + +import xyz.driver.pdsuicommon.domain._ +import play.api.libs.functional.syntax._ +import play.api.libs.json.{Format, JsPath} +import xyz.driver.pdsuidomain.entities.Message + +final case class ApiPartialMessage(text: Option[String], + recordId: Option[Long], + documentId: Option[Long], + patientId: Option[String], + trialId: Option[String], + startPage: Option[Double], + endPage: Option[Double], + evidence: Option[String], + archiveRequired: Option[Boolean], + meta: Option[String]) { + + def toDomain(userId: LongId[User]) = Message( + id = LongId(0), + text = text.getOrElse(""), + userId = userId, + isDraft = true, + recordId = recordId.map(LongId(_)), + documentId = documentId.map(LongId(_)), + patientId = patientId.map(UuidId(_)), + trialId = trialId.map(StringId(_)), + startPage = startPage, + endPage = endPage, + evidence = evidence, + archiveRequired = archiveRequired, + meta = meta, + lastUpdate = LocalDateTime.MIN + ) + + def applyTo(orig: Message): Message = { + orig.copy( + text = text.getOrElse(""), + recordId = recordId.map(LongId(_)), + documentId = documentId.map(LongId(_)), + patientId = patientId.map(UuidId(_)), + trialId = trialId.map(StringId(_)), + startPage = startPage, + endPage = endPage, + evidence = evidence, + archiveRequired = archiveRequired, + meta = meta, + lastUpdate = LocalDateTime.MIN + ) + } + +} + +object ApiPartialMessage { + + implicit val format: Format[ApiPartialMessage] = ( + (JsPath \ "text").formatNullable[String] and + (JsPath \ "recordId").formatNullable[Long] and + (JsPath \ "documentId").formatNullable[Long] and + (JsPath \ "patientId").formatNullable[String] and + (JsPath \ "trialId").formatNullable[String] and + (JsPath \ "startPage").formatNullable[Double] and + (JsPath \ "endPage").formatNullable[Double] and + (JsPath \ "evidence").formatNullable[String] and + (JsPath \ "archiveRequired").formatNullable[Boolean] and + (JsPath \ "meta").formatNullable[String] + ) (ApiPartialMessage.apply, unlift(ApiPartialMessage.unapply)) + + def fromDomain(domain: Message) = ApiPartialMessage( + text = Some(domain.text), + recordId = domain.recordId.map(_.id), + documentId = domain.documentId.map(_.id), + patientId = domain.patientId.map(_.toString), + trialId = domain.trialId.map(_.toString), + startPage = domain.startPage, + endPage = domain.endPage, + evidence = domain.evidence, + archiveRequired = domain.archiveRequired, + meta = domain.meta + ) + +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/password/PasswordCreateRequest.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/password/PasswordCreateRequest.scala new file mode 100644 index 0000000..5c12415 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/password/PasswordCreateRequest.scala @@ -0,0 +1,9 @@ +package xyz.driver.pdsuidomain.formats.json.password + +import play.api.libs.json.{Format, Json} + +final case class PasswordCreateRequest(password: String, key: String) + +object PasswordCreateRequest { + implicit val format: Format[PasswordCreateRequest] = Json.format[PasswordCreateRequest] +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/password/PasswordUpdateRequest.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/password/PasswordUpdateRequest.scala new file mode 100644 index 0000000..07851ea --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/password/PasswordUpdateRequest.scala @@ -0,0 +1,9 @@ +package xyz.driver.pdsuidomain.formats.json.password + +import play.api.libs.json.{Format, Json} + +final case class PasswordUpdateRequest(password: String, oldPassword: String) + +object PasswordUpdateRequest { + implicit val format: Format[PasswordUpdateRequest] = Json.format[PasswordUpdateRequest] +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/ApiPatient.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/ApiPatient.scala new file mode 100644 index 0000000..0a3938c --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/ApiPatient.scala @@ -0,0 +1,44 @@ +package xyz.driver.pdsuidomain.formats.json.patient + +import java.time.{LocalDate, ZoneId, ZonedDateTime} + +import xyz.driver.pdsuidomain.entities.Patient +import play.api.libs.functional.syntax._ +import play.api.libs.json.{Format, JsPath} + +final case class ApiPatient(id: String, + status: String, + name: String, + dob: LocalDate, + assignee: Option[Long], + previousStatus: Option[String], + previousAssignee: Option[Long], + lastUpdate: ZonedDateTime, + condition: String) + +object ApiPatient { + + implicit val format: Format[ApiPatient] = ( + (JsPath \ "id").format[String] and + (JsPath \ "status").format[String] and + (JsPath \ "name").format[String] and + (JsPath \ "dob").format[LocalDate] and + (JsPath \ "assignee").formatNullable[Long] and + (JsPath \ "previousStatus").formatNullable[String] and + (JsPath \ "previousAssignee").formatNullable[Long] and + (JsPath \ "lastUpdate").format[ZonedDateTime] and + (JsPath \ "condition").format[String] + ) (ApiPatient.apply, unlift(ApiPatient.unapply)) + + def fromDomain(patient: Patient) = ApiPatient( + id = patient.id.toString, + status = PatientStatus.statusToString(patient.status), + name = patient.name, + dob = patient.dob, + assignee = patient.assignee.map(_.id), + previousStatus = patient.previousStatus.map(PatientStatus.statusToString), + previousAssignee = patient.previousAssignee.map(_.id), + lastUpdate = ZonedDateTime.of(patient.lastUpdate, ZoneId.of("Z")), + condition = patient.condition + ) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/PatientStatus.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/PatientStatus.scala new file mode 100644 index 0000000..d906fc6 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/PatientStatus.scala @@ -0,0 +1,24 @@ +package xyz.driver.pdsuidomain.formats.json.patient + +import xyz.driver.pdsuidomain.entities.Patient.Status + +object PatientStatus { + + val statusFromString: PartialFunction[String, Status] = { + case "New" => Status.New + case "Verified" => Status.Verified + case "Reviewed" => Status.Reviewed + case "Curated" => Status.Curated + case "Flagged" => Status.Flagged + case "Done" => Status.Done + } + + def statusToString(x: Status): String = x match { + case Status.New => "New" + case Status.Verified => "Verified" + case Status.Reviewed => "Reviewed" + case Status.Curated => "Curated" + case Status.Flagged => "Flagged" + case Status.Done => "Done" + } +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/eligible/ApiPartialPatientEligibleTrial.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/eligible/ApiPartialPatientEligibleTrial.scala new file mode 100644 index 0000000..03ff275 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/eligible/ApiPartialPatientEligibleTrial.scala @@ -0,0 +1,18 @@ +package xyz.driver.pdsuidomain.formats.json.patient.eligible + +import xyz.driver.pdsuidomain.entities.PatientTrialArmGroupView +import play.api.libs.json.{Format, Json} + +final case class ApiPartialPatientEligibleTrial(isVerified: Option[Boolean]) { + + def applyTo(orig: PatientTrialArmGroupView): PatientTrialArmGroupView = { + orig.copy( + isVerified = isVerified.getOrElse(orig.isVerified) + ) + } +} + +object ApiPartialPatientEligibleTrial { + + implicit val format: Format[ApiPartialPatientEligibleTrial] = Json.format +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/eligible/ApiPatientEligibleTrial.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/eligible/ApiPatientEligibleTrial.scala new file mode 100644 index 0000000..c1a6e76 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/eligible/ApiPatientEligibleTrial.scala @@ -0,0 +1,46 @@ +package xyz.driver.pdsuidomain.formats.json.patient.eligible + +import java.util.UUID + +import play.api.data.validation.ValidationError +import play.api.libs.functional.syntax._ +import play.api.libs.json._ +import xyz.driver.pdsuicommon.domain.FuzzyValue +import xyz.driver.pdsuidomain.services.PatientEligibleTrialService.RichPatientEligibleTrial + +final case class ApiPatientEligibleTrial(id: Long, + patientId: String, + trialId: String, + trialTitle: String, + arms: List[String], + hypothesisId: UUID, + eligibilityStatus: Option[String], + isVerified: Boolean) + +object ApiPatientEligibleTrial { + + implicit val apiEligibleTrialJsonFormat: Format[ApiPatientEligibleTrial] = ( + (JsPath \ "id").format[Long] and + (JsPath \ "patientId").format[String] and + (JsPath \ "trialId").format[String] and + (JsPath \ "trialTitle").format[String] and + (JsPath \ "arms").format[List[String]] and + (JsPath \ "hypothesisId").format[UUID] and + (JsPath \ "eligibilityStatus").formatNullable[String](Format(Reads.of[String].filter(ValidationError("unknown eligibility status"))({ + case x if FuzzyValue.fromString.isDefinedAt(x) => true + case _ => false + }), Writes.of[String])) and + (JsPath \ "isVerified").format[Boolean] + ) (ApiPatientEligibleTrial.apply, unlift(ApiPatientEligibleTrial.unapply)) + + def fromDomain(eligibleTrialWithTrial: RichPatientEligibleTrial) = ApiPatientEligibleTrial( + id = eligibleTrialWithTrial.group.id.id, + patientId = eligibleTrialWithTrial.group.patientId.toString, + trialId = eligibleTrialWithTrial.group.trialId.id, + trialTitle = eligibleTrialWithTrial.trial.title, + arms = eligibleTrialWithTrial.arms.map(_.name), + hypothesisId = eligibleTrialWithTrial.group.hypothesisId.id, + eligibleTrialWithTrial.group.eligibilityStatus.map(FuzzyValue.valueToString), + eligibleTrialWithTrial.group.isVerified + ) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/hypothesis/ApiPartialPatientHypothesis.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/hypothesis/ApiPartialPatientHypothesis.scala new file mode 100644 index 0000000..0858ce1 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/hypothesis/ApiPartialPatientHypothesis.scala @@ -0,0 +1,27 @@ +package xyz.driver.pdsuidomain.formats.json.patient.hypothesis + +import xyz.driver.pdsuidomain.entities.PatientHypothesis +import org.davidbild.tristate.Tristate +import org.davidbild.tristate.contrib.play.ToJsPathOpsFromJsPath +import play.api.libs.functional.syntax._ +import play.api.libs.json._ + +final case class ApiPartialPatientHypothesis(rationale: Tristate[String]) { + + def applyTo(orig: PatientHypothesis): PatientHypothesis = { + orig.copy( + rationale = rationale.cata(Some(_), None, orig.rationale) + ) + } +} + +object ApiPartialPatientHypothesis { + + implicit val reads: Reads[ApiPartialPatientHypothesis] = + (__ \ "rationale").readTristate[String].map(x => ApiPartialPatientHypothesis(x)) + + implicit val writes: Writes[ApiPartialPatientHypothesis] = + (__ \ "rationale").writeTristate[String].contramap(_.rationale) + + implicit val format: Format[ApiPartialPatientHypothesis] = Format(reads, writes) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/hypothesis/ApiPatientHypothesis.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/hypothesis/ApiPatientHypothesis.scala new file mode 100644 index 0000000..1b0767d --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/hypothesis/ApiPatientHypothesis.scala @@ -0,0 +1,32 @@ +package xyz.driver.pdsuidomain.formats.json.patient.hypothesis + +import java.util.UUID + +import xyz.driver.pdsuidomain.entities.PatientHypothesis +import play.api.libs.functional.syntax._ +import play.api.libs.json._ + +final case class ApiPatientHypothesis(id: UUID, + patientId: String, + hypothesisId: UUID, + matchedTrials: Long, + rationale: Option[String]) + +object ApiPatientHypothesis { + + implicit val apiPatientHypothesisJsonFormat: Format[ApiPatientHypothesis] = ( + (JsPath \ "id").format[UUID] and + (JsPath \ "patientId").format[String] and + (JsPath \ "hypothesisId").format[UUID] and + (JsPath \ "matchedTrials").format[Long] and + (JsPath \ "rationale").formatNullable[String] + ) (ApiPatientHypothesis.apply, unlift(ApiPatientHypothesis.unapply)) + + def fromDomain(patientHypothesis: PatientHypothesis) = ApiPatientHypothesis( + id = patientHypothesis.id.id, + patientId = patientHypothesis.patientId.toString, + hypothesisId = patientHypothesis.hypothesisId.id, + matchedTrials = patientHypothesis.matchedTrials, + rationale = patientHypothesis.rationale + ) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/label/ApiPartialPatientLabel.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/label/ApiPartialPatientLabel.scala new file mode 100644 index 0000000..82e3a3f --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/label/ApiPartialPatientLabel.scala @@ -0,0 +1,38 @@ +package xyz.driver.pdsuidomain.formats.json.patient.label + +import xyz.driver.pdsuidomain.entities.PatientLabel +import org.davidbild.tristate.Tristate +import org.davidbild.tristate.contrib.play.ToJsPathOpsFromJsPath +import play.api.data.validation.ValidationError +import play.api.libs.functional.syntax._ +import play.api.libs.json._ +import xyz.driver.pdsuicommon.domain.FuzzyValue + +final case class ApiPartialPatientLabel(primaryValue: Option[String], + verifiedPrimaryValue: Tristate[String]) { + + def applyTo(orig: PatientLabel): PatientLabel = { + orig.copy( + primaryValue = primaryValue.map(FuzzyValue.fromString).orElse(orig.primaryValue), + verifiedPrimaryValue = verifiedPrimaryValue.cata(x => Some(FuzzyValue.fromString(x)), None, orig.verifiedPrimaryValue) + ) + } + +} + +object ApiPartialPatientLabel { + + implicit val format: Format[ApiPartialPatientLabel] = ( + (JsPath \ "primaryValue").formatNullable[String](Format( + Reads.of[String].filter(ValidationError("unknown primary value"))({ + case x if FuzzyValue.fromString.isDefinedAt(x) => true + case _ => false + }), Writes.of[String])) and + (JsPath \ "verifiedPrimaryValue").formatTristate[String](Format( + Reads.of[String].filter(ValidationError("unknown verified primary value"))({ + case x if FuzzyValue.fromString.isDefinedAt(x) => true + case _ => false + }), Writes.of[String])) + ) (ApiPartialPatientLabel.apply, unlift(ApiPartialPatientLabel.unapply)) + +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/label/ApiPatientLabel.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/label/ApiPatientLabel.scala new file mode 100644 index 0000000..fc8687b --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/label/ApiPatientLabel.scala @@ -0,0 +1,47 @@ +package xyz.driver.pdsuidomain.formats.json.patient.label + +import xyz.driver.pdsuidomain.entities.PatientLabel +import play.api.data.validation.ValidationError +import play.api.libs.functional.syntax._ +import play.api.libs.json._ +import xyz.driver.pdsuicommon.domain.FuzzyValue + +final case class ApiPatientLabel(id: Long, + labelId: Long, + primaryValue: Option[String], + verifiedPrimaryValue: Option[String], + score: Int, + isImplicitMatch: Boolean, + isVisible: Boolean, + isVerified: Boolean) + +object ApiPatientLabel { + + implicit val apiPatientLabelJsonFormat: Format[ApiPatientLabel] = ( + (JsPath \ "id").format[Long] and + (JsPath \ "labelId").format[Long] and + (JsPath \ "primaryValue").formatNullable[String](Format(Reads.of[String].filter(ValidationError("unknown value"))({ + case x if FuzzyValue.fromString.isDefinedAt(x) => true + case _ => false + }), Writes.of[String])) and + (JsPath \ "verifiedPrimaryValue").formatNullable[String](Format(Reads.of[String].filter(ValidationError("unknown value"))({ + case x if FuzzyValue.fromString.isDefinedAt(x) => true + case _ => false + }), Writes.of[String])) and + (JsPath \ "score").format[Int] and + (JsPath \ "isImplicitMatch").format[Boolean] and + (JsPath \ "isVisible").format[Boolean] and + (JsPath \ "isVerified").format[Boolean] + ) (ApiPatientLabel.apply, unlift(ApiPatientLabel.unapply)) + + def fromDomain(patientLabel: PatientLabel, isVerified: Boolean): ApiPatientLabel = ApiPatientLabel( + id = patientLabel.id.id, + labelId = patientLabel.labelId.id, + primaryValue = patientLabel.primaryValue.map(FuzzyValue.valueToString), + verifiedPrimaryValue = patientLabel.verifiedPrimaryValue.map(FuzzyValue.valueToString), + score = patientLabel.score, + isImplicitMatch = patientLabel.isImplicitMatch, + isVisible = patientLabel.isVisible, + isVerified = isVerified + ) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/label/ApiPatientLabelDefiningCriteria.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/label/ApiPatientLabelDefiningCriteria.scala new file mode 100644 index 0000000..3fe135e --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/label/ApiPatientLabelDefiningCriteria.scala @@ -0,0 +1,25 @@ +package xyz.driver.pdsuidomain.formats.json.patient.label + +import play.api.data.validation.ValidationError +import play.api.libs.functional.syntax._ +import play.api.libs.json._ +import xyz.driver.pdsuicommon.domain.FuzzyValue +import xyz.driver.pdsuidomain.entities.PatientLabel + +final case class ApiPatientLabelDefiningCriteria(labelId: Long, value: Option[String]) + +object ApiPatientLabelDefiningCriteria { + + implicit val format: Format[ApiPatientLabelDefiningCriteria] = ( + (JsPath \ "labelId").format[Long] and + (JsPath \ "value").formatNullable[String](Format(Reads.of[String].filter(ValidationError("unknown value"))({ + case x if FuzzyValue.fromString.isDefinedAt(x) => true + case _ => false + }), Writes.of[String])) + ) (ApiPatientLabelDefiningCriteria.apply, unlift(ApiPatientLabelDefiningCriteria.unapply)) + + def fromDomain(x: PatientLabel) = ApiPatientLabelDefiningCriteria( + labelId = x.labelId.id, + value = x.verifiedPrimaryValue.map(FuzzyValue.valueToString) + ) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/trial/ApiPartialPatientCriterion.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/trial/ApiPartialPatientCriterion.scala new file mode 100644 index 0000000..b68dad5 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/trial/ApiPartialPatientCriterion.scala @@ -0,0 +1,40 @@ +package xyz.driver.pdsuidomain.formats.json.patient.trial + +import xyz.driver.pdsuidomain.entities.PatientCriterion +import org.davidbild.tristate.Tristate +import org.davidbild.tristate.contrib.play.ToJsPathOpsFromJsPath +import play.api.data.validation.ValidationError +import play.api.libs.functional.syntax._ +import play.api.libs.json.{Format, JsPath, Reads, Writes} +import xyz.driver.pdsuicommon.domain.FuzzyValue + +final case class ApiPartialPatientCriterion(eligibilityStatus: Option[String], + verifiedEligibilityStatus: Tristate[String]) { + + def applyTo(orig: PatientCriterion): PatientCriterion = { + orig.copy( + eligibilityStatus = eligibilityStatus.map(FuzzyValue.fromString).orElse(orig.eligibilityStatus), + verifiedEligibilityStatus = verifiedEligibilityStatus.cata(x => + Some(FuzzyValue.fromString(x)), + None, + orig.verifiedEligibilityStatus + ) + ) + } +} + +object ApiPartialPatientCriterion { + + implicit val format: Format[ApiPartialPatientCriterion] = ( + (JsPath \ "eligibilityStatus").formatNullable[String](Format( + Reads.of[String].filter(ValidationError("unknown eligibility status"))({ + case x if FuzzyValue.fromString.isDefinedAt(x) => true + case _ => false + }), Writes.of[String])) and + (JsPath \ "verifiedEligibilityStatus").formatTristate[String](Format( + Reads.of[String].filter(ValidationError("unknown verified eligibility status"))({ + case x if FuzzyValue.fromString.isDefinedAt(x) => true + case _ => false + }), Writes.of[String])) + ) (ApiPartialPatientCriterion.apply, unlift(ApiPartialPatientCriterion.unapply)) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/trial/ApiPartialPatientCriterionList.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/trial/ApiPartialPatientCriterionList.scala new file mode 100644 index 0000000..71cb58f --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/trial/ApiPartialPatientCriterionList.scala @@ -0,0 +1,32 @@ +package xyz.driver.pdsuidomain.formats.json.patient.trial + +import xyz.driver.pdsuicommon.domain.{FuzzyValue, LongId} +import xyz.driver.pdsuidomain.entities.PatientCriterion +import play.api.data.validation.ValidationError +import play.api.libs.functional.syntax._ +import play.api.libs.json.{Format, JsPath, Reads, Writes} +import xyz.driver.pdsuidomain.services.PatientCriterionService.DraftPatientCriterion + +final case class ApiPartialPatientCriterionList(id: Long, + eligibilityStatus: Option[String], + isVerified: Option[Boolean]) { + + def toDomain: DraftPatientCriterion = DraftPatientCriterion( + id = LongId[PatientCriterion](id), + eligibilityStatus = eligibilityStatus.map(FuzzyValue.fromString), + isVerified = isVerified + ) +} + +object ApiPartialPatientCriterionList { + + implicit val format: Format[ApiPartialPatientCriterionList] = ( + (JsPath \ "id").format[Long] and + (JsPath \ "eligibilityStatus").formatNullable[String](Format( + Reads.of[String].filter(ValidationError("unknown eligibility status"))({ + case x if FuzzyValue.fromString.isDefinedAt(x) => true + case _ => false + }), Writes.of[String])) and + (JsPath \ "isVerified").formatNullable[Boolean] + ) (ApiPartialPatientCriterionList.apply, unlift(ApiPartialPatientCriterionList.unapply)) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/trial/ApiPatientCriterion.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/trial/ApiPatientCriterion.scala new file mode 100644 index 0000000..3e2de99 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/patient/trial/ApiPatientCriterion.scala @@ -0,0 +1,72 @@ +package xyz.driver.pdsuidomain.formats.json.patient.trial + +import java.time.{ZoneId, ZonedDateTime} + +import xyz.driver.pdsuicommon.domain.{FuzzyValue, LongId} +import xyz.driver.pdsuidomain.entities.{Arm, Label, PatientCriterion} +import play.api.data.validation.ValidationError +import play.api.libs.functional.syntax._ +import play.api.libs.json.{Format, JsPath, Reads, Writes} + +final case class ApiPatientCriterion(id: Long, + labelId: Long, + nctId: String, + criterionText: String, + criterionValue: Option[String], + criterionIsDefining: Boolean, + criterionIsCompound: Boolean, + arms: List[String], + eligibilityStatus: Option[String], + verifiedEligibilityStatus: Option[String], + isVerified: Boolean, + isVisible: Boolean, + lastUpdate: ZonedDateTime) + +object ApiPatientCriterion { + + implicit val format: Format[ApiPatientCriterion] = ( + (JsPath \ "id").format[Long] and + (JsPath \ "labelId").format[Long] and + (JsPath \ "nctId").format[String] and + (JsPath \ "criterionText").format[String] and + (JsPath \ "criterionValue").formatNullable[String](Format(Reads.of[String].filter(ValidationError("unknown value"))({ x => + x == "Yes" || x == "No" + }), Writes.of[String])) and + (JsPath \ "criterionIsDefining").format[Boolean] and + (JsPath \ "criterionIsCompound").format[Boolean] and + (JsPath \ "arms").format[List[String]] and + (JsPath \ "eligibilityStatus").formatNullable[String](Format(Reads.of[String].filter(ValidationError("unknown status"))({ + case x if FuzzyValue.fromString.isDefinedAt(x) => true + case _ => false + }), Writes.of[String])) and + (JsPath \ "verifiedEligibilityStatus").formatNullable[String](Format( + Reads.of[String].filter(ValidationError("unknown status"))({ + case x if FuzzyValue.fromString.isDefinedAt(x) => true + case _ => false + }), Writes.of[String])) and + (JsPath \ "isVerified").format[Boolean] and + (JsPath \ "isVisible").format[Boolean] and + (JsPath \ "lastUpdate").format[ZonedDateTime] + ) (ApiPatientCriterion.apply, unlift(ApiPatientCriterion.unapply)) + + def fromDomain(patientCriterion: PatientCriterion, + labelId: LongId[Label], + arms: List[Arm], + criterionIsCompound: Boolean) = ApiPatientCriterion( + id = patientCriterion.id.id, + labelId = labelId.id, + nctId = patientCriterion.nctId.id, + criterionText = patientCriterion.criterionText, + criterionValue = patientCriterion.criterionValue.map { x => + FuzzyValue.valueToString(FuzzyValue.fromBoolean(x)) + }, + criterionIsDefining = patientCriterion.criterionIsDefining, + criterionIsCompound = criterionIsCompound, + arms = arms.map(_.name), + eligibilityStatus = patientCriterion.eligibilityStatus.map(FuzzyValue.valueToString), + verifiedEligibilityStatus = patientCriterion.verifiedEligibilityStatus.map(FuzzyValue.valueToString), + isVerified = patientCriterion.isVerified, + isVisible = patientCriterion.isVisible, + lastUpdate = ZonedDateTime.of(patientCriterion.lastUpdate, ZoneId.of("Z")) + ) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiCreateRecord.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiCreateRecord.scala new file mode 100644 index 0000000..57c030b --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiCreateRecord.scala @@ -0,0 +1,37 @@ +package xyz.driver.pdsuidomain.formats.json.record + +import java.time.LocalDateTime +import java.util.UUID + +import xyz.driver.pdsuicommon.domain._ +import xyz.driver.pdsuidomain.entities._ +import play.api.libs.json._ + +final case class ApiCreateRecord(disease: String, + patientId: String, + requestId: UUID, + filename: String) { + + def toDomain = MedicalRecord( + id = LongId(0), + status = MedicalRecord.Status.New, + previousStatus = None, + assignee = None, + previousAssignee = None, + patientId = UuidId(patientId), + requestId = RecordRequestId(requestId), + disease = disease, + caseId = None, + physician = None, + sourceName = filename, + meta = None, + predictedMeta = None, + predictedDocuments = None, + lastUpdate = LocalDateTime.now() + ) +} + +object ApiCreateRecord { + + implicit val format: Format[ApiCreateRecord] = Json.format[ApiCreateRecord] +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiRecord.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiRecord.scala new file mode 100644 index 0000000..6096006 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiRecord.scala @@ -0,0 +1,57 @@ +package xyz.driver.pdsuidomain.formats.json.record + +import java.time.{ZoneId, ZonedDateTime} + +import xyz.driver.pdsuidomain.entities.MedicalRecord +import play.api.data.validation.ValidationError +import play.api.libs.functional.syntax._ +import play.api.libs.json._ +import xyz.driver.pdsuicommon.json.JsonSerializer + +final case class ApiRecord(id: Long, + patientId: String, + caseId: Option[String], + physician: Option[String], + lastUpdate: ZonedDateTime, + status: String, + previousStatus: Option[String], + assignee: Option[Long], + previousAssignee: Option[Long], + meta: String) + +object ApiRecord { + + private val statusFormat = Format( + Reads.StringReads.filter(ValidationError("unknown status")) { + case x if MedicalRecordStatus.statusFromString.isDefinedAt(x) => true + case _ => false + }, + Writes.StringWrites + ) + + implicit val format: Format[ApiRecord] = ( + (JsPath \ "id").format[Long] and + (JsPath \ "patientId").format[String] and + (JsPath \ "caseId").formatNullable[String] and + (JsPath \ "physician").formatNullable[String] and + (JsPath \ "lastUpdate").format[ZonedDateTime] and + (JsPath \ "status").format(statusFormat) and + (JsPath \ "previousStatus").formatNullable(statusFormat) and + (JsPath \ "assignee").formatNullable[Long] and + (JsPath \ "previousAssignee").formatNullable[Long] and + (JsPath \ "meta").format(Format(Reads { x => JsSuccess(Json.stringify(x)) }, Writes[String](Json.parse))) + ) (ApiRecord.apply, unlift(ApiRecord.unapply)) + + def fromDomain(record: MedicalRecord) = ApiRecord( + id = record.id.id, + patientId = record.patientId.toString, + caseId = record.caseId.map(_.id), + physician = record.physician, + lastUpdate = ZonedDateTime.of(record.lastUpdate, ZoneId.of("Z")), + status = MedicalRecordStatus.statusToString(record.status), + previousStatus = record.previousStatus.map(MedicalRecordStatus.statusToString), + assignee = record.assignee.map(_.id), + previousAssignee = record.previousAssignee.map(_.id), + meta = record.meta.map(x => JsonSerializer.serialize(x.content)).getOrElse("[]") + ) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiUpdateRecord.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiUpdateRecord.scala new file mode 100644 index 0000000..b752a4c --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiUpdateRecord.scala @@ -0,0 +1,47 @@ +package xyz.driver.pdsuidomain.formats.json.record + +import xyz.driver.pdsuidomain.entities.MedicalRecord.Meta +import xyz.driver.pdsuidomain.entities._ +import xyz.driver.pdsuicommon.domain.{LongId, TextJson, User} +import org.davidbild.tristate.Tristate +import org.davidbild.tristate.contrib.play.ToJsPathOpsFromJsPath +import play.api.data.validation.ValidationError +import play.api.libs.functional.syntax._ +import play.api.libs.json._ +import xyz.driver.pdsuicommon.json.JsonSerializer + +final case class ApiUpdateRecord(status: Option[String], + assignee: Tristate[Long], + meta: Tristate[String]) { + + def applyTo(orig: MedicalRecord): MedicalRecord = { + orig.copy( + status = status.map(MedicalRecordStatus.statusFromString).getOrElse(orig.status), + assignee = assignee.map(LongId[User]).cata(Some(_), None, orig.assignee), + meta = meta.cata(x => Some(TextJson(JsonSerializer.deserialize[List[Meta]](x))), None, orig.meta) + ) + } +} + +object ApiUpdateRecord { + + private val reads: Reads[ApiUpdateRecord] = ( + (JsPath \ "status").readNullable[String](Reads.of[String].filter(ValidationError("unknown status"))({ + case x if MedicalRecordStatus.statusFromString.isDefinedAt(x) => true + case _ => false + })) and + (JsPath \ "assignee").readTristate[Long] and + (JsPath \ "meta").readTristate(Reads { x => JsSuccess(Json.stringify(x)) }).map { + case Tristate.Present("{}") => Tristate.Absent + case x => x + } + ) (ApiUpdateRecord.apply _) + + private val writes: Writes[ApiUpdateRecord] = ( + (JsPath \ "status").writeNullable[String] and + (JsPath \ "assignee").writeTristate[Long] and + (JsPath \ "meta").writeTristate(Writes[String](Json.parse)) + ) (unlift(ApiUpdateRecord.unapply)) + + implicit val format: Format[ApiUpdateRecord] = Format(reads, writes) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/MedicalRecordStatus.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/MedicalRecordStatus.scala new file mode 100644 index 0000000..bde4af0 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/MedicalRecordStatus.scala @@ -0,0 +1,34 @@ +package xyz.driver.pdsuidomain.formats.json.record + +import xyz.driver.pdsuidomain.entities.MedicalRecord.Status + +object MedicalRecordStatus { + + val statusFromString: PartialFunction[String, Status] = { + case "Unprocessed" => Status.Unprocessed + case "PreCleaning" => Status.PreCleaning + case "New" => Status.New + case "Cleaned" => Status.Cleaned + case "PreOrganized" => Status.PreOrganized + case "PreOrganizing" => Status.PreOrganizing + case "Reviewed" => Status.Reviewed + case "Organized" => Status.Organized + case "Done" => Status.Done + case "Flagged" => Status.Flagged + case "Archived" => Status.Archived + } + + def statusToString(x: Status): String = x match { + case Status.Unprocessed => "Unprocessed" + case Status.PreCleaning => "PreCleaning" + case Status.New => "New" + case Status.Cleaned => "Cleaned" + case Status.PreOrganized => "PreOrganized" + case Status.PreOrganizing => "PreOrganizing" + case Status.Reviewed => "Reviewed" + case Status.Organized => "Organized" + case Status.Done => "Done" + case Status.Flagged => "Flagged" + case Status.Archived => "Archived" + } +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/session/NewSessionRequest.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/session/NewSessionRequest.scala new file mode 100644 index 0000000..fbe9689 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/session/NewSessionRequest.scala @@ -0,0 +1,12 @@ +package xyz.driver.pdsuidomain.formats.json.session + +import xyz.driver.pdsuicommon.domain.Email +import play.api.libs.json._ +import xyz.driver.pdsuicommon.json.Serialization._ + +final case class NewSessionRequest(email: Email, password: String) + +object NewSessionRequest { + + implicit val format: Format[NewSessionRequest] = Json.format[NewSessionRequest] +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/session/NewSessionResponse.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/session/NewSessionResponse.scala new file mode 100644 index 0000000..55e05b1 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/session/NewSessionResponse.scala @@ -0,0 +1,11 @@ +package xyz.driver.pdsuidomain.formats.json.session + +import play.api.libs.json.Json +import xyz.driver.pdsuidomain.formats.json.user.ApiUser + +final case class NewSessionResponse(token: String, user: ApiUser) + +object NewSessionResponse { + + implicit val format = Json.format[NewSessionResponse] +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/studydesign/ApiStudyDesign.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/studydesign/ApiStudyDesign.scala new file mode 100644 index 0000000..958ff5d --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/studydesign/ApiStudyDesign.scala @@ -0,0 +1,20 @@ +package xyz.driver.pdsuidomain.formats.json.studydesign + +import xyz.driver.pdsuidomain.entities.StudyDesign +import play.api.libs.functional.syntax._ +import play.api.libs.json.{Format, JsPath} + +final case class ApiStudyDesign(id: Long, name: String) + +object ApiStudyDesign { + + implicit val format: Format[ApiStudyDesign] = ( + (JsPath \ "id").format[Long] and + (JsPath \ "name").format[String] + ) (ApiStudyDesign.apply, unlift(ApiStudyDesign.unapply)) + + def fromDomain(studyDesign: StudyDesign) = ApiStudyDesign( + id = studyDesign.id.id, + name = studyDesign.name + ) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/trial/ApiPartialTrial.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/trial/ApiPartialTrial.scala new file mode 100644 index 0000000..0dc1446 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/trial/ApiPartialTrial.scala @@ -0,0 +1,44 @@ +package xyz.driver.pdsuidomain.formats.json.trial + +import java.util.UUID + +import xyz.driver.pdsuicommon.domain.{LongId, UuidId} +import xyz.driver.pdsuidomain.entities.Trial +import org.davidbild.tristate.Tristate +import org.davidbild.tristate.contrib.play.ToJsPathOpsFromJsPath +import play.api.libs.functional.syntax._ +import play.api.libs.json._ + +final case class ApiPartialTrial(hypothesisId: Tristate[UUID], + studyDesignId: Tristate[Long], + overview: Tristate[String], + title: Tristate[String]) { + + def applyTo(orig: Trial): Trial = { + orig.copy( + hypothesisId = hypothesisId.map(UuidId(_)).cata(Some(_), None, orig.hypothesisId), + studyDesignId = studyDesignId.map(LongId(_)).cata(Some(_), None, orig.studyDesignId), + overview = overview.cata(Some(_), None, orig.overview), + title = title.cata(Some(_).getOrElse(""), "", orig.title) + ) + } +} + +object ApiPartialTrial { + + private val reads: Reads[ApiPartialTrial] = ( + (JsPath \ "hypothesisId").readTristate[UUID] and + (JsPath \ "studyDesignId").readTristate[Long] and + (JsPath \ "overview").readTristate[String] and + (JsPath \ "title").readTristate[String] + ) (ApiPartialTrial.apply _) + + private val writes: Writes[ApiPartialTrial] = ( + (JsPath \ "hypothesisId").writeTristate[UUID] and + (JsPath \ "studyDesignId").writeTristate[Long] and + (JsPath \ "overview").writeTristate[String] and + (JsPath \ "title").writeTristate[String] + ) (unlift(ApiPartialTrial.unapply)) + + implicit val format: Format[ApiPartialTrial] = Format(reads, writes) +} 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 new file mode 100644 index 0000000..3267617 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/trial/ApiTrial.scala @@ -0,0 +1,63 @@ +package xyz.driver.pdsuidomain.formats.json.trial + +import java.time.{ZoneId, ZonedDateTime} +import java.util.UUID + +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], + status: String, + assignee: Option[Long], + previousStatus: Option[String], + previousAssignee: Option[Long], + condition: Option[String], + phase: Option[String], + hypothesisId: Option[UUID], + studyDesignId: Option[Long], + isPartner: Boolean, + overview: Option[String], + overviewTemplate: String, + isUpdated: Boolean, + title: String) + +object ApiTrial { + + implicit val format: Format[ApiTrial] = ( + (JsPath \ "id").format[String] and + (JsPath \ "lastUpdate").formatNullable[ZonedDateTime] and + (JsPath \ "status").format[String] and + (JsPath \ "assignee").formatNullable[Long] and + (JsPath \ "previousStatus").formatNullable[String] and + (JsPath \ "previousAssignee").formatNullable[Long] and + (JsPath \ "condition").formatNullable[String] and + (JsPath \ "phase").formatNullable[String] and + (JsPath \ "hypothesisId").formatNullable[UUID] and + (JsPath \ "studyDesignId").formatNullable[Long] 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] + ) (ApiTrial.apply, unlift(ApiTrial.unapply)) + + def fromDomain(trial: Trial): ApiTrial = ApiTrial( + id = trial.id.id, + status = TrialStatus.statusToString(trial.status), + assignee = trial.assignee.map(_.id), + previousStatus = trial.previousStatus.map(TrialStatus.statusToString), + previousAssignee = trial.previousAssignee.map(_.id), + lastUpdate = Option(ZonedDateTime.of(trial.lastUpdate, ZoneId.of("Z"))), + condition = Option(trial.condition.toString), + phase = Option(trial.phase), + hypothesisId = trial.hypothesisId.map(_.id), + studyDesignId = trial.studyDesignId.map(_.id), + isPartner = trial.isPartner, + overview = trial.overview, + overviewTemplate = trial.overviewTemplate, + isUpdated = trial.isUpdated, + title = trial.title + ) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/trial/TrialStatus.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/trial/TrialStatus.scala new file mode 100644 index 0000000..49bcbcb --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/trial/TrialStatus.scala @@ -0,0 +1,30 @@ +package xyz.driver.pdsuidomain.formats.json.trial + +import xyz.driver.pdsuidomain.entities.Trial.Status + +object TrialStatus { + + val statusFromString: PartialFunction[String, Status] = { + case "New" => Status.New + case "ReviewSummary" => Status.ReviewSummary + case "Summarized" => Status.Summarized + case "PendingUpdate" => Status.PendingUpdate + case "Update" => Status.Update + case "ReviewCriteria" => Status.ReviewCriteria + case "Done" => Status.Done + case "Flagged" => Status.Flagged + case "Archived" => Status.Archived + } + + def statusToString(x: Status): String = x match { + case Status.New => "New" + case Status.ReviewSummary => "ReviewSummary" + case Status.Summarized => "Summarized" + case Status.PendingUpdate => "PendingUpdate" + case Status.Update => "Update" + case Status.ReviewCriteria => "ReviewCriteria" + case Status.Done => "Done" + case Status.Flagged => "Flagged" + case Status.Archived => "Archived" + } +} 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 new file mode 100644 index 0000000..977934b --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiPartialUser.scala @@ -0,0 +1,83 @@ +package xyz.driver.pdsuidomain.formats.json.user + +import java.math.BigInteger +import java.security.SecureRandom + +import xyz.driver.pdsuicommon.domain.{Email, LongId, PasswordHash, User} +import play.api.data.validation._ +import play.api.libs.functional.syntax._ +import play.api.libs.json._ + +import scala.collection._ +import scala.util.Try +import ApiPartialUser._ +import xyz.driver.pdsuicommon.json.JsonValidationException +import xyz.driver.pdsuicommon.validation.{AdditionalConstraints, JsonValidationErrors} + +final case class ApiPartialUser(email: Option[String], + name: Option[String], + roleId: Option[String]) { + + def applyTo(orig: User): Try[User] = Try { + val validation = Map( + JsPath \ "name" -> AdditionalConstraints.optionNonEmptyConstraint(name) + ) + + val validationErrors: JsonValidationErrors = validation.collect({ + case (fieldName, e: Invalid) => (fieldName, e.errors) + })(breakOut) + + if (validationErrors.isEmpty) { + orig.copy(name = name.get) + } else { + throw new JsonValidationException(validationErrors) + } + } + + def toDomain(id: LongId[User] = LongId(0L)): Try[User] = Try { + val validation = Map( + JsPath \ "email" -> AdditionalConstraints.optionNonEmptyConstraint(email), + JsPath \ "name" -> AdditionalConstraints.optionNonEmptyConstraint(name), + JsPath \ "roleId" -> AdditionalConstraints.optionNonEmptyConstraint(roleId) + ) + + val validationErrors: JsonValidationErrors = validation.collect({ + case (fieldName, e: Invalid) => (fieldName, e.errors) + })(breakOut) + + if (validationErrors.isEmpty) { + val userEmail = email.map(x => Email(x.toLowerCase)).get + User( + id = id, + email = userEmail, + name = name.get, + role = roleId.map(UserRole.roleFromString).get, + passwordHash = PasswordHash(createPassword), + latestActivity = None, + deleted = None + ) + } else { + throw new JsonValidationException(validationErrors) + } + } +} + +object ApiPartialUser { + + // SecureRandom is thread-safe, see the implementation + private val random = new SecureRandom() + + def createPassword: String = new BigInteger(240, random).toString(32) + + 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 \ "roleId").formatNullable[String](Format(Reads.of[String].filter(ValidationError("unknown role"))({ + case x if UserRole.roleFromString.isDefinedAt(x) => true + case _ => false + }), Writes.of[String])) + ) (ApiPartialUser.apply, unlift(ApiPartialUser.unapply)) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiUser.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiUser.scala new file mode 100644 index 0000000..c2653ec --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiUser.scala @@ -0,0 +1,32 @@ +package xyz.driver.pdsuidomain.formats.json.user + +import java.time.{ZoneId, ZonedDateTime} + +import xyz.driver.pdsuicommon.domain.User +import play.api.data.validation.ValidationError +import play.api.libs.functional.syntax._ +import play.api.libs.json._ + +final case class ApiUser(id: Long, email: String, name: String, roleId: String, latestActivity: Option[ZonedDateTime]) + +object ApiUser { + + implicit val format: Format[ApiUser] = ( + (JsPath \ "id").format[Long] and + (JsPath \ "email").format[String](Reads.email) and + (JsPath \ "name").format[String] and + (JsPath \ "roleId").format[String](Format(Reads.of[String].filter(ValidationError("unknown role"))({ + case x if UserRole.roleFromString.isDefinedAt(x) => true + case _ => false + }), Writes.of[String])) and + (JsPath \ "latestActivity").formatNullable[ZonedDateTime] + ) (ApiUser.apply, unlift(ApiUser.unapply)) + + def fromDomain(user: User) = ApiUser( + user.id.id, + user.email.value, + user.name, + UserRole.roleToString(user.role), + user.latestActivity.map(ZonedDateTime.of(_, ZoneId.of("Z"))) + ) +} diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/UserRole.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/UserRole.scala new file mode 100644 index 0000000..74acb81 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/user/UserRole.scala @@ -0,0 +1,33 @@ +package xyz.driver.pdsuidomain.formats.json.user + +import xyz.driver.pdsuicommon.domain.User.Role + +object UserRole { + + val roleFromString: PartialFunction[String, Role] = { + case "Cleaner" => Role.RecordCleaner + case "Organizer" => Role.RecordOrganizer + case "Extractor" => Role.DocumentExtractor + case "RecordAdmin" => Role.RecordAdmin + case "TrialSummarizer" => Role.TrialSummarizer + case "CriteriaCurator" => Role.CriteriaCurator + case "TrialAdmin" => Role.TrialAdmin + case "EligibilityVerifier" => Role.EligibilityVerifier + case "TreatmentMatchingAdmin" => Role.TreatmentMatchingAdmin + case "RoutesCurator" => Role.RoutesCurator + // No Mixed at this time + } + + def roleToString(x: Role): String = x match { + case Role.RecordCleaner => "Cleaner" + case Role.RecordOrganizer => "Organizer" + case Role.DocumentExtractor => "Extractor" + case Role.RecordAdmin => "RecordAdmin" + case Role.TrialSummarizer => "TrialSummarizer" + case Role.CriteriaCurator => "CriteriaCurator" + case Role.TrialAdmin => "TrialAdmin" + case Role.EligibilityVerifier => "EligibilityVerifier" + case Role.TreatmentMatchingAdmin => "TreatmentMatchingAdmin" + case Role.RoutesCurator => "RoutesCurator" + } +} -- cgit v1.2.3