Commit a82ebf55 authored by Leonardo Menezes's avatar Leonardo Menezes
Browse files

specify user max request history

parent c50d9581
Loading
Loading
Loading
Loading
+5 −2
Original line number Diff line number Diff line
@@ -5,12 +5,13 @@ import java.text.SimpleDateFormat
import javax.inject.Inject

import controllers.auth.AuthenticationModule
import dao.{RestHistoryDAO, RestRequest}
import dao.{DAOException, RestHistoryDAO, RestRequest}
import elastic.{ElasticClient, Error, Success}
import models.{CerebroResponse, ClusterMapping, Hosts}
import play.api.libs.json.{JsArray, JsString, Json}

import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.Try
import scala.util.control.NonFatal

class RestController @Inject()(val authentication: AuthenticationModule,
@@ -26,7 +27,9 @@ class RestController @Inject()(val authentication: AuthenticationModule,
      case s: Success =>
        val bodyAsString = body.map(_.toString).getOrElse("{}")
        val username = request.user.map(_.name).getOrElse("")
        restHistoryDAO.save(RestRequest(path, method, bodyAsString, username, new Date(System.currentTimeMillis)))
        Try(restHistoryDAO.save(RestRequest(path, method, bodyAsString, username, new Date(System.currentTimeMillis)))).recover {
          case DAOException(msg, e) => logger.error(msg, e)
        }
        CerebroResponse(s.status, s.body)

      case e: Error => CerebroResponse(e.status, e.body)
+3 −0
Original line number Diff line number Diff line
package dao

case class DAOException(message: String, exception: Throwable) extends RuntimeException(message, exception)
+50 −19
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@ package dao
import java.util.Date

import com.google.inject.{ImplementedBy, Inject}
import play.api.Configuration
import play.api.db.slick.DatabaseConfigProvider
import slick.driver.JdbcProfile
import slick.driver.SQLiteDriver.api._
@@ -24,35 +25,65 @@ trait RestHistoryDAO {

}

class RestHistoryDAOImpl @Inject()(dbConfigProvider: DatabaseConfigProvider) extends RestHistoryDAO {
class RestHistoryDAOImpl @Inject()(dbConfigProvider: DatabaseConfigProvider,
                                   config: Configuration) extends RestHistoryDAO {

  val dbConfig = dbConfigProvider.get[JdbcProfile]
  private val max = config.getInt("rest.history.size").getOrElse(50)

  val requests = TableQuery[RestRequests]
  private val dbConfig = dbConfigProvider.get[JdbcProfile]

  def all(username: String): Future[Seq[RestRequest]] = {
    dbConfig.db.run(requests.filter(_.username === username).sortBy(_.createdAt.desc).take(50).result).map { reqs =>
  private val requests = TableQuery[RestRequests]

  def all(username: String): Future[Seq[RestRequest]] =
    dbConfig.db.run(requests.filter(_.username === username).sortBy(_.createdAt.desc).result).map { reqs =>
      reqs.map { r => RestRequest(r.path, r.method, r.body, r.username, new Date(r.createdAt)) }
    }
    }.recover {
      case NonFatal(e) => throw DAOException(s"Error loading requests for [$username]", e)
    }

  def save(req: RestRequest): Future[Option[String]] = {
    val previous = dbConfig.db.run(requests.filter(_.md5 === req.md5).result.headOption)
    previous.flatMap {
  def save(req: RestRequest): Future[Option[String]] =
    findByMd5(req.md5).flatMap {
      case Some(p) =>
        val q = for { r <- requests if r.md5 === p.md5 } yield r.createdAt
        val action = q.update(req.createdAt.getTime)
        dbConfig.db.run(action).map { _ => Some(p.md5) }.recover { case NonFatal(e) => e.printStackTrace(); None }

        update(p, req.createdAt.getTime)
      case None    =>
        create(req).map { md5 => trim(req.username); md5 }
    }

  def clear(username: String): Future[Int] =
    dbConfig.db.run(requests.filter(_.username === username).delete).recover {
      case NonFatal(e) => throw DAOException(s"Error clearing all requests for [$username]", e)
    }

  private def findByMd5(md5: String): Future[Option[RestRequests#TableElementType]] =
    dbConfig.db.run(requests.filter(_.md5 === md5).result.headOption).recover {
      case NonFatal(e) => throw DAOException(s"Error finding request with MD5 [$md5]", e)
    }

  private def update(req: RestRequests#TableElementType, createdAt: Long): Future[Option[String]] = {
    val q = for { r <- requests if r.md5 === req.md5 } yield r.createdAt
    val action = q.update(createdAt)
    dbConfig.db.run(action).map { _ => Some(req.md5) }.recover {
      case NonFatal(e) => throw DAOException(s"Error while updating request [$req]", e)
    }
  }

  private def create(req: RestRequest): Future[Option[String]] = {
    val newReq = HashedRestRequest(req.path, req.method, req.body, req.username, req.createdAt.getTime, req.md5)
    val action = requests returning requests.map(_.id) += newReq
        dbConfig.db.run(action).map { _ => Some(newReq.md5) }.recover { case NonFatal(e) => e.printStackTrace; None }
    dbConfig.db.run(action).map { _ => Some(newReq.md5) }.recover {
      case NonFatal(e) => throw DAOException(s"Error while storing request [$req]", e)
    }
  }

  def clear(username: String): Future[Int] = {
    dbConfig.db.run(requests.filter(_.username === username).delete)
  private def trim(username: String): Unit = {
    val action = sqlu"""
          DELETE FROM rest_requests WHERE id IN (
            SELECT id FROM rest_requests WHERE username=$username ORDER BY created_at DESC LIMIT -1 OFFSET $max
          )
        """
    dbConfig.db.run(action).recover {
      case NonFatal(e) => throw DAOException(s"Error while triming history for [$username]", e)
    }
  }

}
+3 −0
Original line number Diff line number Diff line
@@ -44,7 +44,10 @@ auth {
//  }
}

rest.history.size: 50 // defaults to 50 if not specified

slick.dbs.default.driver="slick.driver.SQLiteDriver$"
slick.dbs.default.db.driver=org.sqlite.JDBC
slick.dbs.default.db.url="jdbc:sqlite:./cerebro.db"
play.evolutions.db.default.autoApply = true
+23 −13
Original line number Diff line number Diff line
@@ -12,20 +12,20 @@ import scala.concurrent.Future

class RestRequestDAOSpec(implicit ee: ExecutionEnv) extends Specification {

  val app = new GuiceApplicationBuilder().build()
  val app = new GuiceApplicationBuilder().configure(
    Map("rest.history.size" -> 3)
  ).build()

  override def is =
    s2"""
    sequential ^ s2"""
    RestRequestDAO should                   ${step(start(app))}
        create a new entry                  $save
        update an existing entry            $update
        return entries                      $all
        ensures history has max size        $maxSize
        clear all entries for given user    $clear
                                            ${step(stop(app))}
      """

  val currentTime = System.currentTimeMillis()

  def save = {
    val dao: RestHistoryDAO = app.injector.instanceOf(classOf[RestHistoryDAO])
    val entry = RestRequest("somePath", "someMethod", "theBody", "admin", new Date(123))
@@ -34,26 +34,36 @@ class RestRequestDAOSpec(implicit ee: ExecutionEnv) extends Specification {

  def update = {
    val dao: RestHistoryDAO = app.injector.instanceOf(classOf[RestHistoryDAO])
    val currentTime = System.currentTimeMillis
    val entry = RestRequest("otherPath", "otherMethod", "otherBody", "admin", new Date(currentTime))
    val existing = dao.save(entry)
    val updated = dao.save(entry.copy(createdAt = new Date(currentTime + 100)))
    val updated = dao.save(entry.copy(createdAt = new Date(currentTime + 1)))
    (existing must beEqualTo(Some("fad24b7447043f5412c89b12e2b7697c")).await and
      (updated must beEqualTo(Some("fad24b7447043f5412c89b12e2b7697c")).await))
  }

  def all = {
  def maxSize = {
    val time = System.currentTimeMillis
    val dao: RestHistoryDAO = app.injector.instanceOf(classOf[RestHistoryDAO])
    val entries: Future[Seq[RestRequest]] = dao.all("admin")
    val expected: Seq[RestRequest] = Seq(
      RestRequest("otherPath", "otherMethod", "otherBody", "admin", new Date(currentTime + 100)),
      RestRequest("somePath", "someMethod", "theBody", "admin", new Date(123))
    val all = Future.sequence(
      Seq(
        dao.save(RestRequest("request1", "GET", "{}", "admin", new Date(time))),
        dao.save(RestRequest("request2", "GET", "{}", "admin", new Date(time + 1))),
        dao.save(RestRequest("request3", "GET", "{}", "admin", new Date(time + 2)))
      )
    ).flatMap { _ => dao.all("admin") }
    val expected = Seq(
      RestRequest("request3", "GET", "{}", "admin", new Date(time + 2)),
      RestRequest("request2", "GET", "{}", "admin", new Date(time + 1)),
      RestRequest("request1", "GET", "{}", "admin", new Date(time))

    )
    entries must beEqualTo(expected).await
    all must beEqualTo(expected).await
  }

  def clear = {
    val dao: RestHistoryDAO = app.injector.instanceOf(classOf[RestHistoryDAO])
    dao.clear("admin") must beEqualTo(2).await
    dao.clear("admin") must beEqualTo(3).await
  }

}