MiniDigger/Hangar

View on GitHub
orePlayCommon/app/models/viewhelper/HeaderData.scala

Summary

Maintainability
A
50 mins
Test Coverage
package models.viewhelper

import scala.language.higherKinds

import play.api.mvc.Request

import ore.db.impl.OrePostgresDriver.api._
import ore.db.impl.schema._
import ore.models.project.{ReviewState, Visibility}
import ore.models.user.User
import ore.db.{DbRef, Model, ModelService}
import ore.permission._
import ore.permission.scope.GlobalScope

import cats.{Functor, Monad}
import cats.data.OptionT
import cats.syntax.all._
import slick.lifted.TableQuery

/**
  * Holds global user specific data - When a User is not authenticated a dummy is used
  */
case class HeaderData(
    currentUser: Option[Model[User]] = None,
    globalPermissions: Permission = Permission.None,
    hasNotice: Boolean = false,
    hasUnreadNotifications: Boolean = false,
    unresolvedFlags: Boolean = false,
    hasProjectApprovals: Boolean = false,
    hasReviewQueue: Boolean = false // queue.nonEmpty
) {

  // Just some helpers in templates:
  def isAuthenticated: Boolean = currentUser.isDefined

  def hasUser: Boolean = currentUser.isDefined

  def isCurrentUser(userId: DbRef[User]): Boolean = currentUser.map(_.id.value).contains(userId)

  def globalPerm(perm: Permission): Boolean = globalPermissions.has(perm)
}

object HeaderData {

  val unAuthenticated: HeaderData = HeaderData()

  def cacheKey(user: Model[User]) = s"""user${user.id}"""

  def of[F[_]](request: Request[_])(
      implicit service: ModelService[F],
      F: Monad[F]
  ): F[HeaderData] =
    OptionT
      .fromOption[F](request.cookies.get("_oretoken"))
      .flatMap(cookie => getSessionUser(cookie.value))
      .semiflatMap(getHeaderData[F])
      .getOrElse(unAuthenticated)

  private def getSessionUser[F[_]](token: String)(implicit service: ModelService[F], F: Functor[F]) = {
    val query = for {
      s <- TableQuery[SessionTable] if s.token === token
      u <- TableQuery[UserTable] if s.username === u.name
    } yield (s, u)

    OptionT(service.runDBIO(query.result.headOption)).collect {
      case (session, user) if !session.hasExpired => user
    }
  }

  private def projectApproval(user: Model[User]) =
    TableQuery[ProjectTableMain]
      .filter(p => p.userId === user.id.value && p.visibility === (Visibility.NeedsApproval: Visibility))
      .exists

  private def reviewQueue: Rep[Boolean] =
    TableQuery[VersionTable].filter(v => v.reviewStatus === (ReviewState.Unreviewed: ReviewState)).exists

  private val flagQueue: Rep[Boolean] = TableQuery[FlagTable].filter(_.isResolved === false).exists

  private def getHeaderData[F[_]](
      user: Model[User]
  )(implicit service: ModelService[F], F: Monad[F]) = {
    user.permissionsIn(GlobalScope).flatMap { perms =>
      val query = Query.apply(
        (
          TableQuery[NotificationTable].filter(n => n.userId === user.id.value && !n.read).exists,
          if (perms.has(Permission.ModNotesAndFlags)) flagQueue else false.bind,
          if (perms.has(Permission.ModNotesAndFlags ++ Permission.SeeHidden)) projectApproval(user)
          else false.bind,
          if (perms.has(Permission.Reviewer)) reviewQueue else false.bind
        )
      )

      service.runDBIO(query.result.head).map {
        case (unreadNotif, unresolvedFlags, hasProjectApprovals, hasReviewQueue) =>
          HeaderData(
            Some(user),
            perms,
            unreadNotif || unresolvedFlags || hasProjectApprovals || hasReviewQueue,
            unreadNotif,
            unresolvedFlags,
            hasProjectApprovals,
            hasReviewQueue
          )
      }
    }
  }
}