Merge branch 'team_declined_requests' of git://github.com/rglbr/lila into rglbr-team_declined_requests

* 'team_declined_requests' of git://github.com/rglbr/lila:
  add explicit conditions
  use team_requests collection for the declined requests
  consistent naming
  Team Declined requests page
  Track declined team requests
hotfix-tablebase
Thibault Duplessis 2021-09-11 19:38:41 +02:00
commit b702160f81
16 changed files with 183 additions and 44 deletions

View File

@ -392,12 +392,21 @@ final class Team(
.fold(
_ => fuccess(routes.Team.show(team.id).toString),
{ case (decision, url) =>
api.processRequest(team, request, decision == "accept") inject url
api.processRequest(team, request, decision) inject url
}
)
}
}
def declinedRequests(id: String, page: Int) =
Auth { implicit ctx => _ =>
WithOwnedTeamEnabled(id) { team =>
paginator.declinedRequests(team, page) map { requests =>
Ok(html.team.declinedRequest.all(team, requests))
}
}
}
def quit(id: String) =
AuthOrScoped(_.Team.Write)(
auth = implicit ctx =>

View File

@ -2,7 +2,7 @@ package lila.app
package mashup
import lila.forum.MiniForumPost
import lila.team.{ RequestRepo, RequestWithUser, Team, TeamApi }
import lila.team.{ Request, RequestRepo, RequestWithUser, Team, TeamApi }
import lila.tournament.{ Tournament, TournamentApi }
import lila.user.User
import lila.swiss.{ Swiss, SwissApi }
@ -11,7 +11,7 @@ import lila.simul.{ Simul, SimulApi }
case class TeamInfo(
mine: Boolean,
ledByMe: Boolean,
requestedByMe: Boolean,
myRequest: Option[Request],
subscribed: Boolean,
requests: List[RequestWithUser],
forum: Option[List[MiniForumPost]],
@ -52,17 +52,17 @@ final class TeamInfoApi(
def apply(team: Team, me: Option[User], withForum: Boolean => Boolean): Fu[TeamInfo] =
for {
requests <- (team.enabled && me.exists(m => team.leaders(m.id))) ?? api.requestsWithUsers(team)
mine <- me.??(m => api.belongsTo(team.id, m.id))
requestedByMe <- !mine ?? me.??(m => requestRepo.exists(team.id, m.id))
subscribed <- me.ifTrue(mine) ?? { api.isSubscribed(team, _) }
forumPosts <- withForum(mine) ?? forumRecent.team(team.id).dmap(some)
tours <- tournaments(team, 5, 5)
simuls <- simulApi.byTeamLeaders(team.id, team.leaders.toSeq)
requests <- (team.enabled && me.exists(m => team.leaders(m.id))) ?? api.requestsWithUsers(team)
mine <- me.??(m => api.belongsTo(team.id, m.id))
myRequest <- !mine ?? me.??(m => requestRepo.find(team.id, m.id))
subscribed <- me.ifTrue(mine) ?? { api.isSubscribed(team, _) }
forumPosts <- withForum(mine) ?? forumRecent.team(team.id).dmap(some)
tours <- tournaments(team, 5, 5)
simuls <- simulApi.byTeamLeaders(team.id, team.leaders.toSeq)
} yield TeamInfo(
mine = mine,
ledByMe = me.exists(m => team.leaders(m.id)),
requestedByMe = requestedByMe,
myRequest = myRequest,
subscribed = subscribed,
requests = requests,
forum = forumPosts,

View File

@ -0,0 +1,65 @@
package views.html.team
import controllers.routes
import lila.api.Context
import lila.app.templating.Environment._
import lila.app.ui.ScalatagsTemplate._
import lila.common.String.html.richText
import lila.common.paginator.Paginator
object declinedRequest {
def all(team: lila.team.Team, requests: Paginator[lila.team.RequestWithUser])(implicit ctx: Context) = {
val title = s"${team.name}${trans.team.declinedRequests.txt()}"
views.html.base.layout(
title = title,
moreCss = frag(cssTag("team"))
) {
val pager = views.html.base.bits
.paginationByQuery(routes.Team.declinedRequests(team.id, 1), requests, showPost = true)
main(cls := "page-menu page-small")(
bits.menu(none),
div(cls := "page-menu__content box box-pad")(
h1(
a(href := routes.Team.show(team.id))(
team.name
),
" • ",
trans.team.declinedRequests()
),
pager,
table(cls := "slist")(
tbody(
requests.currentPageResults.map { request =>
tr(
td(userLink(request.user)),
td(richText(request.message)),
td(momentFromNow(request.date)),
td(cls := "process")(
postForm(
cls := "process-request",
action := routes.Team.requestProcess(request.id)
)(
input(
tpe := "hidden",
name := "url",
value := routes.Team.declinedRequests(team.id, requests.currentPage)
),
button(name := "process", cls := "button button-green", value := "accept-declined")(
trans.accept()
)
)
)
)
}
)
),
pager
)
)
}
}
}

View File

@ -48,7 +48,8 @@ object form {
t.enabled option postForm(cls := "form3", action := routes.Team.update(t.id))(
div(cls := "form-group")(
a(cls := "button button-empty", href := routes.Team.leaders(t.id))(teamLeaders()),
a(cls := "button button-empty", href := routes.Team.kick(t.id))(kickSomeone())
a(cls := "button button-empty", href := routes.Team.kick(t.id))(kickSomeone()),
a(cls := "button button-empty", href := routes.Team.declinedRequests(t.id))(declinedRequests())
),
entryFields(form, t.some),
textFields(form),

View File

@ -90,7 +90,12 @@ object show {
),
div(cls := "team-show__actions")(
(t.enabled && !info.mine) option frag(
if (info.requestedByMe)
if (info.myRequest.map(_.declined) | false)
frag(
strong(requestDeclined()),
a(cls := "button disabled button-metal")(joinTeam())
)
else if (info.myRequest.isDefined)
frag(
strong(beingReviewed()),
postForm(action := routes.Team.quit(t.id))(

View File

@ -365,6 +365,7 @@ POST /team/:id/quit controllers.Team.quit(id: String)
GET /team/:id/request/new controllers.Team.requestForm(id: String)
POST /team/:id/request/new controllers.Team.requestCreate(id: String)
POST /team/:id/request/process controllers.Team.requestProcess(id: String)
GET /team/:id/declined-requests controllers.Team.declinedRequests(id: String, page: Int ?=1)
GET /team/:id/edit controllers.Team.edit(id: String)
POST /team/:id/edit controllers.Team.update(id: String)
GET /team/:id/kick controllers.Team.kickForm(id: String)

View File

@ -1656,6 +1656,7 @@ val `kickSomeone` = new I18nKey("team:kickSomeone")
val `whoToKick` = new I18nKey("team:whoToKick")
val `willBeReviewed` = new I18nKey("team:willBeReviewed")
val `beingReviewed` = new I18nKey("team:beingReviewed")
val `requestDeclined` = new I18nKey("team:requestDeclined")
val `subToTeamMessages` = new I18nKey("team:subToTeamMessages")
val `teamBattle` = new I18nKey("team:teamBattle")
val `teamBattleOverview` = new I18nKey("team:teamBattleOverview")
@ -1677,6 +1678,7 @@ val `incorrectEntryCode` = new I18nKey("team:incorrectEntryCode")
val `teamAlreadyExists` = new I18nKey("team:teamAlreadyExists")
val `upcomingTourns` = new I18nKey("team:upcomingTourns")
val `completedTourns` = new I18nKey("team:completedTourns")
val `declinedRequests` = new I18nKey("team:declinedRequests")
val `nbMembers` = new I18nKey("team:nbMembers")
val `teamLeaders` = new I18nKey("team:teamLeaders")
val `xJoinRequests` = new I18nKey("team:xJoinRequests")

View File

@ -9,10 +9,13 @@ import lila.db.paginator._
final private[team] class PaginatorBuilder(
teamRepo: TeamRepo,
memberRepo: MemberRepo,
requestRepo: RequestRepo,
userRepo: lila.user.UserRepo,
lightUserApi: lila.user.LightUserApi
)(implicit ec: scala.concurrent.ExecutionContext) {
private val maxPerPage = MaxPerPage(15)
private val maxUserPerPage = MaxPerPage(30)
private val maxPerPage = MaxPerPage(15)
private val maxUserPerPage = MaxPerPage(30)
private val maxRequestsPerPage = MaxPerPage(10)
import BSONHandlers._
@ -54,4 +57,29 @@ final private[team] class PaginatorBuilder(
private def selector = memberRepo teamQuery team.id
private def sorting = $sort desc "date"
}
def declinedRequests(team: Team, page: Int): Fu[Paginator[RequestWithUser]] =
Paginator(
adapter = new DeclinedRequestAdapter(team),
page,
maxRequestsPerPage
)
final private class DeclinedRequestAdapter(team: Team) extends AdapterLike[RequestWithUser] {
val nbResults = requestRepo countDeclinedByTeam team.id
private def selector = requestRepo teamDeclinedQuery team.id
private def sorting = $sort desc "date"
def slice(offset: Int, length: Int): Fu[Seq[RequestWithUser]] = {
for {
requests <- requestRepo.coll
.find(selector)
.sort(sorting)
.skip(offset)
.cursor[Request]()
.list(length)
users <- userRepo usersFromSecondary requests.map(_.user)
} yield requests zip users map { case (request, user) => RequestWithUser(request, user) }
}
}
}

View File

@ -9,7 +9,8 @@ case class Request(
team: String,
user: String,
message: String,
date: DateTime
date: DateTime,
declined: Boolean
) {
def id = _id
@ -17,6 +18,7 @@ case class Request(
object Request {
type ID = String
def makeId(team: Team.ID, user: User.ID) = s"$user@$team"
def make(team: Team.ID, user: User.ID, message: String): Request =
@ -25,7 +27,8 @@ object Request {
user = user,
team = team,
message = message.trim,
date = DateTime.now
date = DateTime.now,
declined = false
)
}

View File

@ -2,7 +2,6 @@ package lila.team
import lila.db.dsl._
import lila.user.User
final class RequestRepo(val coll: Coll)(implicit ec: scala.concurrent.ExecutionContext) {
import BSONHandlers._
@ -15,18 +14,21 @@ final class RequestRepo(val coll: Coll)(implicit ec: scala.concurrent.ExecutionC
def find(teamId: ID, userId: ID): Fu[Option[Request]] =
coll.one[Request](selectId(teamId, userId))
def countByTeam(teamId: ID): Fu[Int] =
coll.countSel(teamQuery(teamId))
def countDeclinedByTeam(teamId: ID): Fu[Int] =
coll.countSel(teamDeclinedQuery(teamId))
def findByTeam(teamId: ID): Fu[List[Request]] =
coll.list[Request](teamQuery(teamId))
def findActiveByTeam(teamId: ID): Fu[List[Request]] =
coll.list[Request](teamActiveQuery(teamId))
def findByTeams(teamIds: List[ID]): Fu[List[Request]] =
teamIds.nonEmpty ?? coll.list[Request](teamsQuery(teamIds))
def findActiveByTeams(teamIds: List[ID]): Fu[List[Request]] =
teamIds.nonEmpty ?? coll.list[Request](teamsActiveQuery(teamIds))
def selectId(teamId: ID, userId: ID) = $id(Request.makeId(teamId, userId))
def teamQuery(teamId: ID) = $doc("team" -> teamId)
def teamsQuery(teamIds: List[ID]) = $doc("team" $in teamIds)
def selectId(teamId: ID, userId: ID) = $id(Request.makeId(teamId, userId))
def teamQuery(teamId: ID) = $doc("team" -> teamId)
def teamsQuery(teamIds: List[ID]) = $doc("team" $in teamIds)
def teamDeclinedQuery(teamId: ID) = $and(teamQuery(teamId), $doc("declined" -> true))
def teamActiveQuery(teamId: ID) = $and(teamQuery(teamId), $doc("declined" -> $ne(true)))
def teamsActiveQuery(teamIds: List[ID]) = $and(teamsQuery(teamIds), $doc("declined" -> $ne(true)))
def getByUserId(userId: User.ID) =
coll.list[Request]($doc("user" -> userId))

View File

@ -1,6 +1,7 @@
package lila.team
import actorApi._
import org.joda.time.DateTime
import org.joda.time.Period
import play.api.libs.json.{ JsSuccess, Json }
import reactivemongo.api.{ Cursor, ReadPreference }
@ -40,7 +41,7 @@ final class TeamApi(
def lightsByLeader = teamRepo.lightsByLeader _
def request(id: Team.ID) = requestRepo.coll.byId[Request](id)
def request(id: Request.ID) = requestRepo.coll.byId[Request](id)
def create(setup: TeamSetup, me: User): Fu[Team] = {
val bestId = Team.nameToId(setup.name)
@ -106,7 +107,7 @@ final class TeamApi(
def requestsWithUsers(team: Team): Fu[List[RequestWithUser]] =
for {
requests <- requestRepo findByTeam team.id
requests <- requestRepo findActiveByTeam team.id
users <- userRepo usersFromSecondary requests.map(_.user)
} yield requests zip users map { case (request, user) =>
RequestWithUser(request, user)
@ -115,7 +116,7 @@ final class TeamApi(
def requestsWithUsers(user: User): Fu[List[RequestWithUser]] =
for {
teamIds <- teamRepo enabledTeamIdsByLeader user.id
requests <- requestRepo findByTeams teamIds
requests <- requestRepo findActiveByTeams teamIds
users <- userRepo usersFromSecondary requests.map(_.user)
} yield requests zip users map { case (request, user) =>
RequestWithUser(request, user)
@ -159,16 +160,19 @@ final class TeamApi(
}
}
def processRequest(team: Team, request: Request, accept: Boolean): Funit =
for {
_ <- requestRepo.coll.delete.one(request)
_ = cached.nbRequests invalidate team.createdBy
userOption <- userRepo byId request.user
_ <-
userOption
.filter(_ => accept)
.??(user => doJoin(team, user) >> notifier.acceptRequest(team, request))
} yield ()
def processRequest(team: Team, request: Request, decision: String): Funit = {
cached.nbRequests invalidate team.createdBy
if (decision == "decline")
requestRepo.coll.update.one($id(request.id), request.copy(declined = true)).void
else if (decision == "accept" | decision == "accept-declined")
for {
_ <- requestRepo.remove(request.id)
userOption <- userRepo byId request.user
_ <-
userOption.??(user => doJoin(team, user) >> notifier.acceptRequest(team, request))
} yield ()
else funit
}
def deleteRequestsByUserId(userId: User.ID) =
requestRepo.getByUserId(userId) flatMap {

View File

@ -108,13 +108,25 @@ final class TeamRepo(val coll: Coll)(implicit ec: scala.concurrent.ExecutionCont
.aggregateOne(readPreference = ReadPreference.secondaryPreferred) { implicit framework =>
import framework._
Match($doc("leaders" -> userId)) -> List(
Group(BSONNull)("ids" -> PushField("_id")),
PipelineOperator(
$doc(
"$lookup" -> $doc(
"from" -> requestColl.name,
"localField" -> "_id",
"foreignField" -> "team",
"as" -> "requests"
"from" -> requestColl.name,
"as" -> "requests",
"let" -> $doc("teams" -> "$ids"),
"pipeline" -> $arr(
$doc(
"$match" -> $doc(
"$expr" -> $doc(
"$and" -> $arr(
$doc("$in" -> $arr("$team", "$$teams")),
$doc("$ne" -> $arr("$declined", true))
)
)
)
)
)
)
)
),

View File

@ -29,6 +29,7 @@
</plurals>
<string name="willBeReviewed">Your join request will be reviewed by a team leader.</string>
<string name="beingReviewed">Your join request is being reviewed by a team leader.</string>
<string name="requestDeclined">Your join request was declined by a team leader.</string>
<string name="subToTeamMessages">Subscribe to team messages</string>
<string name="teamBattle">Team Battle</string>
<string name="teamBattleOverview">A battle of multiple teams, each player scores points for their team</string>
@ -52,4 +53,5 @@ Players who don't like receiving your messages might leave the team.</string>
<string name="teamAlreadyExists">This team already exists.</string>
<string name="upcomingTourns">Upcoming tournaments</string>
<string name="completedTourns">Completed tournaments</string>
<string name="declinedRequests">Declined Requests</string>
</resources>

View File

@ -1,4 +1,5 @@
@import '../../../common/css/plugin';
@import '../../../common/css/component/pagination';
@import '../../../common/css/component/slist';
@import '../../../common/css/form/form3';
@import '../../../common/css/form/captcha';

View File

@ -0,0 +1,3 @@
.pagination {
padding: 1em;
}

View File

@ -1,3 +1,4 @@
@import 'list';
@import 'show';
@import 'tournaments';
@import 'declined-request';