Skip to content

Commit

Permalink
lock Q&A questions
Browse files Browse the repository at this point in the history
  • Loading branch information
ornicar committed Jan 5, 2018
1 parent 130ff27 commit 325a51b
Show file tree
Hide file tree
Showing 12 changed files with 66 additions and 42 deletions.
3 changes: 2 additions & 1 deletion app/controllers/QaAnswer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ object QaAnswer extends QaController {
def create(id: QuestionId) = AuthBody { implicit ctx => me =>
WithQuestion(id) { q =>
implicit val req = ctx.body
forms.answer.bindFromRequest.fold(
if (QaAuth canAnswer q) forms.answer.bindFromRequest.fold(
err => renderQuestion(q, Some(err)),
data => api.answer.create(data, q, me) map { answer =>
Redirect(routes.QaQuestion.show(q.id, q.slug) + "#answer-" + answer.id)
}
)
else Redirect(routes.QaQuestion.show(q.id, q.slug)).fuccess
}
}

Expand Down
7 changes: 7 additions & 0 deletions app/controllers/QaQuestion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,11 @@ object QaQuestion extends QaController {
Redirect(routes.QaQuestion.index())
}
}

def lock(questionId: QuestionId) = Secure(_.ModerateQa) { implicit ctx => me =>
WithQuestion(questionId) { q =>
(api.question.lock(q.id, !q.isLocked option me)) inject
Redirect(routes.QaQuestion.show(q.id, q.slug))
}
}
}
2 changes: 1 addition & 1 deletion app/views/qa/answerList.scala.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
@if(lila.qa.QaAuth canEdit a) {
<a class="thin button toggle-edit-answer" data-icon="m"> Edit</a>
}
@nope("Remove", routes.QaAnswer.remove(q.id, a.id), "thin")
@nope("Remove", routes.QaAnswer.remove(q.id, a.id), "q", "thin")
@if(isGranted(_.ModerateQa)) {
<br />
<form class="mod" action="@routes.QaAnswer.moveTo(q.id, a.id)" method="post">
Expand Down
2 changes: 1 addition & 1 deletion app/views/qa/commentList.scala.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<div class="comment" id="[email protected]">
<div class="meta">
<span class="light">@userIdLink(c.userId.some) commented @momentFromNow(c.createdAt):</span>
@nope("Remove", routes.QaComment.remove(q.id, c.id), "thin")
@nope("Remove", routes.QaComment.remove(q.id, c.id), "q", "thin")
</div>
@richText(c.body)
</div>
Expand Down
4 changes: 2 additions & 2 deletions app/views/qa/nope.scala.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@(title: String, url: Call, cssClass: String = "")(implicit ctx: Context)
@(title: String, url: Call, icon: String, cssClass: String = "")(implicit ctx: Context)
@if(isGranted(_.ModerateQa)) {
<form class="mod" action="@url" method="post">
<button type="submit" title="@title" class="@cssClass confirm button" data-icon="q"> @title</button>
<button type="submit" title="@title" class="@cssClass confirm button text" data-icon="@icon">@title</button>
</form>
}
13 changes: 10 additions & 3 deletions app/views/qa/questionShow.scala.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ <h1 class="lichess_title">
@if(lila.qa.QaAuth canEdit q) {
<a class="button" data-icon="m" href="@routes.QaQuestion.edit(q.id, q.slug)"> Edit question</a>
}
@nope("Delete", routes.QaQuestion.remove(q.id))
@nope(q.isLocked.fold("Unlock", "Lock"), routes.QaQuestion.lock(q.id), "a")
@nope("Delete", routes.QaQuestion.remove(q.id), "q")
</div>
<table>
<tbody>
Expand Down Expand Up @@ -77,13 +78,19 @@ <h1 class="lichess_title">
@base.captcha(form("move"), form("gameId"), captcha)
<button class="pure submit button" type="submit">Post your answer</button>
@if(isGranted(_.PublicMod)){
<button class="pure button" type="submit" data-icon="" name="modIcon" value="true"> Answer as mod</button>
<button class="pure button" type="submit" data-icon="" name="modIcon" value="true"> Answer as mod</button>
}
</form>
</div>
}
case _ => {
You cannot answer this question.
<div class="noform">
@q.locked.map { l =>
@userIdLink(l.by.some) locked this question @momentFromNow(l.at)
}.getOrElse {
You cannot answer this question.
}
</div>
}
}
}
Expand Down
1 change: 1 addition & 0 deletions conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,7 @@ GET /qa/:id/:slug/edit controllers.QaQuestion.edit(id: Int, slug: St
POST /qa/:id/edit controllers.QaQuestion.doEdit(id: Int)
POST /qa/:id/vote controllers.QaQuestion.vote(id: Int)
POST /qa/:id/rm controllers.QaQuestion.remove(id: Int)
POST /qa/:id/lock controllers.QaQuestion.lock(id: Int)
POST /qa/:id/answer controllers.QaAnswer.create(id: Int)
POST /qa/:id/:a/vote controllers.QaAnswer.vote(id: Int, a: Int)
POST /qa/:id/:a/accept controllers.QaAnswer.accept(id: Int, a: Int)
Expand Down
57 changes: 28 additions & 29 deletions modules/qa/src/main/QaApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import reactivemongo.bson._

import org.joda.time.DateTime

import lila.common.paginator._
import lila.common.MaxPerPage
import lila.common.paginator._
import lila.db.dsl._
import lila.db.paginator._
import lila.user.User
import lila.security.Granter
import lila.user.User

final class QaApi(
questionColl: Coll,
Expand All @@ -21,11 +21,9 @@ final class QaApi(
notifier: Notifier
) {

object question {
import QaApi._

private implicit val commentBSONHandler = Macros.handler[Comment]
private implicit val voteBSONHandler = Macros.handler[Vote]
private[qa] implicit val questionBSONHandler = Macros.handler[Question]
object question {

def create(data: QuestionData, user: User): Fu[Question] =
lila.db.Util findNextId questionColl flatMap { id =>
Expand Down Expand Up @@ -109,52 +107,47 @@ final class QaApi(
def byTags(tags: List[String], max: Int): Fu[List[Question]] =
questionColl.find($doc("tags" $in tags.map(_.toLowerCase))).cursor[Question]().gather[List](max)

def addComment(c: Comment)(q: Question) = questionColl.update(
$doc("_id" -> q.id),
$doc("$push" -> $doc("comments" -> c))
)
def addComment(c: Comment)(q: Question) = questionColl.update($id(q.id), $push("comments" -> c))

def vote(id: QuestionId, user: User, v: Boolean): Fu[Option[Vote]] =
question findById id flatMap {
_ ?? { q =>
val newVote = q.vote.add(user.id, v)
questionColl.update(
$doc("_id" -> q.id),
$doc("$set" -> $doc("vote" -> newVote))
) inject newVote.some
questionColl.update($id(q.id), $set("vote" -> newVote)) inject newVote.some
}
}

def incViews(q: Question) = questionColl.update(
$id(q.id),
$doc("$inc" -> $doc("views" -> BSONInteger(1)))
)
def incViews(q: Question) = questionColl.update($id(q.id), $inc("views" -> 1))

def recountAnswers(id: QuestionId) = answer.countByQuestionId(id) flatMap {
setAnswers(id, _)
}

def setAnswers(id: QuestionId, nb: Int) = questionColl.update(
$id(id),
$doc(
"$set" -> $doc(
"answers" -> BSONInteger(nb),
"updatedAt" -> DateTime.now
)
$set(
"answers" -> nb,
"updatedAt" -> DateTime.now
)
).void

def remove(id: QuestionId) =
questionColl.remove($doc("_id" -> id)) >>
questionColl.remove($id(id)) >>
(answer removeByQuestion id) >>- {
tag.clearCache
relation.clearCache
}

def removeComment(id: QuestionId, c: CommentId) = questionColl.update(
$id(id),
$doc("$pull" -> $doc("comments" -> $doc("id" -> c)))
$pull("comments" -> $doc("id" -> c))
)

def lock(id: QuestionId, by: Option[User]): Funit =
questionColl.update($id(id), by match {
case None => $unset("locked")
case Some(u) => $set("locked" -> Locked(by = u.id, at = DateTime.now))
}).void
}

object answer {
Expand Down Expand Up @@ -317,10 +310,8 @@ final class QaApi(
val col = questionColl
import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework.{ AddFieldToSet, Group, Project, UnwindField }

col.aggregate(Project($doc("tags" -> BSONBoolean(true))), List(
UnwindField("tags"), Group(
BSONBoolean(true)
)("tags" -> AddFieldToSet("tags"))
col.aggregate(Project($doc("tags" -> true)), List(
UnwindField("tags"), Group(BSONBoolean(true))("tags" -> AddFieldToSet("tags"))
)).
map(_.firstBatch.headOption.flatMap(_.getAs[List[String]]("tags")).
getOrElse(List.empty[String]).map(_.toLowerCase).distinct)
Expand Down Expand Up @@ -350,3 +341,11 @@ final class QaApi(
def clearCache = cache.invalidateAll
}
}

object QaApi {

implicit val commentBSONHandler: BSONDocumentHandler[Comment] = Macros.handler[Comment]
implicit val voteBSONHandler: BSONDocumentHandler[Vote] = Macros.handler[Vote]
implicit val lockedBSONHandler: BSONDocumentHandler[Locked] = Macros.handler[Locked]
implicit val questionBSONHandler: BSONDocumentHandler[Question] = Macros.handler[Question]
}
5 changes: 4 additions & 1 deletion modules/qa/src/main/QaAuth.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ object QaAuth {

def canAsk(implicit ctx: UserContext) = noKid(noTroll(isNotN00b))

def canAnswer(q: Question)(implicit ctx: UserContext) = noKid(noTroll(isNotN00b))
def canAnswer(q: Question)(implicit ctx: UserContext) = noLock(q)(noKid(noTroll(isNotN00b)))

def canVote(implicit ctx: UserContext) = noKid(noTroll(isNotN00b))

Expand All @@ -30,4 +30,7 @@ object QaAuth {
private def isNotN00b(u: User) = !isN00b(u)

def isN00b(u: User) = u.createdAt isAfter DateTime.now.minusWeeks(1)

def noLock(q: Question)(res: => Boolean)(implicit ctx: UserContext) =
(!q.isLocked || ctx.me.??(Granter(_.ModerateQa))) && res
}
4 changes: 1 addition & 3 deletions modules/qa/src/main/Search.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ import lila.db.dsl._

final class Search(collection: Coll) {

private implicit val commentBSONHandler = Macros.handler[Comment]
private implicit val voteBSONHandler = Macros.handler[Vote]
private[qa] implicit val questionBSONHandler = Macros.handler[Question]
import QaApi._

def apply(q: String): Fu[List[Question]] =
collection.find(BSONDocument(
Expand Down
7 changes: 6 additions & 1 deletion modules/qa/src/main/model.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ case class Question(
createdAt: DateTime,
updatedAt: DateTime,
acceptedAt: Option[DateTime],
editedAt: Option[DateTime]
editedAt: Option[DateTime],
locked: Option[Locked] = None
) {

def id = _id
Expand All @@ -35,6 +36,8 @@ case class Question(
def editNow = copy(editedAt = Some(DateTime.now)).updateNow

def accepted = acceptedAt.isDefined

def isLocked = locked.isDefined
}

case class Answer(
Expand Down Expand Up @@ -95,3 +98,5 @@ object Comment {

def makeId = ornicar.scalalib.Random nextString 8
}

case class Locked(by: User.ID, at: DateTime)
3 changes: 3 additions & 0 deletions public/stylesheets/qa.css
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,9 @@ div.side .questions a:hover {
#qa #your-answer textarea {
display: block;
}
#qa .noform {
padding: 20px;
}
#qa .answer-wrap {
overflow: hidden;
padding: 20px 0;
Expand Down

0 comments on commit 325a51b

Please sign in to comment.