Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Store EditionsClientCard model for editions clipboard data #1584

Merged
merged 3 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 14 additions & 23 deletions app/controllers/UserDataController.scala
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
package controllers

import com.amazonaws.services.dynamodbv2.AmazonDynamoDB
import com.gu.facia.client.models.{Trail, TrailMetaData}
import com.gu.facia.client.models.Trail
import model.editions.{CardType, EditionsClientCard}
import org.scanamo._
import org.scanamo.syntax._
import model.{FeatureSwitch, FeatureSwitches, UserData}
import org.scanamo.generic.auto.genericDerivedFormat
import org.scanamo.query.UniqueKey
import play.api.libs.json.{JsArray, JsValue, Json}
import model.{UserData}

import play.api.libs.json.JsValue
import play.api.libs.json.{JsError, JsResult, JsSuccess, JsValue, Json, JsonValidationError, Reads}
import services.FrontsApi
import software.amazon.awssdk.services.dynamodb.DynamoDbClient

import scala.concurrent.{ExecutionContext, Future}
import scala.concurrent.ExecutionContext
import org.scanamo.generic.semiauto._

import scala.util.{Failure, Success, Try}
Expand All @@ -28,35 +25,29 @@ class UserDataController(frontsApi: FrontsApi, dynamoClient: DynamoDbClient, val
x => (Json.stringify(x))
)
implicit val trail: DynamoFormat[Trail] = deriveDynamoFormat[Trail]
implicit val cardType: DynamoFormat[CardType] = deriveDynamoFormat[CardType]
implicit val editionsCard: DynamoFormat[EditionsClientCard] = deriveDynamoFormat[EditionsClientCard]
implicit val userData: DynamoFormat[UserData] = deriveDynamoFormat[UserData]
private lazy val userDataTable = Table[UserData](config.faciatool.userDataTable)

