give team creators moderator powers on their forums
parent
14a53b0a6f
commit
046399060d
|
@ -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"))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 ⇒
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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
6
todo
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue