broadcast tournament/round WIP

broadcast-tournament
Thibault Duplessis 2021-04-24 10:41:32 +02:00
parent 3670ab3820
commit 592b776fef
14 changed files with 127 additions and 101 deletions

View File

@ -19,9 +19,9 @@ final class RelayRound(
def form(tourId: String) =
Auth { implicit ctx => me =>
NoLameOrBot {
WithTour(tourId) { tour =>
(tour.owner == me.id) ?? {
Ok(html.relay.form.create(env.relay.roundForm.create, tour)).fuccess
WithTourAndRounds(tourId) { trs =>
(trs.tour.ownerId == me.id) ?? {
Ok(html.relay.roundForm.create(env.relay.roundForm.create(trs), trs.tour)).fuccess
}
}
}
@ -32,15 +32,16 @@ final class RelayRound(
auth = implicit ctx =>
me =>
NoLameOrBot {
WithTour(tourId) { tour =>
(tour.owner == me.id) ?? {
env.relay.roundForm.create
WithTourAndRounds(tourId) { trs =>
(trs.tour.ownerId == me.id) ?? {
env.relay.roundForm
.create(trs)
.bindFromRequest()(ctx.body, formBinding)
.fold(
err => BadRequest(html.relay.form.create(err, tour)).fuccess,
err => BadRequest(html.relay.roundForm.create(err, trs.tour)).fuccess,
setup =>
env.relay.api.create(setup, me, tour) map { relay =>
Redirect(relay.withTour(tour).path)
env.relay.api.create(setup, me, trs.tour) map { relay =>
Redirect(routes.RelayTour.show(trs.tour.slug, trs.tour.id.value))
}
)
}
@ -50,13 +51,16 @@ final class RelayRound(
me =>
env.relay.api tourById TourModel.Id(tourId) flatMap {
_ ?? { tour =>
!(me.isBot || me.lame) ??
env.relay.roundForm.create
.bindFromRequest()(req, formBinding)
.fold(
err => BadRequest(apiFormError(err)).fuccess,
setup => env.relay.api.create(setup, me, tour) map env.relay.jsonView.admin map JsonOk
)
env.relay.api.withRounds(tour) flatMap { trs =>
!(me.isBot || me.lame) ??
env.relay.roundForm
.create(trs)
.bindFromRequest()(req, formBinding)
.fold(
err => BadRequest(apiFormError(err)).fuccess,
setup => env.relay.api.create(setup, me, tour) map env.relay.jsonView.admin map JsonOk
)
}
}
}
)
@ -64,7 +68,7 @@ final class RelayRound(
def edit(id: String) =
Auth { implicit ctx => me =>
OptionFuResult(env.relay.api.byIdAndContributor(id, me)) { rt =>
Ok(html.relay.form.edit(rt, env.relay.roundForm.edit(rt.relay))).fuccess
Ok(html.relay.roundForm.edit(rt, env.relay.roundForm.edit(rt.relay))).fuccess
}
}
@ -77,7 +81,7 @@ final class RelayRound(
case Some(res) =>
res
.fold(
{ case (old, err) => BadRequest(html.relay.form.edit(old, err)) },
{ case (old, err) => BadRequest(html.relay.roundForm.edit(old, err)) },
rt => Redirect(rt.path)
)
.fuccess
@ -189,6 +193,13 @@ final class RelayRound(
)(implicit ctx: Context): Fu[Result] =
OptionFuResult(env.relay.api tourById TourModel.Id(id))(f)
private def WithTourAndRounds(id: String)(
f: TourModel.WithRounds => Fu[Result]
)(implicit ctx: Context): Fu[Result] =
WithTour(id) { tour =>
env.relay.api withRounds tour flatMap f
}
private def doShow(rt: RoundModel.WithTour, oldSc: lila.study.Study.WithChapter)(implicit
ctx: Context
): Fu[Result] =

View File

@ -10,20 +10,20 @@ import lila.relay.RelayRound.Sync.UpstreamUrl.LccRegex
import lila.relay.RelayRoundForm.Data
import lila.relay.{ RelayRound, RelayTour }
object form {
object roundForm {
import trans.broadcast._
def create(form: Form[Data], tour: RelayTour)(implicit ctx: Context) =
layout(newBroadcast.txt())(
h1(newBroadcast()),
inner(form, routes.RelayRound.create(tour.id.value))
h1(a(href := views.html.relay.tour.url(tour))(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 ", rt.fullName),
inner(form, routes.RelayRound.update(rt.relay.id.value)),
inner(form, routes.RelayRound.update(rt.relay.id.value), rt.tour),
hr,
postForm(action := routes.RelayRound.cloneRelay(rt.relay.id.value))(
submitButton(
@ -49,7 +49,7 @@ object form {
main(cls := "page-small box box-pad")(body)
)
private def inner(form: Form[Data], url: play.api.mvc.Call)(implicit ctx: Context) =
private def inner(form: Form[Data], url: play.api.mvc.Call, tour: RelayTour)(implicit ctx: Context) =
postForm(cls := "form3", action := url)(
div(cls := "form-group")(
a(dataIcon := "", cls := "text", href := routes.Page.loneBookmark("broadcasts"))(
@ -101,7 +101,7 @@ object form {
),
isGranted(_.Relay) option form3.group(form("credit"), credits())(form3.input(_)),
form3.actions(
a(href := routes.RelayTour.index(1))(trans.cancel()),
a(href := views.html.relay.tour.url(tour))(trans.cancel()),
form3.submit(trans.apply())
)
)

View File

@ -31,4 +31,6 @@ object tour {
)
)
}
def url(t: RelayTour) = routes.RelayTour.show(t.slug, t.id.value)
}

View File

@ -9,13 +9,16 @@ const newId = () => {
db.relay.find().forEach(relay => {
const tourId = newId();
db.relay_tour.insert({
_id: tourId,
name: relay.name,
description: relay.description,
markup: relay.markup,
ownerId: relay.ownerId,
createdAt: relay.createdAt,
official: relay.official,
...{
_id: tourId,
order: NumberInt(0),
name: relay.name,
description: relay.description,
ownerId: relay.ownerId,
createdAt: relay.createdAt,
official: relay.official,
},
...(relay.markup ? { markup: relay.markup } : {}),
});
db.relay.update(
{ _id: relay._id },

View File

@ -1,11 +0,0 @@
var id = 'RWtJ4aKC';
var relay = db.relay.findOne({ _id: id });
if (relay) {
print('Removing ' + relay.games.length + ' games');
relay.games.forEach(function (g) {
db.game5.remove({ _id: g.id });
});
print('Removing relay');
db.relay.remove({ _id: id });
}

View File

@ -38,7 +38,8 @@ object BSONHandlers {
def readRelayWithTour(doc: Bdoc): Option[RelayRound.WithTour] =
for {
relay <- doc.asOpt[RelayRound]
tour <- doc.getAsOpt[RelayTour]("tour")
_ <- Some(println(lila.db.BSON debug doc))
relay <- doc.asOpt[RelayRound].pp
tour <- doc.getAsTry[RelayTour]("tour").pp.toOption
} yield RelayRound.WithTour(relay, tour)
}

View File

@ -15,7 +15,7 @@ import lila.study.{ Settings, Study, StudyApi, StudyMaker, StudyRepo }
import lila.user.User
final class RelayApi(
relayRepo: RelayRoundRepo,
roundRepo: RelayRoundRepo,
tourRepo: RelayTourRepo,
studyApi: StudyApi,
studyRepo: StudyRepo,
@ -26,10 +26,10 @@ final class RelayApi(
import BSONHandlers._
import lila.study.BSONHandlers.StudyBSONHandler
def byId(id: RelayRound.Id) = relayRepo.coll.byId[RelayRound](id.value)
def byId(id: RelayRound.Id) = roundRepo.coll.byId[RelayRound](id.value)
def byIdWithTour(id: RelayRound.Id): Fu[Option[RelayRound.WithTour]] =
relayRepo.coll
roundRepo.coll
.aggregateOne() { framework =>
import framework._
Match($id(id)) -> List(
@ -42,7 +42,8 @@ final class RelayApi(
def byIdAndContributor(id: RelayRound.Id, me: User) =
byIdWithStudy(id) map {
_ collect {
case RelayRound.WithTourAndStudy(relay, tour, study) if study.canContribute(me.id) => relay withTour tour
case RelayRound.WithTourAndStudy(relay, tour, study) if study.canContribute(me.id) =>
relay withTour tour
}
}
@ -56,7 +57,9 @@ final class RelayApi(
}
def byTour(tour: RelayTour): Fu[List[RelayRound.WithTour]] =
relayRepo.byTour(tour).dmap(_.map(_ withTour tour))
roundRepo.byTour(tour).dmap(_.map(_ withTour tour))
def withRounds(tour: RelayTour) = roundRepo.byTour(tour).dmap(tour.withRounds)
def fresh(me: Option[User]): Fu[RelayRound.Fresh] = fuccess(RelayRound.Fresh(Nil, Nil))
// relayRepo.scheduled.flatMap(withStudy andLiked me) zip
@ -76,7 +79,7 @@ final class RelayApi(
)
def fetchWithTours(query: Bdoc, maxDocs: Int, readPreference: ReadPreference = ReadPreference.primary) =
relayRepo.coll
roundRepo.coll
.aggregateList(maxDocs, readPreference) { framework =>
import framework._
Match(query) -> List(
@ -91,25 +94,26 @@ final class RelayApi(
tourRepo.coll.insert.one(tour) inject tour
}
def create(data: RelayRoundForm.Data, user: User, tour: RelayTour): Fu[RelayRound] = {
val relay = data.make(user, tour)
relayRepo.coll.insert.one(relay) >>
studyApi.importGame(
StudyMaker.ImportGame(
id = relay.studyId.some,
name = Study.Name(relay.name).some,
settings = Settings.init
.copy(
chat = Settings.UserSelection.Everyone,
sticky = false
)
.some,
from = Study.From.Relay(none).some
),
user
) >>
studyApi.addTopics(relay.studyId, List("Broadcast")) inject relay
}
def create(data: RelayRoundForm.Data, user: User, tour: RelayTour): Fu[RelayRound] =
roundRepo.nextOrderByTour(tour.id) flatMap { order =>
val relay = data.make(user, tour, order)
roundRepo.coll.insert.one(relay) >>
studyApi.importGame(
StudyMaker.ImportGame(
id = relay.studyId.some,
name = Study.Name(relay.name).some,
settings = Settings.init
.copy(
chat = Settings.UserSelection.Everyone,
sticky = false
)
.some,
from = Study.From.Relay(none).some
),
user
) >>
studyApi.addTopics(relay.studyId, List("Broadcast")) inject relay
}
def requestPlay(id: RelayRound.Id, v: Boolean): Funit =
WithRelay(id) { relay =>
@ -126,7 +130,7 @@ final class RelayApi(
studyApi.rename(relay.studyId, Study.Name(relay.name)) >> {
if (relay == from) fuccess(relay)
else
relayRepo.coll.update.one($id(relay.id), relay).void >> {
roundRepo.coll.update.one($id(relay.id), relay).void >> {
(relay.sync.playing != from.sync.playing) ?? publishRelay(relay)
} >>- {
relay.sync.log.events.lastOption.ifTrue(relay.sync.log != from.sync.log).foreach { event =>
@ -148,7 +152,7 @@ final class RelayApi(
)
def getOngoing(id: RelayRound.Id): Fu[Option[RelayRound.WithTour]] =
relayRepo.coll.one[RelayRound]($doc("_id" -> id, "finished" -> false)) flatMap {
roundRepo.coll.one[RelayRound]($doc("_id" -> id, "finished" -> false)) flatMap {
_ ?? { relay =>
tourById(relay.tourId) map2 relay.withTour
}
@ -162,7 +166,7 @@ final class RelayApi(
// .map(jsonView.public)
private[relay] def autoStart: Funit =
relayRepo.coll.list[RelayRound](
roundRepo.coll.list[RelayRound](
$doc(
"startsAt" $lt DateTime.now.plusMinutes(30) // start 30 minutes early to fetch boards
$gt DateTime.now.minusDays(1), // bit late now
@ -177,7 +181,7 @@ final class RelayApi(
}
private[relay] def autoFinishNotSyncing: Funit =
relayRepo.coll.list[RelayRound](
roundRepo.coll.list[RelayRound](
$doc(
"sync.until" $exists false,
"finished" -> false,
@ -198,7 +202,7 @@ final class RelayApi(
byId(id) flatMap { _ ?? f }
private[relay] def onStudyRemove(studyId: String) =
relayRepo.coll.delete.one($id(RelayRound.Id(studyId))).void
roundRepo.coll.delete.one($id(RelayRound.Id(studyId))).void
private[relay] def publishRelay(relay: RelayRound): Funit =
sendToContributors(relay.id, "relayData", jsonView admin relay)

View File

@ -39,12 +39,14 @@ final class RelayPager(roundRepo: RelayRoundRepo, tourRepo: RelayTourRepo, study
roundRepo.coll
.aggregateList(length, readPreference = ReadPreference.secondaryPreferred) { framework =>
import framework._
Match(selector) -> List(
Sort(Descending("startedAt")),
Sort(Descending("startedAt")) -> List(
GroupField("tourId")("round" -> FirstField("$ROOT")),
Skip(offset),
Limit(length),
ReplaceRootField("round"),
AddFields($doc("sync.log" -> $arr())),
PipelineOperator(tourRepo lookup "tourId"),
Unwind("tour")
UnwindField("tour")
)
}
.map(_ flatMap readRelayWithTour)

View File

@ -8,6 +8,7 @@ import lila.user.User
case class RelayRound(
_id: RelayRound.Id,
tourId: RelayTour.Id,
order: Int,
name: String,
description: String,
markup: Option[String] = None,

View File

@ -17,7 +17,7 @@ final class RelayRoundForm {
import RelayRoundForm._
import lila.common.Form.ISODateTimeOrTimestamp
val form = Form(
val roundMapping =
mapping(
"name" -> cleanText(minLength = 3, maxLength = 80),
"description" -> cleanText(minLength = 3, maxLength = 400),
@ -31,11 +31,21 @@ final class RelayRoundForm {
"throttle" -> optional(number(min = 2, max = 60))
)(Data.apply)(Data.unapply)
.verifying("This source requires a round number. See the new form field below.", !_.roundMissing)
def create(trs: RelayTour.WithRounds) = Form {
roundMapping
.verifying(
s"Maximum rounds per tournament: ${RelayTour.maxRelays}",
_ => trs.rounds.sizeIs < RelayTour.maxRelays
)
}.fill(
Data(
name = s"Round ${trs.rounds.size + 1}",
description = ""
)
)
def create = form
def edit(r: RelayRound) = form fill Data.make(r)
def edit(r: RelayRound) = Form(roundMapping) fill Data.make(r)
}
object RelayRoundForm {
@ -84,12 +94,12 @@ object RelayRoundForm {
case class Data(
name: String,
description: String,
markup: Option[String],
syncUrl: Option[String],
syncUrlRound: Option[Int],
credit: Option[String],
startsAt: Option[DateTime],
throttle: Option[Int]
markup: Option[String] = None,
syncUrl: Option[String] = None,
syncUrlRound: Option[Int] = None,
credit: Option[String] = None,
startsAt: Option[DateTime] = None,
throttle: Option[Int] = None
) {
def requiresRound = syncUrl exists RelayRound.Sync.UpstreamUrl.LccRegex.matches
@ -129,10 +139,11 @@ object RelayRoundForm {
log = SyncLog.empty
)
def make(user: User, tour: RelayTour) =
def make(user: User, tour: RelayTour, order: Int) =
RelayRound(
_id = RelayRound.makeId,
tourId = tour.id,
order = order,
name = name,
description = description,
markup = markup,

View File

@ -32,14 +32,20 @@ final private class RelayRoundRepo(val coll: Coll)(implicit ec: scala.concurrent
// .batchSize(batchSize)
// .cursor[Relay](ReadPreference.secondaryPreferred)
def nextOrderByTour(tourId: RelayTour.Id): Fu[Int] =
coll.primitiveOne[Int](selectors.tour(tourId), orderSort, "order") dmap { ~_ + 1 }
def byTour(tour: RelayTour): Fu[List[RelayRound]] =
coll
.find($doc("tourId" -> tour.id))
.sort($sort desc "startsAt")
.find(selectors.tour(tour.id))
.sort(orderSort)
.cursor[RelayRound]()
.list(RelayTour.maxRelays)
private val orderSort = $sort asc "order"
private[relay] object selectors {
def tour(id: RelayTour.Id) = $doc("tourId" -> id)
// def scheduled(official: Boolean) =
// officialOption(official) ++ $doc(
// "startsAt" $gt DateTime.now.minusHours(1),

View File

@ -9,7 +9,7 @@ case class RelayTour(
name: String,
description: String,
markup: Option[String] = None,
owner: User.ID,
ownerId: User.ID,
official: Boolean,
createdAt: DateTime
) {
@ -19,6 +19,8 @@ case class RelayTour(
val s = lila.common.String slugify name
if (s.isEmpty) "-" else s
}
def withRounds(rounds: List[RelayRound]) = RelayTour.WithRounds(this, rounds)
}
object RelayTour {
@ -27,5 +29,7 @@ object RelayTour {
case class Id(value: String) extends AnyVal with StringValue
case class WithRounds(tour: RelayTour, rounds: List[RelayRound])
def makeId = Id(lila.common.ThreadLocalRandom nextString 8)
}

View File

@ -53,7 +53,7 @@ object RelayTourForm {
name = name,
description = description,
markup = markup,
owner = user.id,
ownerId = user.id,
official = ~official && Granter(_.Relay)(user),
createdAt = DateTime.now
)

View File

@ -96,15 +96,7 @@ final class ChapterRepo(val coll: AsyncColl)(implicit
}
def nextOrderByStudy(studyId: Study.Id): Fu[Int] =
coll(
_.primitiveOne[Int](
$studyId(studyId),
$sort desc "order",
"order"
)
) dmap { order =>
~order + 1
}
coll(_.primitiveOne[Int]($studyId(studyId), $sort desc "order", "order")) dmap { ~_ + 1 }
def setConceal(chapterId: Chapter.Id, conceal: Chapter.Ply) =
coll(_.updateField($id(chapterId), "conceal", conceal)).void