MiniDigger/Hangar

View on GitHub
orePlayCommon/app/util/syntax/ModelSyntax.scala

Summary

Maintainability
A
0 mins
Test Coverage
package util.syntax

import scala.language.{higherKinds, implicitConversions}

import java.nio.file.Path

import play.api.i18n.Lang
import play.api.mvc.RequestHeader
import play.twirl.api.Html

import db.impl.access.UserBase
import ore.OreConfig
import ore.auth.AuthUser
import ore.db.access.ModelView
import ore.db.{DbRef, Model, ModelService, ObjId}
import ore.db.impl.OrePostgresDriver.api._
import ore.markdown.MarkdownRenderer
import ore.models.organization.Organization
import ore.models.project.io.ProjectFiles
import ore.models.project.{Page, Project}
import ore.models.user.User
import ore.permission.role.Role
import ore.util.OreMDC

import cats.Monad
import cats.syntax.all._
import cats.data.OptionT

trait ModelSyntax {

  implicit def userSyntax(u: User): ModelSyntax.UserSyntax                = new ModelSyntax.UserSyntax(u)
  implicit def userModelRawSyntax(u: Model[User]): ModelSyntax.UserSyntax = new ModelSyntax.UserSyntax(u)
  implicit def userObjSyntax(u: User.type): ModelSyntax.UserObjSyntax     = new ModelSyntax.UserObjSyntax(u)
  implicit def pageSyntax(p: Page): ModelSyntax.PageSyntax                = new ModelSyntax.PageSyntax(p)
  implicit def pageModelRawSyntax(p: Model[Page]): ModelSyntax.PageSyntax = new ModelSyntax.PageSyntax(p)
  implicit def pageObjSyntax(p: Page.type): ModelSyntax.PageObjSyntax     = new ModelSyntax.PageObjSyntax(p)
  implicit def projectSyntax(p: Project): ModelSyntax.ProjectSyntax                = new ModelSyntax.ProjectSyntax(p)
  implicit def projectModelSyntax(p: Model[Project]): ModelSyntax.ProjectModelSyntax =
    new ModelSyntax.ProjectModelSyntax(p)
  implicit def orgSyntax(o: Organization): ModelSyntax.OrganizationSyntax = new ModelSyntax.OrganizationSyntax(o)
  implicit def orgModelRawSyntax(o: Model[Organization]): ModelSyntax.OrganizationSyntax =
    new ModelSyntax.OrganizationSyntax(o)
  implicit def authUserSyntax(u: AuthUser): ModelSyntax.AuthUserSyntax = new ModelSyntax.AuthUserSyntax(u)
}
object ModelSyntax extends ModelSyntax {

  class UserSyntax(private val u: User) extends AnyVal {

    def avatarUrl(implicit config: OreConfig): String = User.avatarUrl(u.name)

    /**
      * Returns this user's current language, or the default language if none
      * was configured.
      */
    implicit def langOrDefault: Lang = u.lang.fold(Lang.defaultLang)(Lang.apply)
  }

  class UserObjSyntax(private val u: User.type) extends AnyVal {

    def avatarUrl(name: String)(implicit config: OreConfig): String =
      if (name == "Spongie") config.sponge.logo
      else config.security.api.avatarUrl.format(name)
  }

  class PageSyntax(private val p: Page) extends AnyVal {

    /**
      * Returns the HTML representation of this Page.
      *
      * @return HTML representation
      */
    def html(project: Option[Project])(implicit renderer: MarkdownRenderer): Html = {
      val settings = MarkdownRenderer.RenderSettings(
        linkEscapeChars = Some(" +<>"),
        linkPrefix = project.map(p => s"/${p.ownerName}/${p.slug}/pages/")
      )
      renderer.render(p.contents, settings)
    }

    /**
      * Returns true if this is the home page.
      *
      * @return True if home page
      */
    def isHome(implicit config: OreConfig): Boolean = p.name.equals(Page.homeName) && p.parentId.isEmpty
  }

  class PageObjSyntax(private val p: Page.type) extends AnyVal {

