applications/app/services/NewspaperQuery.scala (172 lines of code) (raw):
package services
import com.gu.contentapi.client.model.v1.{Tag, Content => ApiContent}
import com.gu.facia.api.utils.{ContentProperties, ResolvedMetaData}
import com.gu.facia.api.{models => fapi}
import common._
import contentapi.ContentApiClient
import implicits.Dates
import layout.{CollectionEssentials, FaciaContainer}
import model.pressed.{CollectionConfig, LinkSnap, PressedContent}
import org.joda.time.format.DateTimeFormat
import org.joda.time.{DateTime, DateTimeConstants, DateTimeZone}
import layout.slices.{ContainerDefinition, Fixed, FixedContainers, TTT}
import scala.concurrent.{ExecutionContext, Future}
case class BookSectionContent(tag: Tag, content: Seq[ApiContent])
case class ContentByPage(page: Int, content: ApiContent)
case class TagWithContent(tag: Tag, content: ApiContent)
case class BookSectionContentByPage(page: Int, booksectionContent: BookSectionContent)
class NewspaperQuery(contentApiClient: ContentApiClient) extends Dates with GuLogging {
val dateForFrontPagePattern = DateTimeFormat.forPattern("EEEE d MMMM y")
private val hrefFormat = DateTimeFormat.forPattern("yyyy/MMM/dd").withZone(DateTimeZone.UTC)
val FRONT_PAGE_DISPLAY_NAME = "Front page"
val pathToTag = Map("theguardian" -> "theguardian/mainsection", "theobserver" -> "theobserver/news")
def fetchLatestGuardianNewspaper()(implicit executionContext: ExecutionContext): Future[List[FaciaContainer]] = {
val now = DateTime.now(DateTimeZone.UTC)
bookSectionContainers("theguardian/mainsection", getLatestGuardianPageFor(now), "theguardian")
}
def fetchNewspaperForDate(path: String, day: String, month: String, year: String)(implicit
executionContext: ExecutionContext,
): Future[List[FaciaContainer]] = {
val dateFormatUTC = DateTimeFormat.forPattern("yyyy/MMM/dd").withZone(DateTimeZone.UTC)
val date = dateFormatUTC
.parseDateTime(s"$year/$month/$day")
.toDateTime
pathToTag.get(path).map(tag => bookSectionContainers(tag, date, path)).getOrElse(Future.successful(Nil))
}
private def bookSectionContainers(itemId: String, newspaperDate: DateTime, publication: String)(implicit
executionContext: ExecutionContext,
): Future[List[FaciaContainer]] = {
val startDate = newspaperDate.withTimeAtStartOfDay()
val itemQuery = contentApiClient
.item(itemId)
.useDate("newspaper-edition")
.showFields("all")
.showElements("all")
.showTags("newspaper-book-section")
.pageSize(200)
.fromDate(jodaToJavaInstant(startDate))
.toDate(jodaToJavaInstant(newspaperDate))
contentApiClient.getResponse(itemQuery).map { resp =>
// filter out the first page results to make a Front Page container
val (firstPageContent, otherContent) =
resp.results.getOrElse(Nil).partition(content => getNewspaperPageNumber(content).contains(1))
val firstPageContainer = {
val content = firstPageContent.map(c => FaciaContentConvert.contentToFaciaContent(c))
// for /theguardian fetch date links either side of date requested, for /theobserver, fetch each sunday around the date and the day before
val snaps = createSnap(newspaperDate, publication)
bookSectionContainer(
None,
Some(FRONT_PAGE_DISPLAY_NAME),
Some(newspaperDate.toString(dateForFrontPagePattern)),
content.toSeq,
0,
snaps,
)
}
val unorderedBookSections = createBookSections(otherContent.toSeq)
val orderedBookSections = orderByPageNumber(unorderedBookSections)
val bookSectionContainers = orderedBookSections.map { list =>
val content = list.content.map(c => FaciaContentConvert.contentToFaciaContent(c))
bookSectionContainer(
Some(list.tag.id),
Some(list.tag.webTitle),
None,
content,
orderedBookSections.indexOf(list) + 1,
Nil,
)
}
firstPageContainer :: bookSectionContainers
}
}
private def createBookSections(contentList: Seq[ApiContent]): List[BookSectionContent] = {
val tagWithContent: Seq[TagWithContent] = contentList.flatMap { content =>
content.tags.find(_.`type`.name == "NewspaperBookSection").map(t => TagWithContent(t, content))
}
// group content by booksection tag type
tagWithContent
.groupBy(_.tag)
.map(bookSectionContent => BookSectionContent(bookSectionContent._1, bookSectionContent._2.map(_.content)))
.toList
}
private def orderByPageNumber(unorderedBookSections: List[BookSectionContent]): List[BookSectionContent] = {
// order content for each book section
val orderedContentForBookSection: List[BookSectionContent] = unorderedBookSections.map { bookSection =>
bookSection.copy(content = orderContentByPageNumber(bookSection.content))
}
// order booksections by first content item in each booksection
val pageNumberToFaciaContainer: List[BookSectionContentByPage] = orderedContentForBookSection.flatMap {
bookSection =>
val pageNumberOpt = bookSection.content.headOption.flatMap(content => getNewspaperPageNumber(content))
pageNumberOpt.map(BookSectionContentByPage(_, bookSection))
}
pageNumberToFaciaContainer.sortBy(_.page).map(_.booksectionContent)
}
private def orderContentByPageNumber(unorderedContent: Seq[ApiContent]): Seq[ApiContent] = {
val pageNumberToContent: Seq[ContentByPage] = unorderedContent.flatMap { content =>
getNewspaperPageNumber(content).map(ContentByPage(_, content))
}
pageNumberToContent.sortBy(_.page).map(_.content)
}
private def bookSectionContainer(
dataId: Option[String],
containerName: Option[String],
containerDescription: Option[String],
trails: Seq[PressedContent],
index: Int,
linkSnaps: List[LinkSnap],
): FaciaContainer = {
val containerDefinition = trails.length match {
case 1 => FixedContainers.fixedSmallSlowI
case 2 => FixedContainers.fixedSmallSlowII
case 3 => ContainerDefinition.ofSlices(TTT)
case 5 => FixedContainers.fixedSmallSlowVThird
case _ => FixedContainers.fixedMediumFastXII
}
FaciaContainer
.fromConfigWithId(
index,
Fixed(containerDefinition),
CollectionConfigWithId(
dataId.getOrElse(""),
CollectionConfig.empty.copy(displayName = containerName, description = containerDescription),
),
CollectionEssentials(trails, linkSnaps, containerName, dataId, None, None),
hasMore = false,
)
.copy(hasShowMoreEnabled = false)
}
private def getNewspaperPageNumber(content: ApiContent) = content.fields.flatMap(_.newspaperPageNumber)
def getPastSundayDateFor(date: DateTime): DateTime = {
if (date.getDayOfWeek != DateTimeConstants.SUNDAY) {
val daysSinceSunday = DateTimeConstants.SUNDAY - date.getDayOfWeek - 7
date.minusDays(Math.abs(daysSinceSunday))
} else date
}
def getLatestGuardianPageFor(date: DateTime): DateTime =
if (date.getDayOfWeek == DateTimeConstants.SUNDAY) date.minusDays(1) else date
private def createSnap(date: DateTime, publication: String) = {
// if /theguardian get links for date either side of the date requests
// else for theobserver get dates either sunday around the date and the previous Saturday.
// filter out any dates in the future
val daysAroundDateToFetchLinksFor = if (publication == "theguardian") List(1, -1) else List(7, -1, -7)
val datesAroundNewspaperDate = daysAroundDateToFetchLinksFor.map(date.plusDays)
datesAroundNewspaperDate.filter(d => d.isBeforeNow).map { d =>
val displayFormat = d.toString(dateForFrontPagePattern)
val hrefDateFormat = d.toString(hrefFormat).toLowerCase
val href =
if (d.getDayOfWeek == DateTimeConstants.SUNDAY) s"/theobserver/$hrefDateFormat"
else s"/theguardian/$hrefDateFormat"
val fapiSnap = fapi.LinkSnap(
id = "no-id",
maybeFrontPublicationDate = None,
snapType = "no-snap-type",
snapUri = None,
snapCss = None,
atomId = None,
headline = Some(displayFormat),
href = Some(href),
trailText = None,
group = "group",
image = None,
properties = ContentProperties.fromResolvedMetaData(ResolvedMetaData.Default),
byline = None,
kicker = None,
brandingByEdition = Map.empty,
)
LinkSnap.make(fapiSnap)
}
}
}