|
| 1 | +package com.gu.mobile.notifications.football.lib |
| 2 | + |
| 3 | +import com.amazonaws.services.dynamodbv2.AmazonDynamoDBAsync |
| 4 | +import com.gu.mobile.notifications.client.models.liveActitivites.FootballContentJsonFormats._ |
| 5 | +import com.gu.mobile.notifications.client.models.liveActitivites.FootballMatchContentState |
| 6 | +import com.gu.mobile.notifications.football.Logging |
| 7 | +import com.gu.mobile.notifications.football.lib.DynamoDistinctCheck.{Distinct, DistinctStatus, Duplicate, Unknown} |
| 8 | +import org.scanamo.{DynamoFormat, ScanamoAsync, Table} |
| 9 | +import org.scanamo.generic.auto._ |
| 10 | +import play.api.libs.json.Json |
| 11 | + |
| 12 | +import scala.concurrent.{ExecutionContext, Future} |
| 13 | + |
| 14 | +case class DynamoMatchState( |
| 15 | + matchId: String, |
| 16 | + matchContentState: FootballMatchContentState, |
| 17 | + lastUpdatedAt: Long, |
| 18 | + ttl: Long |
| 19 | +) |
| 20 | +object DynamoMatchState { |
| 21 | + def apply(matchId: String, contentState: FootballMatchContentState): DynamoMatchState = { |
| 22 | + DynamoMatchState( |
| 23 | + matchId = matchId, |
| 24 | + matchContentState = contentState, |
| 25 | + lastUpdatedAt = System.currentTimeMillis(), |
| 26 | + ttl = (System.currentTimeMillis() / 1000) + (14 * 24 * 3600) |
| 27 | + ) |
| 28 | + } |
| 29 | +} |
| 30 | + |
| 31 | +class DynamoMatchStateDiffer( |
| 32 | + client: AmazonDynamoDBAsync, |
| 33 | + val tableName: String, |
| 34 | + partitionKeyName: String, |
| 35 | +) extends Logging { |
| 36 | + |
| 37 | + def isIdentical(id: String, state: FootballMatchContentState)(implicit ec: ExecutionContext): Future[Boolean] = { |
| 38 | + import org.scanamo.syntax._ |
| 39 | + import DynamoMatchStateDiffer.footballMatchContentStateDynamoFormat |
| 40 | + |
| 41 | + lazy val scanamoAsync: ScanamoAsync = ScanamoAsync(client) |
| 42 | + lazy val matchStateTable = Table[DynamoMatchState](tableName) |
| 43 | + |
| 44 | + scanamoAsync.exec(matchStateTable.get(partitionKeyName -> id)).map { |
| 45 | + // todo test equality is working as expected, especially for the sealed trait hierarchy in FootballMatchContentState |
| 46 | + case Some(Right(existingState)) => existingState.matchContentState == state |
| 47 | + case _ => false |
| 48 | + } recover { |
| 49 | + case e => |
| 50 | + logger.error(s"Failure while checking for dynamodb match state $tableName: ${e.getMessage}.") |
| 51 | + false |
| 52 | + } |
| 53 | + } |
| 54 | + |
| 55 | + def updateState(id: String, state: FootballMatchContentState)(implicit ec: ExecutionContext): Future[Unit] = { |
| 56 | + lazy val scanamoAsync: ScanamoAsync = ScanamoAsync(client) |
| 57 | + lazy val matchStateTable = Table[DynamoMatchState](tableName) |
| 58 | + |
| 59 | + val dynamoPayload = DynamoMatchState(id, state) |
| 60 | + |
| 61 | + scanamoAsync.exec(matchStateTable.put(dynamoPayload)).map { _ => |
| 62 | + logger.info(s"New content state for match id $id written to dynamodb $tableName") |
| 63 | + } recover { |
| 64 | + case e => |
| 65 | + logger.error(s"Failure while writing match state for $id to dynamodb $tableName: ${e.getMessage}") |
| 66 | + throw e // rethrow so the caller's try/Await sees the failure and skips emitting the event |
| 67 | + } |
| 68 | + } |
| 69 | +} |
| 70 | + |
| 71 | +object DynamoMatchStateDiffer { |
| 72 | + // FootballMatchContentState contains a sealed `MatchStatus` hierarchy which Scanamo cannot |
| 73 | + // reliably auto-derive, so we reuse the existing Play JSON formats and store it as a JSON string. |
| 74 | + implicit val footballMatchContentStateDynamoFormat: DynamoFormat[FootballMatchContentState] = { |
| 75 | + // todo copilot |
| 76 | + // If parsing/decoding throws (bad or malformed JSON), coercedXmap catches that Throwable and surfaces it as a DynamoReadError. |
| 77 | + DynamoFormat.coercedXmap[FootballMatchContentState, String, Throwable]( |
| 78 | + json => Json.parse(json).as[FootballMatchContentState] |
| 79 | + )( |
| 80 | + state => Json.toJson(state).toString |
| 81 | + ) |
| 82 | + } |
| 83 | +} |
| 84 | + |
0 commit comments