app/controllers/Application.scala (96 lines of code) (raw):
package controllers
import org.apache.pekko.stream.scaladsl.StreamConverters
import play.api.mvc._
import redact.PdfRedactor
import play.api.data._
import play.api.data.Forms._
import play.api.libs.Files
import java.nio.file.Paths
import java.util.zip.ZipOutputStream
import java.util.zip.ZipEntry
import org.apache.pdfbox.pdmodel.PDDocument
import play.api.Logger
import scala.concurrent.{ExecutionContext, Future}
class Application(cc: ControllerComponents) extends AbstractController(cc) {
val logger = Logger(this.getClass())
implicit val ec: scala.concurrent.ExecutionContext = cc.executionContext
def index() = Action { implicit request: Request[AnyContent] =>
Ok(views.html.index())
}
case class UserData(name: String)
object UserData {
def unapply(u: UserData): Option[(String)] = Some(u.name)
}
val userForm = Form(
mapping(
"name" -> text
)(UserData.apply)(UserData.unapply)
)
def upload: Action[MultipartFormData[Files.TemporaryFile]] = Action(parse.multipartFormData) { implicit request =>
request.body.file("pdf").map { pdf =>
userForm.bindFromRequest().fold(
{ formWithErrors => BadRequest },
{ userData =>
val stream = StreamConverters.asOutputStream().mapMaterializedValue { outputStream =>
Future {
PdfRedactor.redact(pdf.ref, outputStream, splitName(userData.name))
outputStream.close()
}
}
val filename = s"${userData.name.filter(_.isLetter)}.pdf"
Ok.chunked(stream).withHeaders("Content-Disposition" -> s"inline; filename=$filename")
}
)
}.getOrElse {
Redirect(routes.Application.index()).flashing(
"error" -> "Missing file")
}
}
private def splitName(name: String) = name.split(" ").toList.filter(_.nonEmpty)
private def quoteString(s: String) = "\"" + s + "\""
class ControlledCloseZipOutputStream(os: java.io.OutputStream) extends ZipOutputStream(os) {
override def close(): Unit = { }
def closeNow(): Unit = super.close()
}
def importFromTaleo: Action[MultipartFormData[Files.TemporaryFile]] = Action(parse.multipartFormData) { implicit request =>
request.body.file("pdf").map { pdf =>
val candidates = PdfRedactor.candidates(pdf.ref)
val doc = PDDocument.load(pdf.ref)
val docs = PdfRedactor.splitCandidates(doc, candidates)
val uploadedFilename = Paths.get(pdf.filename).getFileName.toString
val filename = uploadedFilename.replace(".pdf", "-anon.zip")
val stream = StreamConverters.asOutputStream().mapMaterializedValue { outputStream =>
Future {
val zos = new ControlledCloseZipOutputStream(outputStream)
docs.foreach { case (candidate, doc) =>
val entryName = s"anon-candidates/${candidate.id}-redact.pdf"
zos.putNextEntry(new ZipEntry(entryName))
try {
candidate.firstName.split(" ").toList
PdfRedactor.redact(doc, zos, splitName(candidate.firstName) ++ splitName(candidate.lastName))
doc.close()
} catch {
case e: Exception => logger.error("Oops", e)
}
zos.closeEntry()
}
doc.close()
zos.putNextEntry(new ZipEntry("anon-candidates/candidates.csv"))
candidates.foreach { c =>
zos.write(List(s"${c.lastName}, ${c.firstName}", c.id, c.jobText, c.jobId).map(quoteString).mkString("", ",", "\n").getBytes)
}
zos.closeEntry()
zos.closeNow()
}
}
Ok.chunked(stream).withHeaders("Content-Disposition" -> s"inline; filename=$filename")
}.getOrElse {
Redirect(routes.Application.index()).flashing(
"error" -> "Missing file")
}
}
def healthcheck = Action {
logger.info("Responding OK from healthcheck")
Ok("Ok")
}
}