lila/modules/practice/src/main/PracticeApi.scala

146 lines
4.4 KiB
Scala
Raw Normal View History

2017-01-20 03:47:56 -07:00
package lila.practice
2017-01-21 06:22:51 -07:00
import scala.concurrent.duration._
import reactivemongo.api.ReadPreference
2017-01-21 06:22:51 -07:00
2019-12-23 18:01:45 -07:00
import lila.common.Bus
2017-01-20 03:47:56 -07:00
import lila.db.dsl._
2019-12-23 18:01:45 -07:00
import lila.memo.CacheApi._
2017-01-21 13:51:10 -07:00
import lila.study.{ Chapter, Study }
import lila.user.User
2017-01-20 03:47:56 -07:00
final class PracticeApi(
coll: Coll,
2017-01-21 06:22:51 -07:00
configStore: lila.memo.ConfigStore[PracticeConfig],
2019-12-23 18:01:45 -07:00
cacheApi: lila.memo.CacheApi,
studyApi: lila.study.StudyApi
)(implicit ec: scala.concurrent.ExecutionContext) {
2017-01-20 03:47:56 -07:00
import BSONHandlers._
2019-12-13 07:30:20 -07:00
def get(user: Option[User]): Fu[UserPractice] =
for {
struct <- structure.get
prog <- user.fold(fuccess(PracticeProgress.anon))(progress.get)
} yield UserPractice(struct, prog)
def getStudyWithFirstOngoingChapter(user: Option[User], studyId: Study.Id): Fu[Option[UserStudy]] =
for {
up <- get(user)
chapters <- studyApi.chapterMetadatas(studyId)
chapter = up.progress firstOngoingIn chapters
studyOption <- chapter.fold(studyApi byIdWithFirstChapter studyId) { chapter =>
studyApi.byIdWithChapter(studyId, chapter.id)
}
} yield makeUserStudy(studyOption, up, chapters)
def getStudyWithChapter(
user: Option[User],
studyId: Study.Id,
chapterId: Chapter.Id
): Fu[Option[UserStudy]] =
for {
up <- get(user)
chapters <- studyApi.chapterMetadatas(studyId)
studyOption <- studyApi.byIdWithChapter(studyId, chapterId)
} yield makeUserStudy(studyOption, up, chapters)
private def makeUserStudy(
studyOption: Option[Study.WithChapter],
up: UserPractice,
chapters: List[Chapter.Metadata]
) =
for {
rawSc <- studyOption
sc = rawSc.copy(
study = rawSc.study.rewindTo(rawSc.chapter).withoutMembers,
chapter = rawSc.chapter.withoutChildrenIfPractice
)
practiceStudy <- up.structure study sc.study.id
section <- up.structure findSection sc.study.id
publishedChapters = chapters.filterNot { c =>
PracticeStructure isChapterNameCommented c.name
}
if publishedChapters.exists(_.id == sc.chapter.id)
} yield UserStudy(up, practiceStudy, publishedChapters, sc, section)
2017-01-21 13:51:10 -07:00
2017-01-21 06:22:51 -07:00
object config {
def get = configStore.get dmap (_ | PracticeConfig.empty)
2019-12-13 07:30:20 -07:00
def set = configStore.set _
def form = configStore.makeForm
}
2017-01-21 06:22:51 -07:00
object structure {
2019-12-23 18:01:45 -07:00
private val cache = cacheApi.unit[PracticeStructure] {
_.expireAfterAccess(3.hours)
.buildAsyncFuture { _ =>
for {
conf <- config.get
chapters <- studyApi.chapterIdNames(conf.studyIds)
2020-04-29 08:58:36 -06:00
} yield PracticeStructure.make(conf, chapters)
2019-12-23 18:01:45 -07:00
}
}
def get = cache.getUnit
def clear() = cache.invalidateUnit()
2020-05-05 22:11:15 -06:00
def onSave(study: Study) =
get foreach { structure =>
if (structure.hasStudy(study.id)) clear()
}
2017-01-21 06:22:51 -07:00
}
object progress {
2017-01-22 03:52:18 -07:00
import PracticeProgress.NbMoves
def get(user: User): Fu[PracticeProgress] =
coll.one[PracticeProgress]($id(user.id)) dmap {
2019-12-13 07:30:20 -07:00
_ | PracticeProgress.empty(PracticeProgress.Id(user.id))
}
2017-01-20 03:47:56 -07:00
private def save(p: PracticeProgress): Funit =
2019-12-03 18:16:42 -07:00
coll.update.one($id(p.id), p, upsert = true).void
2017-01-20 03:47:56 -07:00
2019-12-04 18:47:46 -07:00
def setNbMoves(user: User, chapterId: Chapter.Id, score: NbMoves): Funit = {
get(user) flatMap { prog =>
2017-01-21 05:42:28 -07:00
save(prog.withNbMoves(chapterId, score))
}
2017-07-18 15:18:12 -06:00
} >>- studyApi.studyIdOf(chapterId).foreach {
_ ?? { studyId =>
2019-11-29 17:07:51 -07:00
Bus.publish(PracticeProgress.OnComplete(user.id, studyId, chapterId), "finishPractice")
2017-07-18 15:18:12 -06:00
}
}
2017-01-20 03:47:56 -07:00
def reset(user: User) =
2019-12-03 18:16:42 -07:00
coll.delete.one($id(user.id)).void
def completionPercent(userIds: List[User.ID]): Fu[Map[User.ID, Int]] =
coll
.aggregateList(
maxDocs = Int.MaxValue,
readPreference = ReadPreference.secondaryPreferred
) { framework =>
import framework._
Match($doc("_id" $in userIds)) -> List(
Project(
$doc(
"nb" -> $doc(
"$size" -> $doc(
"$objectToArray" -> "$chapters"
)
)
)
)
)
}
.map {
2020-08-12 00:53:51 -06:00
_.view.flatMap { obj =>
import cats.implicits._
(obj.string("_id"), obj.int("nb")) mapN { (k, v) =>
k -> (v * 100f / PracticeStructure.totalChapters).toInt
}
2020-08-12 00:53:51 -06:00
}.toMap
}
}
2017-01-20 03:47:56 -07:00
}