relay WIP

pull/3627/head
Thibault Duplessis 2017-09-20 20:26:15 -05:00
parent a16966ad02
commit fba3b549c6
17 changed files with 316 additions and 23 deletions

View File

@ -0,0 +1,41 @@
package controllers
import play.api.mvc._
import lila.api.Context
import lila.app._
import lila.relay.{ Relay => RelayModel }
import views._
object Relay extends LilaController {
private val env = Env.relay
def index = Open { implicit ctx =>
env.api.all map { sel =>
Ok(html.relay.index(sel))
}
}
def form = Auth { implicit ctx => me =>
NoLame {
Ok(html.relay.create(env.forms.create)).fuccess
}
}
def create = AuthBody { implicit ctx => implicit me =>
implicit val req = ctx.body
env.forms.create.bindFromRequest.fold(
err => BadRequest(html.relay.create(err)).fuccess,
setup => env.api.create(setup, me) map { relay =>
Redirect(routes.Relay.show(relay.id.value))
}
)
}
def show(id: String) = Open { implicit ctx =>
OptionOk(env.api byId RelayModel.Id(id)) { relay =>
html.relay.show(relay)
}
}
}

View File

@ -0,0 +1,18 @@
@(form: Form[_])(implicit ctx: Context)
@moreCss = {
@cssTag("material.form.css")
@cssTag("relay.css")
}
@base.layout(
title = "New live broadcast",
moreCss = moreCss
) {
<div class="relay_form content_box small_box no_padding">
<h1 class="lichess_title">New live broadcast</h1>
<form class="content_box_content material form" action="@routes.Relay.create" method="POST">
@inForm(form)
</form>
</div>
}

View File

@ -0,0 +1,18 @@
@(form: Form[_])(implicit ctx: Context)
@base.form.group(form("name"), Html("Event name")) {
@base.form.input(form("name"))
}
@defining(form("description")) { field =>
@base.form.group(field, Html("Event description")) {
<textarea class="description" name="@field.name" id="@field.id">@field.value</textarea>
}
}
@base.form.group(form("startsAt"), Html("Start date <strong>UTC</strong>")) {
@base.form.flatpickr(form("startsAt"))
}
@base.form.group(form("pgnUrl"), Html("Live PGN URL")) {
@base.form.input(form("pgnUrl"))
}
@base.form.submit()

View File

@ -0,0 +1,33 @@
@(sel: lila.relay.Relay.Selection)(implicit ctx: Context)
@sublist(relays: List[lila.relay.Relay.WithStudy]) = {
<div class="list">
@relays.map { r =>
<div class="relay">
@widget(r)
</div>
}
</div>
}
@base.layout(
title = "Live tournament broadcasts",
moreCss = cssTag("relay.css")) {
<div class="content_box no_padding relays">
<div class="top">
<h1>Live tournament broadcasts</h1>
</div>
<section>
<h2>Ongoing</h2>
@sublist(sel.started)
</section>
<section>
<h2>Upcoming</h2>
@sublist(sel.created)
</section>
<section>
<h2>Completed</h2>
@sublist(sel.closed)
</section>
</div>
}

View File

@ -0,0 +1,8 @@
@(r: lila.relay.Relay)(implicit ctx: Context)
@base.layout(
title = r.name,
moreCss = cssTag("relay.css")
) {
<h1 class="lichess_title">@r.name</h1>
}

View File

@ -0,0 +1,10 @@
@(r: lila.relay.Relay.WithStudy)(implicit ctx: Context)
<a class="overlay" href="@routes.Relay.show(r.relay.id.value)"></a>
<h2>
<i class="icon" data-icon=""></i>
<strong>@r.relay.name</strong>
</h2>
<div class="body">
<p>@r.relay.description</p>
@momentFromNow(r.relay.startsAt)
</div>

View File

