app/data/AtomListStore.scala (93 lines of code) (raw):

package data import java.time.Instant import com.gu.atom.data.PreviewDynamoDataStore import com.gu.media.CapiAccess import com.gu.media.model.{Image, MediaAtom, ContentChangeDetails} import com.gu.media.util.TestFilters import model.commands.CommandExceptions.AtomDataStoreError import model.{MediaAtomList, MediaAtomSummary} import play.api.libs.json.{JsArray, JsValue} trait AtomListStore { def getAtoms(search: Option[String], limit: Option[Int], shouldUseCreatedDateForSort: Boolean): MediaAtomList } class CapiBackedAtomListStore(capi: CapiAccess) extends AtomListStore { override def getAtoms(search: Option[String], limit: Option[Int], shouldUseCreatedDateForSort: Boolean): MediaAtomList = { // CAPI max page size is 200 val cappedLimit: Option[Int] = limit.map(Math.min(200, _)) val base: Map[String, String] = Map( "types" -> "media", "order-by" -> "newest" ) ++ (if(shouldUseCreatedDateForSort) Map("order-date" -> "first-publication") else Map.empty) val baseWithSearch = search match { case Some(q) => base ++ Map( "q" -> q, "searchFields" -> "title" ) case None => base } val baseWithSearchAndLimit = cappedLimit match { case Some(pageSize) => baseWithSearch ++ Map( "page-size" -> pageSize.toString ) case None => baseWithSearch } val response = capi.capiQuery("atoms", baseWithSearchAndLimit) val total = (response \ "response" \ "total").as[Int] val results = (response \ "response" \ "results").as[JsArray] MediaAtomList(total, results.value.flatMap(fromJson).toList) } private def fromJson(wrapper: JsValue): Option[MediaAtomSummary] = { val id = (wrapper \ "id").as[String] val atom = wrapper \ "data" \ "media" val category = (atom \ "category").as[String] val title = (atom \ "title").as[String] if (title.startsWith(TestFilters.testAtomBaseName)) None /* This filters out test atoms created by the Integration Tests, as we don't want them to be user facing */ else { val posterImage = (atom \ "posterImage").asOpt[Image] val activeVersion = (atom \ "activeVersion").asOpt[Long] val contentChangeDetails = (wrapper \ "contentChangeDetails").as[ContentChangeDetails] val expiryDate = (wrapper \ "contentChangeDetails" \ "expiry" \ "date").asOpt[Long] val versions = (atom \ "assets").as[JsArray].value.map { asset => (asset \ "version").as[Long] } Some(MediaAtomSummary(id, title, posterImage, contentChangeDetails)) } } } class DynamoBackedAtomListStore(store: PreviewDynamoDataStore) extends AtomListStore { override def getAtoms(search: Option[String], limit: Option[Int], shouldUseCreatedDateForSort: Boolean): MediaAtomList = { // We must filter the entire list of atoms rather than use Dynamo limit to ensure stable iteration order. // Without it, the front page will shuffle around when clicking the Load More button. store.listAtoms match { case Left(err) => AtomDataStoreError(err.msg) case Right(atoms) => def sortField(atom: MediaAtom) = if(shouldUseCreatedDateForSort) atom.contentChangeDetails.created else atom.contentChangeDetails.lastModified val mediaAtoms = atoms .map(MediaAtom.fromThrift) .toList .sortBy(sortField(_).map(_.date.getMillis)) .reverse // newest atoms first val filteredAtoms = search match { case Some(str) => mediaAtoms.filter(_.title.contains(str)) case None => mediaAtoms } val limitedAtoms = limit match { case Some(l) => filteredAtoms.take(l) case None => filteredAtoms } MediaAtomList(filteredAtoms.size, limitedAtoms.map(fromAtom)) } } private def fromAtom(atom: MediaAtom): MediaAtomSummary = { val versions = atom.assets.map(_.version).toSet MediaAtomSummary(atom.id, atom.title, atom.posterImage, atom.contentChangeDetails) } } object AtomListStore { def apply(stage: String, capi: CapiAccess, store: PreviewDynamoDataStore): AtomListStore = stage match { case "DEV" => new DynamoBackedAtomListStore(store) case _ => new CapiBackedAtomListStore(capi) } }