Skip to content

Instantly share code, notes, and snippets.

@hanishi
Last active October 24, 2020 12:37
Show Gist options
  • Save hanishi/3f5dff1a407b89bffe10bd7d19612162 to your computer and use it in GitHub Desktop.
Save hanishi/3f5dff1a407b89bffe10bd7d19612162 to your computer and use it in GitHub Desktop.
case class AdAccount(id: String, name: Option[String] = None) extends Entity
implicit class adAccount2AttributeValue(adAccount: AdAccount) {
def asPutItemRequest(
tableName: String,
criteria: Option[Criteria],
status: String
): PutItemRequest =
PutItemRequest
.builder()
.tableName(tableName)
.item(
Map
.empty[String, AttributeValue]
.pipe(_ + (PARTITION_KEY -> S(s"ACCOUNT#${adAccount.id}")))
.pipe(_ + (SORT_KEY -> S(s"ACCOUNT#${adAccount.id}")))
.pipe(_ + (ID -> S(adAccount.id)))
.pipe(map => adAccount.name.fold(map) { x => map + (NAME -> S(x)) })
.pipe(_ + (STATUS -> S(status)))
.pipe(map =>
criteria.fold(map) { x =>
map + (CRITERIA -> x.asAttributeValue)
}
)
.pipe(_ + (GSI1PK -> S(s"ACCOUNT#${adAccount.id}")))
.pipe(_ + (GSI1SK -> S(s"ACCOUNT#${adAccount.id}")))
.pipe(_ + (GSI2PK -> S("ACCOUNTS")))
.pipe(_ + (GSI2SK -> S(s"ACCOUNT#${adAccount.id}")))
.asJava
)
.conditionExpression("attribute_not_exists(#pk)")
.expressionAttributeNames(
Map("#pk" -> PARTITION_KEY).asJava
)
.build()
def asGetItemRequest(tableName: String) =
GetItemRequest
.builder()
.tableName(tableName)
.key(
Map(
PARTITION_KEY -> S(s"ACCOUNT#${adAccount.id}"),
SORT_KEY -> S(s"ACCOUNT#${adAccount.id}")
).asJava
)
.attributesToGet(ID, NAME, STATUS, CRITERIA, CAMPAIGNS)
.build()
def asUpdateItemRequest(
tableName: String,
criteria: Option[Criteria],
status: String
): UpdateItemRequest = {
val builder = UpdateItemRequest
.builder()
.tableName(tableName)
.key(
Map
.empty[String, AttributeValue]
.pipe(_ + (PARTITION_KEY -> S(s"ACCOUNT#${adAccount.id}")))
.pipe(_ + (SORT_KEY -> S(s"ACCOUNT#${adAccount.id}")))
.asJava
)
(
List.empty[String],
List.empty[String],
Map.empty[String, String],
Map.empty[String, AttributeValue]
).pipe {
case (remove, set, expressionNames, expressionValues) =>
adAccount.name.fold(
(
"#name" :: remove,
set,
expressionNames + ("#name" -> "Name"),
expressionValues
)
) { x =>
(
remove,
"#name = :name" :: set,
expressionNames + ("#name" -> "Name"),
expressionValues + (":name" -> S(x))
)
}
}
.pipe {
case (remove, set, expressionNames, expressionValues) =>
criteria.fold(
(
"#criteria" :: remove,
set,
expressionNames + ("#criteria" -> "Criteria"),
expressionValues
)
) { x =>
(
remove,
"#criteria = :criteria" :: set,
expressionNames + ("#criteria" -> "Criteria"),
expressionValues + (":criteria" -> x.asAttributeValue)
)
}
}
.pipe {
case (remove, set, expressionNames, expressionValues) =>
(
remove,
"#status = :status" :: set,
expressionNames + ("#status" -> "Status"),
expressionValues + (":status" -> S(status))
)
} match {
case (remove, set, expressionNames, expressionValues) =>
val updateExpression =
if (set.nonEmpty) s"set ${set.foldLeft("") { (a, b) =>
if (a.nonEmpty) s"$a,$b" else b
}}"
else ""
val deleteExpression = {
if (remove.nonEmpty) s"remove ${remove
.foldLeft("") { (a, b) => if (a.nonEmpty) s"$a,$b" else b }}"
else ""
}
builder.updateExpression(
updateExpression + (if (deleteExpression.nonEmpty)
if (updateExpression.isBlank)
deleteExpression
else " " + deleteExpression
else "")
)
builder.expressionAttributeNames(expressionNames.asJava)
if (expressionValues.nonEmpty)
builder.expressionAttributeValues(expressionValues.asJava)
builder.build()
}
}
def asDeleteItemRequest(tableName: String): DeleteItemRequest =
DeleteItemRequest
.builder()
.tableName(tableName)
.key(
Map
.empty[String, AttributeValue]
.pipe(_ + (PARTITION_KEY -> S(s"ACCOUNT#${adAccount.id}")))
.pipe(_ + (SORT_KEY -> S(s"ACCOUNT#${adAccount.id}")))
.asJava
)
.conditionExpression(
"attribute_not_exists(#campaigns)"
)
.expressionAttributeNames(Map("#campaigns" -> "Campaigns").asJava)
.build()
}
case class Criteria(
startDateTime: Option[OffsetDateTime],
endDateTime: Option[OffsetDateTime],
numberOfActiveAds: Option[Int],
numberOfAdsToCarryOver: Option[Int],
daysOfInterContestInterval: Option[Int],
daysToRunContest: Option[Int]
)
implicit class criteria2AttributeValue(criteria: Criteria) {
lazy val asAttributeValue: AttributeValue =
Map
.empty[String, AttributeValue]
.pipe(map =>
criteria.startDateTime
.fold(map) { x => map + ("start_date_time" -> DATE_TIME(x)) }
)
.pipe(map =>
criteria.endDateTime.fold(map) { x =>
map + ("end_date_time" -> DATE_TIME(x))
}
)
.pipe(map =>
criteria.numberOfActiveAds.fold(map) { x =>
map + ("number_of_active_ads" -> N(x))
}
)
.pipe(map =>
criteria.numberOfAdsToCarryOver.fold(map) { x =>
map + ("number_of_ads_to_carry_over" -> N(x))
}
)
.pipe(map =>
criteria.daysOfInterContestInterval.fold(map) { x =>
map + ("days_of_inter_contest_interval" -> N(x))
}
)
.pipe(map =>
criteria.daysToRunContest.fold(map) { x =>
map + ("days_to_run_contest" -> N(x))
}
)
.asJava
.pipe(
AttributeValue
.builder()
.m
)
.build()
}
DynamoDb.single(adAccount.asDeleteItemRequest(tableName))
DynamoDb.single(adAccount.asUpdateItemRequest(tableName, criteria,"Active"))
DynamoDb.single(adAccount.asPutItemRequest(tableName,criteria,"Paused"))
DynamoDb.single(adAccount.asGetItemRequest(tableName))
@hanishi
Copy link
Author

