app/controllers/Api.scala (362 lines of code) (raw):
package controllers
import com.gu.pandahmac.HMACAuthActions
import com.gu.pandomainauth.PanDomainAuthSettingsRefresher
import com.gu.pandomainauth.action.UserRequest
import com.gu.permissions.PermissionsProvider
import com.gu.workflow.api.{ApiUtils, SectionsAPI, StubAPI}
import com.gu.workflow.lib.DBToAPIResponse.getResponse
import com.gu.workflow.lib.{ContentAPI, Priorities, StatusDatabase}
import com.gu.workflow.util.{SharedSecretAuth, StubDecorator}
import config.Config
import io.circe.syntax._
import io.circe.{Encoder, Json}
import lib.Responses._
import models.EditorialSupportStaff._
import models.api.{ApiError, ApiResponseFt}
import models.{Flag, _}
import play.api.Logging
import play.api.libs.ws.WSClient
import org.joda.time.{DateTime, LocalDate}
import play.api.Logger
import play.api.mvc._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{ExecutionContext, Future}
class Api(
stubsApi: StubAPI,
sectionsApi: SectionsAPI,
editorialSupportTeamsController: EditorialSupportTeamsController,
override val config: Config,
override val controllerComponents: ControllerComponents,
override val wsClient: WSClient,
override val panDomainSettings: PanDomainAuthSettingsRefresher,
override val permissions: PermissionsProvider,
) extends BaseController
with PanDomainAuthActions
with SharedSecretAuth
with HMACAuthActions
with Logging
with ApiUtils {
val contentAPI = new ContentAPI(capiPreviewRole = config.capiPreviewRole, apiRoot = config.capiPreviewIamUrl, ws = wsClient)
val stubDecorator = new StubDecorator(contentAPI)
override def secret: String = config.sharedSecret
implicit val flatStubWrites: Encoder[Stub] = Stub.flatJsonEncoder
// can be hidden behind multiple auth endpoints
private def getContentBlock[R <: Request[_]] = { implicit req: R =>
val qs: Map[String, Seq[String]] = Api.queryString(req)
stubsApi.getStubs(stubDecorator, qs).asFuture.map {
case Left(err) =>
logger.error(s"Unable to get stubs $err")
InternalServerError
case Right(contentResponse) =>
Ok(contentResponse.asJson.noSpaces)
}
}
def content = APIHMACAuthAction.async(getContentBlock)
def getContentByComposerId(composerId: String) = {
APIHMACAuthAction.async { implicit request =>
ApiResponseFt[Option[Stub]](for {
item <- getResponse(stubsApi.getStubByComposerId(stubDecorator, composerId))
} yield item
)}
}
def getContentByEditorId(editorId: String) = {
APIAuthAction.async { implicit request =>
ApiResponseFt[Option[Stub]](for {
item <- getResponse(stubsApi.getStubByEditorId(editorId))
} yield item
)}
}
def sharedAuthGetContentById(composerId: String) =
SharedSecretAuthAction.async {
ApiResponseFt[Option[Stub]](for {
item <- getResponse(stubsApi.getStubByComposerId(stubDecorator, composerId))
} yield item
)}
def validateContentType(body: Json): ApiResponseFt[Json] = {
val atomType: String = body.hcursor.downField("contentType").as[String].getOrElse("")
val allTypes: List[String] = config.atomTypes ++ config.contentTypes
if (allTypes.contains(atomType)) {
ApiResponseFt.Right(body)
} else {
ApiResponseFt.Left(ApiError("InvalidAtomType", s"atoms with type $atomType not supported", 400, "badrequest"))
}
}
def createContent = {
APIAuthAction.async { request =>
ApiResponseFt[models.api.ContentUpdate](for {
json <- readJsonFromRequestResponse(request.body)
jsValueWithValidContentType <- validateContentType(json)
stubId <- stubsApi.createStub(jsValueWithValidContentType)
} yield stubId
)}
}
def putStub(stubId: Long) = {
APIAuthAction.async { request =>
ApiResponseFt[models.api.ContentUpdate](for {
json <- readJsonFromRequestResponse(request.body)
putRes <- stubsApi.putStub(stubId, json)
} yield putRes
)}
}
def putStubAssignee(stubId: Long) = APIAuthAction.async { request =>
ApiResponseFt[Long](for {
json <- readJsonFromRequestResponse(request.body)
assignee <- extractDataResponse[String](json)
assigneeData = Some(assignee).filter(_.nonEmpty)
id <- stubsApi.putStubAssignee(stubId, assigneeData)
} yield id
)}
def putStubAssigneeEmail(stubId: Long) = APIAuthAction.async { request =>
ApiResponseFt[Long](for {
json <- readJsonFromRequestResponse(request.body)
assignee <- extractDataResponse[String](json)
assigneeEmailData = Some(assignee).filter(_.nonEmpty)
id <- stubsApi.putStubAssigneeEmail(stubId, assigneeEmailData)
} yield id
)}
def putStubDueDate(stubId: Long) = APIAuthAction.async { request =>
ApiResponseFt[Long](for {
json <- readJsonFromRequestResponse(request.body)
dueDateOpt <- extractDataResponseOpt[String](json)
dueDateData = dueDateOpt.map(new DateTime(_))
id <- stubsApi.putStubDue(stubId, dueDateData)
} yield id
)}
def putStubNote(stubId: Long) = {
def getNoteOpt(input: String): Option[String] = if(input.length > 0) Some(input) else None
APIAuthAction.async { request =>
ApiResponseFt[Long](for {
json <- readJsonFromRequestResponse(request.body)
note <- extractDataResponse[String](json)
noteOpt = getNoteOpt(note)
id <- stubsApi.putStubNote(stubId, noteOpt)
} yield id
)}
}
def putStubProdOffice(stubId: Long) = {
APIAuthAction.async { request =>
ApiResponseFt[Long](for {
json <- readJsonFromRequestResponse(request.body)
prodOffice <- extractDataResponse[String](json)
id <- stubsApi.putStubProdOffice(stubId, prodOffice)
} yield id
)}
}
def putStubDisplayHint(stubId: Long) = {
def getDisplayHintOpt(input: String): Option[String] = if(input.length > 0) Some(input) else None
APIAuthAction.async { request =>
ApiResponseFt[Long](for {
json <- readJsonFromRequestResponse(request.body)
displayHint <- extractDataResponse[String](json)
displayHintOpt = getDisplayHintOpt(displayHint)
id <- stubsApi.putStubDisplayHint(stubId, displayHintOpt)
} yield id
)}
}
def putStubStatus(stubId: Long) = {
APIAuthAction.async { request =>
ApiResponseFt[Long](for {
json <- readJsonFromRequestResponse(request.body)
status <- extractDataResponse[String](json)
id <- stubsApi.updateContentStatus(stubId, status)
} yield id
)}
}
def putStubCommissionedLength(stubId: Long) = {
APIAuthAction.async { request =>
ApiResponseFt[Long](for {
json <- readJsonFromRequestResponse(request.body)
commissionedLength <- extractDataResponse[Option[Int]](json)
id <- stubsApi.updateContentCommissionedLength(stubId, commissionedLength)
} yield id
)}
}
def putStubMissingCommissionedLengthReason(stubId: Long) = {
APIAuthAction.async { request =>
ApiResponseFt[Long](for {
json <- readJsonFromRequestResponse(request.body)
missingCommissionedLengthReason <- extractDataResponse[Option[String]](json)
id <- stubsApi.updateContentMissingCommissionedLengthReason(stubId, missingCommissionedLengthReason)
} yield id
)
}
}
def putStubStatusByComposerId(composerId: String) = {
APIAuthAction.async { request =>
ApiResponseFt[String](for {
json <- readJsonFromRequestResponse(request.body)
status <- extractDataResponse[String](json)
id <- stubsApi.updateContentStatusByComposerId(composerId, status)
} yield id
)}
}
def putStubSection(stubId: Long) = {
APIAuthAction.async { request =>
ApiResponseFt[Long](for {
json <- readJsonFromRequestResponse(request.body)
section <- extractResponse[String](json.hcursor.downField("data").downField("name").focus.getOrElse(Json.Null))
id <- stubsApi.putStubSection(stubId, section)
} yield id
)}
}
def putStubWorkingTitle(stubId: Long) = {
APIAuthAction.async { request =>
ApiResponseFt[Long](for {
json <- readJsonFromRequestResponse(request.body)
wt <- extractDataResponse[String](json)
id <- stubsApi.putStubWorkingTitle(stubId, wt)
} yield id
)}
}
def putStubPriority(stubId: Long) = {
APIAuthAction.async { request =>
ApiResponseFt[Long](for {
json <- readJsonFromRequestResponse(request.body)
priority <- extractDataResponse[Int](json)
id <- stubsApi.putStubPriority(stubId, priority)
} yield id
)}
}
def putStubLegalStatus(stubId: Long) = {
APIAuthAction.async { request =>
ApiResponseFt[Long](for {
json <- readJsonFromRequestResponse(request.body)
status <- extractDataResponse[Flag](json)
id <- stubsApi.putStubLegalStatus(stubId, status)
} yield id
)}
}
def putStubPictureDesk(stubId: Long) = {
APIAuthAction.async { request =>
ApiResponseFt[Long](for {
json <- readJsonFromRequestResponse(request.body)
status <- extractDataResponse[Option[Flag]](json)
id <- stubsApi.putStubPictureDesk(stubId, status)
} yield id
)}
}
def putStubTrashed(stubId: Long) = {
APIAuthAction.async { request =>
ApiResponseFt[Long](for {
json <- readJsonFromRequestResponse(request.body)
trashed <- extractDataResponse[Boolean](json)
id <- stubsApi.putStubTrashed(stubId, trashed)
} yield id
)}
}
def putStubPlannedPublicationId(stubId: Long) = {
APIAuthAction.async { request =>
ApiResponseFt[Long](for {
json <- readJsonFromRequestResponse(request.body)
plannedPublicationId <- extractDataResponse[Long](json)
id <- stubsApi.putStubPlannedPublicationId(stubId, plannedPublicationId)
} yield id
)}
}
def putStubPlannedBookId(stubId: Long) = {
APIAuthAction.async { request =>
ApiResponseFt[Long](for {
json <- readJsonFromRequestResponse(request.body)
plannedBookId <- extractDataResponse[Long](json)
id <- stubsApi.putStubPlannedBookId(stubId, plannedBookId)
} yield id
)}
}
def putStubPlannedBookSectionId(stubId: Long) = {
APIAuthAction.async { request =>
ApiResponseFt[Long](for {
json <- readJsonFromRequestResponse(request.body)
plannedBookSectionId <- extractDataResponse[Long](json)
id <- stubsApi.putStubPlannedBookSectionId(stubId, plannedBookSectionId)
} yield id
)}
}
def putStubPlannedNewspaperPageNumber(stubId: Long) = {
APIAuthAction.async { request =>
ApiResponseFt[Long](for {
json <- readJsonFromRequestResponse(request.body)
plannedNewspaperPageNumber <- extractDataResponse[Int](json)
id <- stubsApi.putStubPlannedNewspaperPageNumber(stubId, plannedNewspaperPageNumber)
} yield id
)}
}
def putStubPlannedNewspaperPublicationDate(stubId: Long) =
{
APIAuthAction.async { request =>
ApiResponseFt[Long](for {
json <- readJsonFromRequestResponse(request.body)
dateString <- extractDataResponse[String](json)
date = LocalDate.parse(dateString)
id <- stubsApi.putStubPlannedNewspaperPublicationDate(stubId, date)
} yield id
)}
}
def putStubRightsReviewed(stubId: Long) = {
APIAuthAction.async { request =>
ApiResponseFt[Long](for {
json <- readJsonFromRequestResponse(request.body)
rightsReviewed <- extractDataResponse[Boolean](json)
id <- stubsApi.putStubRightsReviewed(stubId, rightsReviewed)
} yield id
)
}
}
def deleteContent(composerId: String) = {
APIAuthAction {
stubsApi.deleteStubs(Seq(composerId)).fold(err =>
logger.error(s"failed to delete content with composer id: $composerId"), identity)
NoContent
}
}
def deleteStub(stubId: Long) = APIAuthAction {
stubsApi.deleteContentByStubId(stubId).fold(err =>
logger.error(s"failed to delete content with stub id: $stubId"), identity)
NoContent
}
def statusus = {
APIAuthAction { implicit req =>
Ok(renderJsonResponse(StatusDatabase.statuses).asJson.noSpaces)
}
}
def sections = {
APIAuthAction.async { _ =>
ApiResponseFt[List[Section]](for {
sections <- sectionsApi.getSections
} yield sections
)}
}
def allowedAtomTypes = {
APIAuthAction {
Ok(config.atomTypes.asJson.noSpaces)
}
}
def priorities = {
APIAuthAction {
Ok(Priorities.all.asJson.noSpaces)
}
}
def editorialSupportTeams = {
APIAuthAction {
val staff = editorialSupportTeamsController.listStaff().filter(_.name.nonEmpty)
val teams = EditorialSupportStaff.groupByTeams(staff)
val fronts = EditorialSupportStaff.groupByPerson(EditorialSupportStaff.getTeam("Fronts", teams))
val other = teams.filterNot(_.name == "Fronts")
Ok((other :+ fronts).asJson.noSpaces)
}
}
def sharedAuthGetContent = SharedSecretAuthAction.async(getContentBlock)
object SharedSecretAuthAction extends ActionBuilder[Request, AnyContent] {
override def parser: BodyParser[AnyContent] = controllerComponents.parsers.default
override protected def executionContext: ExecutionContext = controllerComponents.executionContext
def invokeBlock[A](req: Request[A], block: (Request[A]) => Future[Result]) =
if(!isInOnTheSecret(req))
Future(Results.Forbidden)
else
block(req)
}
}
object Api {
def queryString[R <: Request[_]](req: R): Map[String, Seq[String]] = req match {
case r: UserRequest[_] => r.queryString + ("email" -> Seq(r.user.email))
case r: Request[_] => r.queryString
}
}