diff --git a/app/model/FeastAppModel.scala b/app/model/FeastAppModel.scala index b1276d03992..5b3038f3ffd 100644 --- a/app/model/FeastAppModel.scala +++ b/app/model/FeastAppModel.scala @@ -4,15 +4,31 @@ import model.editions.Edition import play.api.libs.json._ import java.time.LocalDate +import model.editions.Palette object FeastAppModel { sealed trait ContainerItem - case class RecipeIdentifier(id:String) - case class Recipe(recipe:RecipeIdentifier) extends ContainerItem - case class Chef(id:String, image:Option[String], bio: Option[String], backgroundHex:Option[String], foregroundHex:Option[String]) extends ContainerItem - case class Palette(backgroundHex:String, foregroundHex:String) - case class FeastCollection(byline:Option[String], darkPalette:Option[Palette], image:Option[String], body:Option[String], title:String, lightPalette:Option[Palette], recipes:Seq[String]) extends ContainerItem + case class RecipeIdentifier(id: String) + + case class Recipe(recipe: RecipeIdentifier) extends ContainerItem + + case class Chef(id: String, + image: Option[String] = None, + bio: Option[String] = None, + backgroundHex: Option[String] = None, + foregroundHex: Option[String] = None + ) extends ContainerItem + + case class FeastCollection( + byline: Option[String] = None, + darkPalette: Option[Palette] = None, + image: Option[String] = None, + body: Option[String] = None, + title: String, + lightPalette: Option[Palette] = None, + recipes: Seq[String] + ) extends ContainerItem case class FeastAppContainer(id:String, title:String, body:Option[String], items:Seq[ContainerItem]) //type FeastAppCuration = Map[String, IndexedSeq[FeastAppContainer]] diff --git a/app/services/editions/publishing/FeastPublicationTarget.scala b/app/services/editions/publishing/FeastPublicationTarget.scala index 84d321fdda2..b2b78e17494 100644 --- a/app/services/editions/publishing/FeastPublicationTarget.scala +++ b/app/services/editions/publishing/FeastPublicationTarget.scala @@ -10,6 +10,7 @@ import play.api.libs.json.{Json, Writes} import util.TimestampGenerator import scala.jdk.CollectionConverters._ +import logging.Logging object FeastPublicationTarget { object MessageType extends Enumeration { @@ -19,8 +20,8 @@ object FeastPublicationTarget { type MessageType = MessageType.Value } -class FeastPublicationTarget(snsClient: AmazonSNS, config: ApplicationConfiguration, timestamp: TimestampGenerator) extends PublicationTarget { - private def transformArticles(source: EditionsCard): ContainerItem = { +class FeastPublicationTarget(snsClient: AmazonSNS, config: ApplicationConfiguration, timestamp: TimestampGenerator) extends PublicationTarget with Logging { + private def transformCards(source: EditionsCard): ContainerItem = { source match { case _: EditionsArticle => throw new Error("Article not permitted in a Feast Front") case EditionsRecipe(id, _) => Recipe(RecipeIdentifier(id)) @@ -29,7 +30,20 @@ class FeastPublicationTarget(snsClient: AmazonSNS, config: ApplicationConfigurat bio = metadata.flatMap(_.bio), backgroundHex = metadata.flatMap(_.theme.map(_.palette.backgroundHex)), foregroundHex = metadata.flatMap(_.theme.map(_.palette.foregroundHex))) - case _:EditionsFeastCollection => FeastCollection(byline=None, darkPalette=None, image=None, body=None, title="", lightPalette=None, recipes=List.empty) + case EditionsFeastCollection(id, addedOn, metadata) => + val recipes = metadata.map(_.collectionItems.map { + case EditionsRecipe(id, addedOn) => id + }).getOrElse(List.empty) + + FeastCollection( + byline = None, + darkPalette = metadata.flatMap(_.theme.map(_.darkPalette)), + lightPalette = metadata.flatMap(_.theme.map(_.lightPalette)), + image = metadata.flatMap(_.theme.flatMap(_.imageURL)), + body = None, + title = metadata.flatMap(_.title).getOrElse("No title"), + recipes = recipes + ) } } @@ -48,7 +62,7 @@ class FeastPublicationTarget(snsClient: AmazonSNS, config: ApplicationConfigurat id = collection.id, title = collection.displayName, body = Some(""), //TBD, this is just how it appears in the data at the moment - items = collection.items.map(transformArticles) + items = collection.items.map(transformCards) ) def transformContent(source: EditionsIssue, version: String): FeastAppCuration = { @@ -80,6 +94,9 @@ class FeastPublicationTarget(snsClient: AmazonSNS, config: ApplicationConfigurat override def putIssueJson[T: Writes](issue: T, key: String): Unit = { val content = Json.stringify(Json.toJson(issue)) + + logger.info(s"Publishing content for issue $key: $content") + snsClient.publish(createPublishRequest(content, FeastPublicationTarget.MessageType.Issue)) } diff --git a/test/services/editions/publishing/FeastPublicationTargetTest.scala b/test/services/editions/publishing/FeastPublicationTargetTest.scala index 18e9356f01a..7d973cf3e62 100644 --- a/test/services/editions/publishing/FeastPublicationTargetTest.scala +++ b/test/services/editions/publishing/FeastPublicationTargetTest.scala @@ -3,7 +3,6 @@ package services.editions.publishing import com.amazonaws.services.sns.AmazonSNSClient import com.amazonaws.services.sns.model.{MessageAttributeValue, PublishRequest, PublishResult} import conf.ApplicationConfiguration -import model.FeastAppModel import model.editions.{CuratedPlatform, Edition, EditionsCollection, EditionsFront, EditionsIssue, EditionsRecipe, PublishAction} import org.mockito.Mockito._ import org.mockito.ArgumentMatchers._ @@ -11,12 +10,20 @@ import org.scalatest.{FreeSpec, Matchers} import org.scalatestplus.mockito.MockitoSugar import play.api.Configuration import play.api.libs.json.Json -import model.FeastAppModel.{Chef, FeastAppContainer, Recipe, RecipeIdentifier} +import model.FeastAppModel.{Chef, FeastCollection, FeastAppContainer, Recipe, RecipeIdentifier} import util.TimestampGenerator import java.time.LocalDate import scala.jdk.CollectionConverters._ import scala.util.{Failure, Try} +import model.editions.EditionsFeastCollection +import model.editions.EditionsFeastCollectionMetadata +import model.editions.FeastCollectionTheme +import model.editions.Palette +import model.editions.EditionsChef +import model.editions.EditionsChefMetadata +import model.editions.ChefTheme +import model.editions.Image class FeastPublicationTargetTest extends FreeSpec with Matchers with MockitoSugar { val conf = new ApplicationConfiguration( @@ -63,8 +70,40 @@ class FeastPublicationTargetTest extends FreeSpec with Matchers with MockitoSuga contentPrefillTimeWindow = None, items=List( EditionsRecipe( - "id", + "recipe-id", 0L + ), + EditionsChef( + "chef-id", + 0L, + Some(EditionsChefMetadata( + bio = Some("bio"), + theme = Some(ChefTheme( + id = "theme-id", + palette = Palette("#FFF", "#333"), + )), + chefImageOverride = Some(Image( + width = None, + height = None, + origin = "image-origin", + src = "image-src" + )) + )) + ), + EditionsFeastCollection( + "collection-id", + 0L, + Some(EditionsFeastCollectionMetadata( + title = Some("Collection title"), + theme = Some(FeastCollectionTheme( + id = "theme-id", + lightPalette = Palette("#FFF", "#333"), + darkPalette = Palette("#333", "#FFF"), + imageURL = Some("https://example.com/an-image.jpg") + )), + collectionItems = List(EditionsRecipe("nested-recipe-id", 0L)) + + )) ) ) ) @@ -82,7 +121,7 @@ class FeastPublicationTargetTest extends FreeSpec with Matchers with MockitoSuga "recipes" -> FeastAppContainer("recipes", "Recipes", None, Seq(Recipe(RecipeIdentifier("abcdefg")))) ) - "should push the relevant content into SNS" - { + "should push the relevant content into SNS" in { val mockSNS = mock[AmazonSNSClient] when(mockSNS.publish(any[PublishRequest])).thenReturn(new PublishResult()) @@ -101,7 +140,7 @@ class FeastPublicationTargetTest extends FreeSpec with Matchers with MockitoSuga verify(mockSNS, times(1)).publish(expectedRequest) } - "should not catch an SNS exception" - { + "should not catch an SNS exception" in { val mockSNS = mock[AmazonSNSClient] val except = new RuntimeException("My hovercraft is full of eels") when(mockSNS.publish(any[PublishRequest])).thenThrow(except) @@ -114,7 +153,7 @@ class FeastPublicationTargetTest extends FreeSpec with Matchers with MockitoSuga } "putEditionsList" - { - "should push the relevant content into SNS" - { + "should push the relevant content into SNS" in { val mockSNS = mock[AmazonSNSClient] when(mockSNS.publish(any[PublishRequest])).thenReturn(new PublishResult()) @@ -135,7 +174,7 @@ class FeastPublicationTargetTest extends FreeSpec with Matchers with MockitoSuga } "transformContent" - { - "should transform the Editions content" - { + "should transform the Editions content" in { val mockSNS = mock[AmazonSNSClient] when(mockSNS.publish(any[PublishRequest])).thenReturn(new PublishResult()) @@ -148,14 +187,29 @@ class FeastPublicationTargetTest extends FreeSpec with Matchers with MockitoSuga allRecipesFront.head.title shouldBe "Dish of the day" allRecipesFront.head.body shouldBe Some("") //this is just how the `body` field is currently rendered allRecipesFront.head.id shouldBe "98e89761-fdf0-4903-b49d-2af7d66fc930" - allRecipesFront.head.items.length shouldBe 1 - allRecipesFront.head.items.head.asInstanceOf[FeastAppModel.Recipe].recipe.id shouldBe "id" + allRecipesFront.head.items shouldBe List( + Recipe(RecipeIdentifier("recipe-id")), + Chef( + id = "chef-id", + image = Some("image-src"), + bio = Some("bio"), + backgroundHex = Some("#333"), + foregroundHex = Some("#FFF") + ), + FeastCollection( + darkPalette = Some(Palette("#333", "#FFF")), + lightPalette = Some(Palette("#FFF", "#333")), + image = Some("https://example.com/an-image.jpg"), + title = "Collection title", + recipes = List("nested-recipe-id") + ) + ) } } "putIssue" - { - "should output the transformed version of the content" - { - val serializedVersion = """{"id":"123456ABCD","edition":"feast-northern-hemisphere","issueDate":"2024-05-03","version":"v1","fronts":{"all-recipes":[{"id":"98e89761-fdf0-4903-b49d-2af7d66fc930","title":"Dish of the day","body":"","items":[{"recipe":{"id":"id"}}]}]}}""" + "should output the transformed version of the content" in { + val serializedVersion = """{"id":"123456ABCD","edition":"feast-northern-hemisphere","issueDate":"2024-05-03","version":"v1","fronts":{"all-recipes":[{"id":"98e89761-fdf0-4903-b49d-2af7d66fc930","title":"Dish of the day","body":"","items":[{"recipe":{"id":"recipe-id"}},{"id":"chef-id","image":"image-src","bio":"bio","backgroundHex":"#333","foregroundHex":"#FFF"},{"darkPalette":{"foregroundHex":"#333","backgroundHex":"#FFF"},"image":"https://example.com/an-image.jpg","title":"Collection title","lightPalette":{"foregroundHex":"#FFF","backgroundHex":"#333"},"recipes":["nested-recipe-id"]}]}]}}""" val mockSNS = mock[AmazonSNSClient] when(mockSNS.publish(any[PublishRequest])).thenReturn(new PublishResult())