typesafe study chapter id

This commit is contained in:
Thibault Duplessis 2017-01-20 14:00:42 +01:00
parent 8821c2b7ae
commit 127399efe8
15 changed files with 43 additions and 38 deletions

View file

@ -8,7 +8,7 @@ import lila.api.Context
import lila.app._
import lila.common.HTTPRequest
import lila.study.Study.WithChapter
import lila.study.{ Order, Study => StudyModel }
import lila.study.{ Chapter, Order, Study => StudyModel }
import views._
object Study extends LilaController {
@ -277,4 +277,5 @@ object Study extends LilaController {
study.isPublic || ctx.userId.exists(study.members.contains)
private implicit def makeStudyId(id: String): StudyModel.Id = StudyModel.Id(id)
private implicit def makeChapterId(id: String): Chapter.Id = Chapter.Id(id)
}

View file

@ -27,7 +27,7 @@
<div class="embedded_study analyse cg-512">@miniBoardContent</div>
</div>
<footer>
@defining(routes.Study.chapter(s.id.value, chapter.id)) { url =>
@defining(routes.Study.chapter(s.id.value, chapter.id.value)) { 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

@ -12,7 +12,7 @@ case class StudyProgress(moves: StudyProgress.ChapterNbMoves) extends AnyVal {
object StudyProgress {
type ChapterNbMoves = Map[lila.study.Chapter.ID, StudyProgress.NbMoves]
type ChapterNbMoves = Map[lila.study.Chapter.Id, StudyProgress.NbMoves]
def empty = StudyProgress(moves = Map.empty)

View file

@ -20,6 +20,7 @@ object BSONHandlers {
import Chapter._
implicit val StudyIdBSONHandler = stringIsoHandler[Study.Id](Study.idIso)
implicit val ChapterIdBSONHandler = stringIsoHandler[Chapter.Id](Chapter.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}"
@ -209,7 +210,7 @@ object BSONHandlers {
implicit val ChapterBSONHandler = Macros.handler[Chapter]
implicit val ChapterMetadataBSONHandler = Macros.handler[Chapter.Metadata]
private implicit val ChaptersMap = BSON.MapDocument.MapHandler[Chapter.ID, 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}"

View file

@ -10,7 +10,7 @@ import lila.tree.Node.{ Shapes, Comment }
import lila.user.User
case class Chapter(
_id: Chapter.ID,
_id: Chapter.Id,
studyId: Study.Id,
name: String,
setup: Chapter.Setup,
@ -68,10 +68,11 @@ case class Chapter(
object Chapter {
type ID = String
case class Id(value: String) extends AnyVal with StringValue
implicit val idIso = lila.common.Iso.string[Id](Id.apply, _.value)
sealed trait Like {
val _id: Chapter.ID
val _id: Chapter.Id
val name: String
val setup: Chapter.Setup
def id = _id
@ -88,7 +89,7 @@ object Chapter {
}
case class Metadata(
_id: Chapter.ID,
_id: Chapter.Id,
name: String,
setup: Chapter.Setup) extends Like
@ -96,7 +97,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
@ -105,7 +106,7 @@ object Chapter {
val idSize = 8
def makeId = scala.util.Random.alphanumeric take idSize mkString
def makeId = Id(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(
_id = makeId,

View file

@ -188,7 +188,7 @@ private[study] object ChapterMaker {
initial: Boolean) extends ChapterData
case class EditData(
id: String,
id: Chapter.Id,
name: String,
orientation: String,
mode: String) extends ChapterData

View file

@ -12,15 +12,15 @@ final class ChapterRepo(coll: Coll) {
val noRootProjection = $doc("root" -> false)
def byId(id: Chapter.ID): Fu[Option[Chapter]] = coll.byId[Chapter](id)
def byId(id: Chapter.Id): Fu[Option[Chapter]] = coll.byId[Chapter, Chapter.Id](id)
// def metadataById(id: Chapter.ID): Fu[Option[Chapter.Metadata]] =
// def metadataById(id: Chapter.Id): Fu[Option[Chapter.Metadata]] =
// coll.find($id(id), noRootProjection).one[Chapter.Metadata]
def deleteByStudy(s: Study): Funit = coll.remove($studyId(s.id)).void
def byIdAndStudy(id: Chapter.ID, studyId: Study.Id): Fu[Option[Chapter]] =
coll.byId[Chapter](id).map { _.filter(_.studyId == studyId) }
def byIdAndStudy(id: Chapter.Id, studyId: Study.Id): Fu[Option[Chapter]] =
coll.byId[Chapter, Chapter.Id](id).map { _.filter(_.studyId == studyId) }
def firstByStudy(studyId: Study.Id): Fu[Option[Chapter]] =
coll.find($studyId(studyId)).sort($sort asc "order").one[Chapter]
@ -38,7 +38,7 @@ final class ChapterRepo(coll: Coll) {
.cursor[Chapter](readPreference = ReadPreference.secondaryPreferred)
.gather[List](maxChapters)
def sort(study: Study, ids: List[Chapter.ID]): Funit = ids.zipWithIndex.map {
def sort(study: Study, ids: List[Chapter.Id]): Funit = ids.zipWithIndex.map {
case (id, index) =>
coll.updateField($studyId(study.id) ++ $id(id), "order", index + 1)
}.sequenceFu.void
@ -50,10 +50,10 @@ final class ChapterRepo(coll: Coll) {
"order"
) map { order => ~order + 1 }
def setConceal(chapterId: Chapter.ID, conceal: Chapter.Ply) =
def setConceal(chapterId: Chapter.Id, conceal: Chapter.Ply) =
coll.updateField($id(chapterId), "conceal", conceal).void
def removeConceal(chapterId: Chapter.ID) =
def removeConceal(chapterId: Chapter.Id) =
coll.unsetField($id(chapterId), "conceal").void
def setTagsFor(chapter: Chapter) =
@ -84,7 +84,7 @@ final class ChapterRepo(coll: Coll) {
def update(c: Chapter): Funit = coll.update($id(c.id), c).void
def delete(id: Chapter.ID): Funit = coll.remove($id(id)).void
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)

View file

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

View file

@ -4,7 +4,7 @@ case class Position(chapter: Chapter, path: Path)
case object Position {
case class Ref(chapterId: Chapter.ID, path: Path) {
case class Ref(chapterId: Chapter.Id, path: Path) {
def encode = s"$chapterId $path"
@ -16,8 +16,8 @@ case object Position {
object Ref {
def decode(str: String): Option[Ref] = str.split(' ') match {
case Array(chapterId, path) => Ref(chapterId, Path(path)).some
case Array(chapterId) => Ref(chapterId, Path.root).some
case Array(chapterId, path) => Ref(Chapter.Id(chapterId), Path(path)).some
case Array(chapterId) => Ref(Chapter.Id(chapterId), Path.root).some
case _ => none
}
}

View file

@ -241,7 +241,7 @@ private object Socket {
case class ChangeChapter(uid: Uid)
case class SetConceal(position: Position.Ref, ply: Option[Chapter.Ply])
case class SetLiking(liking: Study.Liking, uid: Uid)
case class SetTags(chapterId: Chapter.ID, tags: List[chess.format.pgn.Tag], uid: Uid)
case class SetTags(chapterId: Chapter.Id, tags: List[chess.format.pgn.Tag], uid: Uid)
case class Messadata(trollish: Boolean = false)
case object NotifyCrowd

View file

@ -60,7 +60,7 @@ private[study] final class SocketHandler(
for {
userId <- member.userId
d o obj "d"
chapterId <- d str "chapterId"
chapterId <- d.get[Chapter.Id]("chapterId")
} api.addNode(
userId,
studyId,
@ -85,7 +85,7 @@ private[study] final class SocketHandler(
for {
userId <- member.userId
d o obj "d"
chapterId <- d str "chapterId"
chapterId <- d.get[Chapter.Id]("chapterId")
} api.addNode(
userId,
studyId,
@ -172,7 +172,7 @@ private[study] final class SocketHandler(
case ("setChapter", o) => for {
byUserId <- member.userId
chapterId <- o str "d"
chapterId <- o.get[Chapter.Id]("d")
} api.setChapter(byUserId, studyId, chapterId, socket, uid)
case ("editChapter", o) =>
@ -184,12 +184,12 @@ private[study] final class SocketHandler(
case ("deleteChapter", o) => for {
byUserId <- member.userId
id <- o str "d"
id <- o.get[Chapter.Id]("d")
} api.deleteChapter(byUserId, studyId, id, socket, uid)
case ("sortChapters", o) => for {
byUserId <- member.userId
ids <- o strs "d"
ids <- o.get[List[Chapter.Id]]("d")
} api.sortChapters(byUserId, studyId, ids, socket, uid)
case ("editStudy", o) if owner =>
@ -242,9 +242,10 @@ private[study] final class SocketHandler(
private def reading[A](o: JsValue)(f: A => Unit)(implicit reader: Reads[A]): Unit =
o obj "d" flatMap { d => reader.reads(d).asOpt } foreach f
private case class AtPosition(path: String, chapterId: Chapter.ID) {
private case class AtPosition(path: String, chapterId: Chapter.Id) {
def ref = Position.Ref(chapterId, Path(path))
}
private implicit val chapterIdReader = Reads.of[String].map(Chapter.Id.apply)
private implicit val atPositionReader = Json.reads[AtPosition]
private case class SetRole(userId: String, role: String)
private implicit val SetRoleReader = Json.reads[SetRole]

View file

@ -136,7 +136,7 @@ object Study {
_id = makeId,
name = s"${user.username}'s Study",
members = StudyMembers(Map(user.id -> owner)),
position = Position.Ref("", Path.root),
position = Position.Ref(Chapter.Id(""), Path.root),
ownerId = user.id,
visibility = Visibility.Public,
settings = Settings.init,

View file

@ -32,7 +32,7 @@ final class StudyApi(
def publicByIds(ids: Seq[Study.Id]) = byIds(ids) map { _.filter(_.isPublic) }
private def fetchAndFixChapter(id: Chapter.ID): Fu[Option[Chapter]] =
private def fetchAndFixChapter(id: Chapter.Id): Fu[Option[Chapter]] =
chapterRepo.byId(id) flatMap {
_ ?? { c => tagsFixer(c) map some }
}
@ -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, _) }
@ -303,11 +303,11 @@ 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)
}
private def doSetChapter(study: Study, chapterId: Chapter.ID, socket: ActorRef, uid: Uid) =
private def doSetChapter(study: Study, chapterId: Chapter.Id, socket: ActorRef, uid: Uid) =
(study.position.chapterId != chapterId) ?? {
chapterRepo.byIdAndStudy(chapterId, study.id) 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,7 +372,7 @@ 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)
}

View file

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

View file

@ -6,5 +6,5 @@ package object study extends PackageObject with WithPlay with WithSocket {
private[study] val logger = lila.log("study")
private[study]type ChapterMap = Map[lila.study.Chapter.ID, lila.study.Chapter]
private[study]type ChapterMap = Map[lila.study.Chapter.Id, lila.study.Chapter]
}