Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import com.amazonaws.services.dynamodbv2.document.utils.ValueMap
import com.amazonaws.services.dynamodbv2.document.{DynamoDB => AwsDynamoDB, _}
import com.amazonaws.services.dynamodbv2.model.{AttributeValue, DeleteItemRequest, KeysAndAttributes, ReturnValue}
import com.amazonaws.services.dynamodbv2.{AmazonDynamoDBAsync, AmazonDynamoDBAsyncClientBuilder}
import com.gu.mediaservice.lib.aws.DynamoDB.{deleteExpr, setExpr}
import com.gu.mediaservice.lib.aws.DynamoDB.{deleteExpr, jsonWithNullAsEmptyString, setExpr}
import com.gu.mediaservice.lib.config.CommonConfig
import com.gu.mediaservice.lib.logging.GridLogging
import org.joda.time.DateTime
Expand All @@ -17,7 +17,7 @@ import software.amazon.awssdk.enhanced.dynamodb.document.EnhancedDocument
import software.amazon.awssdk.enhanced.dynamodb.model
import software.amazon.awssdk.enhanced.dynamodb.model.{BatchGetItemEnhancedRequest, ReadBatch}
import software.amazon.awssdk.services.dynamodb.DynamoDbClient
import software.amazon.awssdk.services.dynamodb.model.{UpdateItemRequest, AttributeValue => AttributeValueV2, ReturnValue => ReturnValueV2, QueryRequest => QueryRequestV2}
import software.amazon.awssdk.services.dynamodb.model.{UpdateItemRequest, AttributeValue => AttributeValueV2, QueryRequest => QueryRequestV2, ReturnValue => ReturnValueV2}

import scala.annotation.tailrec
import scala.concurrent.{ExecutionContext, Future}
Expand Down Expand Up @@ -342,21 +342,7 @@ class DynamoDB[T](config: CommonConfig, tableName: String, lastModifiedKey: Opti
def asJsObject(outcome: UpdateItemOutcome): JsObject =
Option(outcome.getItem) map asJsObject getOrElse Json.obj()

// FIXME: Dynamo accepts `null`, but not `""`. This is a well documented issue
// around the community. This guard keeps the introduction of `null` fairly
// fenced in this Dynamo play area. `null` is continual and big annoyance with AWS libs.
// see: https://forums.aws.amazon.com/message.jspa?messageID=389032
// see: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DataModel.html
def mapJsValue(jsValue: JsValue)(f: JsValue => JsValue): JsValue = jsValue match {
case JsObject(items) => JsObject(items.map{ case (k, v) => k -> mapJsValue(v)(f) })
case JsArray(items) => JsArray(items.map(f))
case value => f(value)
}

def jsonWithNullAsEmptyString(jsValue: JsValue): JsValue = mapJsValue(jsValue) {
case JsNull => JsString("")
case value => value
}

}

