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-05-31 08:21:57 -06:00
|
|
|
import scala.concurrent.duration._
|
2016-02-26 05:08:11 -07:00
|
|
|
|
2017-07-01 08:44:24 -06:00
|
|
|
import chess.Centis
|
2017-10-29 11:19:00 -06:00
|
|
|
import chess.format.pgn.{ Tags, Glyph }
|
2017-08-21 08:46:41 -06:00
|
|
|
import lila.chat.Chat
|
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-04-23 04:01:21 -06:00
|
|
|
import lila.socket.Socket.Uid
|
2017-08-14 18:44:04 -06:00
|
|
|
import lila.tree.Node.{ Shapes, Comment, Gamebook }
|
2018-01-17 13:24:42 -07:00
|
|
|
import lila.tree.Eval
|
2017-10-21 14:01:50 -06:00
|
|
|
import lila.user.User
|
2016-02-26 05:08:11 -07:00
|
|
|
|
|
|
|
final class StudyApi(
|
2016-04-22 23:19:07 -06:00
|
|
|
studyRepo: StudyRepo,
|
|
|
|
chapterRepo: ChapterRepo,
|
2018-01-17 13:24:42 -07:00
|
|
|
sequencer: StudySequencer,
|
2016-05-22 08:50:55 -06:00
|
|
|
studyMaker: StudyMaker,
|
2016-04-25 03:09:26 -06:00
|
|
|
chapterMaker: ChapterMaker,
|
2017-05-06 04:30:58 -06:00
|
|
|
inviter: StudyInvite,
|
2016-12-21 06:12:47 -07:00
|
|
|
tagsFixer: ChapterTagsFixer,
|
2017-09-19 07:29:43 -06:00
|
|
|
explorerGameHandler: ExplorerGame,
|
2017-01-25 04:33:04 -07:00
|
|
|
lightUser: lila.common.LightUser.GetterSync,
|
2016-05-31 08:21:57 -06:00
|
|
|
scheduler: akka.actor.Scheduler,
|
2016-04-24 05:54:12 -06:00
|
|
|
chat: ActorSelection,
|
2017-01-28 07:24:22 -07:00
|
|
|
bus: lila.common.Bus,
|
2016-05-30 02:13:32 -06:00
|
|
|
timeline: ActorSelection,
|
Study icon for friends in a study
The study icon will appear when:
* A friend, who is a contributor, joins a study, or makes a move in a study if he does not have the icon yet (the latter happens if you have two studies open and close one).
* A friend is in a study and gets added as contributor.
* A friend is a contributor in a private study and the study becomes public.
The study icon will disappear when:
* A friend, who is a contributor, leaves a study.
* A friend, who is a contributor, gets his status revoked, or kicked.
* A friend, who is a contributor, is in a public study that becomes private.
2017-02-12 14:09:23 -07:00
|
|
|
socketHub: ActorRef,
|
2018-01-17 13:24:42 -07:00
|
|
|
serverEvalRequester: ServerEval.Requester,
|
2017-02-14 08:34:07 -07:00
|
|
|
lightStudyCache: LightStudyCache
|
|
|
|
) {
|
2016-02-26 05:08:11 -07:00
|
|
|
|
2018-01-17 13:24:42 -07:00
|
|
|
import sequencer._
|
|
|
|
|
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 _
|
|
|
|
|
2017-07-21 04:51:56 -06:00
|
|
|
def publicIdNames = studyRepo publicIdNames _
|
2017-07-21 04:49:54 -06:00
|
|
|
|
2017-01-20 05:47:52 -07:00
|
|
|
def publicByIds(ids: Seq[Study.Id]) = byIds(ids) map { _.filter(_.isPublic) }
|
2016-09-07 17:11:43 -06:00
|
|
|
|
2017-04-01 06:16:56 -06:00
|
|
|
def byIdAndOwner(id: Study.Id, owner: User) = byId(id) map {
|
|
|
|
_.filter(_ isOwner owner.id)
|
|
|
|
}
|
|
|
|
|
|
|
|
def isOwner(id: Study.Id, owner: User) = byIdAndOwner(id, owner).map(_.isDefined)
|
|
|
|
|
2017-01-20 06:00:42 -07:00
|
|
|
private def fetchAndFixChapter(id: Chapter.Id): Fu[Option[Chapter]] =
|
2016-12-21 06:12:47 -07:00
|
|
|
chapterRepo.byId(id) flatMap {
|
|
|
|
_ ?? { c => tagsFixer(c) map some }
|
|
|
|
}
|
|
|
|
|
2017-01-20 05:47:52 -07:00
|
|
|
def byIdWithChapter(id: Study.Id): Fu[Option[Study.WithChapter]] = byId(id) flatMap {
|
2016-04-22 23:19:07 -06:00
|
|
|
_ ?? { study =>
|
2016-12-21 06:12:47 -07:00
|
|
|
fetchAndFixChapter(study.position.chapterId) flatMap {
|
2016-07-26 03:47:15 -06:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-20 06:00:42 -07:00
|
|
|
def byIdWithChapter(id: Study.Id, chapterId: Chapter.Id): Fu[Option[Study.WithChapter]] = byId(id) flatMap {
|
2016-04-26 02:49:39 -06:00
|
|
|
_ ?? { study =>
|
2016-12-21 06:12:47 -07:00
|
|
|
fetchAndFixChapter(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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-22 02:54:43 -07:00
|
|
|
def byIdWithFirstChapter(id: Study.Id): Fu[Option[Study.WithChapter]] = byId(id) flatMap {
|
|
|
|
_ ?? { study =>
|
|
|
|
chapterRepo.firstByStudy(study.id) map {
|
|
|
|
_ ?? { Study.WithChapter(study, _).some }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-18 15:18:12 -06:00
|
|
|
def studyIdOf = chapterRepo.studyIdOf _
|
|
|
|
|
2017-10-05 17:22:29 -06:00
|
|
|
def members(id: Study.Id): Fu[Option[StudyMembers]] = studyRepo membersById id
|
|
|
|
|
2017-09-29 12:33:37 -06:00
|
|
|
def create(data: StudyMaker.Data, user: User): Fu[Option[Study.WithChapter]] = (data.form.as match {
|
2017-03-29 18:05:43 -06:00
|
|
|
case DataForm.AsNewStudy =>
|
2017-09-29 12:33:37 -06:00
|
|
|
studyMaker(data, user) flatMap { res =>
|
2017-03-29 18:05:43 -06:00
|
|
|
studyRepo.insert(res.study) >>
|
|
|
|
chapterRepo.insert(res.chapter) >>-
|
|
|
|
indexStudy(res.study) >>-
|
|
|
|
scheduleTimeline(res.study.id) inject res.some
|
|
|
|
}
|
|
|
|
case DataForm.AsChapterOf(studyId) => byId(studyId) flatMap {
|
|
|
|
case Some(study) if study.canContribute(user.id) =>
|
|
|
|
import akka.pattern.ask
|
|
|
|
import makeTimeout.short
|
|
|
|
for {
|
|
|
|
socket <- socketHub ? lila.hub.actorApi.map.Get(studyId.value) mapTo manifest[ActorRef]
|
|
|
|
_ <- addChapter(
|
|
|
|
byUserId = user.id,
|
|
|
|
studyId = study.id,
|
2017-09-29 12:33:37 -06:00
|
|
|
data = data.form.toChapterData,
|
2017-06-19 00:44:25 -06:00
|
|
|
sticky = study.settings.sticky,
|
2017-03-29 18:05:43 -06:00
|
|
|
socket = socket,
|
|
|
|
uid = Uid("") // the user is not in the study yet
|
|
|
|
)
|
|
|
|
made <- byIdWithChapter(studyId)
|
|
|
|
} yield made
|
|
|
|
case _ => fuccess(none)
|
2017-09-29 12:33:37 -06:00
|
|
|
} orElse create(data.copy(form = data.form.copy(asStr = none)), user)
|
2017-07-21 04:49:54 -06:00
|
|
|
}) addEffect {
|
|
|
|
_ ?? { sc =>
|
|
|
|
bus.publish(actorApi.StartStudy(sc.study.id), 'startStudy)
|
|
|
|
}
|
2017-03-29 18:05:43 -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)
|
2016-10-21 08:49:28 -06:00
|
|
|
newChapters.headOption.map(study1.rewindTo) ?? { study =>
|
2016-10-12 07:37:40 -06:00
|
|
|
studyRepo.insert(study) >>
|
|
|
|
newChapters.map(chapterRepo.insert).sequenceFu >>- {
|
|
|
|
chat ! lila.chat.actorApi.SystemTalk(
|
2017-08-17 14:25:50 -06:00
|
|
|
Chat.Id(study.id.value),
|
2017-02-14 08:34:07 -07:00
|
|
|
s"Cloned from lichess.org/study/${prev.id}"
|
|
|
|
)
|
2016-10-12 07:37:40 -06:00
|
|
|
} inject study.some
|
|
|
|
}
|
|
|
|
}
|
2016-08-31 10:20:25 -06:00
|
|
|
}
|
2016-08-31 10:00:45 -06:00
|
|
|
|
2017-06-09 18:47:38 -06:00
|
|
|
def resetIfOld(study: Study, chapters: List[Chapter.Metadata]): Fu[(Study, Option[Chapter])] =
|
2016-07-30 03:43:19 -06:00
|
|
|
chapters.headOption match {
|
|
|
|
case Some(c) if study.isOld && study.position != c.initialPosition =>
|
2016-10-21 08:49:28 -06:00
|
|
|
val newStudy = study rewindTo c
|
2017-06-09 18:47:38 -06:00
|
|
|
studyRepo.updateSomeFields(newStudy) zip chapterRepo.byId(c.id) map {
|
|
|
|
case (_, chapter) => newStudy -> chapter
|
|
|
|
}
|
|
|
|
case _ => fuccess(study -> none)
|
2016-07-30 03:43:19 -06:00
|
|
|
}
|
|
|
|
|
2017-01-20 05:47:52 -07:00
|
|
|
private def scheduleTimeline(studyId: Study.Id) = scheduler.scheduleOnce(1 minute) {
|
2016-05-31 08:21:57 -06:00
|
|
|
byId(studyId) foreach {
|
|
|
|
_.filter(_.isPublic) foreach { study =>
|
2017-01-20 06:05:09 -07:00
|
|
|
timeline ! (Propagate(StudyCreate(study.ownerId, study.id.value, study.name.value)) toFollowersOf study.ownerId)
|
2016-05-31 08:21:57 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-20 05:47:52 -07:00
|
|
|
def talk(userId: User.ID, studyId: Study.Id, text: String, socket: ActorRef) = byId(studyId) foreach {
|
2016-05-10 23:42:03 -06:00
|
|
|
_ foreach { study =>
|
2016-06-13 05:29:00 -06:00
|
|
|
(study canChat userId) ?? {
|
2017-12-16 16:02:47 -07:00
|
|
|
chat ! lila.chat.actorApi.UserTalk(
|
|
|
|
Chat.Id(studyId.value),
|
|
|
|
userId = userId,
|
|
|
|
text = text,
|
|
|
|
publicSource = lila.hub.actorApi.shutup.PublicSource.Study(studyId.value).some
|
|
|
|
)
|
2016-05-10 23:42:03 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-20 05:47:52 -07: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
|
|
|
|
2017-10-09 10:52:52 -06:00
|
|
|
def addNode(userId: User.ID, studyId: Study.Id, position: Position.Ref, node: Node, uid: Uid, opts: MoveOpts, relay: Option[Chapter.Relay] = None) =
|
|
|
|
sequenceStudyWithChapter(studyId, position.chapterId) {
|
|
|
|
case Study.WithChapter(study, chapter) => Contribute(userId, study) {
|
|
|
|
doAddNode(userId, study, Position(chapter, position.path), node, uid, opts, relay).void
|
|
|
|
}
|
2016-04-18 04:51:48 -06:00
|
|
|
}
|
2016-02-28 17:28:35 -07:00
|
|
|
|
2018-01-17 13:24:42 -07:00
|
|
|
private[study] def doAddNode(userId: User.ID, study: Study, position: Position, rawNode: Node, uid: Uid, opts: MoveOpts, relay: Option[Chapter.Relay]): Funit = {
|
2017-10-05 11:28:27 -06:00
|
|
|
val node = rawNode.withoutChildren
|
2017-10-05 17:22:29 -06:00
|
|
|
position.chapter.addNode(node, position.path, relay) match {
|
2017-09-20 13:25:05 -06:00
|
|
|
case None =>
|
|
|
|
fufail(s"Invalid addNode ${study.id} ${position.ref} $node") >>-
|
|
|
|
reloadUidBecauseOf(study, uid, position.chapter.id) inject none
|
|
|
|
case Some(chapter) =>
|
|
|
|
chapter.root.nodeAt(position.path) ?? { parent =>
|
|
|
|
val newPosition = position.ref + node
|
|
|
|
chapterRepo.setChildren(chapter, position.path, parent.children) >>
|
2017-10-05 17:22:29 -06:00
|
|
|
(relay ?? { chapterRepo.setRelay(chapter.id, _) }) >>
|
2017-09-20 13:25:05 -06:00
|
|
|
(opts.sticky ?? studyRepo.setPosition(study.id, newPosition)) >>
|
|
|
|
updateConceal(study, chapter, newPosition) >>- {
|
2017-10-05 17:22:29 -06:00
|
|
|
sendTo(study, Socket.AddNode(
|
|
|
|
position.ref,
|
|
|
|
node,
|
|
|
|
chapter.setup.variant,
|
|
|
|
uid,
|
|
|
|
sticky = opts.sticky,
|
|
|
|
relay = relay
|
|
|
|
))
|
2017-09-20 13:25:05 -06:00
|
|
|
sendStudyEnters(study, userId)
|
|
|
|
if (opts.promoteToMainline && !Path.isMainline(chapter.root, newPosition.path))
|
|
|
|
promote(userId, study.id, position.ref + node, toMainline = true, uid)
|
2017-10-09 10:52:52 -06:00
|
|
|
}
|
2017-09-20 13:25:05 -06:00
|
|
|
}
|
|
|
|
}
|
2017-10-05 00:30:40 -06:00
|
|
|
}
|
2017-09-20 13:25:05 -06: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))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-16 10:20:44 -06:00
|
|
|
def deleteNodeAt(userId: User.ID, studyId: Study.Id, position: Position.Ref, uid: Uid) = sequenceStudyWithChapter(studyId, position.chapterId) {
|
2016-04-27 01:54:56 -06:00
|
|
|
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))
|
2017-06-23 09:21:34 -06:00
|
|
|
case None =>
|
|
|
|
fufail(s"Invalid delNode $studyId $position") >>-
|
|
|
|
reloadUidBecauseOf(study, uid, chapter.id)
|
2016-04-27 01:54:56 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-04-22 23:19:07 -06:00
|
|
|
|
2017-06-16 10:20:44 -06:00
|
|
|
def promote(userId: User.ID, studyId: Study.Id, position: Position.Ref, toMainline: Boolean, uid: Uid) = sequenceStudyWithChapter(studyId, position.chapterId) {
|
2016-04-27 22:43:50 -06:00
|
|
|
case Study.WithChapter(study, chapter) => Contribute(userId, study) {
|
|
|
|
chapter.updateRoot { root =>
|
2016-12-15 15:16:44 -07:00
|
|
|
root.withChildren { children =>
|
|
|
|
if (toMainline) children.promoteToMainlineAt(position.path)
|
|
|
|
else children.promoteUpAt(position.path).map(_._1)
|
|
|
|
}
|
2016-04-27 22:43:50 -06:00
|
|
|
} match {
|
|
|
|
case Some(newChapter) =>
|
|
|
|
chapterRepo.update(newChapter) >>-
|
2016-12-15 15:16:44 -07:00
|
|
|
sendTo(study, Socket.Promote(position, toMainline, uid))
|
2017-06-23 09:21:34 -06:00
|
|
|
case None =>
|
|
|
|
fufail(s"Invalid promoteToMainline $studyId $position") >>-
|
|
|
|
reloadUidBecauseOf(study, uid, chapter.id)
|
2016-04-27 22:43:50 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-04-22 23:19:07 -06:00
|
|
|
|
2017-01-20 05:47:52 -07:00
|
|
|
def setRole(byUserId: User.ID, studyId: Study.Id, userId: User.ID, roleStr: String) = sequenceStudy(studyId) { study =>
|
2016-04-22 23:19:07 -06:00
|
|
|
(study isOwner byUserId) ?? {
|
|
|
|
val role = StudyMember.Role.byId.getOrElse(roleStr, StudyMember.Role.Read)
|
2017-02-14 06:38:30 -07:00
|
|
|
study.members.get(userId) ifTrue study.isPublic foreach { member =>
|
2017-02-14 06:41:32 -07:00
|
|
|
if (!member.role.canWrite && role.canWrite)
|
2017-02-14 06:38:30 -07:00
|
|
|
bus.publish(lila.hub.actorApi.study.StudyMemberGotWriteAccess(userId, studyId.value), 'study)
|
2017-02-14 06:41:32 -07:00
|
|
|
else if (member.role.canWrite && !role.canWrite)
|
2017-02-14 06:38:30 -07:00
|
|
|
bus.publish(lila.hub.actorApi.study.StudyMemberLostWriteAccess(userId, studyId.value), 'study)
|
Study icon for friends in a study
The study icon will appear when:
* A friend, who is a contributor, joins a study, or makes a move in a study if he does not have the icon yet (the latter happens if you have two studies open and close one).
* A friend is in a study and gets added as contributor.
* A friend is a contributor in a private study and the study becomes public.
The study icon will disappear when:
* A friend, who is a contributor, leaves a study.
* A friend, who is a contributor, gets his status revoked, or kicked.
* A friend, who is a contributor, is in a public study that becomes private.
2017-02-12 14:09:23 -07:00
|
|
|
}
|
2017-02-14 06:02:42 -07:00
|
|
|
studyRepo.setRole(study, userId, role) >>-
|
2017-06-10 08:09:00 -06:00
|
|
|
onMembersChange(study)
|
2016-04-19 22:33:59 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-06 05:30:37 -06:00
|
|
|
def invite(byUserId: User.ID, studyId: Study.Id, username: String, socket: ActorRef, onError: String => Unit) = sequenceStudy(studyId) { study =>
|
|
|
|
inviter(byUserId, study, username, socket).addEffects(
|
|
|
|
err => onError(err.getMessage),
|
2017-06-10 08:09:00 -06:00
|
|
|
_ => onMembersChange(study)
|
2017-05-06 05:30:37 -06:00
|
|
|
)
|
2016-04-19 22:33:59 -06:00
|
|
|
}
|
|
|
|
|
2017-06-16 08:27:38 -06:00
|
|
|
def kick(byUserId: User.ID, studyId: Study.Id, userId: User.ID) = sequenceStudy(studyId) { study =>
|
|
|
|
(study.isMember(userId) && (study.isOwner(byUserId) ^ (byUserId == userId))) ?? {
|
2017-02-14 06:38:30 -07:00
|
|
|
if (study.isPublic && study.canContribute(userId))
|
|
|
|
bus.publish(lila.hub.actorApi.study.StudyMemberLostWriteAccess(userId, studyId.value), 'study)
|
2017-06-10 08:09:00 -06:00
|
|
|
studyRepo.removeMember(study, userId)
|
|
|
|
} >>- onMembersChange(study)
|
|
|
|
}
|
|
|
|
|
2017-06-10 11:48:04 -06:00
|
|
|
def isContributor = studyRepo.isContributor _
|
|
|
|
|
2017-06-10 08:09:00 -06:00
|
|
|
private def onMembersChange(study: Study) = {
|
|
|
|
lightStudyCache.refresh(study.id)
|
2017-06-10 12:21:56 -06:00
|
|
|
studyRepo.membersById(study.id).foreach {
|
|
|
|
_ foreach { members =>
|
|
|
|
sendTo(study, Socket.ReloadMembers(members))
|
|
|
|
}
|
|
|
|
}
|
2017-06-10 08:09:00 -06:00
|
|
|
indexStudy(study)
|
2016-04-20 22:42:49 -06:00
|
|
|
}
|
|
|
|
|
2017-01-20 05:47:52 -07: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)
|
2017-03-31 05:37:08 -06:00
|
|
|
chapterRepo.setShapes(newChapter, position.path, shapes) >>-
|
2016-05-11 02:30:23 -06:00
|
|
|
sendTo(study, Socket.SetShapes(position, shapes, uid))
|
2017-06-23 09:21:34 -06:00
|
|
|
case None =>
|
|
|
|
fufail(s"Invalid setShapes $position $shapes") >>-
|
|
|
|
reloadUidBecauseOf(study, uid, chapter.id)
|
2016-04-25 21:51:05 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-04-21 20:53:16 -06:00
|
|
|
}
|
|
|
|
|
2017-10-29 11:19:00 -06:00
|
|
|
def setClock(studyId: Study.Id, position: Position.Ref, clock: Option[Centis], uid: Uid): Funit =
|
2017-10-09 12:36:36 -06:00
|
|
|
sequenceStudyWithChapter(studyId, position.chapterId) { sc =>
|
|
|
|
sc.chapter.setClock(clock, position.path) match {
|
|
|
|
case Some(newChapter) =>
|
|
|
|
studyRepo.updateNow(sc.study)
|
|
|
|
chapterRepo.setClock(newChapter, position.path, clock) >>-
|
|
|
|
sendTo(sc.study, Socket.SetClock(position, clock, uid))
|
|
|
|
case None =>
|
|
|
|
fufail(s"Invalid setClock $position $clock") >>-
|
|
|
|
reloadUidBecauseOf(sc.study, uid, position.chapterId)
|
|
|
|
}
|
2017-09-20 13:53:52 -06:00
|
|
|
}
|
|
|
|
|
2017-10-29 11:19:00 -06:00
|
|
|
def setTag(userId: User.ID, studyId: Study.Id, setTag: actorApi.SetTag, uid: Uid) = sequenceStudyWithChapter(studyId, setTag.chapterId) {
|
|
|
|
case Study.WithChapter(study, chapter) => Contribute(userId, study) {
|
|
|
|
doSetTags(study, chapter, PgnTags(chapter.tags + setTag.tag), uid)
|
2016-12-21 11:18:38 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-29 11:19:00 -06:00
|
|
|
def setTags(userId: User.ID, studyId: Study.Id, chapterId: Chapter.Id, tags: Tags, uid: Uid) = sequenceStudyWithChapter(studyId, chapterId) {
|
|
|
|
case Study.WithChapter(study, chapter) => Contribute(userId, study) {
|
|
|
|
doSetTags(study, chapter, tags, uid)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private def doSetTags(study: Study, oldChapter: Chapter, tags: Tags, uid: Uid): Funit = {
|
|
|
|
val chapter = oldChapter.copy(tags = tags)
|
|
|
|
(chapter.tags != oldChapter.tags) ?? {
|
|
|
|
chapterRepo.setTagsFor(chapter) >> {
|
|
|
|
PgnTags.setRootClockFromTags(chapter) ?? { c =>
|
|
|
|
setClock(study.id, Position(c, Path.root).ref, c.root.clock, uid)
|
|
|
|
}
|
|
|
|
} >>-
|
|
|
|
sendTo(study, Socket.SetTags(chapter.id, chapter.tags, uid))
|
|
|
|
} >>- indexStudy(study)
|
|
|
|
}
|
|
|
|
|
2017-06-16 10:20:44 -06:00
|
|
|
def setComment(userId: User.ID, studyId: Study.Id, position: Position.Ref, text: Comment.Text, uid: Uid) = sequenceStudyWithChapter(studyId, position.chapterId) {
|
2016-04-28 00:18:45 -06:00
|
|
|
case Study.WithChapter(study, chapter) => Contribute(userId, study) {
|
2017-09-19 07:29:43 -06:00
|
|
|
lightUser(userId) ?? { author =>
|
|
|
|
val comment = Comment(
|
|
|
|
id = Comment.Id.make,
|
|
|
|
text = text,
|
|
|
|
by = Comment.Author.User(author.id, author.titleName)
|
|
|
|
)
|
2017-09-19 10:22:42 -06:00
|
|
|
doSetComment(userId, study, Position(chapter, position.path), comment, uid)
|
2016-05-10 04:46:04 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-19 10:22:42 -06:00
|
|
|
private def doSetComment(userId: User.ID, study: Study, position: Position, comment: Comment, uid: Uid): Funit =
|
|
|
|
position.chapter.setComment(comment, position.path) match {
|
|
|
|
case Some(newChapter) =>
|
|
|
|
studyRepo.updateNow(study)
|
|
|
|
newChapter.root.nodeAt(position.path) ?? { node =>
|
|
|
|
node.comments.findBy(comment.by) ?? { c =>
|
|
|
|
chapterRepo.setComments(newChapter, position.path, node.comments.filterEmpty) >>- {
|
|
|
|
sendTo(study, Socket.SetComment(position.ref, c, uid))
|
|
|
|
indexStudy(study)
|
|
|
|
sendStudyEnters(study, userId)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case None =>
|
|
|
|
fufail(s"Invalid setComment ${study.id} $position") >>-
|
|
|
|
reloadUidBecauseOf(study, uid, position.chapter.id)
|
|
|
|
}
|
|
|
|
|
2017-06-16 10:20:44 -06:00
|
|
|
def deleteComment(userId: User.ID, studyId: Study.Id, position: Position.Ref, id: Comment.Id, uid: Uid) = sequenceStudyWithChapter(studyId, position.chapterId) {
|
2016-05-17 08:23:09 -06:00
|
|
|
case Study.WithChapter(study, chapter) => Contribute(userId, study) {
|
2017-09-19 07:29:43 -06:00
|
|
|
chapter.deleteComment(id, position.path) match {
|
|
|
|
case Some(newChapter) =>
|
|
|
|
chapterRepo.update(newChapter) >>-
|
|
|
|
sendTo(study, Socket.DeleteComment(position, id, uid)) >>-
|
|
|
|
indexStudy(study)
|
|
|
|
case None =>
|
|
|
|
fufail(s"Invalid deleteComment $studyId $position $id") >>-
|
|
|
|
reloadUidBecauseOf(study, uid, chapter.id)
|
2016-05-17 08:23:09 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-16 10:20:44 -06:00
|
|
|
def toggleGlyph(userId: User.ID, studyId: Study.Id, position: Position.Ref, glyph: Glyph, uid: Uid) = sequenceStudyWithChapter(studyId, position.chapterId) {
|
2016-05-10 04:46:04 -06:00
|
|
|
case Study.WithChapter(study, chapter) => Contribute(userId, study) {
|
2017-09-19 07:29:43 -06:00
|
|
|
chapter.toggleGlyph(glyph, position.path) match {
|
|
|
|
case Some(newChapter) =>
|
|
|
|
studyRepo.updateNow(study)
|
|
|
|
newChapter.root.nodeAt(position.path) ?? { node =>
|
|
|
|
chapterRepo.setGlyphs(newChapter, position.path, node.glyphs) >>-
|
|
|
|
newChapter.root.nodeAt(position.path).foreach { node =>
|
|
|
|
sendTo(study, Socket.SetGlyphs(position, node.glyphs, uid))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case None =>
|
|
|
|
fufail(s"Invalid toggleGlyph $studyId $position $glyph") >>-
|
|
|
|
reloadUidBecauseOf(study, uid, chapter.id)
|
2016-04-28 00:18:45 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-14 18:44:04 -06:00
|
|
|
def setGamebook(userId: User.ID, studyId: Study.Id, position: Position.Ref, gamebook: Gamebook, uid: Uid) = sequenceStudyWithChapter(studyId, position.chapterId) {
|
|
|
|
case Study.WithChapter(study, chapter) => Contribute(userId, study) {
|
2017-08-15 10:14:12 -06:00
|
|
|
chapter.setGamebook(gamebook, position.path) match {
|
2017-08-14 18:44:04 -06:00
|
|
|
case Some(newChapter) =>
|
|
|
|
studyRepo.updateNow(study)
|
|
|
|
chapterRepo.setGamebook(newChapter, position.path, gamebook) >>- {
|
|
|
|
indexStudy(study)
|
|
|
|
sendStudyEnters(study, userId)
|
|
|
|
}
|
|
|
|
case None =>
|
|
|
|
fufail(s"Invalid setGamebook $studyId $position") >>-
|
|
|
|
reloadUidBecauseOf(study, uid, chapter.id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-19 07:29:43 -06:00
|
|
|
def explorerGame(userId: User.ID, studyId: Study.Id, data: actorApi.ExplorerGame, uid: Uid) = sequenceStudyWithChapter(studyId, data.position.chapterId) {
|
|
|
|
case Study.WithChapter(study, chapter) => Contribute(userId, study) {
|
2017-09-19 13:58:35 -06:00
|
|
|
if (data.insert) explorerGameHandler.insert(userId, study, Position(chapter, data.position.path), data.gameId) flatMap {
|
|
|
|
case None =>
|
|
|
|
fufail(s"Invalid explorerGame insert $studyId $data") >>-
|
|
|
|
reloadUidBecauseOf(study, uid, chapter.id)
|
|
|
|
case Some((chapter, path)) =>
|
|
|
|
studyRepo.updateNow(study)
|
|
|
|
chapter.root.nodeAt(path) ?? { parent =>
|
|
|
|
chapterRepo.setChildren(chapter, path, parent.children) >>- {
|
|
|
|
sendStudyEnters(study, userId)
|
|
|
|
sendTo(study, Socket.ReloadAll)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-09-19 10:22:42 -06:00
|
|
|
else explorerGameHandler.quote(data.gameId) flatMap {
|
|
|
|
_ ?? {
|
|
|
|
doSetComment(userId, study, Position(chapter, data.position.path), _, uid)
|
2017-09-19 07:29:43 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-19 00:44:25 -06:00
|
|
|
def addChapter(byUserId: User.ID, studyId: Study.Id, data: ChapterMaker.Data, sticky: Boolean, socket: ActorRef, uid: Uid) = sequenceStudy(studyId) { study =>
|
2016-05-11 11:49:36 -06:00
|
|
|
Contribute(byUserId, study) {
|
2017-10-19 08:31:34 -06:00
|
|
|
chapterRepo.countByStudyId(study.id) flatMap { count =>
|
|
|
|
if (count >= Study.maxChapters) funit
|
|
|
|
else chapterRepo.nextOrderByStudy(study.id) flatMap { order =>
|
|
|
|
chapterMaker(study, data, order, byUserId) flatMap {
|
|
|
|
_ ?? { chapter =>
|
|
|
|
data.initial ?? {
|
|
|
|
chapterRepo.firstByStudy(study.id) flatMap {
|
|
|
|
_.filter(_.isEmptyInitial) ?? chapterRepo.delete
|
|
|
|
}
|
|
|
|
} >> doAddChapter(study, chapter, sticky, uid)
|
|
|
|
}
|
2016-04-25 03:09:26 -06:00
|
|
|
}
|
|
|
|
}
|
2016-04-24 03:15:18 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-20 13:25:05 -06:00
|
|
|
def doAddChapter(study: Study, chapter: Chapter, sticky: Boolean, uid: Uid) =
|
|
|
|
chapterRepo.insert(chapter) >> {
|
|
|
|
val newStudy = study withChapter chapter
|
|
|
|
(sticky ?? studyRepo.updateSomeFields(newStudy)) >>-
|
|
|
|
sendTo(study, Socket.AddChapter(uid, newStudy.position, sticky))
|
|
|
|
} >>-
|
|
|
|
studyRepo.updateNow(study) >>-
|
|
|
|
indexStudy(study)
|
|
|
|
|
2017-10-01 17:17:04 -06:00
|
|
|
def setChapter(byUserId: User.ID, studyId: Study.Id, chapterId: Chapter.Id, uid: Uid) = sequenceStudy(studyId) { study =>
|
|
|
|
study.canContribute(byUserId) ?? doSetChapter(study, chapterId, uid)
|
2016-04-25 03:09:26 -06:00
|
|
|
}
|
|
|
|
|
2017-10-01 17:17:04 -06:00
|
|
|
private def doSetChapter(study: Study, chapterId: Chapter.Id, uid: Uid) =
|
2016-07-26 03:47:15 -06:00
|
|
|
(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 =>
|
2017-06-14 00:50:51 -06:00
|
|
|
val newStudy = study withChapter chapter
|
|
|
|
studyRepo.updateSomeFields(newStudy) >>-
|
|
|
|
sendTo(study, Socket.ChangeChapter(uid, newStudy.position))
|
2016-04-24 03:38:35 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-01 17:17:04 -06:00
|
|
|
def editChapter(byUserId: User.ID, studyId: Study.Id, data: ChapterMaker.EditData, 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 =>
|
2017-01-20 06:05:09 -07:00
|
|
|
val name = Chapter fixName data.name
|
2016-05-19 09:27:32 -06:00
|
|
|
val newChapter = chapter.copy(
|
|
|
|
name = name,
|
2017-01-18 09:49:46 -07:00
|
|
|
practice = data.isPractice option true,
|
2017-08-10 20:05:03 -06:00
|
|
|
gamebook = data.isGamebook option true,
|
2017-01-18 09:49:46 -07:00
|
|
|
conceal = (chapter.conceal, data.isConceal) match {
|
2017-02-14 08:34:07 -07:00
|
|
|
case (None, true) => Chapter.Ply(chapter.root.ply).some
|
2016-05-19 09:27:32 -06:00
|
|
|
case (Some(_), false) => None
|
2017-02-14 08:34:07 -07:00
|
|
|
case _ => chapter.conceal
|
2016-05-31 05:16:07 -06:00
|
|
|
},
|
2017-08-19 17:05:41 -06:00
|
|
|
setup = chapter.setup.copy(orientation = data.realOrientation),
|
2017-08-21 08:46:41 -06:00
|
|
|
description = data.hasDescription option {
|
|
|
|
chapter.description | "-"
|
|
|
|
}
|
2017-02-14 08:34:07 -07:00
|
|
|
)
|
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)
|
2017-08-23 17:56:39 -06:00
|
|
|
} else fuccess {
|
2017-01-19 03:39:43 -07:00
|
|
|
val shouldReload =
|
|
|
|
(newChapter.setup.orientation != chapter.setup.orientation) ||
|
2017-08-15 14:28:16 -06:00
|
|
|
(newChapter.practice != chapter.practice) ||
|
2017-08-19 17:26:19 -06:00
|
|
|
(newChapter.gamebook != chapter.gamebook) ||
|
2017-08-21 08:46:41 -06:00
|
|
|
(newChapter.description != chapter.description)
|
2017-08-22 16:45:35 -06:00
|
|
|
if (shouldReload) sendTo(study, Socket.UpdateChapter(uid, chapter.id))
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-01 17:17:04 -06:00
|
|
|
def descChapter(byUserId: User.ID, studyId: Study.Id, data: ChapterMaker.DescData, uid: Uid) = sequenceStudy(studyId) { study =>
|
2017-08-21 08:46:41 -06:00
|
|
|
Contribute(byUserId, study) {
|
|
|
|
chapterRepo.byIdAndStudy(data.id, studyId) flatMap {
|
|
|
|
_ ?? { chapter =>
|
|
|
|
val newChapter = chapter.copy(
|
|
|
|
description = data.description.nonEmpty option data.description
|
|
|
|
)
|
|
|
|
(chapter != newChapter) ?? {
|
|
|
|
chapterRepo.update(newChapter) >>- {
|
|
|
|
sendTo(study, Socket.DescChapter(uid, newChapter.id, newChapter.description))
|
|
|
|
indexStudy(study)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-01 17:17:04 -06:00
|
|
|
def deleteChapter(byUserId: User.ID, studyId: Study.Id, chapterId: Chapter.Id, uid: Uid) = sequenceStudy(studyId) { study =>
|
2016-05-16 04:01:03 -06:00
|
|
|
Contribute(byUserId, study) {
|
|
|
|
chapterRepo.byIdAndStudy(chapterId, studyId) flatMap {
|
|
|
|
_ ?? { chapter =>
|
2017-10-14 09:49:52 -06:00
|
|
|
chapterRepo.orderedMetadataByStudy(studyId).flatMap { chaps =>
|
|
|
|
// deleting the only chapter? Automatically create an empty one
|
|
|
|
if (chaps.size < 2) {
|
|
|
|
chapterMaker(study, ChapterMaker.Data(Chapter.Name("Chapter 1")), 1, byUserId) flatMap {
|
|
|
|
_ ?? { c =>
|
|
|
|
doAddChapter(study, c, sticky = true, uid) >> doSetChapter(study, c.id, uid)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // deleting the current chapter? Automatically move to another one
|
|
|
|
else (study.position.chapterId == chapterId).?? {
|
2016-05-16 04:01:03 -06:00
|
|
|
chaps.find(_.id != chapterId) ?? { newChap =>
|
2017-10-01 17:17:04 -06:00
|
|
|
doSetChapter(study, newChap.id, uid)
|
2016-05-16 04:01:03 -06:00
|
|
|
}
|
2017-10-14 09:49:52 -06:00
|
|
|
}
|
|
|
|
} >> chapterRepo.delete(chapter.id) >>- reloadChapters(study)
|
2016-07-25 14:34:15 -06:00
|
|
|
} >>- indexStudy(study)
|
2016-04-24 23:23:13 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-01 17:17:04 -06:00
|
|
|
def sortChapters(byUserId: User.ID, studyId: Study.Id, chapterIds: List[Chapter.Id], uid: Uid) = sequenceStudy(studyId) { study =>
|
2016-05-16 04:01:03 -06:00
|
|
|
Contribute(byUserId, study) {
|
|
|
|
chapterRepo.sort(study, chapterIds) >>- reloadChapters(study)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-16 08:27:38 -06:00
|
|
|
def editStudy(byUserId: User.ID, studyId: Study.Id, data: Study.Data) = sequenceStudy(studyId) { study =>
|
|
|
|
data.settings.ifTrue(study isOwner byUserId) ?? { settings =>
|
2016-05-19 11:43:19 -06:00
|
|
|
val newStudy = study.copy(
|
|
|
|
name = Study toName data.name,
|
|
|
|
settings = settings,
|
2017-02-14 08:34:07 -07:00
|
|
|
visibility = data.vis
|
|
|
|
)
|
2017-02-13 11:00:24 -07:00
|
|
|
if (!study.isPublic && newStudy.isPublic) {
|
2017-03-31 04:33:31 -06:00
|
|
|
bus.publish(lila.hub.actorApi.study.StudyBecamePublic(studyId.value, study.members.contributorIds), 'study)
|
2017-08-23 17:56:39 -06:00
|
|
|
} else if (study.isPublic && !newStudy.isPublic) {
|
2017-03-31 04:33:31 -06:00
|
|
|
bus.publish(lila.hub.actorApi.study.StudyBecamePrivate(studyId.value, study.members.contributorIds), 'study)
|
Study icon for friends in a study
The study icon will appear when:
* A friend, who is a contributor, joins a study, or makes a move in a study if he does not have the icon yet (the latter happens if you have two studies open and close one).
* A friend is in a study and gets added as contributor.
* A friend is a contributor in a private study and the study becomes public.
The study icon will disappear when:
* A friend, who is a contributor, leaves a study.
* A friend, who is a contributor, gets his status revoked, or kicked.
* A friend, who is a contributor, is in a public study that becomes private.
2017-02-12 14:09:23 -07:00
|
|
|
}
|
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) >>-
|
Study icon for friends in a study
The study icon will appear when:
* A friend, who is a contributor, joins a study, or makes a move in a study if he does not have the icon yet (the latter happens if you have two studies open and close one).
* A friend is in a study and gets added as contributor.
* A friend is a contributor in a private study and the study becomes public.
The study icon will disappear when:
* A friend, who is a contributor, leaves a study.
* A friend, who is a contributor, gets his status revoked, or kicked.
* A friend, who is a contributor, is in a public study that becomes private.
2017-02-12 14:09:23 -07:00
|
|
|
indexStudy(study) >>-
|
2017-02-14 06:09:05 -07:00
|
|
|
lightStudyCache.put(studyId, newStudy.light.some)
|
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) >>-
|
2017-04-17 06:10:21 -06:00
|
|
|
bus.publish(lila.hub.actorApi.study.RemoveStudy(study.id.value, study.members.contributorIds), 'study) >>-
|
2017-02-14 06:02:42 -07:00
|
|
|
lightStudyCache.put(study.id, none)
|
2016-05-12 08:21:25 -06:00
|
|
|
}
|
|
|
|
|
2017-10-01 17:17:04 -06:00
|
|
|
def like(studyId: Study.Id, userId: User.ID, v: Boolean, uid: Uid): Funit =
|
2016-05-26 14:01:43 -06:00
|
|
|
studyRepo.like(studyId, userId, v) map { likes =>
|
|
|
|
sendTo(studyId, Socket.SetLiking(Study.Liking(likes, v), uid))
|
2017-10-06 18:56:06 -06:00
|
|
|
bus.publish(actorApi.StudyLikes(studyId, likes), 'studyLikes)
|
2017-02-08 03:33:37 -07:00
|
|
|
if (v) studyRepo byId studyId foreach {
|
|
|
|
_ foreach { study =>
|
2017-07-06 07:26:02 -06:00
|
|
|
if (userId != study.ownerId && study.isPublic)
|
2017-02-08 03:33:37 -07:00
|
|
|
timeline ! (Propagate(StudyLike(userId, study.id.value, study.name.value)) toFollowersOf userId)
|
2016-05-31 18:14:02 -06:00
|
|
|
}
|
|
|
|
}
|
2016-05-26 14:01:43 -06:00
|
|
|
}
|
|
|
|
|
2016-06-21 07:02:23 -06:00
|
|
|
def resetAllRanks = studyRepo.resetAllRanks
|
|
|
|
|
2017-01-21 06:22:51 -07:00
|
|
|
def chapterIdNames(studyIds: List[Study.Id]): Fu[Map[Study.Id, Vector[Chapter.IdName]]] =
|
|
|
|
chapterRepo.idNamesByStudyIds(studyIds)
|
|
|
|
|
2017-01-22 05:29:45 -07:00
|
|
|
def chapterMetadatas = chapterRepo.orderedMetadataByStudy _
|
|
|
|
|
2017-10-06 18:56:06 -06:00
|
|
|
def withLiked(me: Option[User])(studies: Seq[Study]): Fu[Seq[Study.WithLiked]] =
|
|
|
|
me.?? { u => studyRepo.filterLiked(u, studies.map(_.id)) } map { liked =>
|
|
|
|
studies.map { study =>
|
|
|
|
Study.WithLiked(study, liked(study.id))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-16 09:53:12 -07:00
|
|
|
def analysisRequest(studyId: Study.Id, chapterId: Chapter.Id, userId: User.ID): Funit =
|
2018-01-15 21:12:10 -07:00
|
|
|
sequenceStudyWithChapter(studyId, chapterId) {
|
|
|
|
case Study.WithChapter(study, chapter) => Contribute(userId, study) {
|
2018-01-17 13:24:42 -07:00
|
|
|
serverEvalRequester(study, chapter, userId)
|
2018-01-15 21:12:10 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-14 19:13:43 -07:00
|
|
|
private def sendStudyEnters(study: Study, userId: User.ID) = bus.publish(
|
|
|
|
lila.hub.actorApi.study.StudyDoor(
|
|
|
|
userId = userId,
|
|
|
|
studyId = study.id.value,
|
|
|
|
contributor = study canContribute userId,
|
|
|
|
public = study.isPublic,
|
|
|
|
enters = true
|
|
|
|
),
|
|
|
|
'study
|
|
|
|
)
|
|
|
|
|
2017-01-28 07:24:22 -07:00
|
|
|
private def indexStudy(study: Study) =
|
|
|
|
bus.publish(actorApi.SaveStudy(study), 'study)
|
2016-07-25 14:34:15 -06:00
|
|
|
|
2017-06-23 09:21:34 -06:00
|
|
|
private def reloadUid(study: Study, uid: Uid, becauseOf: Option[Chapter.Id] = None) =
|
2016-05-11 02:30:23 -06:00
|
|
|
sendTo(study, Socket.ReloadUid(uid))
|
2016-04-23 21:34:26 -06:00
|
|
|
|
2017-06-23 09:21:34 -06:00
|
|
|
private def reloadUidBecauseOf(study: Study, uid: Uid, chapterId: Chapter.Id) =
|
|
|
|
sendTo(study, Socket.ReloadUidBecauseOf(uid, chapterId))
|
|
|
|
|
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-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)
|
|
|
|
|
2017-01-20 05:47:52 -07:00
|
|
|
private def sendTo(studyId: Study.Id, msg: Any): Unit =
|
|
|
|
socketHub ! Tell(studyId.value, msg)
|
2016-02-26 05:08:11 -07:00
|
|
|
}
|