highlighted events WIP (backend)

event
Thibault Duplessis 2016-08-23 00:05:10 +02:00
parent 69bf99ce52
commit 405e90eab5
25 changed files with 426 additions and 33 deletions

3
.gitmodules vendored
View File

@ -34,3 +34,6 @@
[submodule "public/vendor/Sunsetter"]
path = public/vendor/Sunsetter
url = https://github.com/niklasf/Sunsetter.git
[submodule "public/vendor/flatpickr"]
path = public/vendor/flatpickr
url = https://github.com/chmln/flatpickr

View File

@ -91,7 +91,8 @@ final class Env(
Env.fishnet, // required to schedule the cleaner
Env.notifyModule, // required to load the actor
Env.plan, // required to load the actor
Env.studySearch // required to load the actor
Env.studySearch, // required to load the actor
Env.event // required to load the actor
)) { lap =>
lila.log("boot").info(s"${lap.millis}ms Preloading complete")
}
@ -162,4 +163,5 @@ object Env {
def studySearch = lila.studySearch.Env.current
def learn = lila.learn.Env.current
def plan = lila.plan.Env.current
def event = lila.event.Env.current
}

View File

@ -0,0 +1,53 @@
package controllers
import play.api.mvc._
import lila.app._
import views._
object EventCrud extends LilaController {
private def env = Env.event
private def api = env.api
def index = Secure(_.ManageEvent) { implicit ctx =>
me =>
api.list map { events =>
html.event.index(events)
}
}
def edit(id: String) = Secure(_.ManageEvent) { implicit ctx =>
me =>
OptionOk(api one id) { event =>
html.event.edit(event, api editForm event)
}
}
def update(id: String) = SecureBody(_.ManageEvent) { implicit ctx =>
me =>
OptionFuResult(api one id) { event =>
implicit val req = ctx.body
api.editForm(event).bindFromRequest.fold(
err => BadRequest(html.event.edit(event, err)).fuccess,
data => api.update(event, data) inject Redirect(routes.EventCrud.edit(id))
)
}
}
def form = Secure(_.ManageEvent) { implicit ctx =>
me =>
Ok(html.event.create(api.createForm)).fuccess
}
def create = SecureBody(_.ManageEvent) { implicit ctx =>
me =>
implicit val req = ctx.body
api.createForm.bindFromRequest.fold(
err => BadRequest(html.event.create(err)).fuccess,
data => api.create(data, me.id) map { event =>
Redirect(routes.EventCrud.edit(event.id))
}
)
}
}

View File

