Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ lazy val playCommonSettings = Seq(
"ore.db.DbRef",
"ore.models.admin._",
"ore.models.project._",
"ore.models.competition._",
"ore.models.user._",
"ore.models.user.role._",
"ore.permission.NamedPermission",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package ore.db.impl.schema

import ore.db.DbRef
import ore.db.impl.OrePostgresDriver.api._
import ore.models.competition.{Competition, CompetitionEntry}
import ore.models.project.Project
import ore.models.user.User

class CompetitionEntryTable(tag: Tag) extends ModelTable[CompetitionEntry](tag, "project_competition_entries") {

def projectId = column[DbRef[Project]]("project_id")
def userId = column[DbRef[User]]("user_id")
def competitionId = column[DbRef[Competition]]("competition_id")

override def * =
(id.?, createdAt.?, (projectId, userId, competitionId)) <> (mkApply((CompetitionEntry.apply _).tupled), mkUnapply(
CompetitionEntry.unapply
))

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package ore.db.impl.schema

import ore.db.DbRef
import ore.db.impl.OrePostgresDriver.api._
import ore.models.competition.CompetitionEntry
import ore.models.user.User

class CompetitionEntryUserVotesTable(tag: Tag)
extends AssociativeTable[CompetitionEntry, User](tag, "project_competition_entry_votes") {

def userId = column[DbRef[User]]("user_id")
def entryId = column[DbRef[CompetitionEntry]]("entry_id")

override def * = (entryId, userId)

}
52 changes: 52 additions & 0 deletions models/src/main/scala/ore/db/impl/schema/CompetitionTable.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package ore.db.impl.schema

import java.time.Instant

import ore.db.DbRef
import ore.db.impl.OrePostgresDriver.api._
import ore.db.impl.table.common.{DescriptionColumn, NameColumn}
import ore.models.competition.Competition
import ore.models.user.User

class CompetitionTable(tag: Tag)
extends ModelTable[Competition](tag, "project_competitions")
with NameColumn[Competition]
with DescriptionColumn[Competition] {

def userId = column[DbRef[User]]("user_id")
def startDate = column[Instant]("start_date")
def endDate = column[Instant]("end_date")
def timeZone = column[String]("time_zone")
def isVotingEnabled = column[Boolean]("is_voting_enabled")
def isStaffVotingOnly = column[Boolean]("is_staff_voting_only")
def shouldShowVoteCount = column[Boolean]("should_show_vote_count")
def isSpongeOnly = column[Boolean]("is_sponge_only")
def isSourceRequired = column[Boolean]("is_source_required")
def defaultVotes = column[Int]("default_votes")
def staffVotes = column[Int]("staff_votes")
def allowedEntries = column[Int]("allowed_entries")
def maxEntryTotal = column[Int]("max_entry_total")

override def * =
(
id.?,
createdAt.?,
(
userId,
name,
description.?,
startDate,
endDate,
timeZone,
isVotingEnabled,
isStaffVotingOnly,
shouldShowVoteCount,
isSpongeOnly,
isSourceRequired,
defaultVotes,
staffVotes,
allowedEntries,
maxEntryTotal.?
)
) <> (mkApply((Competition.apply _).tupled), mkUnapply(Competition.unapply))
}
84 changes: 84 additions & 0 deletions models/src/main/scala/ore/models/competition/Competition.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package ore.models.competition

import scala.language.higherKinds

import java.time.Instant

import scala.concurrent.duration._

import ore.db.access.QueryView
import ore.db.impl.DefaultModelCompanion
import ore.db.impl.OrePostgresDriver.api._
import ore.db.impl.common.{Describable, Named}
import ore.db.impl.schema.{CompetitionEntryTable, CompetitionTable}
import ore.db.{DbRef, Model, ModelQuery}
import ore.models.user.{User, UserOwned}
import ore.syntax._

import slick.lifted.TableQuery

/**
* Represents a [[ore.models.project.Project]] competition.
*
* @param userId Owner ID
* @param name Competition name
* @param description Competition description
* @param startDate Date when the competition begins
* @param endDate Date when the competition ends
* @param timeZone Time zone of competition
* @param isVotingEnabled True if project voting is enabled
* @param isStaffVotingOnly True if only staff members can vote
* @param shouldShowVoteCount True if the vote count should be displayed
* @param isSpongeOnly True if only Sponge plugins are permitted in the competition
* @param isSourceRequired True if source-code is required for entry to the competition
* @param defaultVotes Default amount of votes a user has
* @param staffVotes The amount of votes staff-members have
* @param allowedEntries The amount of entries a user may submit
* @param maxEntryTotal The total amount of projects allowed in the competition
*/
case class Competition(
userId: DbRef[User],
name: String,
description: Option[String],
startDate: Instant,
endDate: Instant,
timeZone: String,
isVotingEnabled: Boolean = true,
isStaffVotingOnly: Boolean = false,
shouldShowVoteCount: Boolean = true,
isSpongeOnly: Boolean = false,
isSourceRequired: Boolean = false,
defaultVotes: Int = 1,
staffVotes: Int = 1,
allowedEntries: Int = 1,
maxEntryTotal: Option[Int] = None
) extends Named
with Describable {

/**
* Returns the amount of time remaining in the competition.
*
* @return Time remaining in competition
*/
def timeRemaining: FiniteDuration = (this.endDate.toEpochMilli - Instant.now().toEpochMilli).millis
}
object Competition extends DefaultModelCompanion[Competition, CompetitionTable](TableQuery[CompetitionTable]) {

implicit val query: ModelQuery[Competition] =
ModelQuery.from(this)

implicit val competitionIsUserOwned: UserOwned[Competition] = _.userId

implicit class CompetitionModelOps(private val self: Model[Competition]) extends AnyVal {

/**
* Returns this competition's [[CompetitionEntry]]s.
*
* @return Competition entries
*/
def entries[V[_, _]: QueryView](
view: V[CompetitionEntryTable, Model[CompetitionEntry]]
): V[CompetitionEntryTable, Model[CompetitionEntry]] =
view.filterView(_.competitionId === self.id.value)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package ore.models.competition

import scala.language.higherKinds

import ore.db.access.ModelView
import ore.db.impl.DefaultModelCompanion
import ore.db.impl.schema.{CompetitionEntryTable, CompetitionEntryUserVotesTable}
import ore.db.{AssociationQuery, DbRef, Model, ModelQuery, ModelService}
import ore.models.project.{Project, ProjectOwned}
import ore.models.user.{User, UserOwned}

import cats.MonadError
import slick.lifted.TableQuery

/**
* Represents a single entry in a [[Competition]].
*
* @param projectId Project ID
* @param userId User owner ID
* @param competitionId Competition ID
*/
case class CompetitionEntry(
projectId: DbRef[Project],
userId: DbRef[User],
competitionId: DbRef[Competition]
) {

/**
* Returns the [[Competition]] this entry belongs to.
*
* @return Competition entry belongs to
*/
def competition[F[_]](implicit service: ModelService[F], F: MonadError[F, Throwable]): F[Model[Competition]] =
ModelView
.now(Competition)
.get(competitionId)
.getOrElseF(
F.raiseError[Model[Competition]](new IllegalStateException("Found competition entry without competition"))
)
}
object CompetitionEntry
extends DefaultModelCompanion[CompetitionEntry, CompetitionEntryTable](TableQuery[CompetitionEntryTable]) {

implicit val query: ModelQuery[CompetitionEntry] =
ModelQuery.from(this)

implicit val assocEntryVotesQuery: AssociationQuery[CompetitionEntryUserVotesTable, CompetitionEntry, User] =
AssociationQuery.from[CompetitionEntryUserVotesTable, CompetitionEntry, User](
TableQuery[CompetitionEntryUserVotesTable]
)(_.entryId, _.userId)

implicit val competitionEntryIsUserOwned: UserOwned[CompetitionEntry] = _.userId
implicit val competitionEntryIsProjectOwned: ProjectOwned[CompetitionEntry] = _.projectId
}
2 changes: 2 additions & 0 deletions models/src/main/scala/ore/permission/NamedPermission.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ object NamedPermission extends Enum[NamedPermission] {
case object IsStaff extends NamedPermission(Permission.IsStaff)
case object Reviewer extends NamedPermission(Permission.Reviewer)

case object EditCompetition extends NamedPermission(Permission.EditCompetition)

case object ViewHealth extends NamedPermission(Permission.ViewHealth)
case object ViewIp extends NamedPermission(Permission.ViewIp)
case object ViewStats extends NamedPermission(Permission.ViewStats)
Expand Down
2 changes: 2 additions & 0 deletions models/src/main/scala/ore/permission/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ package object permission {
val IsStaff = Permission(1L << 26)
val Reviewer = Permission(1L << 27)

val EditCompetition = Permission(1L << 28)

val ViewHealth = Permission(1L << 32)
val ViewIp = Permission(1L << 33)
val ViewStats = Permission(1L << 34)
Expand Down
10 changes: 7 additions & 3 deletions models/src/main/scala/ore/util/StringLocaleFormatterUtils.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package ore.util

import java.time.format.{DateTimeFormatter, FormatStyle}
import java.time.{Instant, LocalDateTime, ZoneOffset}
import java.time.{Instant, LocalDateTime, ZoneId, ZoneOffset}
import java.util.Locale

object StringLocaleFormatterUtils {

private val dateFormat = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
private val dateFormat = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
private val dateTimeFormat = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)

/**
Expand All @@ -15,7 +15,8 @@ object StringLocaleFormatterUtils {
* @param instant Date to format
* @return Standard formatted date
*/
def prettifyDate(instant: Instant)(implicit locale: Locale): String = dateFormat.withLocale(locale).format(LocalDateTime.ofInstant(instant, ZoneOffset.UTC))
def prettifyDate(instant: Instant)(implicit locale: Locale): String =
dateFormat.withLocale(locale).format(LocalDateTime.ofInstant(instant, ZoneOffset.UTC))

/**
* Formats the specified date into the standard application form time.
Expand All @@ -25,4 +26,7 @@ object StringLocaleFormatterUtils {
*/
def prettifyDateAndTime(instant: Instant)(implicit locale: Locale): String =
dateTimeFormat.withLocale(locale).format(LocalDateTime.ofInstant(instant, ZoneOffset.UTC))

def localDateTime2Instant(date: LocalDateTime, timeZone: String): Instant =
date.atZone(ZoneId.of(timeZone)).toInstant
}
Loading