@ -1,13 +1,5 @@
@(title: String, active: String, order: lila.study.Order, pag: Paginator[lila.study.Study.WithChaptersAndLiked], url: controllers.Study.ListUrl, searchFilter: String)(titleHtml: Html)(implicit ctx: Context)
@moreCss = {
@cssTag("studyList.css")
}
@moreJs = {
@jsTag("vendor/jquery.infinitescroll.min.js")
}
@menu = {
<a class="@active.active("all")" href="@routes.Study.all(order.key)">All studies</a>
@ctx.me.map { me =>
@ -19,8 +11,8 @@
@base.layout(
title = title,
menu = menu.some,
moreCss = moreCss,
moreJs = moreJs) {
moreCss = cssTag("studyList.css"),
moreJs = jsTag("vendor/jquery.infinitescroll.min.js")) {
<div class="content_box no_padding studies">
<div class="top">
<form class="search" action="@routes.Study.search()" method="get">

View File

@ -521,6 +521,9 @@ irwin {
collection.report = irwin_report
collection.request = irwin_request
}
relay {
collection.relay = relay
}
activity {
collection.activity = activity
}

View File

@ -149,6 +149,12 @@ GET /study/$id<\w{8}>/$chapterId<\w{8}>/meta controllers.Study.chapterMeta(i
GET /study/embed/$id<\w{8}>/$chapterId<\w{8}> controllers.Study.embed(id: String, chapterId: String)
POST /study/$id<\w{8}>/clear-chat controllers.Study.clearChat(id: String)
# Relay
GET /relay controllers.Relay.index
GET /relay/new controllers.Relay.form
POST /relay/new controllers.Relay.create
GET /relay/$id<\w{8}> controllers.Relay.show(id: String)
# Learn
GET /learn controllers.Learn.index
POST /learn/score controllers.Learn.score

View File

@ -46,7 +46,7 @@ object HTTPRequest {
case class UaMatcher(regex: String) {
val pattern = regex.r.pattern
def apply(req: RequestHeader): Boolean =
userAgent(req) ?? { ua => pattern.matcher(ua).find }
}

View File

@ -0,0 +1,12 @@
package lila.relay
import lila.db.dsl._
import lila.db.BSON
import reactivemongo.bson._
object BSONHandlers {
implicit val relayIdHandler = stringAnyValHandler[Relay.Id](_.value, Relay.Id.apply)
implicit val relayHandler = Macros.handler[Relay]
}

View File

@ -1,32 +1,41 @@
package lila.relay
import akka.actor._
import com.typesafe.config.Config
import scala.concurrent.duration._
final class Env(
config: Config,
db: lila.db.Env,
studyEnv: lila.study.Env,
system: ActorSystem
) {
private val relayColl = db(config getString "collection.relay")
private val sync = new RelaySync(
studyApi = studyEnv.api,
chapterRepo = studyEnv.chapterRepo
)
lazy val forms = RelayForm
val api = new RelayApi(
coll = relayColl,
studyApi = studyEnv.api
)
private val fetch = system.actorOf(Props(new RelayFetch(
sync = sync,
getCurrents = () => fuccess(List(
Relay(
lila.study.Study.Id("AoUZ6bOS"),
url = "http://localhost:3000"
)
))
getStarted = () => api.started
)))
}
object Env {
lazy val current: Env = "relay" boot new Env(
db = lila.db.Env.current,
config = lila.common.PlayApp loadConfig "relay",
studyEnv = lila.study.Env.current,
system = lila.common.PlayApp.system
)

View File

@ -1,11 +1,39 @@
package lila.relay
import org.joda.time.DateTime
import lila.study.{ Study }
import lila.user.User
case class Relay(
studyId: Study.Id,
url: String
_id: Relay.Id,
name: String,
description: String,
pgnUrl: String,
ownerId: User.ID,
createdAt: DateTime,
startsAt: DateTime,
closedAt: Option[DateTime]
) {
override def toString = s"study:$studyId url:$url"
def id = _id
def studyId = Study.Id(id.value)
override def toString = s"id:$id pgnUrl:$pgnUrl"
}
object Relay {
case class Id(value: String) extends AnyVal with StringValue
def makeId = Id(ornicar.scalalib.Random nextString 8)
case class WithStudy(relay: Relay, study: Study)
case class Selection(
created: List[WithStudy],
started: List[WithStudy],
closed: List[WithStudy]
)
}

View File

@ -0,0 +1,50 @@
package lila.relay
import org.joda.time.DateTime
import reactivemongo.bson._
import lila.db.dsl._
import lila.study.StudyApi
import lila.user.User
final class RelayApi(
coll: Coll,
studyApi: StudyApi
) {
import BSONHandlers._
def byId(id: Relay.Id) = coll.byId[Relay](id.value)
def all: Fu[Relay.Selection] =
created.flatMap(withStudy) zip
started.flatMap(withStudy) zip
closed.flatMap(withStudy) map {
case c ~ s ~ t => Relay.Selection(c, s, t)
}
def created = coll.find($doc(
"startsAt" $gt DateTime.now
)).sort($sort asc "startsAt").list[Relay]()
def started = coll.find($doc(
"startsAt" $lt DateTime.now,
"closedAt" $exists false
)).sort($sort asc "startsAt").list[Relay]()
def closed = coll.find($doc(
"closedAt" $exists false
)).sort($sort asc "startsAt").list[Relay]()
def create(data: RelayForm.Data, user: User): Fu[Relay] = {
val relay = data make user
coll.insert(relay) inject relay
}
private def withStudy(relays: List[Relay]): Fu[List[Relay.WithStudy]] =
studyApi byIds relays.map(_.studyId) map { studies =>
relays.flatMap { relay =>
studies.find(_.id == relay.studyId) map { Relay.WithStudy(relay, _) }
}
}
}

View File

@ -7,7 +7,7 @@ import scala.concurrent.duration._
private final class RelayFetch(
sync: RelaySync,
getCurrents: () => Fu[List[Relay]]
getStarted: () => Fu[List[Relay]]
) extends Actor {
override def preStart {
@ -30,9 +30,9 @@ private final class RelayFetch(
case Tick =>
val startAt = nowMillis
getCurrents().flatMap {
getStarted().flatMap {
_.map { relay =>
WS.url(relay.url).get().flatMap { res =>
WS.url(relay.pgnUrl).get().flatMap { res =>
sync(relay, res.body)
} recover {
case e: Exception =>

View File

@ -0,0 +1,58 @@
package lila.relay
import org.joda.time.DateTime
import play.api.data._
import play.api.data.Forms._
import play.api.data.validation.Constraints._
import lila.user.User
object RelayForm {
import lila.common.Form.UTCDate._
val form = Form(mapping(
"name" -> nonEmptyText(minLength = 3, maxLength = 80),
"description" -> nonEmptyText(minLength = 3, maxLength = 4000),
"pgnUrl" -> nonEmptyText,
"startsAt" -> utcDate
)(Data.apply)(Data.unapply))
def create = form
case class Data(
name: String,
description: String,
pgnUrl: String,
startsAt: DateTime
) {
def update(relay: Relay) = relay.copy(
name = name,
description = description,
pgnUrl = pgnUrl,
startsAt = startsAt
)
def make(user: User) = Relay(
_id = Relay.makeId,
name = name,
description = description,
pgnUrl = pgnUrl,
ownerId = user.id,
createdAt = DateTime.now,
startsAt = startsAt,
closedAt = none
)
}
object Data {
def make(relay: Relay) = Data(
name = relay.name,
description = relay.description,
pgnUrl = relay.pgnUrl,
startsAt = relay.startsAt
)
}
}

View File

@ -0,0 +1,7 @@
.relay_form h1.lichess_title {
text-align: center;
margin-bottom: 0!important;
}
.relay_form textarea.description {
height: 10em;
}