app/services/editions/publishing/FeastPublicationTarget.scala (170 lines of code) (raw):
package services.editions.publishing
import com.amazonaws.services.sns.AmazonSNS
import com.amazonaws.services.sns.model.{MessageAttributeValue, PublishRequest}
import conf.ApplicationConfiguration
import model.FeastAppModel.{
Chef,
ChefContent,
ContainerItem,
FeastAppContainer,
FeastAppCuration,
FeastCollection,
FeastCollectionContent,
Recipe,
RecipeContent
}
import model.editions.PublishAction.PublishAction
import model.editions.{
Edition,
EditionsArticle,
EditionsCard,
EditionsChef,
EditionsCollection,
EditionsFeastCollection,
EditionsIssue,
EditionsRecipe,
FeastAppTemplates
}
import play.api.libs.json.{Json, Writes}
import util.TimestampGenerator
import scala.jdk.CollectionConverters._
import logging.Logging
import scala.util.{Failure, Success}
object FeastPublicationTarget {
object MessageType extends Enumeration {
val Issue, EditionsList = Value
}
type MessageType = MessageType.Value
}
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(RecipeContent(id))
case EditionsChef(id, _, metadata) =>
Chef(
ChefContent(
id = id,
image = metadata.flatMap(_.chefImageOverride.map(_.src)),
bio = metadata.flatMap(_.bio),
backgroundHex =
metadata.flatMap(_.theme.map(_.palette.backgroundHex)),
foregroundHex =
metadata.flatMap(_.theme.map(_.palette.foregroundHex))
)
)
case EditionsFeastCollection(_, _, metadata) =>
val recipes = metadata
.map(_.collectionItems.collect { case EditionsRecipe(id, _) =>
id
})
.getOrElse(List.empty)
FeastCollection(
FeastCollectionContent(
byline = None,
darkPalette = metadata.flatMap(_.theme.map(_.darkPalette)),
lightPalette = metadata.flatMap(_.theme.map(_.lightPalette)),
image = metadata.flatMap(_.theme.flatMap(_.imageURL)),
body = Some(
""
), // The apps appear to require this to be present, even if it is empty
title = metadata.flatMap(_.title).getOrElse("No title"),
recipes = recipes
)
)
}
}
private val findSpace = "\\s+".r
/** Feast app expects a name like `all-recipes` wheras we have `All Recipes`
*
* @param originalName
* name to transform
* @return
* name in kebab-case
*/
private def transformName(originalName: String): String =
findSpace.replaceAllIn(originalName.toLowerCase, "-")
private def transformCollections(
collection: EditionsCollection
): FeastAppContainer =
FeastAppContainer(
id = collection.id,
title = collection.displayName,
targetedRegions = collection.targetedRegions,
excludedRegions = collection.excludedRegions,
body =
Some(""), // TBD, this is just how it appears in the data at the moment
items = collection.items.map(transformCards)
)
def transformContent(
source: EditionsIssue,
version: String
): Either[String, FeastAppCuration] = {
FeastAppTemplates.templates.get(source.edition) match {
case Some(template) =>
Right(
FeastAppCuration(
id = source.id,
issueDate = source.issueDate,
edition = source.edition,
path = template.path,
version = version,
fronts = source.fronts
.map(f => {
(
transformName(f.getName),
f.collections.map(transformCollections).toIndexedSeq
)
})
.toMap
)
)
case None =>
Left(
s"No backend edition name found for issue ${source.edition.entryName}"
)
}
}
override def putIssue(
issue: EditionsIssue,
version: String,
action: PublishAction
): Either[String, Unit] = {
val outputKey = createKey(issue, version)
for {
content <- transformContent(issue, version)
} yield putIssueJson(content, outputKey)
}
private def createPublishRequest(
content: String,
messageType: FeastPublicationTarget.MessageType
): PublishRequest = {
new PublishRequest()
.withMessage(content)
.withTopicArn(config.aws.feastAppPublicationTopic)
.withMessageAttributes(
Map(
"timestamp" -> new MessageAttributeValue()
.withDataType("Number")
.withStringValue(timestamp.getTimestamp.toString),
"type" -> new MessageAttributeValue()
.withDataType("String")
.withStringValue(messageType.toString)
).asJava
)
}
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)
)
}
override def putEditionsList(rawJson: String): Unit = {
snsClient.publish(
createPublishRequest(
rawJson,
FeastPublicationTarget.MessageType.EditionsList
)
)
}
}