give team creators moderator powers on their forums

pull/83/head
Thibault Duplessis 2013-05-28 15:53:32 +02:00
parent 14a53b0a6f
commit 046399060d
15 changed files with 84 additions and 42 deletions

View File

@ -1,11 +1,11 @@
package controllers
import lila.app._
import lila.user.Context
import lila.forum
import play.api.mvc._, Results._
import lila.app._
import lila.forum
import lila.user.Context
private[controllers] trait ForumController extends forum.Granter { self: LilaController
protected def categApi = Env.forum.categApi
@ -18,6 +18,9 @@ private[controllers] trait ForumController extends forum.Granter { self: LilaCon
protected def userBelongsToTeam(teamId: String, userId: String): Fu[Boolean] =
Env.team.api.belongsTo(teamId, userId)
protected def userOwnsTeam(teamId: String, userId: String): Fu[Boolean] =
Env.team.api.owns(teamId, userId)
protected def CategGrantRead[A <: Result](categSlug: String)(a: Fu[A])(implicit ctx: Context): Fu[Result] =
isGrantedRead(categSlug).fold(a,
fuccess(Forbidden("You cannot access to this category"))
@ -30,4 +33,12 @@ private[controllers] trait ForumController extends forum.Granter { self: LilaCon
fuccess(Forbidden("You cannot post to this category"))
)
}
protected def CategGrantMod[A <: Result](categSlug: String)(a: Fu[A])(implicit ctx: Context): Fu[Result] =
isGrantedMod(categSlug) flatMap { granted
(granted | isGranted(_.ModerateForum)) fold (
a,
fuccess(Forbidden("You cannot post to this category"))
)
}
}

View File

@ -38,9 +38,11 @@ object ForumPost extends LilaController with ForumController {
}
}
def delete(id: String) = Secure(_.ModerateForum) { implicit ctx
def delete(categSlug: String, id: String) = Auth { implicit ctx
me
postApi.delete(id, me)
CategGrantMod(categSlug) {
postApi.delete(categSlug, id, me) map { Ok(_) }
}
}
def redirect(id: String) = Open { implicit ctx

View File

@ -42,12 +42,13 @@ object ForumTopic extends LilaController with ForumController {
}
}
def close(categSlug: String, slug: String) =
Secure(_.ModerateForum) { implicit ctx
me
def close(categSlug: String, slug: String) = Auth { implicit ctx
me
CategGrantMod(categSlug) {
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

@ -161,7 +161,7 @@ private[controllers] trait LilaController
isGranted(permission(Permission))
protected def isGranted(permission: Permission)(implicit ctx: Context): Boolean =
ctx.me.??(Granter(permission))
ctx.me ?? Granter(permission)
protected def authenticationFailed(implicit req: RequestHeader): Result =
Redirect(routes.Auth.signup) withCookies LilaCookie.session(Env.security.api.AccessUri, req.uri)

View File

@ -1,26 +1,31 @@
package lila.app
package templating
import lila.forum.Post
import lila.team.Env.{ current teamEnv }
import lila.user.Context
import play.api.templates.Html
import lila.forum.Post
import lila.user.Context
trait ForumHelper { self: UserHelper with StringHelper
private object Granter extends lila.forum.Granter {
protected def userBelongsToTeam(teamId: String, userId: String): Fu[Boolean] =
teamEnv.api.belongsTo(teamId, userId)
Env.team.api.belongsTo(teamId, userId)
protected def userOwnsTeam(teamId: String, userId: String): Fu[Boolean] =
Env.team.api.owns(teamId, userId)
}
def isGrantedRead(categSlug: String)(implicit ctx: Context) =
def isGrantedRead(categSlug: String)(implicit ctx: Context) =
Granter.isGrantedRead(categSlug)
def isGrantedWrite(categSlug: String)(implicit ctx: Context) =
Granter.isGrantedWrite(categSlug).await
def isGrantedMod(categSlug: String)(implicit ctx: Context) =
Granter.isGrantedMod(categSlug).await
def authorName(post: Post) =
post.userId.fold(escape(post.showAuthor))(userIdToUsername)

View File

@ -28,7 +28,7 @@ searchText = text
<p>@shortenWithBr(view.post.text, 200)</p>
</td>
<td class="info">
<span class="date">@timeago(view.post.createdAt)</span>
@timeago(view.post.createdAt)
by @Html(authorName(view.post))
</td>
</tr>

View File

@ -25,15 +25,15 @@ title = topic.name) {
<div class="post" id="@post.number">
<div class="metas clearfix">
@authorLink(post, "author".some)
<span class="createdAt">@timeago(post.createdAt)</span>
@timeago(post.createdAt)
<a class="anchor" href="@routes.ForumTopic.show(categ.slug, topic.slug, posts.currentPage)#@post.number">#@post.number</a>
@if(isGranted(_.SuperAdmin)) {
@post.ip.map { ip =>
<a class="ipban" href="@routes.Mod.ipban(ip)">Ban @ip</a>
}
}
@if(isGranted(_.ModerateForum)) {
<a class="delete" href="@routes.ForumPost.delete(post.id)">Delete</a>
@if(isGrantedMod(categ.slug)) {
<a class="delete" href="@routes.ForumPost.delete(categ.slug, post.id)">Delete</a>
}
</div>
<p class="message">@autoLink(post.text)</p>
@ -41,15 +41,15 @@ title = topic.name) {
}
</div>
@if(isGranted(_.ModerateForum)) {
@if(isGrantedMod(categ.slug)) {
<form class="close" method="post" action="@routes.ForumTopic.close(categ.slug, topic.slug)">
<button>@topic.closed.fold("Reopen the topic", "Close the topic") </button>
</form>
}
<div class="topicReply">
@formWithCaptcha.map {
case (form, captcha) => {
@formWithCaptcha.map {
case (form, captcha) => {
<h2 class="postNewTitle" id="reply">Reply to this topic</h2>
<form class="wide" action="@routes.ForumPost.create(categ.slug, topic.slug, posts.currentPage)#reply" method="POST" novalidate>
@forum.post.formFields(form("text"), form("author"))
@ -58,12 +58,18 @@ title = topic.name) {
<input type="submit" class="submit button" value="Reply" />
<a href="@routes.ForumCateg.show(categ.slug)" style="margin-left:20px">@trans.cancel()</a>
</form>
}
}.getOrElse {
@if(topic.closed) {
<p>This topic is now closed.</p>
}
}
}
}.getOrElse {
@if(topic.closed) {
<p>This topic is now closed.</p>
} else {
@categ.team.map { teamId =>
@if(!myTeam(teamId)) {
<p><a href="@routes.Team.show(teamId)">Join the @teamIdToName(teamId) team</a> to post in this forum</p>
}
}
}
}
</div>
<div class="bar bottom clearfix">

View File

@ -150,7 +150,7 @@ POST /forum/:categSlug/new controllers.ForumTopic.create(categSlug:
GET /forum/:categSlug/:slug controllers.ForumTopic.show(categSlug: String, slug: String, page: Int ?= 1)
POST /forum/:categSlug/:slug/close controllers.ForumTopic.close(categSlug: String, slug: String)
POST /forum/:categSlug/:slug/new controllers.ForumPost.create(categSlug: String, slug: String, page: Int ?= 1)
POST /forum/delete/post/:id controllers.ForumPost.delete(id: String)
POST /forum/:categSlug/delete/:id controllers.ForumPost.delete(categSlug: String, id: String)
GET /forum/redirect/post/:id controllers.ForumPost.redirect(id: String)
# Message

View File

@ -1,6 +1,6 @@
package lila.forum
import lila.security.{ Permission, Granter => Master }
import lila.security.{ Permission, Granter Master }
import lila.user.Context
trait Granter {
@ -8,7 +8,8 @@ trait Granter {
private val TeamSlugPattern = """^team-([\w-]+)$""".r
private val StaffSlug = "staff"
protected def userBelongsToTeam(teamId: String, userId: String): Fu[Boolean]
protected def userBelongsToTeam(teamId: String, userId: String): Fu[Boolean]
protected def userOwnsTeam(teamId: String, userId: String): Fu[Boolean]
def isGrantedRead(categSlug: String)(implicit ctx: Context): Boolean =
(categSlug == StaffSlug).fold(
@ -23,4 +24,12 @@ trait Granter {
ctx.me.??(me userBelongsToTeam(teamId, me.id))
case _ fuccess(true)
}
def isGrantedMod(categSlug: String)(implicit ctx: Context): Fu[Boolean] =
categSlug match {
case _ if (ctx.me ?? Master(Permission.ModerateForum)) fuccess(true)
case TeamSlugPattern(teamId)
ctx.me ?? { me userOwnsTeam(teamId, me.id) }
case _ fuccess(false)
}
}

View File

@ -99,8 +99,8 @@ final class PostApi(
currentPage = page,
maxPerPage = maxPerPage)
def delete(postId: String, mod: User): Funit = (for {
post optionT($find.byId[Post](postId))
def delete(categSlug: String, postId: String, mod: User): Funit = (for {
post optionT(PostRepo(true).byCategAndId(categSlug, postId))
view optionT(view(post))
_ optionT(for {
first PostRepo.isFirstPost(view.topic.id, view.post.id)

View File

@ -20,6 +20,9 @@ sealed abstract class PostRepo(troll: Boolean) {
Json.obj("troll" -> false)
)
def byCategAndId(categSlug: String, id: String): Fu[Option[Post]] =
$find.one(selectCateg(categSlug) ++ $select(id))
def countBeforeNumber(topicId: String, number: Int): Fu[Int] =
$count(selectTopic(topicId) ++ Json.obj("number" -> $lt(number)))
@ -45,6 +48,7 @@ sealed abstract class PostRepo(troll: Boolean) {
def selectTopic(topicId: String) = Json.obj("topicId" -> topicId) ++ trollFilter
def selectTopics(topicIds: List[String]) = Json.obj("topicId" -> $in(topicIds)) ++ trollFilter
def selectCateg(categId: String) = Json.obj("categId" -> categId) ++ trollFilter
def selectCategs(categIds: List[String]) = Json.obj("categId" -> $in(categIds)) ++ trollFilter
def sortQuery = $sort.createdAsc

View File

@ -150,7 +150,7 @@ final class TeamApi(
def disable(team: Team): Funit =
TeamRepo.disable(team) >>- (indexer ! RemoveTeam(team.id))
// // delete for ever, with members but not forums
// delete for ever, with members but not forums
def delete(team: Team): Funit =
$remove(team) >>
MemberRepo.removeByteam(team.id) >>-
@ -159,6 +159,9 @@ final class TeamApi(
def belongsTo(teamId: String, userId: String): Fu[Boolean] =
cached teamIds userId map (_ contains teamId)
def owns(teamId: String, userId: String): Fu[Boolean] =
TeamRepo ownerOf teamId map (Some(userId) ==)
def teamIds(userId: String) = cached teamIds userId
def teamName(teamId: String) = cached name teamId

View File

@ -28,6 +28,9 @@ object TeamRepo {
"createdBy" -> userId
))
def ownerOf(teamId: String): Fu[Option[String]] =
$primitive.one($select(teamId), "createdBy")(_.asOpt[String])
def incMembers(teamId: String, by: Int): Funit =
$update($select(teamId), $inc("nbMembers" -> by))

View File

@ -56,7 +56,7 @@ ol.crumbs li:first-child {
#lichess_forum .search_results {
margin-top: 1em;
}
#lichess_forum .search_results .date {
#lichess_forum .search_results time {
font-size: 0.8em;
white-space: nowrap;
display: block
@ -164,7 +164,7 @@ div.post .author a {
display: block;
}
div.post .createdAt {
div.post time {
font-size: 0.8em;
font-style: italic;
color: #aaa;

6
todo
View File

@ -1,7 +1,5 @@
start chess960 after both player move http://fr.lichess.org/forum/lichess-feedback/clock-fairness-in-chess960-games
guess friend list
takeback play 2 times ?? http://en.lichess.org/analyse/nfjchnmo/black move 45
show lobby chat to anon (and rated games?) or show empty chat
also translate websockets error message
@someone = link to someone's profile
analysis not the fastest checkmate, but no best move? http://en.lichess.org/analyse/w3xw8fsw/black
@ -25,7 +23,6 @@ takeback substract time for person granting
add fullscreen mode for spectators and load new games in a loop (based on certain filter options) - thus creating a chess tv channel
only team forum posts on the homepage?
team list sort by date, name, popularity, avg elo, team elo, ...?
team timeline (forum posts, joins)
download 10.000 best elo games http://en.lichess.org/inbox/v1p6hokg#bottom
sound config (like sound on game join, not moves)
block user creation from an IP
@ -64,13 +61,13 @@ from MoralIntentions email:
- Several options of how points should be given to players (for example: Score * Sonneborn-Berger * Tournament Performance Rating * Percentage Score [for example: 0.64] / 1000) and therefore also several Leaderboards.
- The possibility of team matches (with solutions for players who are in several teams), where the team leader has to choose the board order, where the team results are shown and where the team player (who has played all of the games of the match) with the highest Percentage Score gets crowned Player of the Match of team "A".
safari has high bounce rate
send message to sockets when a friendship is created or revoked
correspondance chess http://en.lichess.org/forum/lichess-feedback/correspondence-chess#1
takeback/enpassant glitch http://en.lichess.org/forum/lichess-feedback/i-found-a-glitch#1
show teams in user mini
badges for top players in ELO and number of games
top menu is too long in french with long username
at least locally, the forum search user:mephostophilis returns no result
check team average elo http://en.lichess.org/inbox/benuegti#bottom
DEPLOY p21
----------
@ -93,3 +90,4 @@ users - more translations
- more bugs :p
- advanced search = only analyzed games
- no more reloads when game finishes
- team forum admin