Expand Down Expand Up @@ -447,4 +433,20 @@ object DynamoDB {
def generateExpression(baseExpression: String, lastModifiedKey: Option[String]) = {
lastModifiedKey.fold(baseExpression)(lastModifiedKey => s"$baseExpression SET $lastModifiedKey = :$lastModifiedKey")
}

// FIXME: Dynamo accepts `null`, but not `""`. This is a well documented issue
// around the community. This guard keeps the introduction of `null` fairly
// fenced in this Dynamo play area. `null` is continual and big annoyance with AWS libs.
// see: https://forums.aws.amazon.com/message.jspa?messageID=389032
// see: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DataModel.html
def mapJsValue(jsValue: JsValue)(f: JsValue => JsValue): JsValue = jsValue match {
case JsObject(items) => JsObject(items.map{ case (k, v) => k -> mapJsValue(v)(f) })
case JsArray(items) => JsArray(items.map(f))
case value => f(value)
}
def jsonWithNullAsEmptyString(jsValue: JsValue): JsValue = mapJsValue(jsValue) {
case JsNull => JsString("")
case value => value
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.gu.mediaservice.lib.dynamo
import software.amazon.awssdk.services.dynamodb.model.AttributeValue
import scala.jdk.CollectionConverters._

sealed trait DynamoElement {
def toAttrValue: AttributeValue
}


case class DbString(value: String) extends DynamoElement {
def toAttrValue: AttributeValue = AttributeValue.builder().s(value).build()
}

case class DbLong(value: Long) extends DynamoElement {
def toAttrValue: AttributeValue = AttributeValue.builder().n(value.toString).build()
}

case class DbInt(value: Int) extends DynamoElement {
def toAttrValue: AttributeValue = AttributeValue.builder().n(value.toString).build()
}

case class DbNestedMap(value: Map[String, DynamoElement]) extends DynamoElement {
def toAttrValueRec(value: Map[String, DynamoElement], acc: Map[String, AttributeValue]): Map[String, AttributeValue] = {
value.flatMap { case (k, v) =>
v match {
case DbString(_) | DbLong(_) | DbInt(_) => acc.updated(k, v.toAttrValue)
case DbNestedMap(m) => toAttrValueRec(m, acc)
}
}
}
val javaMap = toAttrValueRec(value, Map[String, AttributeValue]()).asJava

def toAttrValue = AttributeValue.builder().m(javaMap).build()
}

Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,46 @@ import com.amazonaws.services.dynamodbv2.document.Item
import com.gu.mediaservice.model.usage._
import org.joda.time.DateTime
import org.joda.time.format.ISODateTimeFormat
import software.amazon.awssdk.enhanced.dynamodb.document.EnhancedDocument

import scala.jdk.CollectionConverters._
import scala.util.Try

object ItemToMediaUsage {

def transform(item: Item): MediaUsage = {
def transform(doc: EnhancedDocument): MediaUsage = {
MediaUsage(
UsageId(item.getString("usage_id")),
item.getString("grouping"),
item.getString("media_id"),
UsageType(item.getString("usage_type")),
item.getString("media_type"),
UsageStatus(item.getString("usage_status")),
Option(item.getMap[Any]("print_metadata"))
.map(_.asScala.toMap).flatMap(buildPrint),
Option(item.getMap[Any]("digital_metadata"))
.map(_.asScala.toMap).flatMap(buildDigital),
Option(item.getMap[Any]("syndication_metadata"))
.map(_.asScala.toMap).flatMap(buildSyndication),
Option(item.getMap[Any]("front_metadata"))
.map(_.asScala.toMap).flatMap(buildFront),
Option(item.getMap[Any]("download_metadata"))
.map(_.asScala.toMap).flatMap(buildDownload),
Option(item.getMap[Any]("child_metadata"))
.map(_.asScala.toMap).flatMap(buildChild),
new DateTime(item.getLong("last_modified")),
Try {
item.getLong("date_added")
}.toOption.map(new DateTime(_)),
Try {
item.getLong("date_removed")
}.toOption.map(new DateTime(_))
UsageId(doc.getString("usage_id")),
doc.getString("grouping"),
doc.getString("media_id"),
UsageType(doc.getString("usage_type")),
doc.getString("media_type"),
UsageStatus(doc.getString("usage_status")),
Option(doc.getMapOfUnknownType("print_metadata"))
.map(_.asScala.toMap)
.flatMap(buildPrint),
Option(doc.getMapOfUnknownType("digital_metadata"))
.map(_.asScala.toMap)
.flatMap(buildDigital),
Option(doc.getMapOfUnknownType("syndication_metadata"))
.map(_.asScala.toMap)
.flatMap(buildSyndication),
Option(doc.getMapOfUnknownType("front_metadata"))
.map(_.asScala.toMap)
.flatMap(buildFront),
Option(doc.getMapOfUnknownType("download_metadata"))
.map(_.asScala.toMap)
.flatMap(buildDownload),
Option(doc.getMapOfUnknownType("child_metadata"))
.map(_.asScala.toMap)
.flatMap(buildChild),
new DateTime(doc.getNumber("last_modified").longValue()),
Try(doc.getNumber("date_added").longValue()).toOption.map(new DateTime(_)),
Try(doc.getNumber("date_removed").longValue()).toOption.map(new DateTime(_))
)
}


private def buildFront(metadataMap: Map[String, Any]): Option[FrontUsageMetadata] = {
Try {
FrontUsageMetadata(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.gu.mediaservice.model.usage

import com.gu.mediaservice.lib.dynamo.{DbString, DynamoElement}
import play.api.libs.json.{Json, Reads, Writes}

case class ChildUsageMetadata(
Expand All @@ -11,6 +12,11 @@ case class ChildUsageMetadata(
"addedBy" -> addedBy,
"childMediaId" -> childMediaId
)

override def toDynamoMap: Map[String, DynamoElement] = Map(
"addedBy" -> DbString(addedBy),
"childMediaId" -> DbString(childMediaId)
)
}

object ChildUsageMetadata {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.gu.mediaservice.model.usage

import com.gu.mediaservice.lib.dynamo.{DbString, DynamoElement}

import java.net.URI
import play.api.libs.json._
import com.gu.mediaservice.syntax._
Expand All @@ -18,6 +20,12 @@ case class DigitalUsageMetadata (
"webTitle" -> dynamoSafeWebTitle,
"sectionId" -> sectionId
) ++ composerUrl.map("composerUrl" -> _.toString)

override def toDynamoMap: Map[String, DynamoElement] = Map(
"webUrl" -> DbString(webUrl.toString),
"webTitle" -> DbString(dynamoSafeWebTitle),
"sectionId" -> DbString(sectionId)
) ++ composerUrl.map(c => "composerUrl" -> DbString(c.toString))
}

object DigitalUsageMetadata {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.gu.mediaservice.model.usage

import com.gu.mediaservice.lib.dynamo.{DbString, DynamoElement}
import play.api.libs.json.{Json, Reads, Writes}

case class DownloadUsageMetadata(
Expand All @@ -8,6 +9,10 @@ case class DownloadUsageMetadata(
override def toMap: Map[String, Any] = Map(
"downloadedBy" -> downloadedBy
)

override def toDynamoMap: Map[String, DynamoElement] = Map(
"downloadedBy" -> DbString(downloadedBy)
)
}

object DownloadUsageMetadata {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.gu.mediaservice.model.usage

import com.gu.mediaservice.lib.dynamo.{DbString, DynamoElement}
import play.api.libs.json._

case class FrontUsageMetadata(
Expand All @@ -10,6 +11,11 @@ case class FrontUsageMetadata(
"addedBy" -> addedBy,
"front" -> front
)

override def toDynamoMap: Map[String, DynamoElement] = Map(
"addedBy" -> DbString(addedBy),
"front" -> DbString(front)
)
}

object FrontUsageMetadata {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.gu.mediaservice.model.usage

import com.gu.mediaservice.lib.dynamo.{DbInt, DbLong, DbNestedMap, DbString, DynamoElement}
import play.api.libs.json._
import org.joda.time.DateTime

Expand All @@ -13,6 +14,7 @@ case class PrintImageSize(
"x" -> x,
"y" -> y
)
override def toDynamoMap: Map[String, DynamoElement] = Map("x" -> DbInt(x), "y" -> DbInt(y))
}
object PrintImageSize {
implicit val reads: Reads[PrintImageSize] = Json.reads[PrintImageSize]
Expand Down Expand Up @@ -54,6 +56,22 @@ case class PrintUsageMetadata(
edition.foldLeft[IntElement](Nil)((_,i) => List("edition" -> i)) ++
notes.foldLeft[StringElement](Nil)((_,s) => if(s.isEmpty) Nil else List("notes" -> s)) ++
source.foldLeft[StringElement](Nil)((_,s) => if(s.isEmpty) Nil else List("source" -> s))

override def toDynamoMap: Map[String, DynamoElement] = Map(
"sectionName" -> DbString(sectionName),
"issueDate" -> DbString(issueDate.toString),
"pageNumber" -> DbInt(pageNumber),
"storyName" -> DbString(storyName),
"publicationCode" -> DbString(publicationCode),
"publicationName" -> DbString(publicationName),
"sectionCode" -> DbString(sectionCode)
) ++ size.map(s => "size" -> DbNestedMap(s.toDynamoMap)) ++
orderedBy.map(o => "orderedBy" -> DbString(o)) ++
layoutId.map(l => "layoutId" -> DbLong(l)) ++
edition.map(e => "edition" -> DbInt(e)) ++
notes.map(n => "notes" -> DbString(n)) ++
source.map(s => "source" -> DbString(s))

}
object PrintUsageMetadata {
import JodaWrites._
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package com.gu.mediaservice.model.usage

import com.gu.mediaservice.lib.dynamo.{DbString, DynamoElement}
import play.api.libs.json._

case class SyndicationUsageMetadata(
partnerName: String,
syndicatedBy: Option[String] = None
) extends UsageMetadata {
override def toMap: Map[String, Any] = Map(
override def toMap: Map[String, String] = Map(
"partnerName" -> partnerName
) ++ syndicatedBy.map("syndicatedBy" -> _)

override def toDynamoMap: Map[String, DynamoElement] = Map(
"partnerName" -> DbString(partnerName)
) ++ syndicatedBy.map(s => "syndicatedBy" -> DbString(s))
}

object SyndicationUsageMetadata {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.gu.mediaservice.model.usage

import com.gu.mediaservice.lib.dynamo.DynamoElement

trait UsageMetadata {
def toMap: Map[String, Any]
def toDynamoMap: Map[String, DynamoElement]
}
Loading
Loading