sport/app/football/model/DotcomRenderingFootballDataModel.scala (313 lines of code) (raw):
package football.model
import common.{CanonicalLink, Edition}
import conf.Configuration
import experiments.ActiveExperiments
import football.controllers.{CompetitionFilter, FootballPage, MatchDataAnswer, MatchPage}
import model.dotcomrendering.DotcomRenderingUtils.{assetURL, withoutDeepNull, withoutNull}
import model.dotcomrendering.{Config, PageFooter, PageType, Trail}
import model.{ApplicationContext, Competition, CompetitionSummary, Group, StandalonePage, Table, TeamUrl}
import navigation.{FooterLinks, Nav}
import pa.{
Fixture,
FootballMatch,
LeagueStats,
LeagueTableEntry,
LeagueTeam,
LiveMatch,
MatchDay,
MatchDayTeam,
Official,
Result,
Round,
Stage,
Venue,
Competition => PaCompetition,
}
import play.api.libs.json._
import play.api.mvc.RequestHeader
import views.support.{CamelCase, JavaScriptPage}
import java.time.LocalDate
import java.time.format.DateTimeFormatter
case class TeamScore(id: String, name: String, score: Option[Int])
case class TeamResult(matchId: String, self: TeamScore, foe: TeamScore)
case class CompetitionMatches(competitionSummary: CompetitionSummary, matches: List[FootballMatch])
case class MatchesByDateAndCompetition(date: LocalDate, competitionMatches: List[CompetitionMatches])
trait DotcomRenderingFootballDataModel {
def nav: Nav
def editionId: String
def guardianBaseURL: String
def config: JsObject
def pageFooter: PageFooter
def isAdFreeUser: Boolean
def contributionsServiceUrl: String
def canonicalUrl: String
def pageId: String
}
private object DotcomRenderingFootballDataModel {
def getConfig(page: StandalonePage)(implicit
request: RequestHeader,
context: ApplicationContext,
): JsObject = {
val pageType: PageType = PageType(page, request, context)
val switches: Map[String, Boolean] = conf.switches.Switches.all
.filter(_.exposeClientSide)
.foldLeft(Map.empty[String, Boolean])((acc, switch) => {
acc + (CamelCase.fromHyphenated(switch.name) -> switch.isSwitchedOn)
})
val config = Config(
switches = switches,
abTests = ActiveExperiments.getJsMap(request),
ampIframeUrl = assetURL("data/vendor/amp-iframe.html"),
googletagUrl = Configuration.googletag.jsLocation,
stage = common.Environment.stage,
frontendAssetsFullURL = Configuration.assets.fullURL(common.Environment.stage),
)
val combinedConfig: JsObject = {
val jsPageConfig: Map[String, JsValue] =
JavaScriptPage.getMap(page, Edition(request), pageType.isPreview, request)
Json.toJsObject(config).deepMerge(JsObject(jsPageConfig))
}
combinedConfig
}
}
private object DotcomRenderingFootballDataModelImplicits {
implicit val localDateWrites: Writes[LocalDate] = Writes[LocalDate] { date =>
JsString(date.format(DateTimeFormatter.ISO_LOCAL_DATE))
}
implicit val stageFormat: Writes[Stage] = Json.writes[Stage]
implicit val roundFormat: Writes[Round] = Json.writes[Round]
implicit val venueFormat: Writes[Venue] = Json.writes[Venue]
implicit val paCompetitionFormat: Writes[PaCompetition] = Json.writes[PaCompetition]
implicit val officialFormat: Writes[Official] = Json.writes[Official]
implicit val leagueStatsWrites: Writes[LeagueStats] = Json.writes[LeagueStats]
implicit val leagueTeamWrites: Writes[LeagueTeam] = Json.writes[LeagueTeam]
implicit val leagueTableEntryWrites: Writes[LeagueTableEntry] = Json.writes[LeagueTableEntry]
implicit val competitionFormat: Writes[CompetitionSummary] = (competition: CompetitionSummary) =>
Json.obj(
"id" -> competition.id,
"url" -> competition.url,
"fullName" -> competition.fullName,
"nation" -> competition.nation,
"tableDividers" -> competition.tableDividers,
)
implicit val competitionFilterFormat: Writes[CompetitionFilter] = Json.writes[CompetitionFilter]
}
case class DotcomRenderingFootballMatchListDataModel(
matchesList: Seq[MatchesByDateAndCompetition],
nextPage: Option[String],
filters: Map[String, Seq[CompetitionFilter]],
previousPage: Option[String],
nav: Nav,
editionId: String,
guardianBaseURL: String,
config: JsObject,
pageFooter: PageFooter,
isAdFreeUser: Boolean,
contributionsServiceUrl: String,
canonicalUrl: String,
pageId: String,
) extends DotcomRenderingFootballDataModel
object DotcomRenderingFootballMatchListDataModel {
def apply(
page: FootballPage,
matchesList: MatchesList,
filters: Map[String, Seq[CompetitionFilter]],
)(implicit
request: RequestHeader,
context: ApplicationContext,
): DotcomRenderingFootballMatchListDataModel = {
val edition = Edition(request)
val nav = Nav(page, edition)
val combinedConfig: JsObject = DotcomRenderingFootballDataModel.getConfig(page)
val matches =
getMatchesList(matchesList.matchesGroupedByDateAndCompetition)
DotcomRenderingFootballMatchListDataModel(
matchesList = matches,
nextPage = matchesList.nextPage,
filters = filters,
previousPage = matchesList.previousPage,
nav = nav,
editionId = edition.id,
guardianBaseURL = Configuration.site.host,
config = combinedConfig,
pageFooter = PageFooter(FooterLinks.getFooterByEdition(edition)),
isAdFreeUser = views.support.Commercial.isAdFree(request),
contributionsServiceUrl = Configuration.contributionsService.url,
canonicalUrl = CanonicalLink(request, page.metadata.webUrl),
pageId = page.metadata.id,
)
}
import football.model.DotcomRenderingFootballDataModelImplicits._
private implicit val matchDayTeamFormat: Writes[MatchDayTeam] = Json.writes[MatchDayTeam]
// Writes for Fixture with a type discriminator
private implicit val fixtureWrites: Writes[Fixture] = Writes { fixture =>
Json.writes[Fixture].writes(fixture).as[JsObject] + ("type" -> JsString("Fixture"))
}
// Writes for MatchDay with a type discriminator
private implicit val matchDayWrites: Writes[MatchDay] = Writes { matchDay =>
Json.writes[MatchDay].writes(matchDay).as[JsObject] + ("type" -> JsString("MatchDay"))
}
// Writes for Result with a type discriminator
private implicit val resultWrites: Writes[Result] = Writes { result =>
Json.writes[Result].writes(result).as[JsObject] + ("type" -> JsString("Result"))
}
// Writes for LiveMatch with a type discriminator
private implicit val liveMatchWrites: Writes[LiveMatch] = Writes { liveMatch =>
Json.writes[LiveMatch].writes(liveMatch).as[JsObject] + ("type" -> JsString("LiveMatch"))
}
private implicit val footballMatchWrites: Writes[FootballMatch] = Writes { matchInstance =>
matchInstance match {
case f: Fixture => Json.toJson(f)(fixtureWrites)
case m: MatchDay => Json.toJson(m)(matchDayWrites)
case r: Result => Json.toJson(r)(resultWrites)
case l: LiveMatch => Json.toJson(l)(liveMatchWrites)
}
}
private implicit val competitionMatchesFormat: Writes[CompetitionMatches] = Json.writes[CompetitionMatches]
private implicit val dateCompetitionMatchesFormat: Writes[MatchesByDateAndCompetition] =
Json.writes[MatchesByDateAndCompetition]
implicit def dotcomRenderingFootballMatchListDataModel: Writes[DotcomRenderingFootballMatchListDataModel] =
Json.writes[DotcomRenderingFootballMatchListDataModel]
def toJson(model: DotcomRenderingFootballMatchListDataModel): JsValue = {
val jsValue = Json.toJson(model)
withoutNull(jsValue)
}
private def getMatchesList(
matches: Seq[(LocalDate, List[(Competition, List[FootballMatch])])],
): Seq[MatchesByDateAndCompetition] = {
matches.map { case (date, competitionMatches) =>
MatchesByDateAndCompetition(
date = date,
competitionMatches = competitionMatches.map { case (competition, matches) =>
CompetitionMatches(
competitionSummary = competition,
matches = matches,
)
},
)
}
}
}
case class DotcomRenderingFootballTablesDataModel(
tables: Seq[Table],
filters: Map[String, Seq[CompetitionFilter]],
nav: Nav,
editionId: String,
guardianBaseURL: String,
config: JsObject,
pageFooter: PageFooter,
isAdFreeUser: Boolean,
contributionsServiceUrl: String,
canonicalUrl: String,
pageId: String,
) extends DotcomRenderingFootballDataModel
object DotcomRenderingFootballTablesDataModel {
def apply(
page: FootballPage,
tables: Seq[Table],
filters: Map[String, Seq[CompetitionFilter]],
)(implicit
request: RequestHeader,
context: ApplicationContext,
): DotcomRenderingFootballTablesDataModel = {
val edition = Edition(request)
val nav = Nav(page, edition)
val combinedConfig: JsObject = DotcomRenderingFootballDataModel.getConfig(page)
DotcomRenderingFootballTablesDataModel(
tables = tables,
filters = filters,
nav = nav,
editionId = edition.id,
guardianBaseURL = Configuration.site.host,
config = combinedConfig,
pageFooter = PageFooter(FooterLinks.getFooterByEdition(edition)),
isAdFreeUser = views.support.Commercial.isAdFree(request),
contributionsServiceUrl = Configuration.contributionsService.url,
canonicalUrl = CanonicalLink(request, page.metadata.webUrl),
pageId = page.metadata.id,
)
}
import football.model.DotcomRenderingFootballDataModelImplicits._
def getEntries(competition: Competition, group: Group): Seq[JsObject] = {
group.entries.map { entry =>
Json.obj(
"stageNumber" -> entry.stageNumber,
"round" -> entry.round,
"team" -> entry.team,
"teamUrl" -> TeamUrl(entry.team),
"results" ->
competition
.teamResults(entry.team.id)
.takeRight(5)
.map(result =>
TeamResult(
matchId = result.matchId,
self = TeamScore(result.self.id, result.self.name, result.self.score),
foe = TeamScore(result.foe.id, result.foe.name, result.foe.score),
),
),
)
}
}
private implicit val teamScoreFormat: Writes[TeamScore] = Json.writes[TeamScore]
private implicit val teamResultFormat: Writes[TeamResult] = Json.writes[TeamResult]
private implicit val groupFormat: Writes[Group] = Json.writes[Group]
private implicit val tableWrites: Writes[Table] = (table: Table) =>
withoutDeepNull(
Json.obj(
"competition" -> Json.toJson(table.competition: CompetitionSummary),
"groups" -> table.groups.map { group =>
Json.obj(
"round" -> group.round,
"entries" -> getEntries(table.competition, group),
)
},
"hasGroups" -> table.multiGroup,
),
)
implicit def dotcomRenderingFootballTablesDataModel: Writes[DotcomRenderingFootballTablesDataModel] =
Json.writes[DotcomRenderingFootballTablesDataModel]
def toJson(model: DotcomRenderingFootballTablesDataModel): JsValue = {
val jsValue = Json.toJson(model)
withoutNull(jsValue)
}
}
case class DotcomRenderingFootballMatchSummaryDataModel(
footballMatch: MatchDataAnswer,
nav: Nav,
editionId: String,
guardianBaseURL: String,
config: JsObject,
pageFooter: PageFooter,
isAdFreeUser: Boolean,
contributionsServiceUrl: String,
canonicalUrl: String,
pageId: String,
) extends DotcomRenderingFootballDataModel
object DotcomRenderingFootballMatchSummaryDataModel {
def apply(
page: MatchPage,
footballMatch: MatchDataAnswer,
)(implicit
request: RequestHeader,
context: ApplicationContext,
): DotcomRenderingFootballMatchSummaryDataModel = {
val edition = Edition(request)
val nav = Nav(page, edition)
val combinedConfig: JsObject = DotcomRenderingFootballDataModel.getConfig(page)
DotcomRenderingFootballMatchSummaryDataModel(
footballMatch = footballMatch,
nav = nav,
editionId = edition.id,
guardianBaseURL = Configuration.site.host,
config = combinedConfig,
pageFooter = PageFooter(FooterLinks.getFooterByEdition(edition)),
isAdFreeUser = views.support.Commercial.isAdFree(request),
contributionsServiceUrl = Configuration.contributionsService.url,
canonicalUrl = CanonicalLink(request, page.metadata.webUrl),
pageId = page.metadata.id,
)
}
implicit def dotcomRenderingFootballMatchSummaryDataModel: Writes[DotcomRenderingFootballMatchSummaryDataModel] =
Json.writes[DotcomRenderingFootballMatchSummaryDataModel]
def toJson(model: DotcomRenderingFootballMatchSummaryDataModel): JsValue = {
val jsValue = Json.toJson(model)
withoutNull(jsValue)
}
}