common-lib/src/main/scala/models/Stub.scala (134 lines of code) (raw):

package models import cats.syntax.either._ import enumeratum.EnumEntry.Uppercase import enumeratum._ import io.circe.Decoder.Result import io.circe._ import io.circe.generic.extras.Configuration import io.circe.generic.extras.semiauto.{deriveConfiguredDecoder, deriveConfiguredEncoder} import io.circe.parser.decode import io.circe.syntax._ import org.joda.time.{DateTime, LocalDate} import DateFormat._ // Required for serialisation / deserialisation of DateTime import scala.collection.immutable case class ExternalData( path: Option[String] = None, lastModified: Option[DateTime] = None, lastModifiedBy: Option[String] = None, status: Status = Status.Writers, published: Option[Boolean] = None, timePublished: Option[DateTime] = None, revision: Option[Long] = None, storyBundleId: Option[String] = None, activeInInCopy: Option[Boolean] = None, takenDown: Option[Boolean] = None, timeTakenDown: Option[DateTime] = None, wordCount: Option[Int] = None, printWordCount: Option[Int] = None, embargoedUntil: Option[DateTime] = None, embargoedIndefinitely: Option[Boolean] = None, scheduledLaunchDate: Option[DateTime] = None, optimisedForWeb: Option[Boolean] = None, optimisedForWebChanged: Option[Boolean] = None, sensitive: Option[Boolean] = None, legallySensitive: Option[Boolean] = None, headline: Option[String] = None, hasMainMedia: Option[Boolean] = None, commentable: Option[Boolean] = None, commissionedLength: Option[Int] = None, missingCommissionedLengthReason: Option[String] = None, actualPublicationId: Option[Long] = None, actualBookId: Option[Long] = None, actualBookSectionId: Option[Long] = None, actualNewspaperPageNumber: Option[Int] = None, actualNewspaperPublicationDate: Option[LocalDate] = None, // Description enriched for use by WF front end client code. shortActualPrintLocationDescription: Option[String] = None, longActualPrintLocationDescription: Option[String] = None, statusInPrint: Option[OctopusStatus] = None, lastModifiedInPrintBy: Option[String] = None, rightsSyndicationAggregate: Option[Boolean] = None, rightsSubscriptionDatabases: Option[Boolean] = None, rightsDeveloperCommunity: Option[Boolean] = None, byline: Option[String] = None, displayHint: Option[String] = None) { } object ExternalData { implicit val customConfig: Configuration = Configuration.default.withDefaults implicit val encoder: Encoder[ExternalData] = deriveConfiguredEncoder implicit val decoder: Decoder[ExternalData] = deriveConfiguredDecoder } case class Stub(id: Option[Long] = None, title: String, section: String, due: Option[DateTime] = None, assignee: Option[String] = None, assigneeEmail: Option[String] = None, composerId: Option[String] = None, contentType: String, priority: Int = 0, needsLegal: Flag = Flag.NA, needsPictureDesk: Flag = Flag.NA, note: Option[String] = None, prodOffice: String, createdAt: DateTime = DateTime.now, lastModified: DateTime = DateTime.now, trashed: Boolean = false, commissioningDesks: Option[String] = None, editorId: Option[String] = None, externalData: Option[ExternalData], plannedPublicationId: Option[Long] = None, plannedBookId: Option[Long] = None, plannedBookSectionId: Option[Long] = None, plannedNewspaperPageNumber: Option[Int] = None, plannedNewspaperPublicationDate: Option[LocalDate] = None, // Description enriched for use by WF front end client code. shortPlannedPrintLocationDescription: Option[String] = None, longPlannedPrintLocationDescription: Option[String] = None, rightsReviewed: Boolean = false ) object Stub { implicit val customConfig: Configuration = Configuration.default.withDefaults implicit val encoder: Encoder[Stub] = deriveConfiguredEncoder implicit val decoderHelper: Decoder[Stub] = new Decoder[Stub] { def apply(c: HCursor): Result[Stub] = { // This is needed because sometimes the JSON contains a nested section object val sectionName: String = c.downField("section").downField("name").as[String].getOrElse(c.downField("section").as[String].getOrElse("")) val newJson: ACursor = c.withFocus(j => j.asObject.map(_.remove("section").add("section", Json.fromString(sectionName))).map(Json.fromJsonObject).getOrElse(j)) decode[Stub](newJson.focus.getOrElse(Json.Null).noSpaces)(decoder).fold[Decoder.Result[Stub]](err => Left(DecodingFailure(s"Could not decode stub: $err while decoding the json.", c.history)), stub => Right(stub)) } } def decoder: Decoder[Stub] = deriveConfiguredDecoder // This takes a flat json and converts it to a stub val flatJsonDecoder: Decoder[Stub] = new Decoder[Stub] { def apply(c: HCursor): Result[Stub] = c.as[ExternalData].fold[Decoder.Result[Stub]](err => Left(DecodingFailure(s"Decoding the flat json - Could not decode externalData: ${err.message}.", c.history)), exData => { val wfLastMod: Json = c.downField("wfLastModified").focus.getOrElse(Json.Null) val updatedLastModCursor = if(wfLastMod != Json.Null) c.downField("lastModified").set(wfLastMod).up else c updatedLastModCursor.as[Stub].fold[Decoder.Result[Stub]](err => Left(DecodingFailure(s"Decoding the flat json - Could not decode stub: ${err.message}.", c.history)), stub => { Right(stub.copy(externalData = Some(exData))) }) }) } // This takes a stub and converts it to a flat json val flatJsonEncoder: Encoder[Stub] = new Encoder[Stub] { def apply(stub: Stub): Json = { (for { stubObj <- stub.asJson.asObject wfLastModified = stub.lastModified.asJson extDataJson <- stubObj("externalData") extDataObj <- extDataJson.asObject stubObjWithoutExData = stubObj.remove("externalData") } yield ( stubObjWithoutExData.toMap ++ extDataObj.toMap ++ Map( "wfLastModified" -> wfLastModified )).asJson ).getOrElse(Json.Null) } } } sealed trait Flag extends EnumEntry with Uppercase case object Flag extends Enum[Flag] with CirceEnum[Flag] { case object NA extends Flag case object Required extends Flag case object Complete extends Flag val values: immutable.IndexedSeq[Flag] = findValues }