2016-02-26 05:08:11 -07:00
|
|
|
package lila.study
|
|
|
|
|
2016-04-24 05:54:12 -06:00
|
|
|
import akka.actor.{ ActorRef, ActorSelection }
|
2016-04-25 21:51:05 -06:00
|
|
|
import org.apache.commons.lang3.StringEscapeUtils.escapeHtml4
|
2016-02-26 05:08:11 -07:00
|
|
|
|
2016-04-16 07:26:01 -06:00
|
|
|
import chess.format.{ Forsyth, FEN }
|
2016-04-24 06:48:32 -06:00
|
|
|
import lila.chat.actorApi.SystemTalk
|
2016-02-26 05:08:11 -07:00
|
|
|
import lila.hub.actorApi.map.Tell
|
2016-02-27 04:30:38 -07:00
|
|
|
import lila.hub.Sequencer
|
2016-04-23 04:01:21 -06:00
|
|
|
import lila.socket.Socket.Uid
|
2016-04-25 21:51:05 -06:00
|
|
|
import lila.socket.tree.Node.Shape
|
2016-04-20 22:42:49 -06:00
|
|
|
import lila.user.{ User, UserRepo }
|
2016-02-26 05:08:11 -07:00
|
|
|
|
|
|
|
final class StudyApi(
|
2016-04-22 23:19:07 -06:00
|
|
|
studyRepo: StudyRepo,
|
|
|
|
chapterRepo: ChapterRepo,
|
2016-02-27 04:30:38 -07:00
|
|
|
sequencers: ActorRef,
|
2016-04-25 03:09:26 -06:00
|
|
|
chapterMaker: ChapterMaker,
|
2016-04-24 05:54:12 -06:00
|
|
|
chat: ActorSelection,
|
2016-02-26 05:08:11 -07:00
|
|
|
socketHub: akka.actor.ActorRef) {
|
|
|
|
|
2016-04-22 23:19:07 -06:00
|
|
|
def byId = studyRepo byId _
|
2016-02-27 04:30:38 -07:00
|
|
|
|
2016-04-22 23:19:07 -06:00
|
|
|
def byIdWithChapter(id: Study.ID): Fu[Option[Study.WithChapter]] = byId(id) flatMap {
|
|
|
|
_ ?? { study =>
|
|
|
|
chapterRepo.byId(study.position.chapterId) map {
|
|
|
|
_ map { Study.WithChapter(study, _) }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-26 02:49:39 -06:00
|
|
|
def byIdWithChapter(id: Study.ID, chapterId: Chapter.ID): Fu[Option[Study.WithChapter]] = byId(id) flatMap {
|
|
|
|
_ ?? { study =>
|
|
|
|
chapterRepo.byId(chapterId) map {
|
|
|
|
_ map { Study.WithChapter(study, _) }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-22 23:19:07 -06:00
|
|
|
def create(user: User): Fu[Study.WithChapter] = {
|
|
|
|
val preStudy = Study.make(user = user.light)
|
|
|
|
val chapter: Chapter = Chapter.make(
|
|
|
|
studyId = preStudy.id,
|
|
|
|
name = "Chapter 1",
|
2016-04-16 07:26:01 -06:00
|
|
|
setup = Chapter.Setup(
|
|
|
|
gameId = none,
|
|
|
|
variant = chess.variant.Standard,
|
2016-04-22 23:19:07 -06:00
|
|
|
orientation = chess.White),
|
|
|
|
root = Node.Root.default,
|
|
|
|
order = 1)
|
|
|
|
val study = preStudy withChapter chapter
|
|
|
|
studyRepo.insert(study) zip chapterRepo.insert(chapter) inject
|
|
|
|
Study.WithChapter(study, chapter)
|
2016-04-16 07:26:01 -06:00
|
|
|
}
|
|
|
|
|
2016-04-23 21:34:26 -06:00
|
|
|
private def pathExists(position: Position.Ref): Fu[Boolean] =
|
|
|
|
chapterRepo.byId(position.chapterId) map {
|
|
|
|
_ ?? { _.root pathExists position.path }
|
|
|
|
}
|
|
|
|
|
2016-04-23 04:01:21 -06:00
|
|
|
def setPath(userId: User.ID, studyId: Study.ID, position: Position.Ref, uid: Uid) = sequenceStudy(studyId) { study =>
|
2016-04-22 23:19:07 -06:00
|
|
|
Contribute(userId, study) {
|
2016-04-23 21:34:26 -06:00
|
|
|
pathExists(position) flatMap { exists =>
|
|
|
|
if (exists && study.position.chapterId == position.chapterId) {
|
|
|
|
(study.position.path != position.path) ?? {
|
|
|
|
studyRepo.setPosition(study.id, position) >>-
|
|
|
|
sendTo(study.id, Socket.SetPath(position, uid))
|
|
|
|
}
|
2016-04-22 23:52:09 -06:00
|
|
|
}
|
2016-04-23 21:34:26 -06:00
|
|
|
else funit >>- reloadUid(study, uid)
|
2016-04-22 23:52:09 -06:00
|
|
|
}
|
2016-04-20 22:42:49 -06:00
|
|
|
}
|
|
|
|
}
|
2016-02-27 04:30:38 -07:00
|
|
|
|
2016-04-23 04:01:21 -06:00
|
|
|
def addNode(studyId: Study.ID, position: Position.Ref, node: Node, uid: Uid) = sequenceStudyWithChapter(studyId) {
|
2016-04-22 23:19:07 -06:00
|
|
|
case Study.WithChapter(study, chapter) => Contribute(node.by, study) {
|
2016-04-25 22:28:35 -06:00
|
|
|
chapter.addNode(node, position.path) match {
|
2016-04-24 03:15:18 -06:00
|
|
|
case None => fufail(s"Invalid addNode $position $node") >>- reloadUid(study, uid)
|
2016-04-22 23:19:07 -06:00
|
|
|
case Some(newChapter) =>
|
|
|
|
chapterRepo.update(newChapter) >>
|
|
|
|
studyRepo.setPosition(study.id, position + node) >>-
|
2016-04-23 04:01:21 -06:00
|
|
|
sendTo(study.id, Socket.AddNode(position, node, uid))
|
2016-04-20 02:03:42 -06:00
|
|
|
}
|
2016-04-18 04:51:48 -06:00
|
|
|
}
|
2016-02-28 17:28:35 -07:00
|
|
|
}
|
|
|
|
|
2016-04-23 04:01:21 -06:00
|
|
|
def deleteNodeAt(userId: User.ID, studyId: Study.ID, position: Position.Ref, uid: Uid) = ???
|
2016-04-22 23:19:07 -06:00
|
|
|
// sequenceLocation(ref) { location =>
|
|
|
|
// (location.study canWrite userId) ?? {
|
|
|
|
// val newChapter = location.chapter.updateRoot { root =>
|
|
|
|
// root.withChildren(_.deleteNodeAt(path))
|
|
|
|
// }
|
|
|
|
// studyRepo.setChapter(location withChapter newChapter) >>-
|
2016-04-23 04:01:21 -06:00
|
|
|
// sendTo(ref.studyId, Socket.DelNode(Position.Ref(ref.chapterId, path), uid))
|
2016-04-22 23:19:07 -06:00
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
2016-04-23 04:01:21 -06:00
|
|
|
def promoteNodeAt(userId: User.ID, studyId: Study.ID, position: Position.Ref, uid: Uid) = ???
|
2016-04-22 23:19:07 -06:00
|
|
|
// sequenceLocation(ref) { location =>
|
|
|
|
// (location.study canWrite userId) ?? {
|
|
|
|
// val newChapter = location.chapter.updateRoot { root =>
|
|
|
|
// root.withChildren(_.promoteNodeAt(path))
|
|
|
|
// }
|
|
|
|
// studyRepo.setChapter(location withChapter newChapter)
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
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)
|
2016-04-19 22:33:59 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-22 23:19:07 -06:00
|
|
|
def invite(byUserId: User.ID, studyId: Study.ID, username: String) = sequenceStudy(studyId) { study =>
|
|
|
|
(study isOwner byUserId) ?? {
|
|
|
|
UserRepo.named(username).flatMap {
|
|
|
|
_.filterNot(study.members.contains) ?? { user =>
|
|
|
|
studyRepo.addMember(study, StudyMember.make(study, user))
|
|
|
|
}
|
|
|
|
} >>- reloadMembers(study)
|
2016-04-19 22:33:59 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-22 23:19:07 -06:00
|
|
|
def kick(byUserId: User.ID, studyId: Study.ID, userId: User.ID) = sequenceStudy(studyId) { study =>
|
2016-04-20 22:42:49 -06:00
|
|
|
study.members.contains(userId) ?? {
|
2016-04-22 23:19:07 -06:00
|
|
|
studyRepo.removeMember(study, userId)
|
2016-04-20 22:42:49 -06:00
|
|
|
} >>- reloadMembers(study)
|
|
|
|
}
|
|
|
|
|
2016-04-25 21:51:05 -06:00
|
|
|
def setShapes(userId: User.ID, studyId: Study.ID, position: Position.Ref, shapes: List[Shape], uid: Uid) = sequenceStudy(studyId) { study =>
|
2016-04-22 23:19:07 -06:00
|
|
|
Contribute(userId, study) {
|
2016-04-25 21:51:05 -06:00
|
|
|
chapterRepo.byIdAndStudy(position.chapterId, study.id) flatMap {
|
|
|
|
_ ?? { chapter =>
|
2016-04-25 22:28:35 -06:00
|
|
|
chapter.setShapes(shapes, position.path) match {
|
2016-04-25 21:51:05 -06:00
|
|
|
case Some(newChapter) =>
|
|
|
|
chapterRepo.update(newChapter) >>-
|
2016-04-25 22:29:13 -06:00
|
|
|
sendTo(study.id, Socket.SetShapes(position, shapes, uid))
|
2016-04-25 21:51:05 -06:00
|
|
|
case None => fufail(s"Invalid setShapes $position $shapes") >>- reloadUid(study, uid)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-04-21 20:53:16 -06:00
|
|
|
}
|
|
|
|
|
2016-04-25 04:52:29 -06:00
|
|
|
def addChapter(byUserId: User.ID, studyId: Study.ID, data: ChapterMaker.Data, socket: ActorRef) = sequenceStudy(studyId) { study =>
|
2016-04-24 03:15:18 -06:00
|
|
|
(study isOwner byUserId) ?? {
|
|
|
|
chapterRepo.nextOrderByStudy(study.id) flatMap { order =>
|
2016-04-25 03:09:26 -06:00
|
|
|
chapterMaker(study, data, order) flatMap {
|
|
|
|
_ ?? { chapter =>
|
|
|
|
chapterRepo.insert(chapter) >>
|
|
|
|
doSetChapter(byUserId, study, chapter.id, socket)
|
|
|
|
}
|
|
|
|
}
|
2016-04-24 03:15:18 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-24 05:54:12 -06:00
|
|
|
def setChapter(byUserId: User.ID, studyId: Study.ID, chapterId: Chapter.ID, socket: ActorRef) = sequenceStudy(studyId) { study =>
|
2016-04-25 03:09:26 -06:00
|
|
|
doSetChapter(byUserId, study, chapterId, socket)
|
|
|
|
}
|
|
|
|
|
|
|
|
private def doSetChapter(byUserId: User.ID, study: Study, chapterId: Chapter.ID, socket: ActorRef) =
|
2016-04-24 05:54:12 -06:00
|
|
|
(study.canContribute(byUserId) && study.position.chapterId != chapterId) ?? {
|
2016-04-25 03:09:26 -06:00
|
|
|
chapterRepo.byIdAndStudy(chapterId, study.id) flatMap {
|
2016-04-24 04:45:34 -06:00
|
|
|
_ ?? { chapter =>
|
2016-04-24 05:54:12 -06:00
|
|
|
studyRepo.update(study withChapter chapter) >>- {
|
2016-04-26 11:09:27 -06:00
|
|
|
sendTo(study.id, Socket.ChangeChapter)
|
2016-04-24 05:54:12 -06:00
|
|
|
study.members.get(byUserId).foreach { member =>
|
2016-04-25 21:51:05 -06:00
|
|
|
chat ! SystemTalk(study.id, escapeHtml4(chapter.name), socket)
|
2016-04-24 05:54:12 -06:00
|
|
|
}
|
|
|
|
}
|
2016-04-24 03:38:35 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-24 04:45:34 -06:00
|
|
|
def renameChapter(byUserId: User.ID, studyId: Study.ID, chapterId: Chapter.ID, name: String) = sequenceStudy(studyId) { study =>
|
|
|
|
chapterRepo.byIdAndStudy(chapterId, studyId) flatMap {
|
|
|
|
_ ?? { chapter =>
|
|
|
|
chapterRepo.update(chapter.copy(name = name)) >>- reloadChapters(study)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-25 03:09:26 -06:00
|
|
|
def deleteChapter(byUserId: User.ID, studyId: Study.ID, chapterId: Chapter.ID, socket: ActorRef) = sequenceStudy(studyId) { study =>
|
2016-04-24 23:23:13 -06:00
|
|
|
chapterRepo.byIdAndStudy(chapterId, studyId) flatMap {
|
|
|
|
_ ?? { chapter =>
|
2016-04-25 03:09:26 -06:00
|
|
|
chapterRepo.orderedMetadataByStudy(studyId).flatMap {
|
|
|
|
case chaps if chaps.size > 1 => (study.position.chapterId == chapterId).?? {
|
|
|
|
chaps.find(_.id != chapterId) ?? { newChap =>
|
|
|
|
doSetChapter(byUserId, study, newChap.id, socket)
|
|
|
|
}
|
|
|
|
} >> chapterRepo.delete(chapter.id)
|
|
|
|
case _ => funit
|
2016-04-24 23:23:13 -06:00
|
|
|
} >>- reloadChapters(study)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-23 21:34:26 -06:00
|
|
|
private def reloadUid(study: Study, uid: Uid) =
|
|
|
|
sendTo(study.id, Socket.ReloadUid(uid))
|
|
|
|
|
2016-04-20 22:42:49 -06:00
|
|
|
private def reloadMembers(study: Study) =
|
2016-04-22 23:19:07 -06:00
|
|
|
studyRepo.membersById(study.id).foreach {
|
2016-04-20 22:42:49 -06:00
|
|
|
_ foreach { members =>
|
|
|
|
sendTo(study.id, Socket.ReloadMembers(members))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-24 03:15:18 -06:00
|
|
|
private def reloadChapters(study: Study) =
|
|
|
|
chapterRepo.orderedMetadataByStudy(study.id).foreach { chapters =>
|
|
|
|
sendTo(study.id, Socket.ReloadChapters(chapters))
|
|
|
|
}
|
|
|
|
|
2016-04-20 04:19:34 -06:00
|
|
|
private def sequenceStudy(studyId: String)(f: Study => Funit): Funit =
|
|
|
|
byId(studyId) flatMap {
|
|
|
|
_ ?? { study =>
|
|
|
|
sequence(studyId)(f(study))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-22 23:19:07 -06:00
|
|
|
private def sequenceStudyWithChapter(studyId: String)(f: Study.WithChapter => Funit): Funit =
|
|
|
|
sequenceStudy(studyId) { study =>
|
|
|
|
chapterRepo.byId(study.position.chapterId) flatMap {
|
|
|
|
_ ?? { chapter =>
|
|
|
|
f(Study.WithChapter(study, chapter))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-27 04:30:38 -07:00
|
|
|
private def sequence(studyId: String)(f: => Funit): Funit = {
|
|
|
|
val promise = scala.concurrent.Promise[Unit]
|
|
|
|
val work = Sequencer.work(f, promise.some)
|
|
|
|
sequencers ! Tell(studyId, work)
|
|
|
|
promise.future
|
|
|
|
}
|
2016-04-20 01:04:38 -06:00
|
|
|
|
2016-04-22 23:19:07 -06:00
|
|
|
import ornicar.scalalib.Zero
|
|
|
|
private def Contribute[A](userId: User.ID, study: Study)(f: => A)(implicit default: Zero[A]): A =
|
|
|
|
if (study canContribute userId) f else default.zero
|
|
|
|
|
2016-04-20 01:04:38 -06:00
|
|
|
private def sendTo(studyId: String, msg: Any) {
|
|
|
|
socketHub ! Tell(studyId, msg)
|
|
|
|
}
|
2016-02-26 05:08:11 -07:00
|
|
|
}
|