broadcast tournament/round WIP

broadcast-tournament
Thibault Duplessis 2021-04-26 11:18:03 +02:00
parent 06d01fd08f
commit af408f6288
11 changed files with 122 additions and 58 deletions

View File

@ -5,6 +5,7 @@ import play.api.mvc._
import lila.api.Context
import lila.app._
// import lila.common.config.MaxPerSecond
import lila.relay.{ RelayRound => RoundModel, RelayTour => TourModel, RelayRoundForm }
import lila.user.{ User => UserModel }
@ -41,7 +42,7 @@ final class RelayRound(
err => BadRequest(html.relay.roundForm.create(err, trs.tour)).fuccess,
setup =>
env.relay.api.create(setup, me, trs.tour) map { relay =>
Redirect(routes.RelayTour.show(trs.tour.slug, trs.tour.id.value))
Redirect(routes.RelayRound.form(trs.tour.id.value))
}
)
}
@ -129,7 +130,7 @@ final class RelayRound(
OpenOrScoped(_.Study.Read)(
open = implicit ctx => {
pageHit
WithRelay(ts, rs, id) { rt =>
WithRoundAndTour(ts, rs, id) { rt =>
val sc =
if (rt.round.sync.ongoing)
env.study.chapterRepo relaysAndTagsByStudyId rt.round.studyId flatMap { chapters =>
@ -150,15 +151,9 @@ final class RelayRound(
}
)
def bcShow(@nowarn("unused") slug: String, roundId: String) = Open { implicit ctx =>
env.relay.api byIdWithTour RoundModel.Id(roundId) map2 { rt =>
Redirect(rt.path)
}
}
def chapter(ts: String, rs: String, id: String, chapterId: String) =
Open { implicit ctx =>
WithRelay(ts, rs, id) { rt =>
WithRoundAndTour(ts, rs, id) { rt =>
env.study.api.byIdWithChapter(rt.round.studyId, chapterId) flatMap {
_ ?? { doShow(rt, _) }
}
@ -189,12 +184,11 @@ final class RelayRound(
// }.fuccess
// }
private def WithRelay(ts: String, rs: String, id: String)(
private def WithRoundAndTour(ts: String, rs: String, id: String)(
f: RoundModel.WithTour => Fu[Result]
)(implicit ctx: Context): Fu[Result] =
OptionFuResult(env.relay.api byIdWithTour id) { rt =>
if (rt.tour.slug != ts) Redirect(rt.path).fuccess
if (rt.round.slug != rs) Redirect(rt.path).fuccess
if (ctx.req.path != rt.path) Redirect(rt.path).fuccess
else f(rt)
}

View File

@ -2,12 +2,13 @@ package controllers
import play.api.data.Form
import play.api.mvc._
import scala.annotation.nowarn
import views._
import lila.api.Context
import lila.app._
import lila.relay.{ RelayRound => RoundModel, RelayTour => TourModel }
import lila.user.{ User => UserModel }
import views._
final class RelayTour(env: Env) extends LilaController(env) {
@ -17,7 +18,7 @@ final class RelayTour(env: Env) extends LilaController(env) {
for {
active <- (page == 1).??(env.relay.api.officialActive)
pager <- env.relay.pager.inactive(page)
} yield Ok(html.tour.index(active, pager))
} yield Ok(html.relay.tour.index(active, pager))
}
}
@ -35,7 +36,7 @@ final class RelayTour(env: Env) extends LilaController(env) {
err => BadRequest(html.relay.tourForm.create(err)).fuccess,
setup =>
env.relay.api.tourCreate(setup, me) map { tour =>
Redirect(routes.RelayTour.show(tour.slug, tour.id.value)).flashSuccess
Redirect(routes.RelayRound.form(tour.id.value)).flashSuccess
}
)
}
@ -59,11 +60,25 @@ final class RelayTour(env: Env) extends LilaController(env) {
err => BadRequest(html.relay.tourForm.edit(tour, err)).fuccess,
setup =>
env.relay.api.tourUpdate(tour, setup, me) inject
Redirect(routes.RelayTour.show(tour.slug, tour.id.value)).flashSuccess
Redirect(routes.RelayTour.edit(tour.id.value)).flashSuccess
)
}
}
def redirect(@nowarn("msg=unused") slug: String, anyId: String) = Open { implicit ctx =>
env.relay.api byIdWithTour RoundModel.Id(anyId) flatMap {
case Some(rt) => Redirect(rt.path).fuccess // BC old broadcast URLs
case None => env.relay.api tourById TourModel.Id(anyId) flatMap { _ ?? redirectToTour }
}
}
private def redirectToTour(tour: TourModel)(implicit ctx: Context): Fu[Result] =
env.relay.api.activeTourNextRound(tour) orElse env.relay.api.tourLastRound(tour) flatMap {
case None =>
ctx.me.exists(tour.ownedBy) ?? Redirect(routes.RelayRound.form(tour.id.value)).fuccess
case Some(round) => Redirect(round.withTour(tour).path).fuccess
}
private def WithTour(id: String)(
f: TourModel => Fu[Result]
)(implicit ctx: Context): Fu[Result] =

View File

@ -300,7 +300,7 @@ final class Study(
_ ?? { study =>
env.study.api.delete(study) >> env.relay.api.deleteRound(lila.relay.RelayRound.Id(id)).map {
case None => Redirect(routes.Study.mine("hot"))
case Some(tour) => Redirect(routes.RelayTour.show(tour.slug, tour.id.value))
case Some(tour) => Redirect(routes.RelayTour.redirect(tour.slug, tour.id.value))
}
}
}

View File

@ -16,13 +16,18 @@ object roundForm {
def create(form: Form[Data], tour: RelayTour)(implicit ctx: Context) =
layout(newBroadcast.txt())(
h1(a(href := views.html.relay.tour.url(tour))(tour.name), " • ", addRound()),
h1(a(href := routes.RelayTour.redirect(tour.slug, tour.id.value))(tour.name), " • ", addRound()),
inner(form, routes.RelayRound.create(tour.id.value), tour)
)
def edit(rt: RelayRound.WithTour, form: Form[Data])(implicit ctx: Context) =
layout(rt.fullName)(
h1("Edit ", a(href := tour.url(rt.tour))(rt.tour.name), " > ", a(href := rt.path)(rt.round.name)),
h1(
"Edit ",
a(href := routes.RelayTour.redirect(rt.tour.slug, rt.tour.id.value))(rt.tour.name),
" > ",
a(href := rt.path)(rt.round.name)
),
inner(form, routes.RelayRound.update(rt.round.id.value), rt.tour),
div(cls := "relay-round__actions")(
postForm(action := routes.RelayRound.cloneRound(rt.round.id.value))(
@ -95,7 +100,7 @@ object roundForm {
)(form3.input(_, typ = "number"))
),
form3.actions(
a(href := tour.url(t))(trans.cancel()),
a(href := routes.RelayTour.redirect(t.slug, t.id.value))(trans.cancel()),
form3.submit(trans.apply())
)
)

View File

@ -54,14 +54,14 @@ object tour {
}
),
st.section(cls := "infinite-scroll")(
pager.currentPageResults map { tour =>
pager.currentPageResults map { rt =>
div(cls := "relay-widget paginated", dataIcon := "")(
a(cls := "overlay", href := views.html.relay.tour.url(tour)),
a(cls := "overlay", href := rt.path),
div(
h2(tour.name),
h2(rt.tour.name),
div(cls := "relay-widget__info")(
p(tour.description),
tour.syncedAt.map(momentFromNow(_))
p(rt.tour.description),
rt.tour.syncedAt.map(momentFromNow(_))
)
)
)

View File

@ -27,11 +27,11 @@ object tourForm {
def edit(t: RelayTour, form: Form[Data])(implicit ctx: Context) =
layout(t.name)(
h1("Edit ", a(href := tour.url(t))(t.name)),
h1("Edit ", a(href := routes.RelayTour.redirect(t.slug, t.id.value))(t.name)),
postForm(cls := "form3", action := routes.RelayTour.update(t.id.value))(
inner(form),
form3.actions(
a(href := tour.url(t))(trans.cancel()),
a(href := routes.RelayTour.redirect(t.slug, t.id.value))(trans.cancel()),
form3.submit(trans.apply())
)
)

View File

@ -169,9 +169,9 @@ GET /study/topic/autocomplete controllers.Study.topicAutocomplete
GET /broadcast controllers.RelayTour.index(page: Int ?= 1)
GET /broadcast/new controllers.RelayTour.form
POST /broadcast/new controllers.RelayTour.create
GET /broadcast/:rs/$anyId<\w{8}> controllers.RelayTour.redirect(rs: String, anyId: String)
GET /broadcast/$tourId<\w{8}>/edit controllers.RelayTour.edit(tourId: String)
POST /broadcast/$tourId<\w{8}>/edit controllers.RelayTour.update(tourId: String)
GET /broadcast/:rs/$roundId<\w{8}> controllers.RelayRound.bcShow(rs: String, roundId: String)
GET /broadcast/$tourId<\w{8}>/new controllers.RelayRound.form(tourId: String)
POST /broadcast/$tourId<\w{8}>/new controllers.RelayRound.create(tourId: String)
GET /broadcast/:ts/:rs/$roundId<\w{8}> controllers.RelayRound.show(ts: String, rs: String, roundId: String)

View File

@ -61,6 +61,24 @@ final class RelayApi(
def withRounds(tour: RelayTour) = roundRepo.byTour(tour).dmap(tour.withRounds)
private val nextRoundSort = $doc(
"startedAt" -> 1,
"startsAt" -> 1,
"name" -> 1
)
def activeTourNextRound(tour: RelayTour): Fu[Option[RelayRound]] = tour.active ??
roundRepo.coll
.find($doc("tourId" -> tour.id, "finished" -> false))
.sort(nextRoundSort)
.one[RelayRound]
def tourLastRound(tour: RelayTour): Fu[Option[RelayRound]] =
roundRepo.coll
.find($doc("tourId" -> tour.id))
.sort($doc("startedAt" -> -1, "startsAt" -> -1))
.one[RelayRound]
def officialActive: Fu[List[RelayTour.ActiveWithNextRound]] =
tourRepo.coll
.aggregateList(20) { framework =>
@ -84,14 +102,8 @@ final class RelayApi(
)
),
$doc("$addFields" -> $doc("sync.log" -> $arr())),
$doc(
"$sort" -> $doc(
"startedAt" -> 1,
"startsAt" -> 1,
"name" -> 1
)
),
$doc("$limit" -> 1)
$doc("$sort" -> nextRoundSort),
$doc("$limit" -> 1)
)
)
)

View File

@ -3,28 +3,65 @@ package lila.relay
import reactivemongo.api.ReadPreference
import lila.common.config.MaxPerPage
import lila.common.paginator.Paginator
import lila.common.paginator.{ AdapterLike, Paginator }
import lila.db.dsl._
import lila.db.paginator.{ Adapter, CachedAdapter }
final class RelayPager(tourRepo: RelayTourRepo)(implicit
final class RelayPager(tourRepo: RelayTourRepo, roundRepo: RelayRoundRepo)(implicit
ec: scala.concurrent.ExecutionContext
) {
import BSONHandlers._
def inactive(page: Int): Fu[Paginator[RelayTour]] =
def inactive(page: Int): Fu[Paginator[RelayTour.WithLastRound]] =
Paginator(
adapter = new CachedAdapter(
nbResults = fuccess(9999),
adapter = new Adapter[RelayTour](
collection = tourRepo.coll,
selector = tourRepo.selectors.official ++ tourRepo.selectors.inactive,
projection = none,
sort = $sort desc "syncedAt",
readPreference = ReadPreference.secondaryPreferred
)
),
adapter = new AdapterLike[RelayTour.WithLastRound] {
def nbResults: Fu[Int] = fuccess(9999)
def slice(offset: Int, length: Int): Fu[List[RelayTour.WithLastRound]] =
tourRepo.coll
.aggregateList(length, readPreference = ReadPreference.secondaryPreferred) { framework =>
import framework._
Match(tourRepo.selectors.official ++ tourRepo.selectors.inactive) -> List(
Sort(Descending("syncedAt")),
PipelineOperator(
$doc(
"$lookup" -> $doc(
"from" -> roundRepo.coll.name,
"as" -> "round",
"let" -> $doc("id" -> "$_id"),
"pipeline" -> $arr(
$doc(
"$match" -> $doc(
"$expr" -> $doc(
$doc("$eq" -> $arr("$tourId", "$$id"))
)
)
),
$doc(
"$sort" -> $doc(
"startedAt" -> -1,
"startsAt" -> -1,
"name" -> -1
)
),
$doc("$limit" -> 1),
$doc("$addFields" -> $doc("sync.log" -> $arr()))
)
)
)
),
UnwindField("round")
)
}
.map { docs =>
for {
doc <- docs
tour <- doc.asOpt[RelayTour]
round <- doc.getAsOpt[RelayRound]("round")
} yield RelayTour.WithLastRound(tour, round)
}
},
currentPage = page,
maxPerPage = MaxPerPage(20)
)

View File

@ -130,17 +130,19 @@ object RelayRound {
case class UpstreamIds(ids: List[lila.game.Game.ID]) extends Upstream
}
case class WithTour(round: RelayRound, tour: RelayTour) {
trait AndTour {
val round: RelayRound
val tour: RelayTour
def fullName = s"${tour.name}${round.name}"
def withStudy(study: Study) = WithTourAndStudy(round, tour, study)
def path: String =
s"/broadcast/${tour.slug}/${if (round.slug == tour.slug) "-" else round.slug}/${round.id}"
def path(chapterId: Chapter.Id): String = s"$path/$chapterId"
}
case class WithTour(round: RelayRound, tour: RelayTour) extends AndTour {
def withStudy(study: Study) = WithTourAndStudy(round, tour, study)
}
case class WithTourAndStudy(relay: RelayRound, tour: RelayTour, study: Study) {
def path = WithTour(relay, tour).path
def fullName = WithTour(relay, tour).fullName

View File

@ -36,12 +36,11 @@ object RelayTour {
case class WithRounds(tour: RelayTour, rounds: List[RelayRound])
case class ActiveWithNextRound(tour: RelayTour, round: RelayRound) {
def path = RelayRound.WithTour(round, tour).path
case class ActiveWithNextRound(tour: RelayTour, round: RelayRound) extends RelayRound.AndTour {
def ongoing = round.startedAt.isDefined
}
case class WithLastRound(tour: RelayTour, round: RelayRound)
case class WithLastRound(tour: RelayTour, round: RelayRound) extends RelayRound.AndTour
def makeId = Id(lila.common.ThreadLocalRandom nextString 8)
}