private def updateClipboardContentByFieldName(articles: Option[JsValue], userEmail: String, fieldName: String) = {
val clipboardArticles: Option[List[Trail]] = articles.flatMap(jsValue =>
jsValue.asOpt[List[Trail]])

clipboardArticles match {
case Some(articles) => {

Scanamo(dynamoClient).exec(userDataTable.update(UniqueKey("email" === userEmail), set(fieldName, articles)))
private def updateClipboardContentByFieldName[T](maybeJson: Option[JsValue], userEmail: String, fieldName: String)(implicit dynamoFormat: DynamoFormat[T], jsonFormat: Reads[T]) = {
maybeJson.map(_.validate[T]) match {
case Some(JsSuccess(model, _)) =>
Scanamo(dynamoClient).exec(userDataTable.update(UniqueKey("email" === userEmail), set(fieldName, model)))
Ok
}
case Some(JsError(errors)) =>
BadRequest(errors.toString())
case None => BadRequest
}

}


def putClipboardContent() = APIAuthAction { request =>

updateClipboardContentByFieldName(request.body.asJson, request.user.email, "clipboardArticles")

updateClipboardContentByFieldName[List[Trail]](request.body.asJson, request.user.email, "clipboardArticles")
}

def putEditionsClipboardContent() = APIAuthAction { request =>

updateClipboardContentByFieldName(request.body.asJson, request.user.email, "editionsClipboardArticles")

updateClipboardContentByFieldName[List[EditionsClientCard]](request.body.asJson, request.user.email, "editionsClipboardArticles")
}

def putFrontIds() = APIAuthAction { request =>
Expand Down
6 changes: 3 additions & 3 deletions app/controllers/V2App.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package controllers
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB
import org.scanamo._
import org.scanamo.syntax._
import model.{FeatureSwitch, UserData, UserDataForDefaults}
import model.{ClipboardCard, FeatureSwitch, UserData, UserDataForDefaults}

import scala.concurrent.ExecutionContext
import com.gu.facia.client.models.{Metadata, TargetedTerritory}
Expand Down Expand Up @@ -48,9 +48,9 @@ class V2App(isDev: Boolean, val acl: Acl, dynamoClient: DynamoDbClient, val deps
userDataTable.get("email" === userEmail)).flatMap(_.toOption)

val clipboardArticles = if (editingEdition)
maybeUserData.map(_.editionsClipboardArticles.getOrElse(List()))
maybeUserData.map(_.editionsClipboardArticles.getOrElse(List()).map(ClipboardCard.apply))
else
maybeUserData.map(_.clipboardArticles.getOrElse(List()))
maybeUserData.map(_.clipboardArticles.getOrElse(List()).map(ClipboardCard.apply))

val userDataForDefaults = UserDataForDefaults.fromUserData(
maybeUserData.getOrElse(UserData(userEmail)),
Expand Down
27 changes: 27 additions & 0 deletions app/model/ClipboardCard.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package model

import com.gu.facia.client.models.Trail
import model.editions.EditionsClientCard
import play.api.libs.json.{Format, JsPath, JsValue, Json, Reads, Writes}
import play.api.libs.functional.syntax._

object ClipboardCard {
def apply(trail: Trail): ClipboardCard = ClipboardCard(Left(trail))
def apply(editionsCard: EditionsClientCard): ClipboardCard = ClipboardCard(Right(editionsCard))

val reads: Reads[ClipboardCard] =
JsPath.read[EditionsClientCard].map(ClipboardCard.apply) or
JsPath.read[Trail].map(ClipboardCard.apply)

val writes = new Writes[ClipboardCard] {
override def writes(o: ClipboardCard): JsValue =
o.card.fold(
trail => Json.toJson(trail),
editionsCard => Json.toJson(editionsCard)
)
}

implicit val format = Format(reads, writes)
}

case class ClipboardCard(card: Either[Trail, EditionsClientCard])
7 changes: 4 additions & 3 deletions app/model/UserData.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package model

import com.gu.facia.client.models.Trail
import model.editions.EditionsClientCard
import org.scanamo.{DynamoFormat, TypeCoercionError}
import play.api.libs.json.{JsValue, Json, OFormat}

Expand Down Expand Up @@ -31,7 +32,7 @@ object UserData {
case class UserData(
email: String,
clipboardArticles: Option[List[Trail]] = None,
editionsClipboardArticles: Option[List[Trail]] = None,
editionsClipboardArticles: Option[List[EditionsClientCard]] = None,
frontIds: Option[List[String]] = None,
frontIdsByPriority: Option[Map[String, List[String]]] = None,
favouriteFrontIdsByPriority: Option[Map[String, List[String]]] = None,
Expand All @@ -41,7 +42,7 @@ case class UserData(
object UserDataForDefaults {
implicit val jsonFormat: OFormat[UserDataForDefaults] = Json.format[UserDataForDefaults]

def fromUserData(userData: UserData, clipboardArticles: Option[List[Trail]]): UserDataForDefaults = {
def fromUserData(userData: UserData, clipboardArticles: Option[List[ClipboardCard]]): UserDataForDefaults = {
val featureSwitches = userData.featureSwitches.fold(FeatureSwitches.all) { userFeatureSwitches =>
val userFeatureSwitchKeys = userFeatureSwitches.map(_.key)
val unsetFeatureSwitches = FeatureSwitches.all.filter(featureSwitch => !userFeatureSwitchKeys.contains(featureSwitch.key))
Expand All @@ -58,7 +59,7 @@ object UserDataForDefaults {
}

case class UserDataForDefaults(
clipboardArticles: Option[List[Trail]],
clipboardArticles: Option[List[ClipboardCard]],
frontIds: Option[List[String]],
frontIdsByPriority: Option[Map[String, List[String]]],
favouriteFrontIdsByPriority: Option[Map[String, List[String]]],
Expand Down
15 changes: 15 additions & 0 deletions app/model/editions/EditionsCard.scala
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,21 @@ object CardType extends PlayEnum[CardType] {
override def values = findValues
}

/**
* A Card for Editions-based platforms. Analogous to the `Trail` type in
* facia-scala-client.
*
* I suspect it's distinct from `Trail` because the Editions cards have
* slightly different properties:
* - `frontPublicationDate` does not make sense in this context and is
* replaced with `addedOn`
* - `publishedBy` is not required ... and `Trail` is in a library upstream
* that is not used by the Editions product.
*
* Ideally, this and Trail would be perhaps be represented by a sealed trait
* and a discriminator field (arguably cardType) – the client does not
* distinguish between these two types.
*/
case class EditionsCard(id: String, cardType: CardType, addedOn: Long, metadata: Option[CardMetadata]) extends Logging {

def toPublishedCard: PublishedArticle = {
Expand Down
2 changes: 2 additions & 0 deletions app/model/editions/EditionsClientCollection.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import services.editions.prefills.CapiQueryTimeWindow
case class EditionsClientCard(id: String, cardType: Option[CardType], frontPublicationDate: Long, meta: Option[ClientCardMetadata])

object EditionsClientCard {
implicit val format: OFormat[EditionsClientCard] = Json.format[EditionsClientCard]

def fromCard(card: EditionsCard): EditionsClientCard = {
val id = card.cardType match {
case CardType.Article => "internal-code/page/" + card.id
Expand Down
Loading