@ -1,21 +1,14 @@
package controllers
import play.api.data.Form
import play.api.libs.json._
import play.api.mvc._
import lila.api.Context
import lila.app._
import lila.common.HTTPRequest
import lila.game.{ Pov, GameRepo }
import lila.tournament.{ System, TournamentRepo, PairingRepo, Tournament => Tourney, VisibleTournaments }
import lila.user.UserRepo
import views._
object TournamentCrud extends LilaController {
private def env = Env.tournament
private def crud = Env.tournament.crudApi
private def crud = env.crudApi
def index = Secure(_.ManageTournament) { implicit ctx =>
me =>
@ -36,7 +29,7 @@ object TournamentCrud extends LilaController {
OptionFuResult(crud one id) { tour =>
implicit val req = ctx.body
crud.editForm(tour).bindFromRequest.fold(
err => BadRequest(html.tournament.crud.edit(tour, err.pp)).fuccess,
err => BadRequest(html.tournament.crud.edit(tour, err)).fuccess,
data => crud.update(tour, data) inject Redirect(routes.TournamentCrud.edit(id))
)
}

View File

@ -0,0 +1,10 @@
@(form: Form[_])(implicit ctx: Context)
@layout(title = "New event", active = "form") {
<div class="event_crud content_box small_box no_padding">
<h1 class="lichess_title">New event</h1>
<form class="content_box_content material form" action="@routes.EventCrud.create" method="POST">
@inForm(form)
</form>
</div>
}

View File

@ -0,0 +1,13 @@
@(event: lila.event.Event, form: Form[_])(implicit ctx: Context)
@layout(title = event.title, active = "index") {
<div class="event_crud edit content_box small_box no_padding">
<h1 class="lichess_title">
@event.title
<span>Created by @usernameOrId(event.createdBy.value) on @showDate(event.createdAt)</span>
</h1>
<form class="content_box_content material form" action="@routes.EventCrud.update(event.id)" method="POST">
@inForm(form)
</form>
</div>
}

View File

@ -0,0 +1,38 @@
@(form: Form[_])(implicit ctx: Context)
@group(field: play.api.data.Field, name: Html, half: Boolean = false)(html: Html) = {
<div class="form-group@if(half){ half}@if(field.hasErrors){ has-error}">
@html
<label for="@field.id" class="control-label">@name</label>
<i class="bar"></i>
</div>
}
@dateInput(field: play.api.data.Field) = {
<input class="flatpickr" data-enable-time="true" value="@field.value" name="@field.name" id="@field.id" />
}
@group(form("startsAt"), Html("Start date <strong>UTC</strong>"), half = true) {
@dateInput(form("startsAt"))
}
@group(form("finishesAt"), Html("End date <strong>UTC</strong>"), half = true) {
@dateInput(form("finishesAt"))
}
@group(form("title"), Html("Title")) {
@base.input(form("title"))
}
@group(form("headline"), Html("Headline")) {
@base.input(form("headline"))
}
<div>
@group(form("homepageHours"), Html("Hours on homepage (0 to 24)"), half = true) {
@base.input(form("homepageHours"))
}
@group(form("url"), Html("External URL"), half = true) {
@base.input(form("url"))
}
</div>
<div class="button-container">
<button type="submit" class="submit button text" data-icon="E">Apply now</button>
</div>

View File

@ -0,0 +1,35 @@
@(events: List[lila.event.Event])(implicit ctx: Context)
@title = {Event manager}
@layout(title = title.body, active = "index") {
<div class="event_crud content_box no_padding">
<h1 class="lichess_title">@title</h1>
<table class="slist">
<thead>
<tr>
<th></th>
<th>UTC start</th>
<th>UTC end</th>
<th></th>
</tr>
</thead>
<tbody>
@events.map { event =>
<tr>
<td><a href="@routes.EventCrud.edit(event.id)">@event.title</a></td>
<td>
@showDateTimeUTC(event.startsAt)
@momentFromNow(event.startsAt)
</td>
<td>
@showDateTimeUTC(event.finishesAt)
@momentFromNow(event.finishesAt)
</td>
<td><a class="text"href="@event.url" data-icon="v">URL</a></td>
</tr>
}
</tbody>
</table>
</div>
}

View File

@ -0,0 +1,25 @@
@(title: String, active: String = "")(body: Html)(implicit ctx: Context)
@menu = {
<a class="@active.active("index")" href="@routes.EventCrud.index">Recent events</a>
<a class="@active.active("form")" href="@routes.EventCrud.form">Create an event</a>
}
@moreCss = {
@cssAt("vendor/flatpickr/dist/flatpickr.min.css")
@cssTag("material.form.css")
@cssTag("event.crud.css")
}
@moreJs = {
@jsAt("vendor/flatpickr/dist/flatpickr.min.js")
@embedJs {
$(".flatpickr").flatpickr();
}
}
@base.layout(
title = title,
menu = menu.some,
moreCss = moreCss,
moreJs = moreJs)(body)

View File

@ -13,7 +13,10 @@
<a class="@active.active("stream")" href="@routes.Tv.streamConfig">Streams</a>
}
@if(isGranted(_.ManageTournament)) {
<a class="@active.active("index")" href="@routes.TournamentCrud.index">Recent tournaments</a>
<a class="@active.active("tour")" href="@routes.TournamentCrud.index">Manage tournaments</a>
}
@if(isGranted(_.ManageEvent)) {
<a class="@active.active("event")" href="@routes.EventCrud.index">Manage events</a>
}
@if(isGranted(_.SeeReport)) {
<a class="@active.active("log")" href="@routes.Mod.log">Mod log</a>

View File

@ -36,10 +36,10 @@
@group(form("image"), Html("Custom icon"), half = true) {
@base.select(form("image"), imageChoices)
}
@group(form("headline"), Html("Homepage headline")) {
@base.input(form("headline"))
}
</div>
@group(form("headline"), Html("Homepage headline")) {
@base.input(form("headline"))
}
@group(form("description"), Html("Full description")) {
<textarea name="@form("description").name" id="@form("description").id">@form("description").value</textarea>
}

View File

@ -135,6 +135,11 @@ coordinate {
score = coordinate_score
}
}
event {
collection {
event = event
}
}
opening {
collection {
opening = opening

View File

@ -418,6 +418,13 @@ GET /api/tournament controllers.Api.currentTournaments
GET /api/tournament/:id controllers.Api.tournament(id: String)
GET /api/status controllers.Api.status
# Events
GET /event/manager controllers.EventCrud.index
GET /event/manager/$id<\w{8}> controllers.EventCrud.edit(id: String)
POST /event/manager/$id<\w{8}> controllers.EventCrud.update(id: String)
GET /event/manager/new controllers.EventCrud.form
POST /event/manager controllers.EventCrud.create
# Misc
POST /cli controllers.Cli.command
GET /captcha/$id<\w{8}> controllers.Main.captchaCheck(id: String)

View File

@ -0,0 +1,11 @@
package lila.event
import lila.db.dsl._
import reactivemongo.bson._
private[event] object BsonHandlers {
implicit val UserIdBsonHandler = stringAnyValHandler[Event.UserId](_.value, Event.UserId.apply)
implicit val EventBsonHandler = Macros.handler[Event]
}

View File

@ -0,0 +1,24 @@
package lila.event
import akka.actor._
import com.typesafe.config.Config
final class Env(
config: Config,
db: lila.db.Env,
system: ActorSystem) {
private val CollectionEvent = config getString "collection.event"
private lazy val eventColl = db(CollectionEvent)
lazy val api = new EventApi(coll = eventColl)
}
object Env {
lazy val current = "event" boot new Env(
config = lila.common.PlayApp loadConfig "event",
db = lila.db.Env.current,
system = lila.common.PlayApp.system)
}

View File

@ -0,0 +1,26 @@
package lila.event
import org.joda.time.DateTime
import lila.db.dsl._
case class Event(
_id: String,
title: String,
headline: String,
homepageHours: Int,
url: String,
createdBy: Event.UserId,
createdAt: DateTime,
startsAt: DateTime,
finishesAt: DateTime) {
def id = _id
}
object Event {
def makeId = ornicar.scalalib.Random nextStringUppercase 8
case class UserId(value: String) extends AnyVal
}

View File

@ -0,0 +1,28 @@
package lila.event
import org.joda.time.{ DateTime, DateTimeZone }
import lila.db.dsl._
final class EventApi(coll: Coll) {
import BsonHandlers._
def list = coll.find($empty).sort($doc("startsAt" -> -1)).list[Event](50)
def one(id: String) = coll.byId[Event](id)
def editForm(event: Event) = EventForm.form fill {
EventForm.Data make event
}
def update(old: Event, data: EventForm.Data) =
coll.update($id(old.id), data update old).void
def createForm = EventForm.form
def create(data: EventForm.Data, userId: String): Fu[Event] = {
val event = data make userId
coll.insert(event) inject event
}
}

View File

@ -0,0 +1,65 @@
package lila.event
import org.joda.time.format.DateTimeFormat
import org.joda.time.{ DateTime, DateTimeZone }
import play.api.data._
import play.api.data.Forms._
import play.api.data.validation.Constraints._
import scala.util.Try
import lila.common.Form._
object EventForm {
private val dateTimePattern = "yyyy-MM-dd HH:mm"
private implicit val dateTimeFormat = format.Formats.jodaDateTimeFormat(dateTimePattern)
val form = Form(mapping(
"title" -> nonEmptyText(minLength = 3, maxLength = 40),
"headline" -> nonEmptyText(minLength = 5, maxLength = 30),
"homepageHours" -> number(min = 0, max = 24),
"url" -> nonEmptyText,
"startsAt" -> jodaDate(dateTimePattern),
"finishesAt" -> jodaDate(dateTimePattern)
)(Data.apply)(Data.unapply))
case class Data(
title: String,
headline: String,
homepageHours: Int,
url: String,
startsAt: DateTime,
finishesAt: DateTime) {
def update(event: Event) = event.copy(
title = title,
headline = headline,
homepageHours = homepageHours,
url = url,
startsAt = startsAt,
finishesAt = finishesAt)
def make(userId: String) = Event(
_id = Event.makeId,
title = title,
headline = headline,
homepageHours = homepageHours,
url = url,
startsAt = startsAt,
finishesAt = finishesAt,
createdBy = Event.UserId(userId),
createdAt = DateTime.now)
}
object Data {
def make(event: Event) = Data(
title = event.title,
headline = event.headline,
homepageHours = event.homepageHours,
url = event.url,
startsAt = event.startsAt,
finishesAt = event.finishesAt)
}
}

View File

@ -0,0 +1,3 @@
package lila
package object event extends PackageObject with WithPlay

View File

@ -2,8 +2,7 @@ package lila.security
sealed abstract class Permission(val name: String, val children: List[Permission] = Nil) {
final def is(p: Permission): Boolean =
this == p || (children exists (_ is p))
final def is(p: Permission): Boolean = this == p || (children exists (_ is p))
}
object Permission {
@ -35,6 +34,7 @@ object Permission {
case object CloseTeam extends Permission("ROLE_CLOSE_TEAM")
case object TerminateTournament extends Permission("ROLE_TERMINATE_TOURNAMENT")
case object ManageTournament extends Permission("ROLE_MANAGE_TOURNAMENT")
case object ManageEvent extends Permission("ROLE_MANAGE_EVENT")
case object ChangePermission extends Permission("ROLE_CHANGE_PERMISSION")
case object PublicMod extends Permission("ROLE_PUBLIC_MOD", List(GuineaPig))
case object Developer extends Permission("ROLE_DEVELOPER", List(GuineaPig))
@ -47,7 +47,8 @@ object Permission {
case object Admin extends Permission("ROLE_ADMIN", List(
Hunter, ModerateForum, IpBan, CloseAccount, ReopenAccount,
ChatTimeout, MarkTroll, SetTitle, SetEmail, ModerateQa, StreamConfig,
MessageAnyone, CloseTeam, TerminateTournament, ManageTournament, GuineaPig))
MessageAnyone, CloseTeam, TerminateTournament, ManageTournament, ManageEvent,
GuineaPig))
case object SuperAdmin extends Permission("ROLE_SUPER_ADMIN", List(
Admin, ChangePermission, PublicMod, Developer))
@ -55,7 +56,7 @@ object Permission {
lazy val allButSuperAdmin: List[Permission] = List(
Admin, Hunter, MarkTroll, ChatTimeout, ChangePermission, ViewBlurs, StaffForum, ModerateForum,
UserSpy, MarkEngine, MarkBooster, IpBan, ModerateQa, StreamConfig,
Beta, MessageAnyone, UserSearch, CloseTeam, TerminateTournament, ManageTournament,
Beta, MessageAnyone, UserSearch, CloseTeam, TerminateTournament, ManageTournament, ManageEvent,
PublicMod, Developer, GuineaPig)
lazy private val all: List[Permission] = SuperAdmin :: allButSuperAdmin

View File

@ -174,7 +174,7 @@ object TournamentRepo {
coll.find(selectUnique)
.sort($doc("startsAt" -> -1))
.hint($doc("startsAt" -> -1))
.list[Tournament]()
.list[Tournament](max)
def scheduledUnfinished: Fu[List[Tournament]] =
coll.find(scheduledSelect ++ unfinishedSelect)

View File

@ -7,7 +7,7 @@ import lila.user.User
final class CrudApi {
def list = TournamentRepo uniques 30
def list = TournamentRepo uniques 50
def one(id: String) = TournamentRepo uniqueById id

View File

@ -60,7 +60,7 @@ object ApplicationBuild extends Build {
evaluation, chat, puzzle, tv, coordinate, blog, qa,
history, worldMap, opening, video, shutup, push,
playban, insight, perfStat, slack, quote, challenge,
study, studySearch, fishnet, explorer, learn, plan)
study, studySearch, fishnet, explorer, learn, plan, event)
lazy val moduleRefs = modules map projectToRef
lazy val moduleCPDeps = moduleRefs map { new sbt.ClasspathDependency(_, None) }
@ -148,37 +148,35 @@ object ApplicationBuild extends Build {
)
lazy val timeline = project("timeline", Seq(common, db, game, user, hub, security, relation)).settings(
libraryDependencies ++= provided(
play.api, play.test, RM)
libraryDependencies ++= provided(play.api, play.test, RM)
)
lazy val event = project("event", Seq(common, db)).settings(
libraryDependencies ++= provided(play.api, play.test, RM)
)
lazy val mod = project("mod", Seq(common, db, user, hub, security, game, analyse, evaluation, report, notifyModule)).settings(
libraryDependencies ++= provided(
play.api, play.test, RM)
libraryDependencies ++= provided(play.api, play.test, RM)
)
lazy val user = project("user", Seq(common, memo, db, hub, chess, rating)).settings(
libraryDependencies ++= provided(
play.api, play.test, RM, hasher)
libraryDependencies ++= provided(play.api, play.test, RM, hasher)
)
lazy val game = project("game", Seq(common, memo, db, hub, user, chess, chat)).settings(
libraryDependencies ++= provided(
play.api, RM)
libraryDependencies ++= provided(play.api, RM)
)
lazy val gameSearch = project("gameSearch", Seq(common, hub, chess, search, game)).settings(
libraryDependencies ++= provided(
play.api, RM)
)
play.api, RM))
lazy val tv = project("tv", Seq(common, db, hub, socket, game, user, chess)).settings(
libraryDependencies ++= provided(play.api, RM, hasher)
)
lazy val analyse = project("analyse", Seq(common, hub, chess, game, user, notifyModule)).settings(
libraryDependencies ++= provided(
play.api, RM, spray.caching)
libraryDependencies ++= provided(play.api, RM, spray.caching)
)
lazy val round = project("round", Seq(

View File

@ -0,0 +1,49 @@
h1.lichess_title {
text-align: center;
margin-bottom: 0!important;
}
.event_crud.edit h1.lichess_title {
background: #303F9F;
color: #ddd;
letter-spacing: 0.1em;
padding: 30px!important;
font-size: 27px!important;
}
h1.lichess_title span {
display: block;
font-size: 0.35em;
opacity: 0.8;
margin-top: 10px;
text-transform: uppercase;
}
table.slist {
font-size: 1.2em;
width: 100%;
}
table.slist td {
padding: 20px 10px;
}
table.slist time {
display: block;
font-size: 0.8em;
opacity: 0.8;
}
.material.form {
margin: 30px 20px 20px 20px;
}
.material.form button {
font-size: 1.7em;
font-weight: normal;
padding: 10px 20px;
}
.material.form textarea {
height: 10em;
}
.event_crud a {
color: #3893E8!important;
}
.event_crud h2 {
font-size: 1.5em;
margin: 0px 0 30px 0;
text-align: center;
}

1
public/vendor/flatpickr vendored 160000

@ -0,0 +1 @@
Subproject commit 6b7b340b6d26c8bda5176325bde32ea55ae83fda