app/model/Tag.scala (256 lines of code) (raw):

package model import com.amazonaws.services.dynamodbv2.document.Item import play.api.Logging import play.api.libs.json._ import ai.x.play.json.Encoders.encoder import ai.x.play.json.Jsonx import com.gu.tagmanagement.{TagReindexBatch, TagType, BlockingLevel => ThriftAdBlockingLevel, Tag => ThriftTag} import helpers.XmlHelpers._ import repositories.{SectionRepository, SponsorshipRepository} import scala.util.control.NonFatal import scala.xml.Node case class Tag( id: Long, path: String, pageId: Long, `type`: String, internalName: String, externalName: String, slug: String, hidden: Boolean = false, legallySensitive: Boolean = false, comparableValue: String, categories: Set[String] = Set(), section: Option[Long], publication: Option[Long], description: Option[String] = None, parents: Set[Long] = Set(), externalReferences: List[Reference] = Nil, podcastMetadata: Option[PodcastMetadata] = None, contributorInformation: Option[ContributorInformation] = None, publicationInformation: Option[PublicationInformation] = None, isMicrosite: Boolean, capiSectionId: Option[String] = None, trackingInformation: Option[TrackingInformation], campaignInformation: Option[CampaignInformation], activeSponsorships: List[Long] = Nil, sponsorship: Option[Long] = None, // for paid content tags, they have an associated sponsorship but it may not be active paidContentInformation: Option[PaidContentInformation] = None, expired: Boolean = false, adBlockingLevel: Option[BlockingLevel], contributionBlockingLevel: Option[BlockingLevel], var updatedAt: Long = 0L ) { def toItem = Item.fromJSON(Json.toJson(this).toString()) def asThrift = ThriftTag( id = id, path = path, pageId = pageId, `type` = TagType.valueOf(`type`).get, internalName = internalName, externalName = externalName, slug = slug, hidden = hidden, legallySensitive = legallySensitive, comparableValue = comparableValue, section = section, publication = publication, description = description, parents = parents, references = externalReferences.map(_.asThrift), podcastMetadata = podcastMetadata.map(_.asThrift), contributorInformation = contributorInformation.map(_.asThrift), publicationInformation = publicationInformation.map(_.asThrift), isMicrosite = isMicrosite, capiSectionId = capiSectionId, trackingInformation = trackingInformation.map(_.asThrift), updatedAt = Some(updatedAt), // TODO should this line be flatMap? if there is an active sponsorship ID here, then that sponsorship should // exist in the repository. If it doesn't, unexpected behaviour can & will happen downstream!! activeSponsorships = if (activeSponsorships.isEmpty) None else Some(activeSponsorships.flatMap {sid => SponsorshipRepository.getSponsorship(sid).map(_.asThrift) }), sponsorshipId = sponsorship, paidContentInformation = paidContentInformation.map(_.asThrift), expired = expired, campaignInformation = campaignInformation.map(_.asThrift), adBlockingLevel = adBlockingLevel.flatMap(level => ThriftAdBlockingLevel.valueOf(level.entryName)), contributionBlockingLevel = contributionBlockingLevel.flatMap(level => ThriftAdBlockingLevel.valueOf(level.entryName)) ) // in this limited format for inCopy to consume def asExportedXml(sectionCache: Map[Long, Section]) = { val oldType = this.`type` match { case "Topic" => "Keyword" case "NewspaperBook" => "Newspaper Book" case "NewspaperBookSection" => "Newspaper Book Section" case t => t } val section = this.section.map(sectionCache.get(_)) val el = createElem("tag") val id = createAttribute("id", Some(this.id)) val externalName = createAttribute("externalname", Some(this.externalName)) val internalName = createAttribute("internalname", Some(this.internalName)) val urlWords = createAttribute("words-for-url", Some(this.slug)) val sectionId = createAttribute("section-id", Some(this.section.getOrElse(281))) //R2 Global Id val sectionName = createAttribute("section", Some(section.map(_.map(_.name).getOrElse(None)).getOrElse("Global"))) val sectionUrl = createAttribute("section-words-for-url", Some(section.map(_.map(_.wordsForUrl).getOrElse(None)).getOrElse("global"))) val `type` = createAttribute("type", Some(oldType)) val cmsPrefix = createAttribute("section-cms-path-prefix", Some("/Guardian/" + section.map(_.map(_.path).getOrElse("")).getOrElse("global"))) val withAttrs = el % id % externalName % internalName % urlWords % sectionId % sectionName % sectionUrl % `type` % cmsPrefix val withRefs: Node = this.externalReferences.foldLeft(withAttrs: Node) { (x, y) => addChild(x, y.asExportedXml) } val withParents: Node = this.parents.foldLeft(withRefs: Node) { (x, parent) => val el = createElem("parent") % createAttribute("id", Some(parent)) addChild(x, el) } withParents } } object Tag extends Logging { implicit val tagFormat: Format[Tag] = Jsonx.formatCaseClassUseDefaults[Tag] def fromItem(item: Item) = try { Json.parse(item.toJSON).as[Tag] } catch { case NonFatal(e) => { logger.error(s"failed to load tag ${item.toJSON}", e) throw e } } def createReindexBatch(toBatch: List[Tag]): TagReindexBatch = { TagReindexBatch( tags = toBatch.map(_.asThrift) ) } def fromJson(json: JsValue) = json.as[Tag] def apply(thriftTag: ThriftTag): Tag = Tag( id = thriftTag.id, path = thriftTag.path, pageId = thriftTag.pageId, `type` = thriftTag.`type`.name, internalName = thriftTag.internalName, externalName = thriftTag.externalName, slug = thriftTag.slug, hidden = thriftTag.hidden, legallySensitive = thriftTag.legallySensitive, comparableValue = thriftTag.comparableValue, section = thriftTag.section, publication = thriftTag.publication, description = thriftTag.description, parents = thriftTag.parents.toSet, externalReferences = thriftTag.references.map(Reference(_)).toList, podcastMetadata = thriftTag.podcastMetadata.map(PodcastMetadata(_)), contributorInformation = thriftTag.contributorInformation.map(ContributorInformation(_)), publicationInformation = thriftTag.publicationInformation.map(PublicationInformation(_)), isMicrosite = thriftTag.isMicrosite, capiSectionId = thriftTag.capiSectionId, trackingInformation = thriftTag.trackingInformation.map(TrackingInformation(_)), campaignInformation = thriftTag.campaignInformation.map(CampaignInformation(_)), updatedAt = thriftTag.updatedAt.getOrElse(0L), activeSponsorships = thriftTag.activeSponsorships.map(_.map(_.id).toList).getOrElse(Nil), sponsorship = thriftTag.sponsorshipId, paidContentInformation = thriftTag.paidContentInformation.map(PaidContentInformation(_)), expired = thriftTag.expired, adBlockingLevel = thriftTag.adBlockingLevel.map(tLevel => BlockingLevel.withName(tLevel.name)), contributionBlockingLevel = thriftTag.contributionBlockingLevel.map(tLevel => BlockingLevel.withName(tLevel.name)) ) } // The difference between a denormalised tag and a regular tag is the sponsorship object is copied in for the // denormalised version and the "normalised" version has it stored as a Long id case class DenormalisedTag ( id: Long, path: String, pageId: Long, `type`: String, internalName: String, externalName: String, slug: String, hidden: Boolean = false, legallySensitive: Boolean = false, comparableValue: String, categories: Set[String] = Set(), section: Option[Long], publication: Option[Long], description: Option[String] = None, parents: Set[Long] = Set(), externalReferences: List[Reference] = Nil, podcastMetadata: Option[PodcastMetadata] = None, contributorInformation: Option[ContributorInformation] = None, publicationInformation: Option[PublicationInformation] = None, isMicrosite: Boolean, capiSectionId: Option[String] = None, trackingInformation: Option[TrackingInformation], campaignInformation: Option[CampaignInformation], activeSponsorships: List[Long] = Nil, sponsorship: Option[Sponsorship] = None, // for paid content tags, they have an associated sponsorship but it may not be active paidContentInformation: Option[PaidContentInformation] = None, expired: Boolean = false, adBlockingLevel: Option[BlockingLevel], contributionBlockingLevel: Option[BlockingLevel] ) { def normalise(): (Tag, Option[Sponsorship]) = { val tag = Tag( id = id, path = path, pageId = pageId, `type` = `type`, internalName = internalName, externalName = externalName, slug = slug, hidden = hidden, legallySensitive = legallySensitive, comparableValue = comparableValue, categories = categories, section = section, publication = publication, description = description, parents = parents, externalReferences = externalReferences, podcastMetadata = podcastMetadata, contributorInformation = contributorInformation, publicationInformation = publicationInformation, isMicrosite = isMicrosite, capiSectionId = capiSectionId, trackingInformation = trackingInformation, campaignInformation = campaignInformation, activeSponsorships = activeSponsorships, sponsorship = sponsorship.map(_.id), // for paid content tags, they have an associated sponsorship but it may not be active paidContentInformation = paidContentInformation, expired = expired, adBlockingLevel = adBlockingLevel, contributionBlockingLevel = contributionBlockingLevel ) (tag, sponsorship) } } object DenormalisedTag{ implicit val tagFormat: OFormat[DenormalisedTag] = Jsonx.formatCaseClassUseDefaults[DenormalisedTag] def apply(t: Tag): DenormalisedTag = DenormalisedTag( id = t.id, path = t.path, pageId = t.pageId, `type` = t.`type`, internalName = t.internalName, externalName = t.externalName, slug = t.slug, hidden = t.hidden, legallySensitive = t.legallySensitive, comparableValue = t.comparableValue, categories = t.categories, section = t.section, publication = t.publication, description = t.description, parents = t.parents, externalReferences = t.externalReferences, podcastMetadata = t.podcastMetadata, contributorInformation = t.contributorInformation, publicationInformation = t.publicationInformation, isMicrosite = t.isMicrosite, capiSectionId = t.capiSectionId, trackingInformation = t.trackingInformation, campaignInformation = t.campaignInformation, activeSponsorships = t.activeSponsorships, sponsorship = t.sponsorship.flatMap(SponsorshipRepository.getSponsorship), // for paid content tags, they have an associated sponsorship but it may not be active paidContentInformation = t.paidContentInformation, expired = t.expired, adBlockingLevel = t.adBlockingLevel, contributionBlockingLevel = t.contributionBlockingLevel ) }