study cloneable setting

pull/2308/head
Thibault Duplessis 2016-10-12 15:37:40 +02:00
parent ae19121f3b
commit 7d7be8629b
9 changed files with 70 additions and 55 deletions

View File

@ -190,20 +190,19 @@ object Study extends LilaController {
name = "clone study per IP",
key = "clone_study.ip")
def cloneApply(id: String) = Auth { implicit ctx =>
me =>
implicit val default = ornicar.scalalib.Zero.instance[Fu[Result]](notFound)
CloneLimitPerUser(me.id, cost = 1) {
CloneLimitPerIP(HTTPRequest lastRemoteAddress ctx.req, cost = 1) {
OptionFuResult(env.api.byId(id)) { prev =>
CanViewResult(prev) {
env.api.clone(me, prev) map { study =>
Redirect(routes.Study.show(study.id))
}
def cloneApply(id: String) = Auth { implicit ctx => me =>
implicit val default = ornicar.scalalib.Zero.instance[Fu[Result]](notFound)
CloneLimitPerUser(me.id, cost = 1) {
CloneLimitPerIP(HTTPRequest lastRemoteAddress ctx.req, cost = 1) {
OptionFuResult(env.api.byId(id)) { prev =>
CanViewResult(prev) {
env.api.clone(me, prev) map { study =>
Redirect(routes.Study.show((study | prev).id))
}
}
}
}
}
}
private val PgnRateLimitGlobal = new lila.memo.RateLimit(

View File

@ -191,7 +191,6 @@ private object BSONHandlers {
def read(b: BSONInteger): Variant = Variant(b.value) err s"No such variant: ${b.value}"
def write(x: Variant) = BSONInteger(x.id)
}
private implicit val StudyViewsBSONHandler = intAnyValHandler[Study.Views](_.value, Study.Views.apply)
private implicit val PgnTagBSONHandler = new BSONHandler[BSONString, Tag] {
def read(b: BSONString): Tag = b.value.split(":", 2) match {
@ -253,7 +252,14 @@ private object BSONHandlers {
def read(bs: BSONString) = UserSelection.byKey get bs.value err s"Invalid user selection ${bs.value}"
def write(x: UserSelection) = BSONString(x.key)
}
implicit val SettingsBSONHandler = Macros.handler[Settings]
implicit val SettingsBSONHandler = new BSON[Settings] {
def reads(r: Reader) = Settings(
computer = r.get[UserSelection]("computer"),
explorer = r.get[UserSelection]("explorer"),
cloneable = r.getO[UserSelection]("cloneable") | UserSelection.Everyone)
private val writer = Macros.writer[Settings]
def writes(w: Writer, s: Settings) = writer write s
}
import Study.Likes
private[study] implicit val LikesBSONHandler = intAnyValHandler[Likes](_.value, Likes.apply)

View File

@ -25,11 +25,18 @@ final class JsonView(
chapters: List[Chapter.Metadata],
currentChapter: Chapter,
me: Option[User]) = {
def allowed(selection: Settings.UserSelection): Boolean =
Settings.UserSelection.allows(selection, study, me.map(_.id))
currentChapter.setup.gameId.??(GameRepo.gameWithInitialFen) zip
me.?? { studyRepo.liked(study, _) } map {
case (gameOption, liked) =>
studyWrites.writes(study) ++ Json.obj(
"liked" -> liked,
"features" -> Json.obj(
"cloneable" -> allowed(study.settings.cloneable)
),
"chapters" -> chapters.map(chapterMetadataWrites.writes),
"chapter" -> Json.obj(
"ownerId" -> currentChapter.ownerId,
@ -42,8 +49,8 @@ final class JsonView(
"game" -> gameOption,
"conceal" -> currentChapter.conceal,
"features" -> Json.obj(
"computer" -> Settings.UserSelection.allows(study.settings.computer, study, me.map(_.id)),
"explorer" -> Settings.UserSelection.allows(study.settings.explorer, study, me.map(_.id))
"computer" -> allowed(study.settings.computer),
"explorer" -> allowed(study.settings.explorer)
)
)
)

View File

@ -3,16 +3,16 @@ package lila.study
import lila.user.User
case class Settings(
computer: Settings.UserSelection,
explorer: Settings.UserSelection) {
}
computer: Settings.UserSelection,
explorer: Settings.UserSelection,
cloneable: Settings.UserSelection)
object Settings {
val init = Settings(
computer = UserSelection.Everyone,
explorer = UserSelection.Everyone)
explorer = UserSelection.Everyone,
cloneable = UserSelection.Everyone)
sealed trait UserSelection {
lazy val key = toString.toLowerCase

View File

@ -62,8 +62,6 @@ object Study {
def toName(str: String) = str.trim take 100
case class Views(value: Int) extends AnyVal
sealed trait Visibility {
lazy val key = toString.toLowerCase
}
@ -98,13 +96,15 @@ object Study {
name: String,
visibility: String,
computer: String,
explorer: String) {
explorer: String,
cloneable: String) {
import Settings._
def vis = Visibility.byKey get visibility getOrElse Visibility.Public
def settings = for {
comp <- UserSelection.byKey get computer
expl <- UserSelection.byKey get explorer
} yield Settings(comp, expl)
clon <- UserSelection.byKey get cloneable
} yield Settings(comp, expl, clon)
}
case class WithChapter(study: Study, chapter: Chapter)

View File

@ -64,20 +64,21 @@ final class StudyApi(
scheduleTimeline(res.study.id) inject res
}
def clone(me: User, prev: Study): Fu[Study] = {
chapterRepo.orderedByStudy(prev.id).flatMap { chapters =>
val study1 = prev.cloneFor(me)
val newChapters = chapters.map(_ cloneFor study1)
val study = study1.withChapter(newChapters.headOption.err {
s"Cloning study ${study1.id} from ${prev.id} has no first chapter!"
})
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
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
}
}
}
}
def resetIfOld(study: Study, chapters: List[Chapter.Metadata]): Fu[Study] =
chapters.headOption match {

View File

@ -87,15 +87,10 @@ module.exports = {
if (data.visibility === 'public' && s.visibility === 'private' && !members.myMember())
return lichess.reload();
if (s.position !== data.position) commentForm.close();
data.position = s.position;
data.name = document.title = s.name;
data.visibility = s.visibility;
data.settings = s.settings;
data.visibility = s.visibility;
data.views = s.views;
data.chapter = s.chapter;
data.likes = s.likes;
data.liked = s.liked;
['position', 'name', 'visibility', 'features', 'settings', 'chapter', 'likes', 'liked'].forEach(function(key) {
data[key] = s[key];
});
document.title = data.name;
members.dict(s.members);
chapters.list(s.chapters);
ctrl.reloadData(d.analysis);

View File

@ -67,7 +67,8 @@ module.exports = {
name: e.target.querySelector('#study-name').value,
visibility: e.target.querySelector('#study-visibility').value,
computer: e.target.querySelector('#study-computer').value,
explorer: e.target.querySelector('#study-explorer').value
explorer: e.target.querySelector('#study-explorer').value,
cloneable: e.target.querySelector('#study-cloneable').value
}, isNew);
e.stopPropagation();
return false;
@ -89,13 +90,19 @@ module.exports = {
m('label.control-label[for=study-name]', 'Name'),
m('i.bar')
]),
m('div.game.form-group', select({
key: 'visibility',
name: 'Visibility',
choices: visibilityChoices,
selected: data.visibility
})),
m('div', [
m('div.game.form-group.half', select({
key: 'visibility',
name: 'Visibility',
choices: visibilityChoices,
selected: data.visibility
})),
m('div.game.form-group.half', select({
key: 'cloneable',
name: 'Allow cloning',
choices: userSelectionChoices,
selected: data.settings.cloneable
})),
m('div.game.form-group.half', select({
key: 'computer',
name: 'Computer analysis',
@ -107,7 +114,7 @@ module.exports = {
name: 'Opening explorer',
choices: userSelectionChoices,
selected: data.settings.explorer
}))
})),
]),
dialog.button(isNew ? 'Start' : 'Save')
]),

View File

@ -39,12 +39,12 @@ function buttons(root) {
'data-hint': 'Download as PGN',
href: '/study/' + ctrl.data.id + '.pgn'
}, m('i[data-icon=x]')),
m('a.button.hint--top', {
ctrl.data.features.cloneable ? m('a.button.hint--top', {
'data-hint': 'Clone this study',
href: '/study/' + ctrl.data.id + '/clone'
}, m('i', {
'data-icon': '{'
})),
})) : null,
canContribute ? [
(function(enabled) {
return m('a.button.comment.hint--top', {