lila/modules/study/src/main/StudyApi.scala

304 lines
11 KiB
Scala
Raw Normal View History

2016-02-26 05:08:11 -07:00
package lila.study
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-05-10 07:21:55 -06:00
import chess.format.pgn.{ Glyphs, Glyph }
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
import lila.socket.tree.Node.{ Shape, Comment }
2016-04-20 22:42:49 -06:00
import lila.user.{ User, UserRepo }
2016-02-26 05:08:11 -07:00
final class StudyApi(
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-05-03 14:52:30 -06:00
notifier: StudyNotifier,
2016-05-11 03:20:19 -06:00
lightUser: lila.common.LightUser.Getter,
chat: ActorSelection,
2016-02-26 05:08:11 -07:00
socketHub: akka.actor.ActorRef) {
def byId = studyRepo byId _
2016-02-27 04:30:38 -07: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, _) }
}
}
}
def create(user: User): Fu[Study.WithChapter] = {
2016-05-11 03:20:19 -06:00
val preStudy = Study.make(user)
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,
orientation = chess.White),
root = Node.Root.default(chess.variant.Standard),
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
}
private def pathExists(position: Position.Ref): Fu[Boolean] =
chapterRepo.byId(position.chapterId) map {
_ ?? { _.root pathExists position.path }
}
2016-05-10 23:42:03 -06:00
def talk(userId: User.ID, studyId: Study.ID, text: String, socket: ActorRef) = byId(studyId) foreach {
_ foreach { study =>
(study.members contains userId) ?? {
chat ! lila.chat.actorApi.UserTalk(studyId, userId, text, socket)
}
}
}
2016-04-23 04:01:21 -06:00
def setPath(userId: User.ID, studyId: Study.ID, position: Position.Ref, uid: Uid) = sequenceStudy(studyId) { study =>
Contribute(userId, study) {
pathExists(position) flatMap { exists =>
if (exists && study.position.chapterId == position.chapterId) {
(study.position.path != position.path) ?? {
studyRepo.setPosition(study.id, position) >>-
2016-05-11 02:30:23 -06:00
sendTo(study, Socket.SetPath(position, uid))
}
2016-04-22 23:52:09 -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-05-10 02:01:48 -06:00
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) {
2016-04-25 22:28:35 -06:00
chapter.addNode(node, position.path) match {
2016-04-27 01:54:56 -06:00
case None => fufail(s"Invalid addNode $studyId $position $node") >>- reloadUid(study, uid)
case Some(newChapter) =>
chapterRepo.update(newChapter) >>
studyRepo.setPosition(study.id, position + node) >>-
2016-05-11 02:30:23 -06:00
sendTo(study, Socket.AddNode(position, node, uid))
}
2016-04-18 04:51:48 -06:00
}
2016-02-28 17:28:35 -07:00
}
2016-04-27 01:54:56 -06:00
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))
} match {
case Some(newChapter) =>
chapterRepo.update(newChapter) >>-
2016-05-11 02:30:23 -06:00
sendTo(study, Socket.DeleteNode(position, uid))
2016-04-27 01:54:56 -06:00
case None => fufail(s"Invalid delNode $studyId $position") >>- reloadUid(study, uid)
}
}
}
2016-04-27 22:43:50 -06:00
def promoteNodeAt(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(_.promoteNodeAt(position.path))
} match {
case Some(newChapter) =>
chapterRepo.update(newChapter) >>-
2016-05-11 02:30:23 -06:00
sendTo(study, Socket.PromoteNode(position, uid))
2016-04-27 22:43:50 -06:00
case None => fufail(s"Invalid promoteNode $studyId $position") >>- reloadUid(study, uid)
}
}
}
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-05-03 14:52:30 -06:00
def invite(byUserId: User.ID, studyId: Study.ID, username: String, socket: ActorRef) = sequenceStudy(studyId) { study =>
(study isOwner byUserId) ?? {
UserRepo.named(username).flatMap {
_.filterNot(study.members.contains) ?? { user =>
2016-05-11 03:20:19 -06:00
studyRepo.addMember(study, StudyMember make user) >>-
2016-05-03 14:52:30 -06:00
notifier(study, user, socket)
}
} >>- reloadMembers(study)
}
}
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) ?? {
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 =>
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-05-11 02:30:23 -06:00
sendTo(study, 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-28 23:32:43 -06:00
def setComment(userId: User.ID, studyId: Study.ID, position: Position.Ref, c: Comment, uid: Uid) = sequenceStudyWithChapter(studyId) {
2016-04-28 00:18:45 -06:00
case Study.WithChapter(study, chapter) => Contribute(userId, study) {
(study.members get userId) ?? { byMember =>
2016-05-11 03:20:19 -06:00
lightUser(byMember.id) ?? { user =>
val comment = Comment(text = Comment sanitize c.text, by = user.titleName)
chapter.setComment(comment, position.path) match {
case Some(newChapter) =>
chapterRepo.update(newChapter) >>-
sendTo(study, Socket.SetComment(position, comment, uid))
case None => fufail(s"Invalid setComment $studyId $position") >>- reloadUid(study, uid)
}
}
}
}
}
2016-05-10 07:21:55 -06:00
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 =>
2016-05-10 07:21:55 -06:00
chapter.toggleGlyph(glyph, position.path) match {
case Some(newChapter) =>
chapterRepo.update(newChapter) >>-
2016-05-10 07:21:55 -06:00
newChapter.root.nodeAt(position.path).foreach { node =>
2016-05-11 02:30:23 -06:00
sendTo(study, Socket.SetGlyphs(position, node.glyphs, uid))
2016-05-10 07:21:55 -06:00
}
case None => fufail(s"Invalid toggleGlyph $studyId $position $glyph") >>- reloadUid(study, uid)
}
2016-04-28 00:18:45 -06:00
}
}
}
def addChapter(byUserId: User.ID, studyId: Study.ID, data: ChapterMaker.Data, socket: ActorRef, uid: Uid) = sequenceStudy(studyId) { study =>
2016-05-11 11:49:36 -06:00
Contribute(byUserId, study) {
2016-04-24 03:15:18 -06:00
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, uid)
2016-04-25 03:09:26 -06:00
}
}
2016-04-24 03:15:18 -06:00
}
}
}
def setChapter(byUserId: User.ID, studyId: Study.ID, chapterId: Chapter.ID, socket: ActorRef, uid: Uid) = sequenceStudy(studyId) { study =>
doSetChapter(byUserId, study, chapterId, socket, uid)
2016-04-25 03:09:26 -06:00
}
private def doSetChapter(byUserId: User.ID, study: Study, chapterId: Chapter.ID, socket: ActorRef, uid: Uid) =
(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 =>
studyRepo.update(study withChapter chapter) >>- {
2016-05-11 02:30:23 -06:00
sendTo(study, Socket.ChangeChapter(uid))
2016-04-27 19:52:44 -06:00
chat ! SystemTalk(study.id, escapeHtml4(chapter.name), socket)
}
}
}
}
def renameChapter(byUserId: User.ID, studyId: Study.ID, chapterId: Chapter.ID, name: String, socket: ActorRef, uid: Uid) = sequenceStudy(studyId) { study =>
2016-04-24 04:45:34 -06:00
chapterRepo.byIdAndStudy(chapterId, studyId) flatMap {
_ ?? { chapter =>
2016-05-09 07:59:17 -06:00
chapterRepo.update(chapter.copy(name = name)) >>- {
reloadChapters(study)
chat ! SystemTalk(study.id, escapeHtml4(name), socket)
}
2016-04-24 04:45:34 -06:00
}
}
}
def deleteChapter(byUserId: User.ID, studyId: Study.ID, chapterId: Chapter.ID, socket: ActorRef, uid: Uid) = 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, uid)
2016-04-25 03:09:26 -06:00
}
} >> chapterRepo.delete(chapter.id)
case _ => funit
2016-04-24 23:23:13 -06:00
} >>- reloadChapters(study)
}
}
}
2016-04-26 21:12:53 -06:00
def editStudy(byUserId: User.ID, studyId: Study.ID, data: Study.Data) = sequenceStudy(studyId) { study =>
data.realVisibility ?? { visibility =>
val newStudy = study.copy(name = data.name, visibility = visibility)
(newStudy != study) ?? {
2016-05-11 02:30:23 -06:00
studyRepo.update(newStudy) >>- sendTo(study, Socket.ReloadAll)
2016-04-26 21:12:53 -06:00
}
}
}
private def reloadUid(study: Study, uid: Uid) =
2016-05-11 02:30:23 -06:00
sendTo(study, Socket.ReloadUid(uid))
2016-04-20 22:42:49 -06:00
private def reloadMembers(study: Study) =
studyRepo.membersById(study.id).foreach {
2016-04-20 22:42:49 -06:00
_ foreach { members =>
2016-05-11 02:30:23 -06:00
sendTo(study, Socket.ReloadMembers(members))
2016-04-20 22:42:49 -06:00
}
}
2016-04-24 03:15:18 -06:00
private def reloadChapters(study: Study) =
chapterRepo.orderedMetadataByStudy(study.id).foreach { chapters =>
2016-05-11 02:30:23 -06:00
sendTo(study, Socket.ReloadChapters(chapters))
2016-04-24 03:15:18 -06:00
}
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))
}
}
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
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-05-11 02:30:23 -06:00
private def sendTo(study: Study, msg: Any) {
socketHub ! Tell(study.id, msg)
2016-04-20 01:04:38 -06:00
}
2016-02-26 05:08:11 -07:00
}