diff --git a/modules/game/src/main/Game.scala b/modules/game/src/main/Game.scala index 0f2c0789f7..10e2aa139f 100644 --- a/modules/game/src/main/Game.scala +++ b/modules/game/src/main/Game.scala @@ -503,7 +503,8 @@ private[game] object RawGame { "lmt" -> none[Int], "bm" -> none[Int], "r960" -> none[Boolean], - "me" -> none[RawMetadata]) + "me" -> none[RawMetadata], + "ua" -> none[DateTime]) private[game] lazy val tube = Tube( (__.json update ( diff --git a/modules/tournament/src/main/Env.scala b/modules/tournament/src/main/Env.scala new file mode 100644 index 0000000000..7691bdf17a --- /dev/null +++ b/modules/tournament/src/main/Env.scala @@ -0,0 +1,39 @@ +package lila.tournament + +import com.typesafe.config.Config +import lila.common.PimpedConfig._ +import akka.actor._ +import akka.pattern.pipe + +final class Env( + config: Config, + db: lila.db.Env, + system: ActorSystem, + hub: lila.hub.Env, + scheduler: lila.common.Scheduler) { + + private val settings = new { + val OrganizerName = config getString "organizer.name" + val CollectionTournament = config getString "collection.tournament" + val CollectionRoom = config getString "collection.room" + val MessageTtl = config duration "message.ttl" + val MemoTtl = config duration "memo.ttl" + val UidTimeout = config duration "uid.timeout" + val HubTimeout = config duration "hub.timeout" + } + import settings._ + + private[tournament] lazy val tournamentColl = db(CollectionTournament) +} + +object Env { + + private def app = play.api.Play.current + + lazy val current = "[boot] tournamen" describes new Env( + config = lila.common.PlayApp loadConfig "tournamen", + db = lila.db.Env.current, + system = lila.common.PlayApp.system, + hub = lila.hub.Env.current, + scheduler = lila.common.PlayApp.scheduler) +} diff --git a/modules/tournament/src/main/Pairing.scala b/modules/tournament/src/main/Pairing.scala index 96b88c9fe2..8941e1ab23 100644 --- a/modules/tournament/src/main/Pairing.scala +++ b/modules/tournament/src/main/Pairing.scala @@ -49,15 +49,6 @@ private[tournament] case class Pairing( ) } -private[tournament] case class RawPairing(g: String, s: Int, u: List[String], w: Option[String], t: Option[Int]) { - - def decode: Option[Pairing] = for { - status ← chess.Status(s) - user1 ← u.lift(0) - user2 ← u.lift(1) - } yield Pairing(g, status, user1, user2, w, t) -} - private[tournament] object Pairing { type P = (String, String) @@ -135,3 +126,28 @@ private[tournament] object Pairing { case _ ⇒ Nil } } + +private[tournament] case class RawPairing(g: String, s: Int, u: List[String], w: Option[String], t: Option[Int]) { + + def decode: Option[Pairing] = for { + status ← chess.Status(s) + user1 ← u.lift(0) + user2 ← u.lift(1) + } yield Pairing(g, status, user1, user2, w, t) +} + +private[tournament] object RawPairing { + + import lila.db.Tube + import Tube.Helpers._ + import play.api.libs.json._ + + private def defaults = Json.obj( + "w" -> none[String], + "t" -> none[Int]) + + private[tournament] lazy val tube = Tube( + (__.json update merge(defaults)) andThen Json.reads[RawPairing], + Json.writes[RawPairing] + ) +} diff --git a/modules/tournament/src/main/Player.scala b/modules/tournament/src/main/Player.scala index ddb2861eb4..e04672ad88 100644 --- a/modules/tournament/src/main/Player.scala +++ b/modules/tournament/src/main/Player.scala @@ -22,7 +22,7 @@ private[tournament] case class Player( private[tournament] object Player { - def apply(user: User): Player = new Player( + def make(user: User): Player = new Player( id = user.id, username = user.username, elo = user.elo) @@ -66,4 +66,20 @@ private[tournament] object Player { winStreak = bestWinSeq, score = score) } + + import lila.db.Tube + import Tube.Helpers._ + import play.api.libs.json._ + + private def defaults = Json.obj( + "withdraw" -> false, + "nbWin" -> 0, + "nbLoss" -> 0, + "winStreak" -> 0, + "score" -> 0) + + private[tournament] lazy val tube = Tube( + (__.json update merge(defaults)) andThen Json.reads[Player], + Json.writes[Player] + ) } diff --git a/modules/tournament/src/main/Tournament.scala b/modules/tournament/src/main/Tournament.scala index af5938baf0..2f7b551e04 100644 --- a/modules/tournament/src/main/Tournament.scala +++ b/modules/tournament/src/main/Tournament.scala @@ -8,7 +8,7 @@ import chess.{ Variant, Mode } import lila.user.User import lila.game.PovRef -case class Data( +private[tournament] case class Data( name: String, clock: TournamentClock, minutes: Int, @@ -127,7 +127,7 @@ case class Created( def join(user: User): Valid[Created] = contains(user).fold( !!("User %s is already part of the tournament" format user.id), - withPlayers(players :+ Player(user)).success + withPlayers(players :+ Player.make(user)).success ) def withdraw(userId: String): Valid[Created] = contains(userId).fold( @@ -217,6 +217,45 @@ case class Finished( def encode = encode(Status.Finished) } +object Tournament { + + import lila.db.Tube + import play.api.libs.json._ + + private[tournament] lazy val tube = Tube( + reader = Reads[Tournament](js ⇒ + ~(for { + obj ← js.asOpt[JsObject] + rawTour ← RawTournament.tube.read(obj).asOpt + tour ← rawTour.decode + } yield JsSuccess(tour): JsResult[Tournament]) + ), + writer = Writes[Tournament](tour ⇒ + RawTournament.tube.write(tour.encode) getOrElse JsUndefined("[db] Can't write tournament " + tour.id) + ) + ) + + def apply( + createdBy: User, + clock: TournamentClock, + minutes: Int, + minPlayers: Int, + variant: Variant, + mode: Mode): Created = Created( + id = Random nextString 8, + data = Data( + name = RandomName(), + clock = clock, + createdBy = createdBy.id, + createdAt = DateTime.now, + variant = variant, + mode = mode, + minutes = minutes, + minPlayers = minPlayers), + players = List(Player make createdBy) + ) +} + private[tournament] case class RawTournament( id: String, name: String, @@ -232,6 +271,8 @@ private[tournament] case class RawTournament( variant: Int = Variant.Standard.id, mode: Int = Mode.Casual.id) { + def decode: Option[Tournament] = created orElse started orElse finished + def created: Option[Created] = (status == Status.Created.id) option Created( id = id, data = Data(name, clock, minutes, minPlayers, Variant orDefault variant, Mode orDefault mode, createdAt, createdBy), @@ -266,27 +307,25 @@ private[tournament] case class RawTournament( } } -object Tournament { +private[tournament] object RawTournament { - import lila.common.Form._ + import lila.db.Tube + import Tube.Helpers._ + import play.api.libs.json._ - def apply( - createdBy: User, - clock: TournamentClock, - minutes: Int, - minPlayers: Int, - variant: Variant, - mode: Mode): Created = Created( - id = Random nextString 8, - data = Data( - name = RandomName(), - clock = clock, - createdBy = createdBy.id, - createdAt = DateTime.now, - variant = variant, - mode = mode, - minutes = minutes, - minPlayers = minPlayers), - players = List(Player(createdBy)) + private implicit def pairingTube = RawPairing.tube + private implicit def clockTube = TournamentClock.tube + private implicit def PlayerTube = Player.tube + + private def defaults = Json.obj( + "startedAt" -> none[DateTime]) + + private[tournament] lazy val tube = Tube( + (__.json update ( + merge(defaults) andThen readDateOpt('startedAt) + )) andThen Json.reads[RawTournament], + Json.writes[RawTournament] andThen (__.json update ( + writeDateOpt('startedAt) + )) ) } diff --git a/modules/tournament/src/main/TournamentClock.scala b/modules/tournament/src/main/TournamentClock.scala index 148ce215fc..f94379e738 100644 --- a/modules/tournament/src/main/TournamentClock.scala +++ b/modules/tournament/src/main/TournamentClock.scala @@ -9,3 +9,15 @@ case class TournamentClock(limit: Int, increment: Int) { def chessClock = chess.Clock(limit, increment) } + +object TournamentClock { + + import lila.db.Tube + import Tube.Helpers._ + import play.api.libs.json._ + + private[tournament] lazy val tube = Tube( + Json.reads[TournamentClock], + Json.writes[TournamentClock] + ) +} diff --git a/modules/tournament/src/main/TournamentRepo.scala b/modules/tournament/src/main/TournamentRepo.scala new file mode 100644 index 0000000000..425974cca1 --- /dev/null +++ b/modules/tournament/src/main/TournamentRepo.scala @@ -0,0 +1,77 @@ +package lila.tournament + +import lila.db.api._ +import lila.db.Implicits._ +import tube.tournamentTube + +import play.api.libs.json._ +import org.joda.time.DateTime +import org.scala_tools.time.Imports._ + +// object TournamentRepo { + +// def createdById(id: String): Fu[Option[Created]] = byIdAs(id, _.created) + +// def startedById(id: String): Fu[Option[Started]] = byIdAs(id, _.started) + +// def createdByIdAndCreator(id: String, userId: String): Fu[Option[Created]] = +// createdById(id) map (_ filter (_ isCreator userId)) + +// private def byIdAs[A](id: String, as: Tournament ⇒ Option[A]): Fu[Option[A]] = io { +// $find byId id map (_ flatMap as) +// } + +// def created: Fu[List[Created]] = +// $find($query( +// Json.obj("status" -> Status.Created.id)) sort $sort.createdDesc +// )).toList map2 { (tour: Tournament) => tour.created } + +// def started: Fu[List[Started]] = io { +// find(DBObject("status" -> Status.Started.id)) +// .sort(DBObject("createdAt" -> -1)) +// .toList.map(_.started).flatten +// } + +// def finished(limit: Int): Fu[List[Finished]] = io { +// find(DBObject("status" -> Status.Finished.id)) +// .sort(DBObject("createdAt" -> -1)) +// .limit(limit) +// .toList.map(_.finished).flatten +// } + +// def setUsers(tourId: String, userIds: List[String]): Fu[Unit] = io { +// update(idSelector(tourId), $set(Seq("data.users" -> userIds.distinct))) +// } + +// def userHasRunningTournament(username: String): Fu[Boolean] = io { +// collection.findOne( +// ("status" $ne Status.Finished.id) ++ ("data.users" -> username) +// ).isDefined +// } + +// val inProgressIds: Fu[List[String]] = io { +// primitiveProjections(DBObject("status" -> Status.Started.id), "_id") +// } + +// def saveFu(tournament: Tournament): Fu[Unit] = io { +// save(tournament.encode) +// } + +// def removeFu(tournament: Tournament): Fu[Unit] = io { +// remove(idSelector(tournament)) +// } + +// def withdraw(userId: String): Fu[List[String]] = for { +// createds ← created +// createdIds ← (createds map (_ withdraw userId) collect { +// case Success(tour) ⇒ saveFu(tour) map (_ ⇒ tour.id) +// }).sequence +// starteds ← started +// startedIds ← (starteds map (_ withdraw userId) collect { +// case Success(tour) ⇒ saveFu(tour) map (_ ⇒ tour.id) +// }).sequence +// } yield createdIds ::: startedIds + +// private def idSelector(id: String): DBObject = DBObject("_id" -> id) +// private def idSelector(tournament: Tournament): DBObject = idSelector(tournament.id) +// } diff --git a/modules/tournament/src/main/package.scala b/modules/tournament/src/main/package.scala index df9a825325..325c95f5ae 100644 --- a/modules/tournament/src/main/package.scala +++ b/modules/tournament/src/main/package.scala @@ -6,7 +6,7 @@ package object tournament extends PackageObject with WithPlay { object tube { - // private[tournament] implicit lazy val pgnTube = Tube.json inColl Env.current.pgnColl + private[tournament] implicit lazy val tournamentTube = Tournament.tube inColl Env.current.tournamentColl } private[tournament]type Pairings = List[tournament.Pairing] diff --git a/old/tournament/TournamentRepo.scala b/old/tournament/TournamentRepo.scala deleted file mode 100644 index 0c20d4e042..0000000000 --- a/old/tournament/TournamentRepo.scala +++ /dev/null @@ -1,87 +0,0 @@ -package lila.app -package tournament - -import com.novus.salat._ -import com.novus.salat.dao._ -import com.mongodb.casbah.MongoCollection -import com.mongodb.casbah.query.Imports._ -import scalaz.effects._ -import scalaz.Success -import org.joda.time.DateTime -import org.scala_tools.time.Imports._ - -import user.User - -class TournamentRepo(collection: MongoCollection) - extends SalatDAO[RawTournament, String](collection) { - - def byId(id: String): IO[Option[Tournament]] = byIdAs(id, _.any) - def byId(id: Option[String]): IO[Option[Tournament]] = - id.fold(io(none[Tournament])) { i ⇒ byIdAs(i, _.any) } - - def createdById(id: String): IO[Option[Created]] = byIdAs(id, _.created) - - def startedById(id: String): IO[Option[Started]] = byIdAs(id, _.started) - - def createdByIdAndCreator(id: String, userId: String): IO[Option[Created]] = - createdById(id) map { tour ⇒ tour filter (_ isCreator userId) } - - private def byIdAs[A](id: String, as: RawTournament ⇒ Option[A]): IO[Option[A]] = io { - findOneById(id) flatMap as - } - - val created: IO[List[Created]] = io { - find(DBObject("status" -> Status.Created.id)) - .sort(DBObject("createdAt" -> -1)) - .toList.map(_.created).flatten - } - - val started: IO[List[Started]] = io { - find(DBObject("status" -> Status.Started.id)) - .sort(DBObject("createdAt" -> -1)) - .toList.map(_.started).flatten - } - - def finished(limit: Int): IO[List[Finished]] = io { - find(DBObject("status" -> Status.Finished.id)) - .sort(DBObject("createdAt" -> -1)) - .limit(limit) - .toList.map(_.finished).flatten - } - - def setUsers(tourId: String, userIds: List[String]): IO[Unit] = io { - update(idSelector(tourId), $set(Seq("data.users" -> userIds.distinct))) - } - - def userHasRunningTournament(username: String): IO[Boolean] = io { - collection.findOne( - ("status" $ne Status.Finished.id) ++ ("data.users" -> username) - ).isDefined - } - - val inProgressIds: IO[List[String]] = io { - primitiveProjections(DBObject("status" -> Status.Started.id), "_id") - } - - def saveIO(tournament: Tournament): IO[Unit] = io { - save(tournament.encode) - } - - def removeIO(tournament: Tournament): IO[Unit] = io { - remove(idSelector(tournament)) - } - - def withdraw(userId: String): IO[List[String]] = for { - createds ← created - createdIds ← (createds map (_ withdraw userId) collect { - case Success(tour) ⇒ saveIO(tour) map (_ ⇒ tour.id) - }).sequence - starteds ← started - startedIds ← (starteds map (_ withdraw userId) collect { - case Success(tour) ⇒ saveIO(tour) map (_ ⇒ tour.id) - }).sequence - } yield createdIds ::: startedIds - - private def idSelector(id: String): DBObject = DBObject("_id" -> id) - private def idSelector(tournament: Tournament): DBObject = idSelector(tournament.id) -}