    /**
      * The name of each Project's homepage.
      */
    def homeName(implicit config: OreConfig): String = config.ore.pages.homeName

    /**
      * The template body for the Home page.
      */
    def homeMessage(implicit config: OreConfig): String = config.ore.pages.homeMessage

    /**
      * The minimum amount of characters a page may have.
      */
    def minLength(implicit config: OreConfig): Int = config.ore.pages.minLen

    /**
      * The maximum amount of characters the home page may have.
      */
    def maxLength(implicit config: OreConfig): Int = config.ore.pages.maxLen

    /**
      * The maximum amount of characters a page may have.
      */
    def maxLengthPage(implicit config: OreConfig): Int = config.ore.pages.pageMaxLen
  }

  class ProjectSyntax(private val p: Project) extends AnyVal {

    def iconUrlOrPath(implicit projectFiles: ProjectFiles, mdc: OreMDC, config: OreConfig): Either[String, Path] =
      projectFiles.getIconPath(p).toRight(User.avatarUrl(p.ownerName))

    def hasIcon(implicit projectFiles: ProjectFiles, mdc: OreMDC): Boolean = projectFiles.getIconPath(p).isDefined

    def iconUrl(implicit projectFiles: ProjectFiles, mdc: OreMDC, header: RequestHeader, config: OreConfig): String =
      iconUrlOrPath.swap.getOrElse(controllers.project.routes.Projects.showIcon(p.ownerName, p.slug).absoluteURL())
  }

  class ProjectModelSyntax(private val p: Model[Project]) extends AnyVal {

    private def getOrInsert[F[_]](name: String, parentId: Option[DbRef[Page]])(
        page: Page
    )(implicit service: ModelService[F], F: Monad[F]): F[Model[Page]] = {
      def like =
        ModelView.now(Page).find { page =>
          page.projectId === p.id.value && page.name.toLowerCase === name.toLowerCase && parentId.fold(
            page.parentId.isEmpty
          )(parentId => (page.parentId === parentId).getOrElse(false: Rep[Boolean]))
        }

      like.value.flatMap {
        case Some(u) => F.pure(u)
        case None    => service.insert(page)
      }
    }

    /**
      * Returns this Project's home page.
      *
      * @return Project home page
      */
    def homePage[F[_]](implicit service: ModelService[F], F: Monad[F], config: OreConfig): F[Model[Page]] = {
      val page = Page(p.id, Page.homeName, Page.template(p.name, Page.homeMessage), isDeletable = false, None)
      getOrInsert(Page.homeName, None)(page)
    }

    /**
      * Returns the specified Page or creates it if it doesn't exist.
      *
      * @param name   Page name
      * @return       Page with name or new name if it doesn't exist
      */
    def getOrCreatePage[F[_]](
        name: String,
        parentId: Option[DbRef[Page]],
        content: Option[String] = None
    )(implicit config: OreConfig, service: ModelService[F], F: Monad[F]): F[Model[Page]] = {
      val c = content match {
        case None => Page.template(name, Page.homeMessage)
        case Some(text) =>
          require(text.length <= Page.maxLengthPage, "contents too long")
          text
      }
      val page = Page(p.id, name, c, isDeletable = true, parentId)
      getOrInsert(name, parentId)(page)
    }
  }

  class OrganizationSyntax(private val o: Organization) extends AnyVal {

    /**
      * Returns this Organization as a [[User]].
      *
      * @return This Organization as a User
      */
    def toUser[F[_]](implicit users: UserBase[F], mdc: OreMDC): OptionT[F, Model[User]] =
      users.withName(o.username)
  }

  class AuthUserSyntax(private val u: AuthUser) extends AnyVal {
    def newGlobalRoles: Option[List[Role]] = u.addGroups.map { groups =>
      if (groups.trim.isEmpty) Nil
      else groups.split(",").flatMap(Role.withValueOpt).toList
    }

    def toUser: User = User(
      id = ObjId(u.id),
      fullName = None,
      name = u.username,
      email = Some(u.email),
      lang = u.lang,
      tagline = None,
      joinDate = None,
      readPrompts = Nil
    )
  }
}