Skip to content

Commit 25fde2e

Browse files
committed
Add new DynamoMatchStateDiffer WIP
1 parent fb80b85 commit 25fde2e

2 files changed

Lines changed: 88 additions & 2 deletions

File tree

football/src/main/scala/com/gu/mobile/notifications/football/Lambda.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import com.amazonaws.services.dynamodbv2.{AmazonDynamoDBAsync, AmazonDynamoDBAsy
88
import com.amazonaws.services.s3.{AmazonS3, AmazonS3ClientBuilder}
99
import com.gu.contentapi.client.GuardianContentClient
1010
import com.gu.mobile.liveactivities.event.bus.LiveActivityPusher
11-
import com.gu.mobile.notifications.football.lib.{ArticleSearcher, DynamoDistinctCheck, DynamoMatchLiveActivity, DynamoMatchNotification, EventConsumer, EventFilter, FootballData, LiveActivityEventConsumer, NotificationHttpProvider, NotificationSender, NotificationsApiClient, PACompetition, PaFootballClient, S3DataStore, SyntheticMatchEventGenerator}
11+
import com.gu.mobile.notifications.football.lib.{ArticleSearcher, DynamoDistinctCheck, DynamoMatchLiveActivity, DynamoMatchNotification, DynamoMatchStateDiffer, EventConsumer, EventFilter, FootballData, LiveActivityEventConsumer, NotificationHttpProvider, NotificationSender, NotificationsApiClient, PACompetition, PaFootballClient, S3DataStore, SyntheticMatchEventGenerator}
1212
import com.gu.mobile.notifications.football.notificationbuilders.{MatchStatusLiveActivityPayloadBuilder, MatchStatusNotificationBuilder}
1313
import play.api.libs.json.Json
1414

@@ -31,6 +31,7 @@ object Lambda extends Logging {
3131

3232
def tableName = s"mobile-notifications-football-notifications-${configuration.stage}"
3333
def liveActivitiesTableName = s"mobile-notifications-liveactivities-payload-${configuration.stage}"
34+
def matchStateTableName = s"mobile-notifications-football-match-state-${configuration.stage}"
3435
lazy val paDataBucket = "mobile-pa-football-data"
3536

3637
lazy val configuration: Configuration = {
@@ -53,7 +54,8 @@ object Lambda extends Logging {
5354

5455
lazy val capiClient = GuardianContentClient(configuration.capiApiKey)
5556

56-
lazy val syntheticMatchEventGenerator = new SyntheticMatchEventGenerator(getZonedDateTime)
57+
lazy val matchStateDiffer = new DynamoMatchStateDiffer(dynamoDBClient, matchStateTableName, "matchId")
58+
lazy val syntheticMatchEventGenerator = new SyntheticMatchEventGenerator(getZonedDateTime, matchStateDiffer)
5759

5860
lazy val notificationHttpProvider = new NotificationHttpProvider()
5961

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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

Comments
 (0)