hanishi commented Oct 24, 2020

Here's how you'd use it. I can't explain much but first you "look up" then compare and persist if necessary

      override def persist(
          entity: Entity,
          current: Option[(Option[Criteria], Boolean, Boolean)],
          update: Option[Criteria],
          activate: Boolean
      ): Future[(Entity, Option[Criteria], Boolean, Boolean)] =
        entity match {
          case adAccount: AdAccount =>
            current match {
              case Some((criteria, status, deletable)) =>
                if (!activate && update.isEmpty && deletable)
                  DynamoDb
                    .single(adAccount.asDeleteItemRequest(tableName))
                    .map { _ => (adAccount, update, activate, deletable) }
                else if (criteria != update || status != activate)
                  DynamoDb
                    .single(
                      adAccount.asUpdateItemRequest(
                        tableName,
                        update,
                        if (activate) "Active" else "Paused"
                      )
                    )
                    .map { _ => (adAccount, update, activate, deletable) }
                else Future.successful((adAccount, update, activate, deletable))
              case None =>
                DynamoDb
                  .single(
                    adAccount.asPutItemRequest(
                      tableName,
                      update,
                      if (activate) "Active" else "Paused"
                    )
                  )
                  .map { _ => (adAccount, update, activate, true) }
            }
...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment