team owners can mass PM all team members
parent
57fa6d32f5
commit
e8103b95ee
|
@ -146,7 +146,7 @@ final class Clas(
|
|||
env.clas.api.student.activeWithUsers(clas) flatMap { students =>
|
||||
val url = routes.Clas.show(clas.id.value).url
|
||||
val full = if (text contains url) text else s"$text\n\n${env.net.baseUrl}$url"
|
||||
env.msg.api.multiPost(me, students.map(_.user), full)
|
||||
env.msg.api.multiPost(me, students.map(_.user.id), full)
|
||||
} inject
|
||||
Redirect(routes.Clas.show(clas.id.value)).flashSuccess
|
||||
)
|
||||
|
|
|
@ -2,6 +2,7 @@ package controllers
|
|||
|
||||
import play.api.libs.json._
|
||||
import play.api.mvc._
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app._
|
||||
|
@ -65,44 +66,38 @@ final class Team(
|
|||
}
|
||||
|
||||
def edit(id: String) = Auth { implicit ctx => _ =>
|
||||
OptionFuResult(api team id) { team =>
|
||||
Owner(team) { fuccess(html.team.form.edit(team, forms edit team)) }
|
||||
WithOwnedTeam(id) { team =>
|
||||
fuccess(html.team.form.edit(team, forms edit team))
|
||||
}
|
||||
}
|
||||
|
||||
def update(id: String) = AuthBody { implicit ctx => implicit me =>
|
||||
OptionFuResult(api team id) { team =>
|
||||
Owner(team) {
|
||||
implicit val req = ctx.body
|
||||
forms
|
||||
.edit(team)
|
||||
.bindFromRequest
|
||||
.fold(
|
||||
err => BadRequest(html.team.form.edit(team, err)).fuccess,
|
||||
data => api.update(team, data, me) inject Redirect(routes.Team.show(team.id))
|
||||
)
|
||||
}
|
||||
def update(id: String) = AuthBody { implicit ctx => me =>
|
||||
WithOwnedTeam(id) { team =>
|
||||
implicit val req = ctx.body
|
||||
forms
|
||||
.edit(team)
|
||||
.bindFromRequest
|
||||
.fold(
|
||||
err => BadRequest(html.team.form.edit(team, err)).fuccess,
|
||||
data => api.update(team, data, me) inject Redirect(routes.Team.show(team.id)).flashSuccess
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def kickForm(id: String) = Auth { implicit ctx => me =>
|
||||
OptionFuResult(api team id) { team =>
|
||||
Owner(team) {
|
||||
env.team.memberRepo userIdsByTeam team.id map { userIds =>
|
||||
html.team.admin.kick(team, userIds - me.id)
|
||||
}
|
||||
WithOwnedTeam(id) { team =>
|
||||
env.team.memberRepo userIdsByTeam team.id map { userIds =>
|
||||
html.team.admin.kick(team, userIds - me.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def kick(id: String) = AuthBody { implicit ctx => me =>
|
||||
OptionFuResult(api team id) { team =>
|
||||
Owner(team) {
|
||||
implicit val req = ctx.body
|
||||
forms.selectMember.bindFromRequest.value ?? { api.kick(team, _, me) } inject Redirect(
|
||||
routes.Team.show(team.id)
|
||||
)
|
||||
}
|
||||
WithOwnedTeam(id) { team =>
|
||||
implicit val req = ctx.body
|
||||
forms.selectMember.bindFromRequest.value ?? { api.kick(team, _, me) } inject Redirect(
|
||||
routes.Team.show(team.id)
|
||||
).flashSuccess
|
||||
}
|
||||
}
|
||||
def kickUser(teamId: String, userId: String) = Scoped(_.Team.Write) { _ => me =>
|
||||
|
@ -115,23 +110,19 @@ final class Team(
|
|||
}
|
||||
|
||||
def changeOwnerForm(id: String) = Auth { implicit ctx => _ =>
|
||||
OptionFuResult(api team id) { team =>
|
||||
Owner(team) {
|
||||
env.team.memberRepo userIdsByTeam team.id map { userIds =>
|
||||
html.team.admin.changeOwner(team, userIds - team.createdBy)
|
||||
}
|
||||
WithOwnedTeam(id) { team =>
|
||||
env.team.memberRepo userIdsByTeam team.id map { userIds =>
|
||||
html.team.admin.changeOwner(team, userIds - team.createdBy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def changeOwner(id: String) = AuthBody { implicit ctx => me =>
|
||||
OptionFuResult(api team id) { team =>
|
||||
Owner(team) {
|
||||
implicit val req = ctx.body
|
||||
forms.selectMember.bindFromRequest.value ?? { api.changeOwner(team, _, me) } inject Redirect(
|
||||
routes.Team.show(team.id)
|
||||
)
|
||||
}
|
||||
WithOwnedTeam(id) { team =>
|
||||
implicit val req = ctx.body
|
||||
forms.selectMember.bindFromRequest.value ?? { api.changeOwner(team, _, me) } inject Redirect(
|
||||
routes.Team.show(team.id)
|
||||
).flashSuccess
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,7 +130,7 @@ final class Team(
|
|||
OptionFuResult(api team id) { team =>
|
||||
(api delete team) >>
|
||||
env.mod.logApi.deleteTeam(me.id, team.name, team.description) inject
|
||||
Redirect(routes.Team all 1)
|
||||
Redirect(routes.Team all 1).flashSuccess
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -161,7 +152,7 @@ final class Team(
|
|||
},
|
||||
data =>
|
||||
api.create(data, me) map { team =>
|
||||
Redirect(routes.Team.show(team.id)): Result
|
||||
Redirect(routes.Team.show(team.id)).flashSuccess
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -175,8 +166,8 @@ final class Team(
|
|||
auth = ctx =>
|
||||
me =>
|
||||
api.join(id, me) flatMap {
|
||||
case Some(Joined(team)) => Redirect(routes.Team.show(team.id)).fuccess
|
||||
case Some(Motivate(team)) => Redirect(routes.Team.requestForm(team.id)).fuccess
|
||||
case Some(Joined(team)) => Redirect(routes.Team.show(team.id)).flashSuccess.fuccess
|
||||
case Some(Motivate(team)) => Redirect(routes.Team.requestForm(team.id)).flashSuccess.fuccess
|
||||
case _ => notFound(ctx)
|
||||
},
|
||||
scoped = req =>
|
||||
|
@ -211,7 +202,7 @@ final class Team(
|
|||
forms.anyCaptcha map { captcha =>
|
||||
BadRequest(html.team.request.requestForm(team, err, captcha))
|
||||
},
|
||||
setup => api.createRequest(team, setup, me) inject Redirect(routes.Team.show(team.id))
|
||||
setup => api.createRequest(team, setup, me) inject Redirect(routes.Team.show(team.id)).flashSuccess
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -221,7 +212,7 @@ final class Team(
|
|||
requestOption <- api request requestId
|
||||
teamOption <- requestOption.??(req => env.team.teamRepo.owned(req.team, me.id))
|
||||
} yield (teamOption |@| requestOption).tupled) {
|
||||
case (team, request) => {
|
||||
case (team, request) =>
|
||||
implicit val req = ctx.body
|
||||
forms.processRequest.bindFromRequest.fold(
|
||||
_ => fuccess(routes.Team.show(team.id).toString), {
|
||||
|
@ -229,7 +220,6 @@ final class Team(
|
|||
api.processRequest(team, request, (decision === "accept")) inject url
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -237,7 +227,7 @@ final class Team(
|
|||
auth = ctx =>
|
||||
me =>
|
||||
OptionResult(api.quit(id, me)) { team =>
|
||||
Redirect(routes.Team.show(team.id))
|
||||
Redirect(routes.Team.show(team.id)).flashSuccess
|
||||
}(ctx),
|
||||
scoped = _ =>
|
||||
me =>
|
||||
|
@ -266,15 +256,48 @@ final class Team(
|
|||
}
|
||||
}
|
||||
|
||||
def pmAll(id: String) = Auth { implicit ctx => _ =>
|
||||
WithOwnedTeam(id) { team =>
|
||||
Ok(html.team.admin.pmAll(team, forms.pmAll)).fuccess
|
||||
}
|
||||
}
|
||||
|
||||
def pmAllSubmit(id: String) = AuthBody { implicit ctx => me =>
|
||||
WithOwnedTeam(id) { team =>
|
||||
implicit val req = ctx.body
|
||||
forms.pmAll.bindFromRequest.fold(
|
||||
err => BadRequest(html.team.admin.pmAll(team, err)).fuccess,
|
||||
msg =>
|
||||
PmAllLimitPerUser(me.id) {
|
||||
val full = s"""$msg
|
||||
---
|
||||
You received this message because you are part of the team lichess.org${routes.Team.show(team.id)}."""
|
||||
env.team.memberRepo.userIdsByTeam(team.id) flatMap {
|
||||
env.msg.api.multiPost(me, _, full)
|
||||
} inject Redirect(routes.Team.show(team.id)).flashSuccess
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val PmAllLimitPerUser = new lila.memo.RateLimit[lila.user.User.ID](
|
||||
credits = 6,
|
||||
duration = 24 hours,
|
||||
name = "team pm all per user",
|
||||
key = "team.pmAll"
|
||||
)
|
||||
|
||||
private def OnePerWeek[A <: Result](me: UserModel)(a: => Fu[A])(implicit ctx: Context): Fu[Result] =
|
||||
api.hasCreatedRecently(me) flatMap { did =>
|
||||
if (did && !Granter(_.ManageTeam)(me)) Forbidden(views.html.site.message.teamCreateLimit).fuccess
|
||||
else a
|
||||
}
|
||||
|
||||
private def Owner(team: TeamModel)(a: => Fu[Result])(implicit ctx: Context): Fu[Result] =
|
||||
if (ctx.me.??(me => team.isCreator(me.id) || isGranted(_.ManageTeam))) a
|
||||
else renderTeam(team) map { Forbidden(_) }
|
||||
private def WithOwnedTeam(teamId: String)(f: TeamModel => Fu[Result])(implicit ctx: Context): Fu[Result] =
|
||||
OptionFuResult(api team teamId) { team =>
|
||||
if (ctx.userId.exists(team.isCreator) || isGranted(_.ManageTeam)) f(team)
|
||||
else renderTeam(team) map { Forbidden(_) }
|
||||
}
|
||||
|
||||
private[controllers] def teamsIBelongTo(me: lila.user.User): Fu[List[LightTeam]] =
|
||||
api mine me map { _.map(_.light) }
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package views.html.team
|
||||
|
||||
import play.api.data.Form
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
|
@ -57,4 +59,35 @@ object admin {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
def pmAll(t: lila.team.Team, form: Form[_])(implicit ctx: Context) = {
|
||||
|
||||
val title = s"${t.name} - message all members"
|
||||
|
||||
views.html.base.layout(
|
||||
title = title,
|
||||
moreCss = cssTag("team")
|
||||
) {
|
||||
main(cls := "page-menu page-small")(
|
||||
bits.menu(none),
|
||||
div(cls := "page-menu__content box box-pad")(
|
||||
h1(title),
|
||||
p(
|
||||
"Send a private message to ALL members of the team.",
|
||||
br,
|
||||
"You can use this to call players to join a tournament or a team battle.",
|
||||
br,
|
||||
"Players who don't like receiving your messages might leave the team."
|
||||
),
|
||||
postForm(cls := "form3", action := routes.Team.pmAllSubmit(t.id))(
|
||||
form3.group(form("message"), trans.message())(form3.textarea(_)(rows := 10)),
|
||||
form3.actions(
|
||||
a(href := routes.Team.show(t.slug))(trans.cancel()),
|
||||
form3.submit(trans.send())
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ object list {
|
|||
bits.menu("mine".some),
|
||||
div(cls := "page-menu__content box")(
|
||||
h1(myTeams()),
|
||||
standardFlash(),
|
||||
table(cls := "slist slist-pad")(
|
||||
if (teams.size > 0) tbody(teams.map(bits.teamTr(_)))
|
||||
else noTeam()
|
||||
|
@ -65,6 +66,7 @@ object list {
|
|||
)
|
||||
)
|
||||
),
|
||||
standardFlash(),
|
||||
table(cls := "slist slist-pad")(
|
||||
if (teams.nbResults > 0)
|
||||
tbody(cls := "infinitescroll")(
|
||||
|
|
|
@ -20,7 +20,7 @@ object request {
|
|||
views.html.base.layout(
|
||||
title = title,
|
||||
moreCss = cssTag("team"),
|
||||
moreJs = frag(infiniteScrollTag, captchaTag)
|
||||
moreJs = captchaTag
|
||||
) {
|
||||
main(cls := "page-menu page-small")(
|
||||
bits.menu("requests".some),
|
||||
|
|
|
@ -52,6 +52,7 @@ object show {
|
|||
)
|
||||
),
|
||||
st.section(cls := "team-show__desc")(
|
||||
standardFlash(),
|
||||
richText(t.description),
|
||||
t.location.map { loc =>
|
||||
frag(br, trans.location(), ": ", richText(loc))
|
||||
|
@ -94,6 +95,16 @@ object show {
|
|||
strong("Team tournament"),
|
||||
em("An Arena tournament that only members of your team can join")
|
||||
)
|
||||
),
|
||||
a(
|
||||
href := routes.Team.pmAll(t.id),
|
||||
cls := "button button-empty text",
|
||||
dataIcon := "e"
|
||||
)(
|
||||
span(
|
||||
strong("Message all members"),
|
||||
em("Send a private message to every member of the team")
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
|
|
|
@ -281,6 +281,8 @@ GET /team/:id/changeOwner controllers.Team.changeOwnerForm(i
|
|||
POST /team/:id/changeOwner controllers.Team.changeOwner(id: String)
|
||||
POST /team/:id/close controllers.Team.close(id: String)
|
||||
GET /team/:id/users controllers.Team.legacyUsers(id: String)
|
||||
GET /team/:id/pm-all controllers.Team.pmAll(id: String)
|
||||
POST /team/:id/pm-all controllers.Team.pmAllSubmit(id: String)
|
||||
GET /api/team/:id/users controllers.Team.users(id: String)
|
||||
|
||||
# Analyse
|
||||
|
|
|
@ -126,12 +126,11 @@ final class MsgApi(
|
|||
def systemPost(destId: User.ID, text: String) =
|
||||
post(User.lichessId, destId, text, unlimited = true)
|
||||
|
||||
def multiPost(orig: User, dests: List[User], text: String): Funit =
|
||||
dests
|
||||
.map { dest =>
|
||||
post(orig.id, dest.id, text, unlimited = true)
|
||||
def multiPost(orig: User, dests: Iterable[User.ID], text: String): Funit =
|
||||
lila.common.Future
|
||||
.linear(dests) {
|
||||
post(orig.id, _, text, unlimited = true).logFailure(logger).nevermind
|
||||
}
|
||||
.sequenceFu
|
||||
.void
|
||||
|
||||
def recentByForMod(user: User, nb: Int): Fu[List[MsgConvo]] =
|
||||
|
|
|
@ -75,6 +75,10 @@ final private[team] class DataForm(
|
|||
|
||||
def createWithCaptcha = withCaptcha(create)
|
||||
|
||||
val pmAll = Form(
|
||||
single("message" -> text(minLength = 3, maxLength = 9000))
|
||||
)
|
||||
|
||||
private def teamExists(setup: TeamSetup) =
|
||||
teamRepo.coll.exists($id(Team nameToId setup.trim.name))
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue