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-05-31 08:21:57 -06:00
|
|
|
import scala.concurrent.duration._
|
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-02-26 05:08:11 -07:00
|
|
|
import lila.hub.actorApi.map.Tell
|
2016-05-31 18:14:02 -06:00
|
|
|
import lila.hub.actorApi.timeline.{ Propagate, StudyCreate, StudyLike }
|
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-05-23 06:53:40 -06:00
|
|
|
import lila.socket.tree.Node.{ Shape, Shapes, 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(
|
2016-04-22 23:19:07 -06:00
|
|
|
studyRepo: StudyRepo,
|
|
|
|
chapterRepo: ChapterRepo,
|
2016-02-27 04:30:38 -07:00
|
|
|
sequencers: ActorRef,
|
2016-05-22 08:50:55 -06:00
|
|
|
studyMaker: StudyMaker,
|
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,
|
2016-05-31 08:21:57 -06:00
|
|
|
scheduler: akka.actor.Scheduler,
|
2016-04-24 05:54:12 -06:00
|
|
|
chat: ActorSelection,
|
2016-07-25 14:34:15 -06:00
|
|
|
indexer: ActorSelection,
|
2016-05-30 02:13:32 -06:00
|
|
|
timeline: ActorSelection,
|
2016-05-26 14:01:43 -06:00
|
|
|
socketHub: ActorRef) {
|
2016-02-26 05:08:11 -07:00
|
|
|
|
2016-04-22 23:19:07 -06:00
|
|
|
def byId = studyRepo byId _
|
2016-02-27 04:30:38 -07:00
|
|
|
|
2016-08-31 04:22:46 -06:00
|
|
|
def byIds = studyRepo byOrderedIds _
|
|
|
|
|
2016-09-07 17:11:43 -06:00
|
|
|
def publicByIds(ids: Seq[String]) = byIds(ids) map { _.filter(_.isPublic) }
|
|
|
|
|
2016-04-22 23:19:07 -06:00
|
|
|
def byIdWithChapter(id: Study.ID): Fu[Option[Study.WithChapter]] = byId(id) flatMap {
|
|
|
|
_ ?? { study =>
|
2016-07-26 03:47:15 -06:00
|
|
|
chapterRepo.byId(study.position.chapterId) flatMap {
|
|
|
|
case None => chapterRepo.firstByStudy(study.id) flatMap {
|
|
|
|
case None => fuccess(none)
|
|
|
|
case Some(chapter) =>
|
|
|
|
val fixed = study withChapter chapter
|
|
|
|
studyRepo.updateSomeFields(fixed) inject
|
|
|
|
Study.WithChapter(fixed, chapter).some
|
|
|
|
}
|
|
|
|
case Some(chapter) => fuccess(Study.WithChapter(study, chapter).some)
|
2016-04-22 23:19:07 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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 {
|
2016-10-08 12:49:51 -06:00
|
|
|
_.filter(_.studyId == study.id) map { Study.WithChapter(study, _) }
|
2016-04-26 02:49:39 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-22 08:50:55 -06:00
|
|
|
def create(data: DataForm.Data, user: User): Fu[Study.WithChapter] =
|
|
|
|
studyMaker(data, user) flatMap { res =>
|
2016-05-26 14:01:43 -06:00
|
|
|
studyRepo.insert(res.study) >>
|
2016-05-31 08:21:57 -06:00
|
|
|
chapterRepo.insert(res.chapter) >>-
|
2016-07-25 14:34:15 -06:00
|
|
|
indexStudy(res.study) >>-
|
2016-05-31 08:21:57 -06:00
|
|
|
scheduleTimeline(res.study.id) inject res
|
2016-05-22 08:50:55 -06:00
|
|
|
}
|
2016-04-16 07:26:01 -06:00
|
|
|
|
2016-10-12 07:37:40 -06:00
|
|
|
def clone(me: User, prev: Study): Fu[Option[Study]] =
|
|
|
|
Settings.UserSelection.allows(prev.settings.cloneable, prev, me.id.some) ?? {
|
|
|
|
chapterRepo.orderedByStudy(prev.id).flatMap { chapters =>
|
|
|
|
val study1 = prev.cloneFor(me)
|
|
|
|
val newChapters = chapters.map(_ cloneFor study1)
|
|
|
|
newChapters.headOption.map(study1.withChapter) ?? { study =>
|
|
|
|
studyRepo.insert(study) >>
|
|
|
|
newChapters.map(chapterRepo.insert).sequenceFu >>- {
|
|
|
|
chat ! lila.chat.actorApi.SystemTalk(
|
|
|
|
study.id,
|
|
|
|
s"Cloned from lichess.org/study/${prev.id}")
|
|
|
|
} inject study.some
|
|
|
|
}
|
|
|
|
}
|
2016-08-31 10:20:25 -06:00
|
|
|
}
|
2016-08-31 10:00:45 -06:00
|
|
|
|
2016-07-30 03:43:19 -06:00
|
|
|
def resetIfOld(study: Study, chapters: List[Chapter.Metadata]): Fu[Study] =
|
|
|
|
chapters.headOption match {
|
|
|
|
case Some(c) if study.isOld && study.position != c.initialPosition =>
|
|
|
|
val newStudy = study withChapter c
|
|
|
|
studyRepo.updateSomeFields(newStudy) inject newStudy
|
|
|
|
case _ => fuccess(study)
|
|
|
|
}
|
|
|
|
|
2016-05-31 08:21:57 -06:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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 =>
|
2016-06-13 05:29:00 -06:00
|
|
|
(study canChat userId) ?? {
|
2016-06-17 05:44:32 -06:00
|
|
|
chat ! lila.chat.actorApi.UserTalk(studyId, userId, text)
|
2016-05-10 23:42:03 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-05-18 08:15:02 -06:00
|
|
|
chapterRepo.byId(position.chapterId).map {
|
|
|
|
_ filter { c =>
|
|
|
|
c.root.pathExists(position.path) && study.position.chapterId == c.id
|
2016-04-22 23:52:09 -06:00
|
|
|
}
|
2016-05-18 08:15:02 -06:00
|
|
|
} flatMap {
|
|
|
|
case None => funit >>- reloadUid(study, uid)
|
|
|
|
case Some(chapter) if study.position.path != position.path =>
|
2016-05-19 06:23:07 -06:00
|
|
|
studyRepo.setPosition(study.id, position) >>
|
|
|
|
updateConceal(study, chapter, position) >>-
|
|
|
|
sendTo(study, Socket.SetPath(position, uid))
|
2016-05-18 08:15:02 -06:00
|
|
|
case _ => funit
|
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)
|
2016-04-22 23:19:07 -06:00
|
|
|
case Some(newChapter) =>
|
|
|
|
chapterRepo.update(newChapter) >>
|
2016-05-19 06:23:07 -06:00
|
|
|
studyRepo.setPosition(study.id, position + node) >>
|
|
|
|
updateConceal(study, newChapter, position + node) >>-
|
2016-05-11 02:30:23 -06:00
|
|
|
sendTo(study, 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-05-19 06:23:07 -06:00
|
|
|
private def updateConceal(study: Study, chapter: Chapter, position: Position.Ref) =
|
|
|
|
chapter.conceal ?? { conceal =>
|
|
|
|
chapter.root.lastMainlinePlyOf(position.path).some.filter(_ > conceal) ?? { newConceal =>
|
|
|
|
if (newConceal >= chapter.root.lastMainlinePly)
|
|
|
|
chapterRepo.removeConceal(chapter.id) >>-
|
|
|
|
sendTo(study, Socket.SetConceal(position, none))
|
|
|
|
else
|
|
|
|
chapterRepo.setConceal(chapter.id, newConceal) >>-
|
|
|
|
sendTo(study, Socket.SetConceal(position, newConceal.some))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-22 23:19:07 -06:00
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-04-22 23:19:07 -06:00
|
|
|
|
|
|
|
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-05-03 14:52:30 -06:00
|
|
|
def invite(byUserId: User.ID, studyId: Study.ID, username: String, socket: ActorRef) = sequenceStudy(studyId) { study =>
|
2016-04-22 23:19:07 -06:00
|
|
|
(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)
|
2016-04-22 23:19:07 -06:00
|
|
|
}
|
2016-07-25 14:34:15 -06:00
|
|
|
} >>- reloadMembers(study) >>- indexStudy(study)
|
2016-04-19 22:33:59 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-01 06:12:40 -06:00
|
|
|
def kick(studyId: Study.ID, userId: User.ID) = sequenceStudy(studyId) { study =>
|
|
|
|
study.isMember(userId) ?? {
|
2016-04-22 23:19:07 -06:00
|
|
|
studyRepo.removeMember(study, userId)
|
2016-07-25 14:34:15 -06:00
|
|
|
} >>- reloadMembers(study) >>- indexStudy(study)
|
2016-04-20 22:42:49 -06:00
|
|
|
}
|
|
|
|
|
2016-05-23 06:53:40 -06:00
|
|
|
def setShapes(userId: User.ID, studyId: Study.ID, position: Position.Ref, shapes: Shapes, 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) =>
|
2016-05-27 04:04:16 -06:00
|
|
|
studyRepo.updateNow(study)
|
2016-04-25 21:51:05 -06:00
|
|
|
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-05-17 08:23:09 -06:00
|
|
|
def setComment(userId: User.ID, studyId: Study.ID, position: Position.Ref, text: Comment.Text, uid: Uid) = sequenceStudyWithChapter(studyId) {
|
2016-04-28 00:18:45 -06:00
|
|
|
case Study.WithChapter(study, chapter) => Contribute(userId, study) {
|
2016-05-10 04:46:04 -06:00
|
|
|
(study.members get userId) ?? { byMember =>
|
2016-05-17 08:23:09 -06:00
|
|
|
lightUser(userId) ?? { author =>
|
|
|
|
val comment = Comment(
|
|
|
|
id = Comment.Id.make,
|
|
|
|
text = text,
|
|
|
|
by = Comment.Author.User(author.id, author.titleName))
|
2016-05-11 03:20:19 -06:00
|
|
|
chapter.setComment(comment, position.path) match {
|
|
|
|
case Some(newChapter) =>
|
2016-05-27 04:04:16 -06:00
|
|
|
studyRepo.updateNow(study)
|
2016-05-17 08:23:09 -06:00
|
|
|
newChapter.root.nodeAt(position.path).flatMap(_.comments findBy comment.by) ?? { c =>
|
|
|
|
chapterRepo.update(newChapter) >>-
|
2016-07-25 14:34:15 -06:00
|
|
|
sendTo(study, Socket.SetComment(position, c, uid)) >>-
|
|
|
|
indexStudy(study)
|
2016-05-17 08:23:09 -06:00
|
|
|
}
|
2016-05-11 03:20:19 -06:00
|
|
|
case None => fufail(s"Invalid setComment $studyId $position") >>- reloadUid(study, uid)
|
|
|
|
}
|
2016-05-10 04:46:04 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-17 08:23:09 -06:00
|
|
|
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 {
|
|
|
|
case Some(newChapter) =>
|
|
|
|
chapterRepo.update(newChapter) >>-
|
2016-07-25 14:34:15 -06:00
|
|
|
sendTo(study, Socket.DeleteComment(position, id, uid)) >>-
|
|
|
|
indexStudy(study)
|
2016-05-17 08:23:09 -06:00
|
|
|
case None => fufail(s"Invalid deleteComment $studyId $position $id") >>- 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) {
|
2016-05-10 04:46:04 -06:00
|
|
|
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 {
|
2016-05-10 04:46:04 -06:00
|
|
|
case Some(newChapter) =>
|
2016-05-27 04:04:16 -06:00
|
|
|
studyRepo.updateNow(study)
|
2016-05-10 04:46:04 -06:00
|
|
|
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-29 02:28:09 -06:00
|
|
|
}
|
2016-04-28 00:18:45 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-09 08:40:50 -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-05-18 06:10:03 -06:00
|
|
|
chapterMaker(study, data, order, byUserId) flatMap {
|
2016-04-25 03:09:26 -06:00
|
|
|
_ ?? { chapter =>
|
2016-05-12 09:07:04 -06:00
|
|
|
data.initial ?? {
|
|
|
|
chapterRepo.firstByStudy(study.id) flatMap {
|
|
|
|
_.filter(_.isEmptyInitial) ?? chapterRepo.delete
|
|
|
|
}
|
|
|
|
} >> chapterRepo.insert(chapter) >>
|
2016-07-26 03:47:15 -06:00
|
|
|
doSetChapter(study, chapter.id, socket, uid) >>-
|
2016-07-25 14:34:15 -06:00
|
|
|
studyRepo.updateNow(study) >>-
|
|
|
|
indexStudy(study)
|
2016-04-25 03:09:26 -06:00
|
|
|
}
|
|
|
|
}
|
2016-04-24 03:15:18 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-09 08:40:50 -06:00
|
|
|
def setChapter(byUserId: User.ID, studyId: Study.ID, chapterId: Chapter.ID, socket: ActorRef, uid: Uid) = sequenceStudy(studyId) { study =>
|
2016-07-26 03:47:15 -06:00
|
|
|
study.canContribute(byUserId) ?? doSetChapter(study, chapterId, socket, uid)
|
2016-04-25 03:09:26 -06:00
|
|
|
}
|
|
|
|
|
2016-07-26 03:47:15 -06:00
|
|
|
private def doSetChapter(study: Study, chapterId: Chapter.ID, socket: ActorRef, uid: Uid) =
|
|
|
|
(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-05-28 01:32:49 -06:00
|
|
|
studyRepo.updateSomeFields(study withChapter chapter) >>-
|
2016-05-11 02:30:23 -06:00
|
|
|
sendTo(study, Socket.ChangeChapter(uid))
|
2016-04-24 03:38:35 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-19 09:27:32 -06:00
|
|
|
def editChapter(byUserId: User.ID, studyId: Study.ID, data: ChapterMaker.EditData, socket: ActorRef, uid: Uid) = sequenceStudy(studyId) { study =>
|
2016-05-16 04:01:03 -06:00
|
|
|
Contribute(byUserId, study) {
|
2016-05-19 09:27:32 -06:00
|
|
|
chapterRepo.byIdAndStudy(data.id, studyId) flatMap {
|
2016-05-16 04:01:03 -06:00
|
|
|
_ ?? { chapter =>
|
2016-05-19 09:27:32 -06:00
|
|
|
val name = Chapter toName data.name
|
|
|
|
val newChapter = chapter.copy(
|
|
|
|
name = name,
|
|
|
|
conceal = (chapter.conceal, data.conceal) match {
|
|
|
|
case (None, true) => Chapter.Ply(0).some
|
|
|
|
case (Some(_), false) => None
|
|
|
|
case _ => chapter.conceal
|
2016-05-31 05:16:07 -06:00
|
|
|
},
|
|
|
|
setup = chapter.setup.copy(orientation = data.realOrientation))
|
2016-05-19 09:27:32 -06:00
|
|
|
if (chapter == newChapter) funit
|
|
|
|
else chapterRepo.update(newChapter) >> {
|
|
|
|
if (chapter.conceal != newChapter.conceal) {
|
|
|
|
(newChapter.conceal.isDefined && study.position.chapterId == chapter.id).?? {
|
|
|
|
val newPosition = study.position.withPath(Path.root)
|
|
|
|
studyRepo.setPosition(study.id, newPosition)
|
|
|
|
} >>-
|
|
|
|
sendTo(study, Socket.ReloadAll)
|
|
|
|
}
|
|
|
|
else fuccess {
|
2016-05-31 05:16:07 -06:00
|
|
|
val changedOrientation = newChapter.setup.orientation != chapter.setup.orientation
|
|
|
|
if (study.position.chapterId == chapter.id && changedOrientation)
|
|
|
|
sendTo(study, Socket.ChangeChapter(uid))
|
|
|
|
else
|
|
|
|
reloadChapters(study)
|
2016-05-19 09:27:32 -06:00
|
|
|
}
|
2016-05-16 04:01:03 -06:00
|
|
|
}
|
2016-07-25 14:34:15 -06:00
|
|
|
} >>- indexStudy(study)
|
2016-04-24 04:45:34 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-09 08:40:50 -06:00
|
|
|
def deleteChapter(byUserId: User.ID, studyId: Study.ID, chapterId: Chapter.ID, socket: ActorRef, uid: Uid) = sequenceStudy(studyId) { study =>
|
2016-05-16 04:01:03 -06:00
|
|
|
Contribute(byUserId, study) {
|
|
|
|
chapterRepo.byIdAndStudy(chapterId, studyId) flatMap {
|
|
|
|
_ ?? { chapter =>
|
|
|
|
chapterRepo.orderedMetadataByStudy(studyId).flatMap {
|
|
|
|
case chaps if chaps.size > 1 => (study.position.chapterId == chapterId).?? {
|
|
|
|
chaps.find(_.id != chapterId) ?? { newChap =>
|
2016-07-26 03:47:15 -06:00
|
|
|
doSetChapter(study, newChap.id, socket, uid)
|
2016-05-16 04:01:03 -06:00
|
|
|
}
|
|
|
|
} >> chapterRepo.delete(chapter.id)
|
|
|
|
case _ => funit
|
|
|
|
} >>- reloadChapters(study)
|
2016-07-25 14:34:15 -06:00
|
|
|
} >>- indexStudy(study)
|
2016-04-24 23:23:13 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-16 04:01:03 -06:00
|
|
|
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 =>
|
2016-05-13 06:14:38 -06:00
|
|
|
data.settings ?? { settings =>
|
2016-05-19 11:43:19 -06:00
|
|
|
val newStudy = study.copy(
|
|
|
|
name = Study toName data.name,
|
|
|
|
settings = settings,
|
|
|
|
visibility = data.vis)
|
2016-04-26 21:12:53 -06:00
|
|
|
(newStudy != study) ?? {
|
2016-07-25 14:34:15 -06:00
|
|
|
studyRepo.updateSomeFields(newStudy) >>-
|
|
|
|
sendTo(study, Socket.ReloadAll) >>-
|
|
|
|
indexStudy(study)
|
2016-04-26 21:12:53 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-12 08:21:25 -06:00
|
|
|
def delete(study: Study) = sequenceStudy(study.id) { study =>
|
2016-07-25 14:34:15 -06:00
|
|
|
studyRepo.delete(study) >>
|
|
|
|
chapterRepo.deleteByStudy(study) >>-
|
|
|
|
(indexer ! actorApi.RemoveStudy(study.id))
|
2016-05-12 08:21:25 -06:00
|
|
|
}
|
|
|
|
|
2016-05-26 14:01:43 -06:00
|
|
|
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))
|
2016-05-31 18:14:02 -06:00
|
|
|
if (v) studyRepo.nameById(studyId) foreach {
|
|
|
|
_ ?? { name =>
|
|
|
|
timeline ! (Propagate(StudyLike(userId, studyId, name)) toFollowersOf userId)
|
|
|
|
}
|
|
|
|
}
|
2016-05-26 14:01:43 -06:00
|
|
|
}
|
|
|
|
|
2016-06-21 07:02:23 -06:00
|
|
|
def resetAllRanks = studyRepo.resetAllRanks
|
|
|
|
|
2016-07-25 14:34:15 -06:00
|
|
|
private def indexStudy(study: Study) = indexer ! actorApi.SaveStudy(study)
|
|
|
|
|
2016-04-23 21:34:26 -06:00
|
|
|
private def reloadUid(study: Study, uid: Uid) =
|
2016-05-11 02:30:23 -06:00
|
|
|
sendTo(study, Socket.ReloadUid(uid))
|
2016-04-23 21:34:26 -06:00
|
|
|
|
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 =>
|
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))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-05-26 14:01:43 -06:00
|
|
|
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)
|
2016-02-26 05:08:11 -07:00
|
|
|
}
|