app/controllers/PromotionController.scala (77 lines of code) (raw):
package controllers
import java.util.{TimeZone, UUID}
import actions.GoogleAuthAction._
import com.gu.memsub.promo.Formatters.Common._
import com.gu.memsub.promo.Formatters.PromotionFormatters._
import com.gu.memsub.promo.Promotion.AnyPromotion
import com.gu.memsub.promo._
import com.gu.memsub.services.JsonDynamoService
import com.gu.memsub.subsv2.services.CatalogService
import com.typesafe.scalalogging.LazyLogging
import org.joda.time.DateTimeZone
import play.api.libs.json.{JsError, JsPath, Json, JsonValidationError}
import play.api.mvc.Result
import play.api.mvc.Results._
import wiring.AppComponents.Stage
import scala.concurrent.{ExecutionContext, Future}
import scala.util.Try
class PromotionController(
stage: Stage,
googleAuthAction: GoogleAuthenticatedAction,
catalogService: CatalogService[Future],
dynamoService: JsonDynamoService[AnyPromotion, Future],
implicit val ec: ExecutionContext
) extends LazyLogging {
private val londonTimezone = DateTimeZone.forTimeZone(TimeZone.getTimeZone("Europe/London"))
/**
* Only dates are shown on screen on the promo tool (with a timezone local to the user's laptop), so the start datetime
* will be normalised to being at the start of the London day, and the end datetime will be normalised to being at the
* end of the London day.
* @param promotion the promotion object marshalled from the frontend POST
* @return a copy of the promotion with its dates normalised.
*/
private def normaliseDateTimes(promotion: AnyPromotion): AnyPromotion = {
promotion.copy(
starts = promotion.starts.withZone(londonTimezone).withTimeAtStartOfDay,
expires = promotion.expires.map(_.withZone(londonTimezone).withTimeAtStartOfDay.plusDays(1).minusSeconds(1))
)
}
def all(campaignCode: Option[String]) = googleAuthAction.async {
campaignCode.map(CampaignCode).fold(dynamoService.all)(dynamoService.find).map(promos => Ok(Json.toJson(promos.sortBy(_.name))))
}
def get(uuid: Option[String]) = googleAuthAction.async {
uuid.flatMap(i => Try(UUID.fromString(i)).toOption).map {
i => dynamoService.find(i).map(_.headOption.fold[Result](NotFound)(promo => Ok(Json.toJson(promo))))
}.getOrElse[Future[Result]](Future.successful(BadRequest))
}
def productRatePlanIdsAreValidForStage(promo: AnyPromotion): Boolean = promo.appliesTo.productRatePlanIds.forall {
productRatePlanId => {
val productRatePlanIdIsInCatalog = catalogService.unsafeCatalog.paid.exists(_.id == productRatePlanId)
if (!productRatePlanIdIsInCatalog) {
logger.info(s"$productRatePlanId was not found in the $stage catalog")
}
productRatePlanIdIsInCatalog
}
}
def validate = googleAuthAction { request =>
val jsonValidationAttempt = for {
jsonToTest <- request.body.asJson.toRight[Seq[(JsPath, Seq[JsonValidationError])]](Seq.empty).right
promo <- Json.fromJson[AnyPromotion](jsonToTest).asEither.right
} yield promo
jsonValidationAttempt match {
case Right(promo) if productRatePlanIdsAreValidForStage(promo) =>
Ok
case Right(promo) =>
logger.warn(s"Failed to validate promotion $promo against $stage catalog")
InternalServerError(Json.obj("failureReason" -> s"Attempted to update a $stage promotion with invalid product rate plan ids"))
case Left(errors) =>
logger.warn(s"Failed to parse promotion JSON correctly due to $errors")
BadRequest(JsError.toJson(errors))
}
}
def upsert = googleAuthAction.async { request =>
request.body.asJson.map { json =>
json.validate[AnyPromotion].map { promotion => {
dynamoService.add(normaliseDateTimes(promotion)).map(_ => Ok(Json.obj("status" -> "ok")))
}
}.recoverTotal{
e => Future.successful(BadRequest("Detected error:"+ JsError.toJson(e)))
}
}.getOrElse {
Future.successful(BadRequest("Could not parse campaign data"))
}
}
}