Skip to content

Commit a9c002d

Browse files
committed
filter matches based on the competitions data in S3
1 parent d546528 commit a9c002d

4 files changed

Lines changed: 97 additions & 14 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import com.gu.{AppIdentity, AwsIdentity, DevIdentity}
66
import com.typesafe.config.Config
77
import software.amazon.awssdk.auth.credentials.{AwsCredentialsProviderChain => AwsCredentialsProviderChainV2, DefaultCredentialsProvider => DefaultCredentialsProviderV2, ProfileCredentialsProvider => ProfileCredentialsProviderV2}
88
import com.gu.conf.{ConfigurationLoader, SSMConfigurationLocation}
9-
import software.amazon.awssdk.regions.Region.EU_WEST_1
109

1110
class Configuration extends Logging {
1211

@@ -47,4 +46,5 @@ class Configuration extends Logging {
4746
val notificationsApiKey = conf.getString("notifications-client.api-key")
4847
val mapiHost = conf.getString("mapi.host")
4948
val capiApiKey = conf.getString("capi.key")
49+
val paDataBucket = conf.getString("buckets.football-feed-data")
5050
}

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ import java.time.ZonedDateTime
55
import java.util.concurrent.TimeUnit
66
import com.amazonaws.regions.Regions
77
import com.amazonaws.services.dynamodbv2.{AmazonDynamoDBAsync, AmazonDynamoDBAsyncClientBuilder}
8+
import com.amazonaws.services.s3.{AmazonS3, AmazonS3ClientBuilder}
89
import com.gu.contentapi.client.GuardianContentClient
910
import com.gu.mobile.liveactivities.event.bus.LiveActivityPusher
10-
import com.gu.mobile.notifications.football.lib.{ArticleSearcher, DynamoDistinctCheck, DynamoMatchLiveActivity, DynamoMatchNotification, EventConsumer, EventFilter, FootballData, LiveActivityEventConsumer, NotificationHttpProvider, NotificationSender, NotificationsApiClient, PaFootballClient, SyntheticMatchEventGenerator}
11+
import com.gu.mobile.notifications.football.lib.{ArticleSearcher, DynamoDistinctCheck, DynamoMatchLiveActivity, DynamoMatchNotification, EventConsumer, EventFilter, FootballData, LiveActivityEventConsumer, NotificationHttpProvider, NotificationSender, NotificationsApiClient, PACompetition, PaFootballClient, S3DataStore, SyntheticMatchEventGenerator}
1112
import com.gu.mobile.notifications.football.notificationbuilders.{MatchStatusLiveActivityPayloadBuilder, MatchStatusNotificationBuilder}
1213
import play.api.libs.json.Json
1314

@@ -22,6 +23,7 @@ import com.gu.mobile.notifications.football.models.MatchDataWithArticle
2223

2324
import scala.concurrent.Future
2425
import org.scanamo.generic.auto._
26+
import pa.Competition
2527

2628
object Lambda extends Logging {
2729

@@ -56,7 +58,14 @@ object Lambda extends Logging {
5658

5759
val apiClient = new NotificationsApiClient(configuration)
5860

59-
lazy val footballData = new FootballData(paFootballClient, syntheticMatchEventGenerator)
61+
lazy val s3Client: AmazonS3 = AmazonS3ClientBuilder.standard
62+
.withRegion(Regions.EU_WEST_1)
63+
.withCredentials(configuration.credentials)
64+
.build()
65+
66+
lazy val competitionsDataStore = new S3DataStore[PACompetition](s3Client, configuration.paDataBucket)
67+
68+
lazy val footballData = new FootballData(paFootballClient, syntheticMatchEventGenerator, competitionsDataStore, configuration.stage)
6069

6170
lazy val articleSearcher = new ArticleSearcher(capiClient)
6271

football/src/main/scala/com/gu/mobile/notifications/football/lib/FootballData.scala

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package com.gu.mobile.notifications.football.lib
22

3-
import java.time.ZonedDateTime
4-
5-
import com.gu.mobile.notifications.football.Logging
3+
import java.time.{LocalDate, ZonedDateTime}
4+
import com.gu.mobile.notifications.football.{Configuration, Logging}
65
import com.gu.mobile.notifications.football.models.RawMatchData
76
import org.joda.time.DateTime
87
import pa.MatchDay
8+
import play.api.libs.json.{Format, Json}
99

1010
import scala.concurrent.ExecutionContext.Implicits.global
1111
import scala.concurrent.Future
@@ -14,9 +14,24 @@ import scala.util.{Failure, Success, Try}
1414

1515
case class EndedMatch(matchId: String, startTime: DateTime)
1616

17+
case class PACompetition(
18+
id: String,
19+
tag: String,
20+
fullName: String,
21+
shortName: String,
22+
startDate: Option[LocalDate] = None,
23+
endDate: Option[LocalDate] = None,
24+
)
25+
26+
object PACompetition {
27+
implicit val competitionFormat: Format[PACompetition] = Json.format[PACompetition]
28+
}
29+
1730
class FootballData(
18-
paClient: PaFootballClient,
19-
syntheticEvents: SyntheticMatchEventGenerator
31+
paClient: PaFootballClient,
32+
syntheticEvents: SyntheticMatchEventGenerator,
33+
competitionsDataStore: S3DataStore[PACompetition],
34+
stage: String
2035
) extends Logging {
2136

2237
implicit class RichMatchDay(matchDay: MatchDay) {
@@ -92,13 +107,29 @@ class FootballData(
92107
}.getOrElse(false) //Shouldn't ever happen
93108
}
94109

110+
def competitionIsSupported(supportedCompetitions: List[PACompetition])(matchDay: MatchDay): Boolean =
111+
matchDay.competition.exists { matchComp =>
112+
supportedCompetitions.exists(_.id == matchComp.id)
113+
}
114+
95115
logger.info(s"Retrieving matches on or around $dateTime from PA")
96-
val matches = paClient.aroundToday(dateTime)
97-
matches.map(
98-
_.filter(inProgress)
99-
.filter(paProvideAlerts)
100-
.filterNot(isMidnight)
101-
)
116+
for {
117+
matches <- paClient.aroundToday(dateTime)
118+
competitions <- competitionsDataStore
119+
.fetch(s"${stage}/competition/competitions.json")
120+
.recover { case exception =>
121+
// We don't want to fail the whole process if we can't retrieve the list of competitions,
122+
// we'll just assume all competitions are supported and log the error
123+
logger.error(s"Failed to retrieve list of competitions: ${exception.getMessage}", exception)
124+
List.empty[PACompetition]
125+
}
126+
} yield {
127+
matches
128+
.filter(m => competitions.isEmpty || competitionIsSupported(competitions)(m))
129+
.filter(inProgress)
130+
.filter(paProvideAlerts)
131+
.filterNot(isMidnight)
132+
}
102133
}
103134

104135
private def processMatch(matchDay: MatchDay): Future[Option[RawMatchData]] = {
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.gu.mobile.notifications.football.lib
2+
3+
import com.amazonaws.services.s3.AmazonS3
4+
import com.amazonaws.services.s3.model.S3Object
5+
import com.amazonaws.util.IOUtils
6+
import com.gu.mobile.notifications.football.Logging
7+
import play.api.libs.json.{Format, JsError, JsSuccess, Json}
8+
9+
import scala.concurrent.Future
10+
import scala.util.{Failure, Success, Try}
11+
12+
class S3DataStore[T](s3Client: AmazonS3, bucketName: String) extends Logging {
13+
def fetch(path: String)(implicit format: Format[T]) : Future[List[T]] = {
14+
Try(parseS3Object(path)) match {
15+
case Success(list) => Future.successful(list)
16+
case Failure(ex) =>
17+
logger.error(s"Error retrieving items from s3. Bucket: $bucketName, Path: $path")
18+
Future.failed(ex)
19+
}
20+
}
21+
22+
private def parseS3Object(path: String)(implicit format: Format[T]) : List[T] = {
23+
Json.fromJson[List[T]](Json.parse(asString(s3Client.getObject(bucketName, path)))) match {
24+
case JsSuccess(list, __) =>
25+
logger.info(s"Got ${list.length} items from s3, path $path")
26+
list
27+
case JsError(errors) =>
28+
val errorPaths = errors.map { error => error._1.toString() }.mkString(",")
29+
logger.error(s"Error parsing S3 items on path $path. Error path(s): $errorPaths")
30+
throw new Exception(s"could not extract list $path. Errors paths(s): $errors")
31+
}
32+
}
33+
34+
private def asString(s3Object: S3Object): String = {
35+
val s3ObjectContent = s3Object.getObjectContent
36+
try {
37+
IOUtils.toString(s3ObjectContent)
38+
}
39+
finally {
40+
s3ObjectContent.close()
41+
}
42+
}
43+
}

0 commit comments

Comments
 (0)