kill the strings! introduce Study.Id type and isomorphism abstractions

practice
Thibault Duplessis 2017-01-20 13:47:52 +01:00
parent 3e87dee7d7
commit 8821c2b7ae
42 changed files with 261 additions and 203 deletions

View File

@ -24,7 +24,7 @@ object Coach extends LilaController {
OptionFuResult(api find username) { c =>
WithVisibleCoach(c) {
Env.study.api.publicByIds {
c.coach.profile.studyIds.map(_.value)
c.coach.profile.studyIds.map(_.value).map(lila.study.Study.Id.apply)
} flatMap Env.study.pager.withChaptersAndLiking(ctx.me) flatMap { studies =>
api.reviews.approvedByCoach(c.coach) flatMap { reviews =>
ctx.me.?? { api.reviews.isPending(_, c.coach) } map { isPending =>

View File

@ -7,8 +7,8 @@ import scala.concurrent.duration._
import lila.api.Context
import lila.app._
import lila.common.HTTPRequest
import lila.study.Order
import lila.study.Study.WithChapter
import lila.study.{ Order, Study => StudyModel }
import views._
object Study extends LilaController {
@ -131,7 +131,7 @@ object Study extends LilaController {
}
private def chatOf(study: lila.study.Study)(implicit ctx: lila.api.Context) =
ctx.noKid ?? Env.chat.api.userChat.findMine(study.id, ctx.me).map(some)
ctx.noKid ?? Env.chat.api.userChat.findMine(study.id.value, ctx.me).map(some)
def websocket(id: String, apiVersion: Int) = SocketOption[JsValue] { implicit ctx =>
get("sri") ?? { uid =>
@ -152,7 +152,7 @@ object Study extends LilaController {
lila.study.DataForm.form.bindFromRequest.fold(
err => Redirect(routes.Study.byOwnerDefault(me.username)).fuccess,
data => env.api.create(data, me) map { sc =>
Redirect(routes.Study.show(sc.study.id))
Redirect(routes.Study.show(sc.study.id.value))
})
}
@ -165,7 +165,7 @@ object Study extends LilaController {
def embed(id: String, chapterId: String) = Open { implicit ctx =>
env.api.byIdWithChapter(id, chapterId) flatMap {
_.fold(embedNotFound) {
case lila.study.Study.WithChapter(study, chapter) => CanViewResult(study) {
case WithChapter(study, chapter) => CanViewResult(study) {
val setup = chapter.setup
val pov = UserAnalysis.makePov(chapter.root.fen.value.some, setup.variant)
Env.round.jsonView.userAnalysisJson(pov, ctx.pref, setup.orientation, owner = false) zip
@ -222,7 +222,7 @@ object Study extends LilaController {
OptionFuResult(env.api.byId(id)) { prev =>
CanViewResult(prev) {
env.api.clone(me, prev) map { study =>
Redirect(routes.Study.show((study | prev).id))
Redirect(routes.Study.show((study | prev).id.value))
}
}
}
@ -257,7 +257,7 @@ object Study extends LilaController {
OnlyHumans {
env.api.byIdWithChapter(id, chapterId) flatMap {
_.fold(notFound) {
case lila.study.Study.WithChapter(study, chapter) => CanViewResult(study) {
case WithChapter(study, chapter) => CanViewResult(study) {
lila.mon.export.pgn.studyChapter()
Ok(env.pgnDump.ofChapter(study, chapter).toString).withHeaders(
CONTENT_TYPE -> pgnContentType,
@ -269,10 +269,12 @@ object Study extends LilaController {
}
}
private def CanViewResult(study: lila.study.Study)(f: => Fu[Result])(implicit ctx: lila.api.Context) =
private def CanViewResult(study: StudyModel)(f: => Fu[Result])(implicit ctx: lila.api.Context) =
if (canView(study)) f
else fuccess(Unauthorized(html.study.restricted(study)))
private def canView(study: lila.study.Study)(implicit ctx: lila.api.Context) =
private def canView(study: StudyModel)(implicit ctx: lila.api.Context) =
study.isPublic || ctx.userId.exists(study.members.contains)
private implicit def makeStudyId(id: String): StudyModel.Id = StudyModel.Id(id)
}

View File

@ -4,7 +4,7 @@
title = s"Clone ${s.name}",
icon = Some(Html("")),
back = false) {
<form action="@routes.Study.cloneApply(s.id)" method="POST">
<form action="@routes.Study.cloneApply(s.id.value)" method="POST">
<p>
This will create a new private study with the same chapters.
</p>
@ -22,7 +22,7 @@ back = false) {
style="margin: 30px auto; display: block; font-size: 2em;">Clone the study</button>
</p>
<p>
<a href="@routes.Study.show(s.id)" class="text" data-icon="I">@trans.cancel()</a>
<a href="@routes.Study.show(s.id.value)" class="text" data-icon="I">@trans.cancel()</a>
</p>
</form>
}

View File

@ -27,7 +27,7 @@
<div class="embedded_study analyse cg-512">@miniBoardContent</div>
</div>
<footer>
@defining(routes.Study.chapter(s.id, chapter.id)) { url =>
@defining(routes.Study.chapter(s.id.value, chapter.id)) { url =>
<div class="left">
<a target="_blank" href="@url"><h1>@s.name</h1></a> <em>brought to you by <a target="_blank" href="@netBaseUrl">@netDomain</a></em>
</div>

View File

@ -17,7 +17,7 @@ explorer: {
endpoint: "@explorerEndpoint",
tablebaseEndpoint: "@tablebaseEndpoint"
},
socketUrl: "@routes.Study.websocket(s.id, apiVersion.value)",
socketUrl: "@routes.Study.websocket(s.id.value, apiVersion.value)",
socketVersion: @socketVersion
};
}

View File

@ -1,5 +1,5 @@
@(s: lila.study.Study.WithChaptersAndLiked)(implicit ctx: Context)
<a class="overlay" href="@routes.Study.show(s.study.id)"></a>
<a class="overlay" href="@routes.Study.show(s.study.id.value)"></a>
<h2>
<i class="icon" data-icon=""></i>
<strong>@s.study.name</strong>

View File

@ -52,6 +52,8 @@ object Analysis {
import lila.db.BSON
import reactivemongo.bson._
type ID = String
private[analyse] implicit val analysisBSONHandler = new BSON[Analysis] {
def reads(r: BSON.Reader) = {
val startPly = r intD "ply"

View File

@ -17,7 +17,7 @@ object AnalysisRepo {
def byId(id: ID): Fu[Option[Analysis]] = coll.byId[Analysis](id)
def byIds(ids: Seq[ID]): Fu[Seq[Option[Analysis]]] =
coll.optionsByOrderedIds[Analysis](ids)(_.id)
coll.optionsByOrderedIds[Analysis, Analysis.ID](ids)(_.id)
def associateToGames(games: List[Game]): Fu[List[(Game, Analysis)]] =
byIds(games.map(_.id)) map { as =>

View File

@ -0,0 +1,24 @@
package lila.common
trait Iso[A, B] {
val from: A => B
val to: B => A
}
object Iso {
type StringIso[B] = Iso[String, B]
type IntIso[B] = Iso[Int, B]
type BooleanIso[B] = Iso[Boolean, B]
def apply[A, B](f: A => B, t: B => A): Iso[A, B] = new Iso[A, B] {
val from = f
val to = t
}
def string[B](from: String => B, to: B => String): StringIso[B] = apply(from, to)
implicit def isoIdentity[A]: Iso[A, A] = apply(identity[A] _, identity[A] _)
implicit val stringIsoIdentity: Iso[String, String] = isoIdentity[String]
}

View File

@ -28,7 +28,7 @@ trait PackageObject extends Steroids with WithFuture {
implicit final def runOptionT[F[+_], A](ot: OptionT[F, A]): F[Option[A]] = ot.run
// from scalaz. We don't want to import all OptionTFunctions, because of the clash with `some`
def optionT[M[_]] = new (({ type λ[α] = M[Option[α]] })#λ ~>({ type λ[α] = OptionT[M, α] })#λ) {
def optionT[M[_]] = new (({ type λ[α] = M[Option[α]] })#λ ~> ({ type λ[α] = OptionT[M, α] })#λ) {
def apply[A](a: M[Option[A]]) = new OptionT[M, A](a)
}

View File

@ -7,8 +7,8 @@ object PimpedJson {
def anyValWriter[O, A: Writes](f: O => A): Writes[O] = Writes[O] { o =>
Json toJson f(o)
}
def intAnyValWriter[O](f: O => Int) = anyValWriter[O, Int](f)
def stringAnyValWriter[O](f: O => String) = anyValWriter[O, String](f)
def intAnyValWriter[O](f: O => Int): Writes[O] = anyValWriter[O, Int](f)
def stringAnyValWriter[O](f: O => String): Writes[O] = anyValWriter[O, String](f)
implicit final class LilaPimpedJsObject(val js: JsObject) extends AnyVal {

View File

@ -1,9 +1,11 @@
package lila.db
import dsl._
import org.joda.time.DateTime
import reactivemongo.bson._
import dsl._
import lila.common.Iso
abstract class BSON[T]
extends BSONHandler[Bdoc, T]
with BSONDocumentReader[T]
@ -46,30 +48,30 @@ object BSON extends Handlers {
object MapDocument {
implicit def MapReader[V](implicit vr: BSONDocumentReader[V]): BSONDocumentReader[Map[String, V]] = new BSONDocumentReader[Map[String, V]] {
def read(bson: Bdoc): Map[String, V] = {
implicit def MapReader[K, V](implicit kIso: Iso.StringIso[K], vr: BSONDocumentReader[V]): BSONDocumentReader[Map[K, V]] = new BSONDocumentReader[Map[K, V]] {
def read(bson: Bdoc): Map[K, V] = {
// mutable optimized implementation
val b = collection.immutable.Map.newBuilder[String, V]
val b = collection.immutable.Map.newBuilder[K, V]
for (tuple <- bson.elements)
// assume that all values in the document are Bdocs
b += (tuple.name -> vr.read(tuple.value.asInstanceOf[Bdoc]))
b += (kIso.from(tuple.name) -> vr.read(tuple.value.asInstanceOf[Bdoc]))
b.result
}
}
implicit def MapWriter[V](implicit vw: BSONDocumentWriter[V]): BSONDocumentWriter[Map[String, V]] = new BSONDocumentWriter[Map[String, V]] {
def write(map: Map[String, V]): Bdoc = BSONDocument {
implicit def MapWriter[K, V](implicit kIso: Iso.StringIso[K], vw: BSONDocumentWriter[V]): BSONDocumentWriter[Map[K, V]] = new BSONDocumentWriter[Map[K, V]] {
def write(map: Map[K, V]): Bdoc = BSONDocument {
map.toStream.map { tuple =>
tuple._1 -> vw.write(tuple._2)
kIso.to(tuple._1) -> vw.write(tuple._2)
}
}
}
implicit def MapHandler[V](implicit vr: BSONDocumentReader[V], vw: BSONDocumentWriter[V]): BSONHandler[Bdoc, Map[String, V]] = new BSONHandler[Bdoc, Map[String, V]] {
private val reader = MapReader[V]
private val writer = MapWriter[V]
def read(bson: Bdoc): Map[String, V] = reader read bson
def write(map: Map[String, V]): Bdoc = writer write map
implicit def MapHandler[K, V](implicit kIso: Iso.StringIso[K], vr: BSONDocumentReader[V], vw: BSONDocumentWriter[V]): BSONHandler[Bdoc, Map[K, V]] = new BSONHandler[Bdoc, Map[K, V]] {
private val reader = MapReader[K, V]
private val writer = MapWriter[K, V]
def read(bson: Bdoc): Map[K, V] = reader read bson
def write(map: Map[K, V]): Bdoc = writer write map
}
}

View File

@ -34,7 +34,7 @@ trait CollExt { self: dsl with QueryBuilderExt =>
def exists(selector: Bdoc): Fu[Boolean] = countSel(selector).map(0!=)
def byOrderedIds[D: BSONDocumentReader](ids: Iterable[String], readPreference: ReadPreference = ReadPreference.primary)(docId: D => String): Fu[List[D]] =
def byOrderedIds[D: BSONDocumentReader, I: BSONValueWriter](ids: Iterable[I], readPreference: ReadPreference = ReadPreference.primary)(docId: D => I): Fu[List[D]] =
coll.find($inIds(ids)).cursor[D](readPreference = readPreference).
collect[List](Int.MaxValue, err = Cursor.FailOnError[List[D]]()).
map { docs =>
@ -45,8 +45,8 @@ trait CollExt { self: dsl with QueryBuilderExt =>
// def byOrderedIds[A <: Identified[String]: TubeInColl](ids: Iterable[String]): Fu[List[A]] =
// byOrderedIds[String, A](ids)
def optionsByOrderedIds[D: BSONDocumentReader](ids: Iterable[String])(docId: D => String): Fu[List[Option[D]]] =
byIds[D](ids) map { docs =>
def optionsByOrderedIds[D: BSONDocumentReader, I: BSONValueWriter](ids: Iterable[I])(docId: D => I): Fu[List[Option[D]]] =
byIds[D, I](ids) map { docs =>
val docsMap = docs.map(u => docId(u) -> u).toMap
ids.map(docsMap.get).toList
}

View File

@ -4,6 +4,9 @@ import org.joda.time.DateTime
import reactivemongo.bson._
import scalaz.NonEmptyList
import lila.common.Iso
import lila.common.Iso._
trait Handlers {
implicit object BSONJodaDateTimeHandler extends BSONHandler[BSONDateTime, DateTime] {
@ -11,26 +14,23 @@ trait Handlers {
def write(x: DateTime) = BSONDateTime(x.getMillis)
}
def stringAnyValHandler[A](from: A => String, to: String => A) = new BSONHandler[BSONString, A] {
def read(x: BSONString) = to(x.value)
def write(x: A) = BSONString(from(x))
}
def intAnyValHandler[A](from: A => Int, to: Int => A) = new BSONHandler[BSONInteger, A] {
def read(x: BSONInteger) = to(x.value)
def write(x: A) = BSONInteger(from(x))
}
def booleanAnyValHandler[A](from: A => Boolean, to: Boolean => A) = new BSONHandler[BSONBoolean, A] {
def read(x: BSONBoolean) = to(x.value)
def write(x: A) = BSONBoolean(from(x))
}
def dateAnyValHandler[A](from: A => DateTime, to: DateTime => A) = new BSONHandler[BSONDateTime, A] {
def read(x: BSONDateTime) = to(BSONJodaDateTimeHandler read x)
def write(x: A) = BSONJodaDateTimeHandler write from(x)
}
def isoHandler[A, B, C <: BSONValue](from: A => B, to: B => A)(implicit handler: BSONHandler[C, B]) = new BSONHandler[C, A] {
def read(x: C): A = to(handler read x)
def write(x: A): C = handler write from(x)
def isoHandler[A, B, C <: BSONValue](iso: Iso[B, A])(implicit handler: BSONHandler[C, B]): BSONHandler[C, A] = new BSONHandler[C, A] {
def read(x: C): A = iso.from(handler read x)
def write(x: A): C = handler write iso.to(x)
}
def isoHandler[A, B, C <: BSONValue](to: A => B, from: B => A)(implicit handler: BSONHandler[C, B]): BSONHandler[C, A] =
isoHandler(Iso(from, to))
def stringIsoHandler[A](implicit iso: StringIso[A]): BSONHandler[BSONString, A] = isoHandler[A, String, BSONString](iso)
def stringAnyValHandler[A](to: A => String, from: String => A): BSONHandler[BSONString, A] = stringIsoHandler(Iso(from, to))
def intIsoHandler[A](implicit iso: IntIso[A]): BSONHandler[BSONInteger, A] = isoHandler[A, Int, BSONInteger](iso)
def intAnyValHandler[A](to: A => Int, from: Int => A): BSONHandler[BSONInteger, A] = intIsoHandler(Iso(from, to))
def booleanIsoHandler[A](implicit iso: BooleanIso[A]): BSONHandler[BSONBoolean, A] = isoHandler[A, Boolean, BSONBoolean](iso)
def booleanAnyValHandler[A](to: A => Boolean, from: Boolean => A): BSONHandler[BSONBoolean, A] = booleanIsoHandler(Iso(from, to))
def dateIsoHandler[A](implicit iso: Iso[DateTime, A]): BSONHandler[BSONDateTime, A] = isoHandler[A, DateTime, BSONDateTime](iso)
implicit def bsonArrayToListHandler[T](implicit reader: BSONReader[_ <: BSONValue, T], writer: BSONWriter[T, _ <: BSONValue]): BSONHandler[BSONArray, List[T]] = new BSONHandler[BSONArray, List[T]] {
def read(array: BSONArray) = readStream(array, reader.asInstanceOf[BSONReader[BSONValue, T]]).toList

View File

@ -62,6 +62,8 @@ case class Post(
object Post {
type ID = String
val idSize = 8
def make(

View File

@ -121,8 +121,8 @@ final class PostApi(
} yield PostView(post, topic, categ, lastPageOf(topic))
} flatten
def viewsFromIds(postIds: Seq[String]): Fu[List[PostView]] =
env.postColl.byOrderedIds[Post](postIds)(_.id) flatMap views
def viewsFromIds(postIds: Seq[Post.ID]): Fu[List[PostView]] =
env.postColl.byOrderedIds[Post, Post.ID](postIds)(_.id) flatMap views
def view(post: Post): Fu[Option[PostView]] =
views(List(post)) map (_.headOption)

View File

@ -26,13 +26,13 @@ object GameRepo {
def game(gameId: ID): Fu[Option[Game]] = coll.byId[Game](gameId)
def gamesFromPrimary(gameIds: Seq[ID]): Fu[List[Game]] = coll.byOrderedIds[Game](gameIds)(_.id)
def gamesFromPrimary(gameIds: Seq[ID]): Fu[List[Game]] = coll.byOrderedIds[Game, Game.ID](gameIds)(_.id)
def gamesFromSecondary(gameIds: Seq[ID]): Fu[List[Game]] =
coll.byOrderedIds[Game](gameIds, readPreference = ReadPreference.secondaryPreferred)(_.id)
coll.byOrderedIds[Game, Game.ID](gameIds, readPreference = ReadPreference.secondaryPreferred)(_.id)
def gameOptions(gameIds: Seq[ID]): Fu[Seq[Option[Game]]] =
coll.optionsByOrderedIds[Game](gameIds)(_.id)
coll.optionsByOrderedIds[Game, Game.ID](gameIds)(_.id)
def finished(gameId: ID): Fu[Option[Game]] =
coll.uno[Game]($id(gameId) ++ Query.finished)
@ -68,7 +68,7 @@ object GameRepo {
def remove(id: ID) = coll.remove($id(id)).void
def userPovsByGameIds(gameIds: List[String], user: User): Fu[List[Pov]] =
coll.byOrderedIds[Game](gameIds)(_.id) map { _.flatMap(g => Pov(g, user)) }
coll.byOrderedIds[Game, Game.ID](gameIds)(_.id) map { _.flatMap(g => Pov(g, user)) }
def recentPovsByUser(user: User, nb: Int): Fu[List[Pov]] =
coll.find(Query user user).sort(Query.sortCreated).cursor[Game]().gather[List](nb)

View File

@ -1,8 +1,10 @@
package lila.learn
import reactivemongo.bson._
import lila.common.Iso
import lila.db.BSON
import lila.db.dsl._
import reactivemongo.bson._
object BSONHandlers {
@ -11,7 +13,8 @@ object BSONHandlers {
private implicit val ScoreHandler = intAnyValHandler[Score](_.value, Score.apply)
private implicit val ScoresHandler = bsonArrayToVectorHandler[Score]
private implicit val StageProgressHandler =
isoHandler[StageProgress, Vector[Score], BSONArray](_.scores, StageProgress.apply)(ScoresHandler)
isoHandler[StageProgress, Vector[Score], BSONArray](
(s: StageProgress) => s.scores, StageProgress.apply _)(ScoresHandler)
implicit val LearnProgressIdHandler = new BSONHandler[BSONString, LearnProgress.Id] {
def read(bs: BSONString): LearnProgress.Id =

View File

@ -19,13 +19,13 @@ case class LearnProgress(
object LearnProgress {
sealed trait Id {
sealed trait Id extends Any {
def str: String
}
case class UserId(value: String) extends Id {
case class UserId(value: String) extends AnyVal with Id {
def str = value
}
case class AnonId(value: String) extends Id {
case class AnonId(value: String) extends AnyVal with Id {
def str = s"anon:$value"
}

View File

@ -2,23 +2,32 @@ package lila.practice
import lila.db.BSON
import lila.db.dsl._
import lila.study.Study
import reactivemongo.bson._
object BSONHandlers {
import lila.study.BSONHandlers.{ StudyIdBSONHandler }
import StudyProgress.NbMoves
private implicit val NbMovesHandler = intAnyValHandler[NbMoves](_.value, NbMoves.apply)
private implicit val ChapterNbMovesHandler = BSON.MapValue.MapHandler[NbMoves]
private implicit val StudyProgressHandler =
isoHandler[StudyProgress, StudyProgress.ChapterNbMoves, BSONDocument](_.moves, StudyProgress.apply)(ChapterNbMovesHandler)
// private implicit val nbMovesHandler = intAnyValHandler[NbMoves](_.value, NbMoves.apply)
// private implicit val chapterNbMovesHandler = BSON.MapValue.MapHandler[NbMoves]
// private implicit val studyProgressHandler: BSONHandler[Bdoc, StudyProgress] =
// isoHandler[StudyProgress, StudyProgress.ChapterNbMoves, BSONDocument]((s: StudyProgress) => s.moves, StudyProgress.apply _)(ChapterNbMovesHandler)
implicit val PracticeProgressIdHandler = new BSONHandler[BSONString, PracticeProgress.Id] {
def read(bs: BSONString): PracticeProgress.Id =
if (bs.value startsWith "anon:") PracticeProgress.AnonId(bs.value drop 5)
else PracticeProgress.UserId(bs.value)
def write(x: PracticeProgress.Id) = BSONString(x.str)
}
private implicit val PracticeProgressStudiesHandler = BSON.MapValue.MapHandler[StudyProgress]
implicit val PracticeProgressHandler = Macros.handler[PracticeProgress]
// implicit val practiceProgressIdHandler = new BSONHandler[BSONString, PracticeProgress.Id] {
// def read(bs: BSONString): PracticeProgress.Id =
// if (bs.value startsWith "anon:") PracticeProgress.AnonId(bs.value drop 5)
// else PracticeProgress.UserId(bs.value)
// def write(x: PracticeProgress.Id) = BSONString(x.str)
// }
// import Study.idIso
// private implicit val practiceProgressStudiesHandler =
// BSON.MapDocument.MapHandler[Study.Id, StudyProgress](
// idIso,
// studyProgressHandler,
// studyProgressHandler)
// implicit val practiceProgressHandler = Macros.handler[PracticeProgress]
}

View File

@ -7,17 +7,17 @@ final class PracticeApi(coll: Coll) {
import BSONHandlers._
def get(user: User): Fu[PracticeProgress] =
coll.uno[PracticeProgress]($id(user.id)) map { _ | PracticeProgress.empty(PracticeProgress.UserId(user.id)) }
// def get(user: User): Fu[PracticeProgress] =
// coll.uno[PracticeProgress]($id(user.id)) map { _ | PracticeProgress.empty(PracticeProgress.UserId(user.id)) }
private def save(p: PracticeProgress): Funit =
coll.update($id(p.id), p, upsert = true).void
// private def save(p: PracticeProgress): Funit =
// coll.update($id(p.id), p, upsert = true).void
def setScore(user: User, stage: String, level: Int, score: StageProgress.Score) =
get(user) flatMap { prog =>
save(prog.withScore(stage, level, score))
}
// def setScore(user: User, stage: String, level: Int, score: StageProgress.Score) =
// get(user) flatMap { prog =>
// save(prog.withScore(stage, level, score))
// }
def reset(user: User) =
coll.remove($id(user.id)).void
// def reset(user: User) =
// coll.remove($id(user.id)).void
}

View File

@ -4,28 +4,28 @@ import org.joda.time.DateTime
case class PracticeProgress(
_id: PracticeProgress.Id,
studies: Map[lila.study.Study.ID, StudyProgress],
studies: Map[lila.study.Study.Id, StudyProgress],
createdAt: DateTime,
updatedAt: DateTime) {
def id = _id
def withNbMoves(chapterId: , level: Int, s: StageProgress.Score) = copy(
studies = stages + (
stage -> stages.getOrElse(stage, StageProgress empty stage).withScore(level, s)
),
updatedAt = DateTime.now)
// def withNbMoves(chapterId: String, level: Int, s: StageProgress.Score) = copy(
// studies = stages + (
// stage -> stages.getOrElse(stage, StageProgress empty stage).withScore(level, s)
// ),
// updatedAt = DateTime.now)
}
object PracticeProgress {
sealed trait Id extends AnyVal {
sealed trait Id extends Any {
def str: String
}
case class UserId(value: String) extends Id {
case class UserId(value: String) extends AnyVal with Id {
def str = value
}
case class AnonId(value: String) extends Id {
case class AnonId(value: String) extends AnyVal with Id {
def str = s"anon:$value"
}

View File

@ -4,10 +4,10 @@ case class StudyProgress(moves: StudyProgress.ChapterNbMoves) extends AnyVal {
import StudyProgress._
def withScore(level: Int, s: Score) = copy(
scores = (0 until scores.size.max(level)).map { i =>
scores.lift(i) | Score(0)
}.updated(level - 1, s).toVector)
// def withScore(level: Int, s: Score) = copy(
// scores = (0 until scores.size.max(level)).map { i =>
// scores.lift(i) | Score(0)
// }.updated(level - 1, s).toVector)
}
object StudyProgress {

View File

@ -1,20 +1,20 @@
package lila.search
case class Index(name: String)
case class Index(name: String) extends AnyVal
case class Id(value: String)
case class Id(value: String) extends AnyVal
case class StringQuery(value: String)
case class From(value: Int)
case class Size(value: Int)
case class StringQuery(value: String) extends AnyVal
case class From(value: Int) extends AnyVal
case class Size(value: Int) extends AnyVal
case class SearchResponse(ids: List[String])
case class SearchResponse(ids: List[String]) extends AnyVal
object SearchResponse {
def apply(txt: String): SearchResponse = SearchResponse(txt.split(',').toList)
}
case class CountResponse(count: Int)
case class CountResponse(count: Int) extends AnyVal
object CountResponse {
def apply(txt: String): CountResponse = CountResponse(~parseIntOption(txt))

View File

@ -12,10 +12,15 @@ import lila.db.BSON.{ Reader, Writer }
import lila.db.dsl._
import lila.tree.Node.{ Shape, Shapes }
private object BSONHandlers {
import lila.common.Iso
import lila.common.Iso._
object BSONHandlers {
import Chapter._
implicit val StudyIdBSONHandler = stringIsoHandler[Study.Id](Study.idIso)
private implicit val PosBSONHandler = new BSONHandler[BSONString, Pos] {
def read(bsonStr: BSONString): Pos = Pos.posAt(bsonStr.value) err s"No such pos: ${bsonStr.value}"
def write(x: Pos) = BSONString(x.key)
@ -110,10 +115,10 @@ private object BSONHandlers {
"b" -> w.strO(writePocket(s.pockets.black)))
}
private implicit val GlyphsBSONHandler = new BSONHandler[BSONArray, Glyphs] {
private implicit val GlyphsBSONHandler = new BSONHandler[Barr, Glyphs] {
private val idsHandler = bsonArrayToListHandler[Int]
def read(b: BSONArray) = Glyphs.fromList(idsHandler read b flatMap Glyph.find)
def write(x: Glyphs) = BSONArray(x.toList.map(_.id).map(BSONInteger.apply))
def read(b: Barr) = Glyphs.fromList(idsHandler read b flatMap Glyph.find)
def write(x: Glyphs) = $arr(x.toList.map(_.id).map(BSONInteger.apply))
}
private implicit def NodeBSONHandler: BSON[Node] = new BSON[Node] {
@ -162,9 +167,9 @@ private object BSONHandlers {
"z" -> s.crazyData,
"n" -> s.children)
}
implicit val ChildrenBSONHandler = new BSONHandler[BSONArray, Node.Children] {
implicit val ChildrenBSONHandler = new BSONHandler[Barr, Node.Children] {
private val nodesHandler = bsonArrayToVectorHandler[Node]
def read(b: BSONArray) = try {
def read(b: Barr) = try {
Node.Children(nodesHandler read b)
}
catch {
@ -204,7 +209,7 @@ private object BSONHandlers {
implicit val ChapterBSONHandler = Macros.handler[Chapter]
implicit val ChapterMetadataBSONHandler = Macros.handler[Chapter.Metadata]
private implicit val ChaptersMap = BSON.MapDocument.MapHandler[Chapter]
private implicit val ChaptersMap = BSON.MapDocument.MapHandler[Chapter.ID, Chapter]
implicit val PositionRefBSONHandler = new BSONHandler[BSONString, Position.Ref] {
def read(b: BSONString) = Position.Ref.decode(b.value) err s"Invalid position ${b.value}"
@ -220,7 +225,7 @@ private object BSONHandlers {
def write(x: StudyMember) = DbMemberBSONHandler write DbMember(x.role, x.addedAt)
}
private[study] implicit val MembersBSONHandler = new BSONHandler[Bdoc, StudyMembers] {
private val mapHandler = BSON.MapDocument.MapHandler[DbMember]
private val mapHandler = BSON.MapDocument.MapHandler[String, DbMember]
def read(b: Bdoc) = StudyMembers(mapHandler read b map {
case (id, dbMember) => id -> StudyMember(id, dbMember.role, dbMember.addedAt)
})
@ -236,7 +241,7 @@ private object BSONHandlers {
def read(bs: BSONString) = bs.value.split(' ') match {
case Array("scratch") => From.Scratch
case Array("game", id) => From.Game(id)
case Array("study", id) => From.Study(id)
case Array("study", id) => From.Study(Study.Id(id))
case _ => sys error s"Invalid from ${bs.value}"
}
def write(x: From) = BSONString(x match {
@ -262,7 +267,7 @@ private object BSONHandlers {
import Study.Likes
private[study] implicit val LikesBSONHandler = intAnyValHandler[Likes](_.value, Likes.apply)
import Study.Rank
private[study] implicit val RankBSONHandler = dateAnyValHandler[Rank](_.value, Rank.apply)
private[study] implicit val RankBSONHandler = dateIsoHandler[Rank](Iso[DateTime, Rank](Rank.apply, _.value))
implicit val StudyBSONHandler = Macros.handler[Study]
}

View File

@ -11,7 +11,7 @@ import lila.user.User
case class Chapter(
_id: Chapter.ID,
studyId: Study.ID,
studyId: Study.Id,
name: String,
setup: Chapter.Setup,
root: Node.Root,
@ -96,7 +96,7 @@ object Chapter {
def compare(that: Ply) = value - that.value
}
case class FullId(studyId: Study.ID, chapterId: Chapter.ID)
case class FullId(studyId: Study.Id, chapterId: Chapter.ID)
private val defaultNamePattern = """^Chapter \d+$""".r.pattern
def isDefaultName(str: String) = defaultNamePattern.matcher(str).matches
@ -107,7 +107,7 @@ object Chapter {
def makeId = scala.util.Random.alphanumeric take idSize mkString
def make(studyId: Study.ID, name: String, setup: Setup, root: Node.Root, tags: List[Tag], order: Int, ownerId: User.ID, practice: Boolean, conceal: Option[Ply]) = Chapter(
def make(studyId: Study.Id, name: String, setup: Setup, root: Node.Root, tags: List[Tag], order: Int, ownerId: User.ID, practice: Boolean, conceal: Option[Ply]) = Chapter(
_id = makeId,
studyId = studyId,
name = toName(name),

View File

@ -19,20 +19,20 @@ final class ChapterRepo(coll: Coll) {
def deleteByStudy(s: Study): Funit = coll.remove($studyId(s.id)).void
def byIdAndStudy(id: Chapter.ID, studyId: Study.ID): Fu[Option[Chapter]] =
def byIdAndStudy(id: Chapter.ID, studyId: Study.Id): Fu[Option[Chapter]] =
coll.byId[Chapter](id).map { _.filter(_.studyId == studyId) }
def firstByStudy(studyId: Study.ID): Fu[Option[Chapter]] =
def firstByStudy(studyId: Study.Id): Fu[Option[Chapter]] =
coll.find($studyId(studyId)).sort($sort asc "order").one[Chapter]
def orderedMetadataByStudy(studyId: Study.ID): Fu[List[Chapter.Metadata]] =
def orderedMetadataByStudy(studyId: Study.Id): Fu[List[Chapter.Metadata]] =
coll.find(
$studyId(studyId),
noRootProjection
).sort($sort asc "order").list[Chapter.Metadata](maxChapters)
// loads all study chapters in memory! only used for search indexing and cloning
def orderedByStudy(studyId: Study.ID): Fu[List[Chapter]] =
def orderedByStudy(studyId: Study.Id): Fu[List[Chapter]] =
coll.find($studyId(studyId))
.sort($sort asc "order")
.cursor[Chapter](readPreference = ReadPreference.secondaryPreferred)
@ -43,7 +43,7 @@ final class ChapterRepo(coll: Coll) {
coll.updateField($studyId(study.id) ++ $id(id), "order", index + 1)
}.sequenceFu.void
def nextOrderByStudy(studyId: Study.ID): Fu[Int] =
def nextOrderByStudy(studyId: Study.Id): Fu[Int] =
coll.primitiveOne[Int](
$studyId(studyId),
$sort desc "order",
@ -59,15 +59,15 @@ final class ChapterRepo(coll: Coll) {
def setTagsFor(chapter: Chapter) =
coll.updateField($id(chapter.id), "tags", chapter.tags).void
private[study] def namesByStudyIds(studyIds: Seq[Study.ID]): Fu[Map[Study.ID, Vector[String]]] =
private[study] def namesByStudyIds(studyIds: Seq[Study.Id]): Fu[Map[Study.Id, Vector[String]]] =
coll.find(
$doc("studyId" $in studyIds),
$doc("studyId" -> true, "name" -> true)
).sort($sort asc "order").list[Bdoc]().map { docs =>
docs.foldLeft(Map.empty[Study.ID, Vector[String]]) {
docs.foldLeft(Map.empty[Study.Id, Vector[String]]) {
case (hash, doc) => {
for {
studyId <- doc.getAs[String]("studyId")
studyId <- doc.getAs[Study.Id]("studyId")
name <- doc.getAs[String]("name")
} yield hash + (studyId -> (hash.get(studyId) match {
case None => Vector(name)
@ -77,7 +77,7 @@ final class ChapterRepo(coll: Coll) {
}
}
def countByStudyId(studyId: Study.ID): Fu[Int] =
def countByStudyId(studyId: Study.Id): Fu[Int] =
coll.countSel($studyId(studyId))
def insert(s: Chapter): Funit = coll.insert(s).void
@ -87,5 +87,5 @@ final class ChapterRepo(coll: Coll) {
def delete(id: Chapter.ID): Funit = coll.remove($id(id)).void
def delete(c: Chapter): Funit = delete(c.id)
private def $studyId(id: Study.ID) = $doc("studyId" -> id)
private def $studyId(id: Study.Id) = $doc("studyId" -> id)
}

View File

@ -37,7 +37,7 @@ final class Env(
private val socketHub = system.actorOf(
Props(new lila.socket.SocketHubActor.Default[Socket] {
def mkActor(studyId: String) = new Socket(
studyId = studyId,
studyId = Study.Id(studyId),
jsonView = jsonView,
studyRepo = studyRepo,
lightUser = getLightUser,
@ -47,8 +47,8 @@ final class Env(
socketTimeout = SocketTimeout)
}), name = SocketName)
def version(studyId: Study.ID): Fu[Int] =
socketHub ? Ask(studyId, GetVersion) mapTo manifest[Int]
def version(studyId: Study.Id): Fu[Int] =
socketHub ? Ask(studyId.value, GetVersion) mapTo manifest[Int]
lazy val socketHandler = new SocketHandler(
hub = hub,

View File

@ -93,6 +93,8 @@ object JsonView {
case class JsData(study: JsObject, analysis: JsObject)
implicit val studyIdWrites: Writes[Study.Id] = stringAnyValWriter[Study.Id](_.value)
private implicit val uciWrites: Writes[Uci] = Writes[Uci] { u =>
JsString(u.uci)
}

View File

@ -42,7 +42,7 @@ final class PgnDump(
s"lichess_study_${slugify(study.name)}_${slugify(chapter.name)}_by_${ownerName(study)}_${date}.pgn", "")
}
private def studyUrl(id: String) = s"$netBaseUrl/study/$id"
private def studyUrl(id: Study.Id) = s"$netBaseUrl/study/$id"
private val dateFormat = DateTimeFormat forPattern "yyyy.MM.dd";

View File

@ -15,7 +15,7 @@ import lila.tree.Node.{ Shapes, Comment }
import lila.user.User
private final class Socket(
studyId: String,
studyId: Study.Id,
jsonView: JsonView,
studyRepo: StudyRepo,
lightUser: lila.common.LightUser.Getter,

View File

@ -37,7 +37,7 @@ private[study] final class SocketHandler(
private def controller(
socket: ActorRef,
studyId: Study.ID,
studyId: Study.Id,
uid: Uid,
member: Socket.Member,
owner: Boolean): Handler.Controller = ({
@ -234,7 +234,7 @@ private[study] final class SocketHandler(
v <- (o \ "d" \ "liked").asOpt[Boolean]
} api.like(studyId, byUserId, v, socket, uid)
}: Handler.Controller) orElse lila.chat.Socket.in(
chatId = studyId,
chatId = studyId.value,
member = member,
socket = socket,
chat = chat)
@ -254,11 +254,11 @@ private[study] final class SocketHandler(
private implicit val setTagReader = Json.reads[actorApi.SetTag]
def join(
studyId: Study.ID,
studyId: Study.Id,
uid: Uid,
user: Option[User],
owner: Boolean): Fu[Option[JsSocketHandler]] = for {
socket socketHub ? Get(studyId) mapTo manifest[ActorRef]
socket socketHub ? Get(studyId.value) mapTo manifest[ActorRef]
join = Socket.Join(uid = uid, userId = user.map(_.id), troll = user.??(_.troll), owner = owner)
handler Handler(hub, socket, uid.value, join) {
case Socket.Connected(enum, member) =>

View File

@ -5,7 +5,7 @@ import org.joda.time.DateTime
import lila.user.User
case class Study(
_id: Study.ID,
_id: Study.Id,
name: String,
members: StudyMembers,
position: Position.Ref,
@ -65,6 +65,9 @@ case class Study(
object Study {
case class Id(value: String) extends AnyVal with StringValue
implicit val idIso = lila.common.Iso.string[Id](Id.apply, _.value)
def toName(str: String) = str.trim take 100
sealed trait Visibility {
@ -94,7 +97,7 @@ object Study {
object From {
case object Scratch extends From
case class Game(id: String) extends From
case class Study(id: String) extends From
case class Study(id: Id) extends From
}
case class Data(
@ -120,11 +123,9 @@ object Study {
case class WithChaptersAndLiked(study: Study, chapters: Seq[String], liked: Boolean)
type ID = String
val idSize = 8
def makeId = scala.util.Random.alphanumeric take idSize mkString
def makeId = Id(scala.util.Random.alphanumeric take idSize mkString)
def make(user: User, from: From) = {
val owner = StudyMember(

View File

@ -30,14 +30,14 @@ final class StudyApi(
def byIds = studyRepo byOrderedIds _
def publicByIds(ids: Seq[String]) = byIds(ids) map { _.filter(_.isPublic) }
def publicByIds(ids: Seq[Study.Id]) = byIds(ids) map { _.filter(_.isPublic) }
private def fetchAndFixChapter(id: Chapter.ID): Fu[Option[Chapter]] =
chapterRepo.byId(id) flatMap {
_ ?? { c => tagsFixer(c) map some }
}
def byIdWithChapter(id: Study.ID): Fu[Option[Study.WithChapter]] = byId(id) flatMap {
def byIdWithChapter(id: Study.Id): Fu[Option[Study.WithChapter]] = byId(id) flatMap {
_ ?? { study =>
fetchAndFixChapter(study.position.chapterId) flatMap {
case None => chapterRepo.firstByStudy(study.id) flatMap {
@ -52,7 +52,7 @@ final class StudyApi(
}
}
def byIdWithChapter(id: Study.ID, chapterId: Chapter.ID): Fu[Option[Study.WithChapter]] = byId(id) flatMap {
def byIdWithChapter(id: Study.Id, chapterId: Chapter.ID): Fu[Option[Study.WithChapter]] = byId(id) flatMap {
_ ?? { study =>
fetchAndFixChapter(chapterId) map {
_.filter(_.studyId == study.id) map { Study.WithChapter(study, _) }
@ -77,7 +77,7 @@ final class StudyApi(
studyRepo.insert(study) >>
newChapters.map(chapterRepo.insert).sequenceFu >>- {
chat ! lila.chat.actorApi.SystemTalk(
study.id,
study.id.value,
s"Cloned from lichess.org/study/${prev.id}")
} inject study.some
}
@ -92,23 +92,23 @@ final class StudyApi(
case _ => fuccess(study)
}
private def scheduleTimeline(studyId: Study.ID) = scheduler.scheduleOnce(1 minute) {
private def scheduleTimeline(studyId: Study.Id) = scheduler.scheduleOnce(1 minute) {
byId(studyId) foreach {
_.filter(_.isPublic) foreach { study =>
timeline ! (Propagate(StudyCreate(study.ownerId, study.id, study.name)) toFollowersOf study.ownerId)
timeline ! (Propagate(StudyCreate(study.ownerId, study.id.value, study.name)) toFollowersOf study.ownerId)
}
}
}
def talk(userId: User.ID, studyId: Study.ID, text: String, socket: ActorRef) = byId(studyId) foreach {
def talk(userId: User.ID, studyId: Study.Id, text: String, socket: ActorRef) = byId(studyId) foreach {
_ foreach { study =>
(study canChat userId) ?? {
chat ! lila.chat.actorApi.UserTalk(studyId, userId, text)
chat ! lila.chat.actorApi.UserTalk(studyId.value, userId, text)
}
}
}
def setPath(userId: User.ID, studyId: Study.ID, position: Position.Ref, uid: Uid) = sequenceStudy(studyId) { study =>
def setPath(userId: User.ID, studyId: Study.Id, position: Position.Ref, uid: Uid) = sequenceStudy(studyId) { study =>
Contribute(userId, study) {
chapterRepo.byId(position.chapterId).map {
_ filter { c =>
@ -125,7 +125,7 @@ final class StudyApi(
}
}
def addNode(userId: User.ID, studyId: Study.ID, position: Position.Ref, node: Node, uid: Uid) = sequenceStudyWithChapter(studyId) {
def addNode(userId: User.ID, studyId: Study.Id, position: Position.Ref, node: Node, uid: Uid) = sequenceStudyWithChapter(studyId) {
case Study.WithChapter(study, chapter) => Contribute(userId, study) {
chapter.addNode(node, position.path) match {
case None => fufail(s"Invalid addNode $studyId $position $node") >>- reloadUid(study, uid)
@ -150,7 +150,7 @@ final class StudyApi(
}
}
def deleteNodeAt(userId: User.ID, studyId: Study.ID, position: Position.Ref, uid: Uid) = sequenceStudyWithChapter(studyId) {
def deleteNodeAt(userId: User.ID, studyId: Study.Id, position: Position.Ref, uid: Uid) = sequenceStudyWithChapter(studyId) {
case Study.WithChapter(study, chapter) => Contribute(userId, study) {
chapter.updateRoot { root =>
root.withChildren(_.deleteNodeAt(position.path))
@ -163,7 +163,7 @@ final class StudyApi(
}
}
def promote(userId: User.ID, studyId: Study.ID, position: Position.Ref, toMainline: Boolean, uid: Uid) = sequenceStudyWithChapter(studyId) {
def promote(userId: User.ID, studyId: Study.Id, position: Position.Ref, toMainline: Boolean, uid: Uid) = sequenceStudyWithChapter(studyId) {
case Study.WithChapter(study, chapter) => Contribute(userId, study) {
chapter.updateRoot { root =>
root.withChildren { children =>
@ -179,14 +179,14 @@ final class StudyApi(
}
}
def setRole(byUserId: User.ID, studyId: Study.ID, userId: User.ID, roleStr: String) = sequenceStudy(studyId) { study =>
def setRole(byUserId: User.ID, studyId: Study.Id, userId: User.ID, roleStr: String) = sequenceStudy(studyId) { study =>
(study isOwner byUserId) ?? {
val role = StudyMember.Role.byId.getOrElse(roleStr, StudyMember.Role.Read)
studyRepo.setRole(study, userId, role) >>- reloadMembers(study)
}
}
def invite(byUserId: User.ID, studyId: Study.ID, username: String, socket: ActorRef) = sequenceStudy(studyId) { study =>
def invite(byUserId: User.ID, studyId: Study.Id, username: String, socket: ActorRef) = sequenceStudy(studyId) { study =>
(study.isOwner(byUserId) && study.nbMembers < 30) ?? {
UserRepo.named(username).flatMap {
_.filterNot(study.members.contains) ?? { user =>
@ -197,13 +197,13 @@ final class StudyApi(
}
}
def kick(studyId: Study.ID, userId: User.ID) = sequenceStudy(studyId) { study =>
def kick(studyId: Study.Id, userId: User.ID) = sequenceStudy(studyId) { study =>
study.isMember(userId) ?? {
studyRepo.removeMember(study, userId)
} >>- reloadMembers(study) >>- indexStudy(study)
}
def setShapes(userId: User.ID, studyId: Study.ID, position: Position.Ref, shapes: Shapes, uid: Uid) = sequenceStudy(studyId) { study =>
def setShapes(userId: User.ID, studyId: Study.Id, position: Position.Ref, shapes: Shapes, uid: Uid) = sequenceStudy(studyId) { study =>
Contribute(userId, study) {
chapterRepo.byIdAndStudy(position.chapterId, study.id) flatMap {
_ ?? { chapter =>
@ -219,7 +219,7 @@ final class StudyApi(
}
}
def setTag(userId: User.ID, studyId: Study.ID, setTag: actorApi.SetTag, uid: Uid) = sequenceStudy(studyId) { study =>
def setTag(userId: User.ID, studyId: Study.Id, setTag: actorApi.SetTag, uid: Uid) = sequenceStudy(studyId) { study =>
Contribute(userId, study) {
chapterRepo.byIdAndStudy(setTag.chapterId, studyId) flatMap {
_ ?? { oldChapter =>
@ -231,7 +231,7 @@ final class StudyApi(
}
}
def setComment(userId: User.ID, studyId: Study.ID, position: Position.Ref, text: Comment.Text, uid: Uid) = sequenceStudyWithChapter(studyId) {
def setComment(userId: User.ID, studyId: Study.Id, position: Position.Ref, text: Comment.Text, uid: Uid) = sequenceStudyWithChapter(studyId) {
case Study.WithChapter(study, chapter) => Contribute(userId, study) {
(study.members get userId) ?? { byMember =>
lightUser(userId) ?? { author =>
@ -254,7 +254,7 @@ final class StudyApi(
}
}
def deleteComment(userId: User.ID, studyId: Study.ID, position: Position.Ref, id: Comment.Id, uid: Uid) = sequenceStudyWithChapter(studyId) {
def deleteComment(userId: User.ID, studyId: Study.Id, position: Position.Ref, id: Comment.Id, uid: Uid) = sequenceStudyWithChapter(studyId) {
case Study.WithChapter(study, chapter) => Contribute(userId, study) {
(study.members get userId) ?? { byMember =>
chapter.deleteComment(id, position.path) match {
@ -268,7 +268,7 @@ final class StudyApi(
}
}
def toggleGlyph(userId: User.ID, studyId: Study.ID, position: Position.Ref, glyph: Glyph, uid: Uid) = sequenceStudyWithChapter(studyId) {
def toggleGlyph(userId: User.ID, studyId: Study.Id, position: Position.Ref, glyph: Glyph, uid: Uid) = sequenceStudyWithChapter(studyId) {
case Study.WithChapter(study, chapter) => Contribute(userId, study) {
(study.members get userId) ?? { byMember =>
chapter.toggleGlyph(glyph, position.path) match {
@ -284,7 +284,7 @@ final class StudyApi(
}
}
def addChapter(byUserId: User.ID, studyId: Study.ID, data: ChapterMaker.Data, socket: ActorRef, uid: Uid) = sequenceStudy(studyId) { study =>
def addChapter(byUserId: User.ID, studyId: Study.Id, data: ChapterMaker.Data, socket: ActorRef, uid: Uid) = sequenceStudy(studyId) { study =>
Contribute(byUserId, study) {
chapterRepo.nextOrderByStudy(study.id) flatMap { order =>
chapterMaker(study, data, order, byUserId) flatMap {
@ -303,7 +303,7 @@ final class StudyApi(
}
}
def setChapter(byUserId: User.ID, studyId: Study.ID, chapterId: Chapter.ID, socket: ActorRef, uid: Uid) = sequenceStudy(studyId) { study =>
def setChapter(byUserId: User.ID, studyId: Study.Id, chapterId: Chapter.ID, socket: ActorRef, uid: Uid) = sequenceStudy(studyId) { study =>
study.canContribute(byUserId) ?? doSetChapter(study, chapterId, socket, uid)
}
@ -317,7 +317,7 @@ final class StudyApi(
}
}
def editChapter(byUserId: User.ID, studyId: Study.ID, data: ChapterMaker.EditData, socket: ActorRef, uid: Uid) = sequenceStudy(studyId) { study =>
def editChapter(byUserId: User.ID, studyId: Study.Id, data: ChapterMaker.EditData, socket: ActorRef, uid: Uid) = sequenceStudy(studyId) { study =>
Contribute(byUserId, study) {
chapterRepo.byIdAndStudy(data.id, studyId) flatMap {
_ ?? { chapter =>
@ -355,7 +355,7 @@ final class StudyApi(
}
}
def deleteChapter(byUserId: User.ID, studyId: Study.ID, chapterId: Chapter.ID, socket: ActorRef, uid: Uid) = sequenceStudy(studyId) { study =>
def deleteChapter(byUserId: User.ID, studyId: Study.Id, chapterId: Chapter.ID, socket: ActorRef, uid: Uid) = sequenceStudy(studyId) { study =>
Contribute(byUserId, study) {
chapterRepo.byIdAndStudy(chapterId, studyId) flatMap {
_ ?? { chapter =>
@ -372,13 +372,13 @@ final class StudyApi(
}
}
def sortChapters(byUserId: User.ID, studyId: Study.ID, chapterIds: List[Chapter.ID], socket: ActorRef, uid: Uid) = sequenceStudy(studyId) { study =>
def sortChapters(byUserId: User.ID, studyId: Study.Id, chapterIds: List[Chapter.ID], socket: ActorRef, uid: Uid) = sequenceStudy(studyId) { study =>
Contribute(byUserId, study) {
chapterRepo.sort(study, chapterIds) >>- reloadChapters(study)
}
}
def editStudy(studyId: Study.ID, data: Study.Data) = sequenceStudy(studyId) { study =>
def editStudy(studyId: Study.Id, data: Study.Data) = sequenceStudy(studyId) { study =>
data.settings ?? { settings =>
val newStudy = study.copy(
name = Study toName data.name,
@ -398,12 +398,12 @@ final class StudyApi(
(indexer ! actorApi.RemoveStudy(study.id))
}
def like(studyId: Study.ID, userId: User.ID, v: Boolean, socket: ActorRef, uid: Uid): Funit =
def like(studyId: Study.Id, userId: User.ID, v: Boolean, socket: ActorRef, uid: Uid): Funit =
studyRepo.like(studyId, userId, v) map { likes =>
sendTo(studyId, Socket.SetLiking(Study.Liking(likes, v), uid))
if (v) studyRepo.nameById(studyId) foreach {
_ ?? { name =>
timeline ! (Propagate(StudyLike(userId, studyId, name)) toFollowersOf userId)
timeline ! (Propagate(StudyLike(userId, studyId.value, name)) toFollowersOf userId)
}
}
}
@ -427,14 +427,14 @@ final class StudyApi(
sendTo(study, Socket.ReloadChapters(chapters))
}
private def sequenceStudy(studyId: String)(f: Study => Funit): Funit =
private def sequenceStudy(studyId: Study.Id)(f: Study => Funit): Funit =
byId(studyId) flatMap {
_ ?? { study =>
sequence(studyId)(f(study))
}
}
private def sequenceStudyWithChapter(studyId: String)(f: Study.WithChapter => Funit): Funit =
private def sequenceStudyWithChapter(studyId: Study.Id)(f: Study.WithChapter => Funit): Funit =
sequenceStudy(studyId) { study =>
chapterRepo.byId(study.position.chapterId) flatMap {
_ ?? { chapter =>
@ -443,10 +443,10 @@ final class StudyApi(
}
}
private def sequence(studyId: String)(f: => Funit): Funit = {
private def sequence(studyId: Study.Id)(f: => Funit): Funit = {
val promise = scala.concurrent.Promise[Unit]
val work = Sequencer.work(f, promise.some)
sequencers ! Tell(studyId, work)
sequencers ! Tell(studyId.value, work)
promise.future
}
@ -456,6 +456,6 @@ final class StudyApi(
private def sendTo(study: Study, msg: Any): Unit = sendTo(study.id, msg)
private def sendTo(studyId: Study.ID, msg: Any): Unit =
socketHub ! Tell(studyId, msg)
private def sendTo(studyId: Study.Id, msg: Any): Unit =
socketHub ! Tell(studyId.value, msg)
}

View File

@ -19,7 +19,10 @@ private final class StudyNotifier(
_ ?? {
socket ? HasUserId(invited.id) mapTo manifest[Boolean] map { isPresent =>
study.owner.ifFalse(isPresent) foreach { owner =>
val notificationContent = InvitedToStudy(InvitedToStudy.InvitedBy(owner.id), InvitedToStudy.StudyName(study.name), InvitedToStudy.StudyId(study.id))
val notificationContent = InvitedToStudy(
InvitedToStudy.InvitedBy(owner.id),
InvitedToStudy.StudyName(study.name),
InvitedToStudy.StudyId(study.id.value))
val notification = Notification.make(Notification.Notifies(invited.id), notificationContent)
notifyApi.addNotification(notification)
}

View File

@ -17,9 +17,9 @@ final class StudyRepo(private[study] val coll: Coll) {
"views" -> false,
"rank" -> false)
def byId(id: Study.ID) = coll.find($id(id), projection).uno[Study]
def byId(id: Study.Id) = coll.find($id(id), projection).uno[Study]
def byOrderedIds(ids: Seq[String]) = coll.byOrderedIds[Study](ids)(_.id)
def byOrderedIds(ids: Seq[Study.Id]) = coll.byOrderedIds[Study, Study.Id](ids)(_.id)
def cursor(
selector: Bdoc,
@ -27,9 +27,9 @@ final class StudyRepo(private[study] val coll: Coll) {
implicit cp: CursorProducer[Study]) =
coll.find(selector).cursor[Study](readPreference)
def nameById(id: Study.ID) = coll.primitiveOne[String]($id(id), "name")
def nameById(id: Study.Id) = coll.primitiveOne[String]($id(id), "name")
def exists(id: Study.ID) = coll.exists($id(id))
def exists(id: Study.Id) = coll.exists($id(id))
private[study] def selectOwnerId(ownerId: User.ID) = $doc("ownerId" -> ownerId)
private[study] def selectMemberId(memberId: User.ID) = $doc("uids" -> memberId)
@ -57,10 +57,10 @@ final class StudyRepo(private[study] val coll: Coll) {
def delete(s: Study): Funit = coll.remove($id(s.id)).void
def membersById(id: Study.ID): Fu[Option[StudyMembers]] =
def membersById(id: Study.Id): Fu[Option[StudyMembers]] =
coll.primitiveOne[StudyMembers]($id(id), "members")
def setPosition(studyId: Study.ID, position: Position.Ref): Funit =
def setPosition(studyId: Study.Id, position: Position.Ref): Funit =
coll.update(
$id(studyId),
$set(
@ -91,10 +91,10 @@ final class StudyRepo(private[study] val coll: Coll) {
$set(s"members.$userId.role" -> role)
).void
def uids(studyId: Study.ID): Fu[Set[User.ID]] =
def uids(studyId: Study.Id): Fu[Set[User.ID]] =
coll.primitiveOne[Set[User.ID]]($id(studyId), "uids") map (~_)
def like(studyId: Study.ID, userId: User.ID, v: Boolean): Fu[Study.Likes] =
def like(studyId: Study.Id, userId: User.ID, v: Boolean): Fu[Study.Likes] =
doLike(studyId, userId, v) >> countLikes(studyId).flatMap {
case None => fuccess(Study.Likes(0))
case Some((likes, createdAt)) => coll.update($id(studyId), $set(
@ -106,14 +106,15 @@ final class StudyRepo(private[study] val coll: Coll) {
def liked(study: Study, user: User): Fu[Boolean] =
coll.exists($id(study.id) ++ selectLiker(user.id))
def filterLiked(user: User, studyIds: Seq[Study.ID]): Fu[Set[Study.ID]] =
coll.primitive[Study.ID]($inIds(studyIds) ++ selectLiker(user.id), "_id").map(_.toSet)
def filterLiked(user: User, studyIds: Seq[Study.Id]): Fu[Set[Study.Id]] =
coll.primitive[Study.Id]($inIds(studyIds) ++ selectLiker(user.id), "_id").map(_.toSet)
def resetAllRanks: Fu[Int] = coll.find(
$empty, $doc("likes" -> true, "createdAt" -> true)
).cursor[Bdoc]().foldWhileM(0) { (count, doc) =>
~(for {
id <- doc.getAs[Study.ID]("_id")
id <- doc.getAs[Study.Id]("_id")
likes <- doc.getAs[Study.Likes]("likes")
createdAt <- doc.getAs[DateTime]("createdAt")
} yield coll.update(
@ -121,14 +122,14 @@ final class StudyRepo(private[study] val coll: Coll) {
).void) inject Cursor.Cont(count + 1)
}
private def doLike(studyId: Study.ID, userId: User.ID, v: Boolean): Funit =
private def doLike(studyId: Study.Id, userId: User.ID, v: Boolean): Funit =
coll.update(
$id(studyId),
if (v) $addToSet("likers" -> userId)
else $pull("likers" -> userId)
).void
private def countLikes(studyId: Study.ID): Fu[Option[(Study.Likes, DateTime)]] =
private def countLikes(studyId: Study.Id): Fu[Option[(Study.Likes, DateTime)]] =
coll.aggregate(
Match($id(studyId)),
List(Project($doc(

View File

@ -2,7 +2,7 @@ package lila.study
package actorApi
case class SaveStudy(study: Study)
case class RemoveStudy(id: Study.ID)
case class RemoveStudy(id: Study.Id)
case class SetTag(chapterId: Chapter.ID, name: String, value: String) {
def tag = chess.format.pgn.Tag(name, value take 140)
}

View File

@ -52,7 +52,7 @@ final class Env(
import lila.study.actorApi._
def receive = {
case SaveStudy(study) => api store study
case RemoveStudy(id) => client deleteById Id(id)
case RemoveStudy(id) => client deleteById Id(id.value)
}
}), name = ActorName)
}

View File

@ -19,7 +19,7 @@ final class StudySearchApi(
def search(query: Query, from: From, size: Size) = {
client.search(query, from, size) flatMap { res =>
studyRepo byOrderedIds res.ids
studyRepo byOrderedIds res.ids.map(Study.Id.apply)
}
}.mon(_.study.search.query.time) >>- lila.mon.study.search.query.count()
@ -27,14 +27,14 @@ final class StudySearchApi(
def store(study: Study) = fuccess {
indexThrottler ! LateMultiThrottler.work(
id = study.id,
id = study.id.value,
run = studyRepo byId study.id flatMap { _ ?? doStore },
delay = 30.seconds.some)
}
private def doStore(study: Study) = {
getChapters(study) flatMap { s =>
client.store(Id(s.study.id), toDoc(s))
client.store(Id(s.study.id.value), toDoc(s))
}
}.mon(_.study.search.index.time) >>- lila.mon.study.search.index.count()

View File

@ -27,6 +27,8 @@ case class Team(
object Team {
type ID = String
def make(
name: String,
location: Option[String],

View File

@ -15,7 +15,7 @@ object TeamRepo {
type ID = String
def byOrderedIds(ids: Seq[String]) = coll.byOrderedIds[Team](ids)(_.id)
def byOrderedIds(ids: Seq[Team.ID]) = coll.byOrderedIds[Team, Team.ID](ids)(_.id)
def cursor(
selector: Bdoc,

View File

@ -45,7 +45,7 @@ object UserRepo {
}
def byOrderedIds(ids: Seq[ID]): Fu[List[User]] =
coll.byOrderedIds[User](ids)(_.id)
coll.byOrderedIds[User, User.ID](ids)(_.id)
def enabledByIds(ids: Iterable[ID]): Fu[List[User]] =
coll.list[User](enabledSelect ++ $inIds(ids))
@ -76,25 +76,25 @@ object UserRepo {
}
def usersFromSecondary(userIds: Seq[ID]): Fu[List[User]] =
coll.byOrderedIds[User](userIds, readPreference = ReadPreference.secondaryPreferred)(_.id)
coll.byOrderedIds[User, User.ID](userIds, readPreference = ReadPreference.secondaryPreferred)(_.id)
private[user] def allSortToints(nb: Int) =
coll.find($empty).sort($sort desc F.toints).cursor[User]().gather[List](nb)
def usernameById(id: ID) =
coll.primitiveOne[String]($id(id), F.username)
coll.primitiveOne[User.ID]($id(id), F.username)
def usernamesByIds(ids: List[ID]) =
coll.distinct[String, List](F.username, $inIds(ids).some)
coll.distinct[User.ID, List](F.username, $inIds(ids).some)
def orderByGameCount(u1: String, u2: String): Fu[Option[(String, String)]] = {
def orderByGameCount(u1: User.ID, u2: User.ID): Fu[Option[(User.ID, User.ID)]] = {
coll.find(
$inIds(List(u1, u2)),
$doc(s"${F.count}.game" -> true)
).cursor[Bdoc]().gather[List]() map { docs =>
docs.sortBy {
_.getAs[Bdoc](F.count).flatMap(_.getAs[BSONNumberLike]("game")).??(_.toInt)
}.map(_.getAs[String]("_id")).flatten match {
}.map(_.getAs[User.ID]("_id")).flatten match {
case List(u1, u2) => (u1, u2).some
case _ => none
}