app/controllers/App.scala (176 lines of code) (raw):

package controllers import cats.syntax.either._ import com.gu.contentatom.thrift.{Atom, AtomType, EventType} import com.gu.fezziwig.CirceScroogeMacros._ import config.Config import db.{AtomDataStores, AtomWorkshopDBAPI} import models._ import play.api.Logging import play.api.mvc._ import util.AtomElementBuilders import util.AtomLogic._ import util.AtomUpdateOperations._ import util.Parser._ import util.CORSable import com.gu.pandomainauth.model.{User => PandaUser} import services.{AtomPublishers, Permissions} import views.html.helper.CSRF class App( val controllerComponents: ControllerComponents, val config: Config, val pandaAuthActions: PanDomainAuthActions, val atomWorkshopDB: AtomWorkshopDBAPI, val atomDataStores: AtomDataStores, val atomPublishers: AtomPublishers, val permissions: Permissions ) extends BaseController with Logging { // These are required even though IntelliJ thinks they are not import io.circe._ import io.circe.syntax._ import pandaAuthActions.AuthAction implicit val executionContext = controllerComponents.executionContext private val previewDataStore = atomDataStores.getDataStore(Preview) private val publishedDataStore = atomDataStores.getDataStore(Live) private val previewAtomPublisher = atomPublishers.previewAtomPublisher private val liveAtomPublisher = atomPublishers.liveAtomPublisher def allowCORSAccess(methods: String, args: Any*) = CORSable(config.workflowUrl) { Action { implicit req => val requestedHeaders = req.headers("Access-Control-Request-Headers") NoContent.withHeaders("Access-Control-Allow-Methods" -> methods, "Access-Control-Allow-Headers" -> requestedHeaders) } } def index(placeholder: String) = AuthAction { implicit req => logger.info(s"I am the ${config.appName}") val clientConfig = ClientConfig( user = User(req.user.firstName, req.user.lastName, req.user.email), gridUrl = config.gridUrl, composerUrl = config.composerUrl, viewerUrl = config.viewerUrl, capiLiveUrl = config.capiLiveUrl, targetingUrl = config.targetingUrl, workflowUrl = config.workflowUrl, isEmbedded = req.queryString.get("embeddedMode").isDefined, embeddedMode = req.queryString.get("embeddedMode").map(_.head), atomEditorGutoolsDomain = config.serviceDomain, presenceEnabled = config.presenceEnabled, presenceDomain = config.presenceDomain, permissions.getAll(req.user.email), stage = config.stage ) val jsFileName = "build/app.js" val jsLocation = sys.env.get("JS_ASSET_HOST").map(_ + jsFileName) .getOrElse(routes.Assets.versioned(jsFileName).toString) val presenceJsFile = if (config.presenceEnabled) { Some(s"https://${config.presenceDomain}/client/1/lib.js") } else { None } Ok(views.html.index( "Atom Workshop", jsLocation, presenceJsFile, clientConfig.asJson.noSpaces, CSRF.getToken.value )) } def getAtom(atomType: String, id: String, version: String) = { AuthAction { APIResponse { for { atomType <- validateAtomType(atomType) ds = atomDataStores.getDataStore(getVersion(version)) atom <- atomWorkshopDB.getAtom(ds, atomType, id) } yield atom } } } def createAtom(atomType: String) = CORSable(config.workflowUrl) { AuthAction { req => APIResponse { for { atomType <- validateAtomType(atomType) createAtomFields <- extractCreateAtomFields(req.body.asJson.map(_.toString)) atomToCreate = AtomElementBuilders.buildDefaultAtom(atomType, req.user, createAtomFields) atom <- atomWorkshopDB.createAtom(previewDataStore, atomType, req.user, atomToCreate) _ <- atomPublishers.sendKinesisEvent(atom, previewAtomPublisher, EventType.Update) } yield atom } } } def publishAtom(atomType: String, id: String) = AuthAction { req => APIResponse { for { atomType <- validateAtomType(atomType) previewDs = previewDataStore currentDraftAtom <- atomWorkshopDB.getAtom(previewDs, atomType, id) updatedAtom <- atomWorkshopDB.publishAtom(publishedDataStore, req.user, updateTopLevelFields(currentDraftAtom, req.user, publish=true)) _ <- atomWorkshopDB.updateAtom(previewDs, updatedAtom) _ <- atomPublishers.sendKinesisEvent(updatedAtom, liveAtomPublisher, EventType.Update) _ <- atomPublishers.sendKinesisEvent(updatedAtom, previewAtomPublisher, EventType.Update) } yield updatedAtom } } def updateEntireAtom(atomType: String, id: String) = { AuthAction { req => APIResponse { for { atomType <- validateAtomType(atomType) payload <- extractRequestBody(req.body.asJson.map(_.toString)) newAtom <- stringToAtom(payload) updatedAtom <- atomWorkshopDB.updateAtom(previewDataStore, updateTopLevelFields(newAtom, req.user)) _ <- atomPublishers.sendKinesisEvent(updatedAtom, previewAtomPublisher, EventType.Update) } yield updatedAtom } } } def updateAtomByPath(atomType: String, id: String) = AuthAction { req => APIResponse { for { atomType <- validateAtomType(atomType) payload <- extractRequestBody(req.body.asJson.map(_.toString)) newJson <- stringToJson(payload) currentAtom <- atomWorkshopDB.getAtom(previewDataStore, atomType, id) newAtom <- updateAtomFromJson(currentAtom, newJson, req.user) updatedAtom <- atomWorkshopDB.updateAtom(previewDataStore, updateTopLevelFields(newAtom, req.user)) _ <- atomPublishers.sendKinesisEvent(updatedAtom, previewAtomPublisher, EventType.Update) } yield updatedAtom } } def deleteAtom(atomType: String, id: String) = AuthAction { req => APIResponse { validateAtomType(atomType).flatMap { atomType => atomWorkshopDB.getAtom(publishedDataStore, atomType, id) match { case Right(publishedAtom) => for { _ <- takedown(atomType, id, req.user) _ <- atomWorkshopDB.deleteAtom(previewDataStore, atomType, id) _ <- atomPublishers.sendKinesisEvent(publishedAtom, previewAtomPublisher, EventType.Takedown) } yield AtomWorkshopAPIResponse(s"Atom $atomType/$id taken down and deleted") case Left(UnknownAtomError(_, _)) => atomWorkshopDB.getAtom(previewDataStore, atomType, id).flatMap { unpublishedAtom => for { _ <- atomWorkshopDB.deleteAtom(previewDataStore, atomType, id) _ <- atomPublishers.sendKinesisEvent(unpublishedAtom, previewAtomPublisher, EventType.Takedown) } yield AtomWorkshopAPIResponse(s"Atom $atomType/$id deleted") } case Left(err) => Left(err) } } } } def takedownAtom(atomType: String, id: String) = AuthAction { req => APIResponse { for { atomType <- validateAtomType(atomType) result <- takedown(atomType, id, req.user) } yield result } } private def takedown(atomType: AtomType, id: String, user: PandaUser): Either[AtomAPIError, Atom] = for { atom <- atomWorkshopDB.getAtom(publishedDataStore, atomType, id) updatedAtom <- atomWorkshopDB.updateAtom(previewDataStore, updateTakenDownChangeRecord(atom, user)) result <- atomWorkshopDB.deleteAtom(publishedDataStore, atomType, id) _ <- atomPublishers.sendKinesisEvent(updatedAtom, liveAtomPublisher, EventType.Takedown) _ <- atomPublishers.sendKinesisEvent(updatedAtom, previewAtomPublisher, EventType.Update) } yield updatedAtom }