app/controllers/TagManagementApi.scala (350 lines of code) (raw):

package controllers import com.gu.pandomainauth.PanDomainAuthSettingsRefresher import model._ import model.command.CommandError._ import model.command._ import model.jobs.JobRunner import helpers.JodaDateTimeFormat._ import org.joda.time.{DateTime, DateTimeZone} import permissions._ import play.api.libs.json._ import play.api.mvc.{BaseController, ControllerComponents, Result} import repositories._ import services.Config import helpers.CORSable import model.forms.{FilterTypes, GetSpreadSheet} import play.api.Logging import play.api.libs.ws.WSClient import services.Config.conf import scala.concurrent.{ExecutionContext, Future} class TagManagementApi( val wsClient: WSClient, override val controllerComponents: ControllerComponents, val panDomainSettings: PanDomainAuthSettingsRefresher )( implicit ec: ExecutionContext ) extends BaseController with PanDomainAuthActions with Logging { def getTag(id: Long) = APIAuthAction { TagRepository.getTag(id).map{ tag => Ok(Json.toJson(DenormalisedTag(tag))) }.getOrElse(NotFound) } def updateTag(id: Long) = (APIAuthAction andThen UpdateTagPermissionsCheck() andThen UpdateTagSpecificPermissionsCheck()).async { req => implicit val username = Option(req.user.email) req.body.asJson.map { json => UpdateTagCommand(json.as[DenormalisedTag]).process().map { result => result.map{t => Ok(Json.toJson(DenormalisedTag(t))) } getOrElse NotFound } recover { commandErrorAsResult } }.getOrElse { Future.successful(BadRequest("Expecting Json data")) } } def createTag = CORSable(conf.corsablePostDomains: _*) { (APIAuthAction andThen CreateTagPermissionsCheck() andThen CreateTagSpecificPermissionsCheck()).async { req => implicit val username = Option(req.user.email) req.body.asJson.map { json => json.as[CreateTagCommand].process().map { result => result.map { t => Ok(Json.toJson(DenormalisedTag(t))) } getOrElse NotFound } recover { commandErrorAsResult } }.getOrElse { Future.successful(BadRequest("Expecting Json data")) } } } def searchTags = APIHMACAuthAction { req => val criteria = TagSearchCriteria( q = req.getQueryString("q"), searchField = req.getQueryString("searchField"), types = req.getQueryString("types").map(_.split(",").toList.map(_.trim())), referenceType = req.getQueryString("referenceType"), hasFields = req.getQueryString("hasFields").map(_.split(",").toList.map(_.trim())) ) val orderBy = req.getQueryString("orderBy").getOrElse("internalName") val tags = TagLookupCache.search(criteria) val orderedTags: List[Tag] = orderBy match { case ("internalName") => tags.sortBy(_.internalName) case ("externalName") => tags.sortBy(_.externalName) case ("path") => tags.sortBy(_.path) case ("id") => tags.sortBy(_.id) case ("type") => tags.sortBy(_.`type`) case (_) => tags.sortBy(_.comparableValue) } val page = req.getQueryString("page").getOrElse("1").toInt val pageSize = req.getQueryString("pageSize").map(_.toInt).getOrElse(Config().tagSearchPageSize) val startIndex = (page - 1) * pageSize val paginatedTagResults = orderedTags.drop(startIndex).take(pageSize) val tagCount = orderedTags.length Ok(Json.toJson(TagSearchResult(paginatedTagResults, tagCount))) } def spreadsheet = APIHMACAuthAction(parse.json[GetSpreadSheet]) { req => val tags = req.body.filters.map { f => f.`type` match { case FilterTypes.HasFields => TagSearchCriteria( hasFields = Some(f.value.split(',').toList) ) case _ => TagSearchCriteria( q = Some(f.value), searchField = Some(f.`type`.entryName) ) } }.foldLeft(TagLookupCache.allTags.get) { (acc, criteria) => criteria.execute(acc) } Ok(Json.toJson(TagSearchResult(tags, tags.length))) } def getSection(id: Long) = APIAuthAction { SectionRepository.getSection(id).map{ section => Ok(Json.toJson(section)) }.getOrElse(NotFound) } def createSection() = (APIAuthAction andThen CreateSectionPermissionsCheck()).async { req => implicit val username = Option(req.user.email) req.body.asJson.map { json => json.as[CreateSectionCommand].process().map { result => result.map{t => Ok(Json.toJson(t)) } getOrElse NotFound } recover { commandErrorAsResult } }.getOrElse { Future.successful(BadRequest("Expecting Json data")) } } def updateSection(id: Long) = (APIAuthAction andThen UpdateSectionPermissionsCheck()).async { req => implicit val username = Option(req.user.email) req.body.asJson.map { json => UpdateSectionCommand(json.as[Section]).process().map { result => result.map{ t => Ok(Json.toJson(t)) } getOrElse NotFound } recover { commandErrorAsResult } }.getOrElse { Future.successful(BadRequest("Expecting Json data")) } } def addEditionToSection(id: Long) = (APIAuthAction andThen AddEditionToSectionPermissionsCheck()).async { req => implicit val username = Option(req.user.email) req.body.asJson.map { json => val editionName = (json \ "editionName").as[String] AddEditionToSectionCommand(id, editionName.toUpperCase).process().map { result => result.map{ t => Ok(Json.toJson(t)) } getOrElse NotFound } recover { commandErrorAsResult } }.getOrElse { Future.successful(BadRequest("Expecting Json data")) } } def removeEditionFromSection(id: Long, editionName: String) = (APIAuthAction andThen RemoveEditionFromSectionPermissionsCheck()).async { req => implicit val username = Option(req.user.email) RemoveEditionFromSectionCommand(id, editionName.toUpperCase).process().map {result => result.map{ t => Ok(Json.toJson(t)) } getOrElse NotFound } recover { commandErrorAsResult } } def listSections() = APIAuthAction { Ok(Json.toJson(SectionRepository.loadAllSections)) } def listPillars() = APIAuthAction { Ok(Json.toJson(PillarRepository.loadAllPillars)) } def getPillar(id: Long) = APIAuthAction { PillarRepository.getPillar(id).map { pillar => Ok(Json.toJson(pillar)) }.getOrElse(NotFound) } def createPillar() = (APIAuthAction andThen PillarPermissionsCheck()).async { req => implicit val username = Option(req.user.email) req.body.asJson.map { json => json.as[CreatePillarCommand].process().map { result => result.map{t => Ok(Json.toJson(t)) } getOrElse NotFound } recover { commandErrorAsResult } }.getOrElse { Future.successful(BadRequest("Expecting Json data")) } } def updatePillar(id: Long) = (APIAuthAction andThen PillarPermissionsCheck()).async { req => implicit val username = Option(req.user.email) req.body.asJson.map { json => UpdatePillarCommand(json.as[Pillar]).process().map { result => result.map { t => Ok(Json.toJson(t)) } getOrElse NotFound } recover { commandErrorAsResult } }.getOrElse { Future.successful(BadRequest("Expecting Json data")) } } def deletePillar(id: Long) = (APIAuthAction andThen PillarPermissionsCheck()).async { req => implicit val username = Option(req.user.email) DeletePillarCommand(id).process().map { result => result.fold[Result](NotFound)(_ => NoContent) } recover { commandErrorAsResult } } def listReferenceTypes() = APIAuthAction { Ok(Json.toJson(ExternalReferencesTypeRepository.loadAllReferenceTypes)) } def checkPathInUse(tagType: String, slug: String, section: Option[Long], tagSubType: Option[String]) = APIAuthAction.async { req => implicit val username = Option(req.user.email) new PathUsageCheck(tagType, slug, section, tagSubType).process().map{ result => result.map{ t => Ok(Json.toJson(t)) } getOrElse BadRequest } recover { commandErrorAsResult } } def batchTag = APIAuthAction.async { req => implicit val username = Option(req.user.email) req.body.asJson.map { json => json.as[BatchTagCommand].process().map{ result => result.map{t => NoContent } getOrElse NotFound } recover { commandErrorAsResult } }.getOrElse { Future.successful(BadRequest("Expecting Json data")) } } def mergeTag = (APIAuthAction andThen MergeTagPermissionsCheck()).async { req => implicit val username = Option(req.user.email) req.body.asJson.map { json => json.as[MergeTagCommand].process().map { result => result.map{t => NoContent } getOrElse NotFound } recover { commandErrorAsResult } }.getOrElse { Future.successful(BadRequest("Expecting Json data")) } } def deleteTag(id: Long) = (APIHMACAuthAction andThen DeleteTagPermissionsCheck()).async { req => implicit val username = Option(req.user.email) (DeleteTagCommand(id)).process().map { result => result.map { t => NoContent } getOrElse NotFound } recover { commandErrorAsResult } } def searchSponsorships = APIAuthAction { req => val criteria = SponsorshipSearchCriteria( q = req.getQueryString("q"), status = req.getQueryString("status"), `type` = req.getQueryString("type") ) val orderBy = req.getQueryString("sortBy").getOrElse("internalName") val sponsorships = SponsorshipRepository.searchSponsorships(criteria) val orderedSponsorships: List[Sponsorship] = orderBy match { case("sponsor") => sponsorships.sortBy(_.sponsorName) case("from") => sponsorships.sortBy(_.validFrom.map(_.getMillis).getOrElse(0L)) case("to") => sponsorships.sortBy(_.validTo.map(_.getMillis).getOrElse(Long.MaxValue)) case("status") => sponsorships.sortBy(_.status) case(_) => sponsorships.sortBy(_.sponsorName) } val resultsCount = req.getQueryString("pageSize").getOrElse("25").toInt Ok(Json.toJson((orderedSponsorships take resultsCount).map(DenormalisedSponsorship(_)))) } def getSponsorship(id: Long) = APIAuthAction { req => Ok(Json.toJson(SponsorshipRepository.getSponsorship(id).map(DenormalisedSponsorship(_)))) } def createSponsorship = (APIAuthAction andThen ManageSponsorshipsPermissionsCheck()).async { req => implicit val username = Option(req.user.email) req.body.asJson.map { json => json.as[CreateSponsorshipCommand].process().map { result => result.map{t => Ok(Json.toJson(t)) } getOrElse NotFound } recover { commandErrorAsResult } }.getOrElse { Future.successful(BadRequest("Expecting Json data")) } } def updateSponsorship(id: Long) = (APIAuthAction andThen ManageSponsorshipsPermissionsCheck()).async { req => implicit val username = Option(req.user.email) req.body.asJson.map { json => json.as[UpdateSponsorshipCommand].process().map { result => result.map{s => Ok(Json.toJson(DenormalisedSponsorship(s))) } getOrElse NotFound } recover { commandErrorAsResult } }.getOrElse { Future.successful(BadRequest("Expecting Json data")) } } def clashingSponsorships(id: Option[Long], tagIds: Option[String], sectionIds: Option[String], validFrom: Option[Long], validTo: Option[Long], editions: Option[String]) = APIAuthAction.async { req => implicit val username = Option(req.user.email) val editionSearch = editions.map(_.split(",").toList) val tagSearch: Option[List[Long]] = tagIds.map(_.split(",").toList.filter(_.length > 0).map(_.toLong)) val sectionSearch: Option[List[Long]] = sectionIds.map(_.split(",").toList.filter(_.length > 0).map(_.toLong)) new ClashingSponsorshipsFetch(id, tagSearch, sectionSearch, validFrom.map(new DateTime(_)), validTo.map(new DateTime(_)), editionSearch) .process().map { result => result.map{ ss => Ok(Json.toJson(ss.map(DenormalisedSponsorship(_)))) } getOrElse BadRequest } recover { commandErrorAsResult } } def activeSponsorshipsForTag(id: Long) = APIAuthAction { req => Ok(Json.toJson(SponsorshipRepository.searchSponsorships(SponsorshipSearchCriteria(tagId = Some(id), status = Some("active"))))) } def activeSponsorshipsForSection(id: Long) = APIAuthAction { req => Ok(Json.toJson(SponsorshipRepository.searchSponsorships(SponsorshipSearchCriteria(sectionId = Some(id), status = Some("active"))))) } def getAuditForTag(tagId: Long) = APIAuthAction { req => Ok(Json.toJson(TagAuditRepository.getAuditTrailForTag(tagId))) } def getAuditForTagOperation(op: String) = APIAuthAction { req => Ok(Json.toJson(TagAuditRepository.getRecentAuditOfTagOperation(op))) } def getAuditForSection(sectionId: Long) = APIAuthAction { req => Ok(Json.toJson(SectionAuditRepository.getAuditTrailForSection(sectionId))) } def getAuditForSectionOperation(op: String) = APIAuthAction { req => Ok(Json.toJson(SectionAuditRepository.getRecentAuditOfSectionOperation(op))) } def getJobs(tagIdParam: Option[Long]) = APIAuthAction { val jobs = tagIdParam match { case Some(tagId) => JobRepository.findJobsForTag(tagId) case None => JobRepository.loadAllJobs } Ok(Json.toJson(jobs)) } def deleteJob(jobId: Long) = (APIAuthAction andThen JobDeletePermissionsCheck()) { req => try { JobRepository.deleteIfTerminal(jobId) Ok } catch { case e: Exception => BadRequest("Job was not in a completed or failed state") } } def rollbackJob(id: Long) = (APIAuthAction andThen JobRollbackPermissionsCheck()) { req => val currentTime = new DateTime(DateTimeZone.UTC).getMillis val lockBreakTime = currentTime - JobRunner.lockTimeOutMillis val nodeId = JobRunner.nodeId JobRepository.getJob(id) .flatMap(JobRepository.lock(_, nodeId, currentTime, lockBreakTime)) .map(job => { try { job.rollback Ok } catch { case e: Exception => InternalServerError("Failed to rollback") } finally { JobRepository.upsertJobIfOwned(job, nodeId) JobRepository.unlock(job, nodeId) } }).getOrElse(NotFound) } }