forum troll isolation complete

pull/83/head
Thibault Duplessis 2013-05-17 00:33:31 -03:00
parent fbf6ff749d
commit 839e2dc163
14 changed files with 145 additions and 66 deletions

View File

@ -6,14 +6,14 @@ import views._
object ForumCateg extends LilaController with ForumController {
def index = Open { implicit ctx
~ctx.userId.map(teamCache.teamIds.apply) flatMap { teamIds
categApi list teamIds map { html.forum.categ.index(_) }
ctx.userId.zmap(teamCache.teamIds.apply) flatMap { teamIds
categApi.list(teamIds, ctx.troll) map { html.forum.categ.index(_) }
}
}
def show(slug: String, page: Int) = Open { implicit ctx
CategGrantRead(slug) {
OptionOk(categApi.show(slug, page)) {
OptionOk(categApi.show(slug, page, ctx.troll)) {
case (categ, topics) html.forum.categ.show(categ, topics)
}
}

View File

@ -23,7 +23,7 @@ object ForumPost extends LilaController with ForumController {
def create(categSlug: String, slug: String, page: Int) = OpenBody { implicit ctx
CategGrantWrite(categSlug) {
implicit val req = ctx.body
OptionFuResult(topicApi.show(categSlug, slug, page)) {
OptionFuResult(topicApi.show(categSlug, slug, page, ctx.troll)) {
case (categ, topic, posts)
if (topic.closed) fuccess(BadRequest("This topic is closed"))
else forms.post.bindFromRequest.fold(

View File

@ -32,7 +32,7 @@ object ForumTopic extends LilaController with ForumController {
def show(categSlug: String, slug: String, page: Int) = Open { implicit ctx
CategGrantRead(categSlug) {
OptionFuOk(topicApi.show(categSlug, slug, page)) {
OptionFuOk(topicApi.show(categSlug, slug, page, ctx.troll)) {
case (categ, topic, posts) isGrantedWrite(categSlug) flatMap { granted
(!posts.hasNextPage && granted && topic.open) ?? forms.postWithCaptcha.map(_.some) map { form
html.forum.topic.show(categ, topic, posts, form)
@ -45,7 +45,7 @@ object ForumTopic extends LilaController with ForumController {
def close(categSlug: String, slug: String) =
Secure(_.ModerateForum) { implicit ctx
me
OptionFuRedirect(topicApi.show(categSlug, slug, 1)) {
OptionFuRedirect(topicApi.show(categSlug, slug, 1, ctx.troll)) {
case (categ, topic, pag) topicApi.toggleClose(categ, topic, me) inject
routes.ForumTopic.show(categSlug, slug, pag.nbPages)
}

View File

@ -21,9 +21,37 @@ db.f_topic.find().forEach(function(topic) {
}});
});
print("add troll fields to the forum categs")
db.f_categ.find().forEach(function(categ) {
db.f_categ.update({'_id': categ['_id']}, { $set: {
nbTopicsTroll: categ['nbTopics'],
nbPostsTroll: categ['nbPosts'],
lastPostIdTroll: categ['lastPostId']
}});
});
print("remove useless author names in forum posts")
db.f_post.update({author:{$exists:true},userId:{$exists:true}},{$unset:{author:true}},{multi:true});
print("mark all forum posts as not troll");
db.f_post.update({},{$set:{troll:false}}, {multi:true});
print("use troll field in forum post indexes")
db.f_post.dropIndex('topicId_1')
db.f_post.dropIndex('topicId_1_createdAt_1')
db.f_post.dropIndex('categId_1')
db.f_post.dropIndex('createdAt_-1')
db.f_post.ensureIndex({topicId: 1, troll: 1})
db.f_post.ensureIndex({topicId: 1, createdAt: 1, troll: 1})
db.f_post.ensureIndex({categId: 1, troll: 1})
db.f_post.ensureIndex({createdAt: -1, troll: 1})
print("use troll field in forum topic indexes")
db.f_topic.dropIndex('categId_1')
db.f_topic.dropIndex('categId_1_updatedAt_-1')
db.f_topic.ensureIndex({categId: 1, troll: 1})
db.f_topic.ensureIndex({categId: 1, updatedAt: -1, troll: 1})
print("user.settings.{chat,sound} should be a string");
['settings.chat', 'settings.sound'].forEach(function(name) {
[true, false].forEach(function(value) {

View File

@ -8,16 +8,26 @@ case class Categ(
team: Option[String] = None,
nbTopics: Int,
nbPosts: Int,
lastPostId: String) {
lastPostId: String,
nbTopicsTroll: Int,
nbPostsTroll: Int,
lastPostIdTroll: String) {
def nbTopics(troll: Boolean): Int = troll.fold(nbTopicsTroll, nbTopics)
def nbPosts(troll: Boolean): Int = troll.fold(nbPostsTroll, nbPosts)
def lastPostId(troll: Boolean): String = troll.fold(lastPostIdTroll, lastPostId)
def isStaff = slug == "staff"
def isTeam = team.nonEmpty
def withTopic(post: Post): Categ = copy(
nbTopics = nbTopics + 1,
nbPosts = nbPosts + 1,
lastPostId = post.id)
nbTopics = post.troll.fold(nbTopics, nbTopics + 1),
nbPosts = post.troll.fold(nbPosts, nbPosts + 1),
lastPostId = post.troll.fold(lastPostId, post.id),
nbTopicsTroll = nbTopicsTroll + 1,
nbPostsTroll = nbPostsTroll + 1,
lastPostIdTroll = post.id)
def slug = id
}

View File

@ -11,15 +11,15 @@ import scalaz.{ OptionT, OptionTs }
private[forum] final class CategApi(env: Env) extends OptionTs {
def list(teams: List[String]): Fu[List[CategView]] = for {
def list(teams: List[String], troll: Boolean): Fu[List[CategView]] = for {
categs CategRepo withTeams teams
views (categs map { categ
env.postApi get categ.lastPostId map { topicPost
env.postApi get (categ lastPostId troll) map { topicPost
CategView(categ, topicPost map {
_ match {
case (topic, post) (topic, post, env.postApi lastPageOf topic)
}
})
}, troll)
}
}).sequence
} yield views
@ -36,7 +36,10 @@ private[forum] final class CategApi(env: Env) extends OptionTs {
team = slug.some,
nbTopics = 0,
nbPosts = 0,
lastPostId = "")
lastPostId = "",
nbTopicsTroll = 0,
nbPostsTroll = 0,
lastPostIdTroll = "")
val topic = Topic.make(
categId = categ.slug,
slug = slug + "-forum",
@ -53,19 +56,13 @@ private[forum] final class CategApi(env: Env) extends OptionTs {
categId = categ.id)
$insert(categ) >>
$insert(post) >>
$insert(topic.copy(
nbPosts = 1,
lastPostId = post.id,
updatedAt = post.createdAt)) >>
$update(categ.copy(
nbTopics = categ.nbTopics + 1,
nbPosts = categ.nbPosts + 1,
lastPostId = post.id))
$insert(topic withPost post) >>
$update(categ withTopic post)
}
def show(slug: String, page: Int): Fu[Option[(Categ, Paginator[TopicView])]] =
def show(slug: String, page: Int, troll: Boolean): Fu[Option[(Categ, Paginator[TopicView])]] =
optionT(CategRepo bySlug slug) flatMap { categ
optionT(env.topicApi.paginator(categ, page) map { (categ, _).some })
optionT(env.topicApi.paginator(categ, page, troll) map { (categ, _).some })
}
def denormalize(categ: Categ): Funit = for {
@ -73,10 +70,17 @@ private[forum] final class CategApi(env: Env) extends OptionTs {
topicIds = topics map (_.id)
nbPosts PostRepo countByTopics topicIds
lastPost PostRepo lastByTopics topicIds
topicsTroll TopicRepoTroll byCateg categ
topicIdsTroll = topicsTroll map (_.id)
nbPostsTroll PostRepoTroll countByTopics topicIdsTroll
lastPostTroll PostRepoTroll lastByTopics topicIdsTroll
_ $update(categ.copy(
nbTopics = topics.size,
nbPosts = nbPosts,
lastPostId = lastPost zmap (_.id)
lastPostId = lastPost zmap (_.id),
nbTopicsTroll = topicsTroll.size,
nbPostsTroll = nbPostsTroll,
lastPostIdTroll = lastPostTroll zmap (_.id)
))
} yield ()

View File

@ -19,7 +19,7 @@ case class Post(
def showAuthor = (author map (_.trim) filter ("" !=)) | User.anonymous
def showUsernameOrAuthor = userId | showAuthor
def showUserIdOrAuthor = userId | showAuthor
def isTeam = categId startsWith teamSlug("")

View File

@ -13,10 +13,10 @@ import play.api.libs.json.JsObject
import scalaz.{ OptionT, OptionTs }
final class PostApi(
env: Env,
indexer: lila.hub.ActorLazyRef,
maxPerPage: Int,
modLog: ModlogApi) extends OptionTs {
env: Env,
indexer: lila.hub.ActorLazyRef,
maxPerPage: Int,
modLog: ModlogApi) extends OptionTs {
def makePost(
categ: Categ,
@ -34,14 +34,9 @@ final class PostApi(
categId = categ.id)
$insert(post) >>
// denormalize topic
$update(topic.copy(
nbPosts = topic.nbPosts + 1,
lastPostId = post.id,
updatedAt = post.createdAt)) >>
$update(topic withPost post) >>
// denormalize categ
$update(categ.copy(
nbPosts = categ.nbPosts + 1,
lastPostId = post.id)) >>-
$update(categ withTopic post) >>-
(indexer ! InsertPost(post)) >>
env.recent.invalidate inject post
}
@ -84,9 +79,9 @@ final class PostApi(
def lastPageOf(topic: Topic) =
math.ceil(topic.nbPosts / maxPerPage.toFloat).toInt
def paginator(topic: Topic, page: Int): Fu[Paginator[Post]] = Paginator(
def paginator(topic: Topic, page: Int, troll: Boolean): Fu[Paginator[Post]] = Paginator(
new Adapter(
selector = PostRepo selectTopic topic,
selector = PostRepo(troll) selectTopic topic,
sort = PostRepo.sortQuery :: Nil),
currentPage = page,
maxPerPage = maxPerPage)

View File

@ -6,7 +6,19 @@ import tube.postTube
import play.api.libs.json.Json
object PostRepo {
object PostRepo extends PostRepo(false) {
def apply(troll: Boolean): PostRepo = troll.fold(PostRepoTroll, PostRepo)
}
object PostRepoTroll extends PostRepo(true)
sealed abstract class PostRepo(troll: Boolean) {
private lazy val trollFilter = troll.fold(
Json.obj(),
Json.obj("troll" -> false)
)
def isFirstPost(topicId: String, postId: String): Fu[Boolean] =
$primitive.one(
@ -27,10 +39,10 @@ object PostRepo {
def removeByTopic(topicId: String): Fu[Unit] =
$remove(selectTopic(topicId))
def selectTopic(topicId: String) = Json.obj("topicId" -> topicId)
def selectTopics(topicIds: List[String]) = Json.obj("topicId" -> $in(topicIds))
def selectTopic(topicId: String) = Json.obj("topicId" -> topicId) ++ trollFilter
def selectTopics(topicIds: List[String]) = Json.obj("topicId" -> $in(topicIds)) ++ trollFilter
def selectCategs(categIds: List[String]) = Json.obj("categId" -> $in(categIds))
def selectCategs(categIds: List[String]) = Json.obj("categId" -> $in(categIds)) ++ trollFilter
def sortQuery = $sort.createdAsc
}

View File

@ -19,6 +19,10 @@ case class Topic(
troll: Boolean,
closed: Boolean) {
def updatedAt(troll: Boolean): DateTime = troll.fold(updatedAtTroll, updatedAt)
def nbPosts(troll: Boolean): Int = troll.fold(nbPostsTroll, nbPosts)
def lastPostId(troll: Boolean): String = troll.fold(lastPostIdTroll, lastPostId)
def open = !closed
def withPost(post: Post): Topic = copy(

View File

@ -16,12 +16,15 @@ private[forum] final class TopicApi(
maxPerPage: Int,
modLog: lila.mod.ModlogApi) extends OptionTs {
def show(categSlug: String, slug: String, page: Int): Fu[Option[(Categ, Topic, Paginator[Post])]] =
def show(categSlug: String, slug: String, page: Int, troll: Boolean): Fu[Option[(Categ, Topic, Paginator[Post])]] =
for {
data get(categSlug, slug)
data (for {
categ optionT(CategRepo bySlug categSlug)
topic optionT(TopicRepo(troll).byTree(categSlug, slug))
} yield categ -> topic).value
res data zmap {
case (categ, topic) (TopicRepo incViews topic) >>
(env.postApi.paginator(topic, page) map { (categ, topic, _).some })
(env.postApi.paginator(topic, page, troll) map { (categ, topic, _).some })
}
} yield res
@ -50,18 +53,13 @@ private[forum] final class TopicApi(
env.recent.invalidate inject topic
}
def get(categSlug: String, slug: String): Fu[Option[(Categ, Topic)]] = for {
categ optionT(CategRepo bySlug categSlug)
topic optionT(TopicRepo.byTree(categSlug, slug))
} yield categ -> topic
def paginator(categ: Categ, page: Int): Fu[Paginator[TopicView]] = Paginator(
def paginator(categ: Categ, page: Int, troll: Boolean): Fu[Paginator[TopicView]] = Paginator(
adapter = new Adapter[Topic](
selector = TopicRepo byCategQuery categ,
selector = TopicRepo(troll) byCategQuery categ,
sort = Seq($sort.createdDesc)
) mapFuture { topic
$find.byId[Post](topic.lastPostId) map { post
TopicView(categ, topic, post, env.postApi lastPageOf topic)
$find.byId[Post](topic lastPostId troll) map { post
TopicView(categ, topic, post, env.postApi lastPageOf topic, troll)
}
},
currentPage = page,
@ -81,10 +79,15 @@ private[forum] final class TopicApi(
def denormalize(topic: Topic): Funit = for {
nbPosts PostRepo countByTopics List(topic)
lastPost PostRepo lastByTopics List(topic)
nbPostsTroll PostRepoTroll countByTopics List(topic)
lastPostTroll PostRepoTroll lastByTopics List(topic)
_ $update(topic.copy(
nbPosts = nbPosts,
lastPostId = lastPost zmap (_.id),
updatedAt = lastPost.fold(topic.updatedAt)(_.createdAt)
updatedAt = lastPost.fold(topic.updatedAt)(_.createdAt),
nbPostsTroll = nbPostsTroll,
lastPostIdTroll = lastPostTroll zmap (_.id),
updatedAtTroll = lastPostTroll.fold(topic.updatedAtTroll)(_.createdAt)
))
} yield ()

View File

@ -6,7 +6,19 @@ import tube.topicTube
import play.api.libs.json.Json
object TopicRepo {
object TopicRepo extends TopicRepo(false) {
def apply(troll: Boolean): TopicRepo = troll.fold(TopicRepoTroll, TopicRepo)
}
object TopicRepoTroll extends TopicRepo(true)
sealed abstract class TopicRepo(troll: Boolean) {
private lazy val trollFilter = troll.fold(
Json.obj(),
Json.obj("troll" -> false)
)
def close(id: String, value: Boolean): Funit =
$update.field(id, "closed", value)
@ -15,11 +27,12 @@ object TopicRepo {
$find(byCategQuery(categ))
def byTree(categSlug: String, slug: String): Fu[Option[Topic]] =
$find one Json.obj("categId" -> categSlug, "slug" -> slug)
$find.one(Json.obj("categId" -> categSlug, "slug" -> slug) ++ trollFilter)
def nextSlug(categ: Categ, name: String, it: Int = 1): Fu[String] = {
val slug = lila.common.String.slugify(name) + ~(it == 1).option("-" + it)
byTree(categ.slug, slug) flatMap {
// also take troll topic into accounts
TopicRepoTroll.byTree(categ.slug, slug) flatMap {
_.isDefined.fold(
nextSlug(categ, name, it + 1),
fuccess(slug)
@ -30,5 +43,5 @@ object TopicRepo {
def incViews(topic: Topic): Funit =
$update($select(topic.id), $inc("views" -> 1))
def byCategQuery(categ: Categ) = Json.obj("categId" -> categ.slug)
def byCategQuery(categ: Categ) = Json.obj("categId" -> categ.slug) ++ trollFilter
}

View File

@ -2,27 +2,34 @@ package lila.forum
case class CategView(
categ: Categ,
lastPost: Option[(Topic, Post, Int)]) {
lastPost: Option[(Topic, Post, Int)],
troll: Boolean) {
def nbTopics = categ nbTopics troll
def nbPosts = categ nbPosts troll
def lastPostId = categ lastPostId troll
def slug = categ.slug
def name = categ.name
def desc = categ.desc
def nbTopics = categ.nbTopics
def nbPosts = categ.nbPosts
}
case class TopicView(
categ: Categ,
topic: Topic,
lastPost: Option[Post],
lastPage: Int) {
lastPage: Int,
troll: Boolean) {
def updatedAt = topic updatedAt troll
def nbPosts = topic nbPosts troll
def lastPostId = topic lastPostId troll
def id = topic.id
def slug = topic.slug
def name = topic.name
def views = topic.views
def createdAt = topic.createdAt
def nbPosts = topic.nbPosts
}
case class PostView(
@ -31,7 +38,7 @@ case class PostView(
categ: Categ,
topicLastPage: Int) {
def show = post.showUsernameOrAuthor + " @ " + topic.name + " - " + post.text.take(80)
def show = post.showUserIdOrAuthor + " @ " + topic.name + " - " + post.text.take(80)
}
case class PostLiteView(post: Post, topic: Topic, topicLastPage: Int)

View File

@ -742,6 +742,9 @@ div.checkmateSection {
float: left;
font-size: 0.9em;
}
div.checkmateSection label {
width: 310px;
}
div.checkmateSection div.result {
display: none;
height: 48px;