lila/modules/team/src/main/TeamApi.scala

258 lines
8.3 KiB
Scala
Raw Normal View History

2013-04-02 09:45:45 -06:00
package lila.team
import org.joda.time.Period
import play.api.libs.json.{ JsSuccess, Json }
import reactivemongo.api.{ Cursor, ReadPreference }
2013-04-04 08:28:52 -06:00
import actorApi._
import lila.common.Bus
2016-04-01 11:50:57 -06:00
import lila.db.dsl._
2017-08-06 01:40:37 -06:00
import lila.hub.actorApi.team.{ CreateTeam, JoinTeam }
2019-12-13 07:30:20 -07:00
import lila.hub.actorApi.timeline.{ Propagate, TeamCreate, TeamJoin }
2019-12-08 09:58:50 -07:00
import lila.hub.LightTeam
2019-12-23 13:19:37 -07:00
import lila.memo.CacheApi._
import lila.mod.ModlogApi
2019-12-08 10:35:26 -07:00
import lila.user.{ User, UserRepo }
2013-04-02 09:45:45 -06:00
final class TeamApi(
2019-12-02 21:31:35 -07:00
teamRepo: TeamRepo,
memberRepo: MemberRepo,
requestRepo: RequestRepo,
userRepo: UserRepo,
cached: Cached,
notifier: Notifier,
2019-12-02 21:31:35 -07:00
timeline: lila.hub.actors.Timeline,
indexer: lila.hub.actors.TeamSearch,
modLog: ModlogApi
)(implicit ec: scala.concurrent.ExecutionContext) {
2013-04-02 09:45:45 -06:00
2016-04-01 22:57:54 -06:00
import BSONHandlers._
2014-11-15 06:53:01 -07:00
val creationPeriod = Period weeks 1
2013-04-02 09:45:45 -06:00
2019-12-02 21:31:35 -07:00
def team(id: Team.ID) = teamRepo.coll.byId[Team](id)
2013-05-06 14:49:12 -06:00
2019-12-02 21:31:35 -07:00
def light(id: Team.ID) = teamRepo.coll.byId[LightTeam](id, $doc("name" -> true))
2019-10-04 14:46:43 -06:00
2019-12-02 21:31:35 -07:00
def request(id: Team.ID) = requestRepo.coll.byId[Request](id)
2013-05-06 14:49:12 -06:00
def create(setup: TeamSetup, me: User): Fu[Team] = {
2013-04-04 08:28:52 -06:00
val s = setup.trim
val team = Team.make(
name = s.name,
location = s.location,
description = s.description,
open = s.isOpen,
createdBy = me
)
2019-12-03 09:52:11 -07:00
teamRepo.coll.insert.one(team) >>
2019-12-02 21:31:35 -07:00
memberRepo.add(team.id, me.id) >>- {
2019-12-13 07:30:20 -07:00
cached invalidateTeamIds me.id
indexer ! InsertTeam(team)
timeline ! Propagate(
TeamCreate(me.id, team.id)
).toFollowersOf(me.id)
Bus.publish(CreateTeam(id = team.id, name = team.name, userId = me.id), "team")
} inject team
2013-04-04 08:28:52 -06:00
}
2014-02-17 02:12:19 -07:00
def update(team: Team, edit: TeamEdit, me: User): Funit = edit.trim |> { e =>
2013-04-04 08:28:52 -06:00
team.copy(
location = e.location,
description = e.description,
open = e.isOpen
) |> { team =>
2019-12-03 09:52:11 -07:00
teamRepo.coll.update.one($id(team.id), team).void >>
!team.leaders(me.id) ?? {
modLog.teamEdit(me.id, team.createdBy, team.name)
} >>-
(indexer ! InsertTeam(team))
}
2013-04-04 08:28:52 -06:00
}
def mine(me: User): Fu[List[Team]] =
2019-12-13 07:30:20 -07:00
cached teamIds me.id flatMap { ids =>
teamRepo.coll.byIds[Team](ids.toArray)
}
2013-04-04 08:28:52 -06:00
2017-02-05 04:11:03 -07:00
def hasTeams(me: User): Fu[Boolean] = cached.teamIds(me.id).map(_.value.nonEmpty)
2015-09-09 09:32:49 -06:00
2013-04-04 08:28:52 -06:00
def hasCreatedRecently(me: User): Fu[Boolean] =
2019-12-02 21:31:35 -07:00
teamRepo.userHasCreatedSince(me.id, creationPeriod)
2013-04-04 08:28:52 -06:00
2019-12-13 07:30:20 -07:00
def requestsWithUsers(team: Team): Fu[List[RequestWithUser]] =
for {
requests <- requestRepo findByTeam team.id
users <- userRepo usersFromSecondary requests.map(_.user)
} yield requests zip users map {
case (request, user) => RequestWithUser(request, user)
}
2013-04-04 08:28:52 -06:00
2019-12-13 07:30:20 -07:00
def requestsWithUsers(user: User): Fu[List[RequestWithUser]] =
for {
teamIds <- teamRepo teamIdsByLeader user.id
2019-12-13 07:30:20 -07:00
requests <- requestRepo findByTeams teamIds
users <- userRepo usersFromSecondary requests.map(_.user)
} yield requests zip users map {
case (request, user) => RequestWithUser(request, user)
}
2013-04-04 08:28:52 -06:00
2019-07-16 12:03:10 -06:00
def join(teamId: Team.ID, me: User): Fu[Option[Requesting]] =
2019-12-02 21:31:35 -07:00
teamRepo.coll.byId[Team](teamId) flatMap {
2019-07-16 12:03:10 -06:00
_ ?? { team =>
if (team.open) doJoin(team, me) inject Joined(team).some
else fuccess(Motivate(team).some)
}
}
def joinApi(teamId: Team.ID, me: User, oAuthAppOwner: User.ID): Fu[Option[Requesting]] =
2019-12-02 21:31:35 -07:00
teamRepo.coll.byId[Team](teamId) flatMap {
2019-07-16 12:03:10 -06:00
_ ?? { team =>
if (team.open || team.createdBy == oAuthAppOwner) doJoin(team, me) inject Joined(team).some
else fuccess(Motivate(team).some)
}
}
2013-04-04 08:28:52 -06:00
2019-12-13 07:30:20 -07:00
def requestable(teamId: Team.ID, user: User): Fu[Option[Team]] =
for {
teamOption <- teamRepo.coll.byId[Team](teamId)
able <- teamOption.??(requestable(_, user))
} yield teamOption filter (_ => able)
2013-04-04 08:28:52 -06:00
2019-12-13 07:30:20 -07:00
def requestable(team: Team, user: User): Fu[Boolean] =
for {
belongs <- belongsTo(team.id, user.id)
requested <- requestRepo.exists(team.id, user.id)
} yield !belongs && !requested
2013-04-04 08:28:52 -06:00
def createRequest(team: Team, setup: RequestSetup, user: User): Funit =
requestable(team, user) flatMap {
_ ?? {
val request = Request.make(team = team.id, user = user.id, message = setup.message)
2019-12-03 09:52:11 -07:00
requestRepo.coll.insert.one(request).void >>- (cached.nbRequests invalidate team.createdBy)
}
2013-04-04 08:28:52 -06:00
}
2019-12-13 07:30:20 -07:00
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 ()
2013-04-04 08:28:52 -06:00
def deleteRequestsByUserId(userId: User.ID) =
2019-12-02 21:31:35 -07:00
requestRepo.getByUserId(userId) flatMap {
_.map { request =>
2019-12-02 21:31:35 -07:00
requestRepo.remove(request.id) >>
teamRepo.leadersOf(request.team).map {
_ foreach cached.nbRequests.invalidate
}
}.sequenceFu
}
2019-07-16 12:03:10 -06:00
def doJoin(team: Team, user: User): Funit = !belongsTo(team.id, user.id) flatMap {
_ ?? {
2019-12-02 21:31:35 -07:00
memberRepo.add(team.id, user.id) >>
teamRepo.incMembers(team.id, +1) >>- {
2019-12-13 07:30:20 -07:00
cached invalidateTeamIds user.id
timeline ! Propagate(TeamJoin(user.id, team.id)).toFollowersOf(user.id)
Bus.publish(JoinTeam(id = team.id, userId = user.id), "team")
}
} recover lila.db.recoverDuplicateKey(_ => ())
}
2013-04-04 08:28:52 -06:00
2019-07-16 14:45:14 -06:00
def quit(teamId: Team.ID, me: User): Fu[Option[Team]] =
2019-12-02 21:31:35 -07:00
teamRepo.coll.byId[Team](teamId) flatMap {
2019-07-16 14:45:14 -06:00
_ ?? { team =>
doQuit(team, me.id) inject team.some
}
}
2013-04-04 08:28:52 -06:00
private def doQuit(team: Team, userId: User.ID): Funit = belongsTo(team.id, userId) flatMap {
_ ?? {
memberRepo.remove(team.id, userId) map { res =>
if (res.n == 1) teamRepo.incMembers(team.id, -1)
2020-01-15 18:26:38 -07:00
cached.invalidateTeamIds(userId)
}
}
2014-04-18 03:51:19 -06:00
}
2013-04-04 08:28:52 -06:00
2019-12-02 21:31:35 -07:00
def quitAll(userId: User.ID): Funit = memberRepo.removeByUser(userId)
2013-04-04 08:28:52 -06:00
2019-07-08 11:59:39 -06:00
def kick(team: Team, userId: User.ID, me: User): Funit =
doQuit(team, userId) >>
!team.leaders(me.id) ?? {
modLog.teamKick(me.id, userId, team.name)
}
2013-04-04 08:28:52 -06:00
private case class TagifyUser(value: String)
implicit private val TagifyUserReads = Json.reads[TagifyUser]
def setLeaders(team: Team, json: String): Funit = {
val leaders: Set[User.ID] = json.trim.nonEmpty ?? {
Json.parse(json).validate[List[TagifyUser]] match {
2020-04-24 08:48:56 -06:00
case JsSuccess(users, _) =>
users
.map(_.value.toLowerCase.trim)
.filter(User.lichessId !=)
.toSet take 30
case _ => Set.empty
}
}
2020-04-24 08:48:56 -06:00
leaders.nonEmpty ?? teamRepo.setLeaders(team.id, leaders).void
}
2013-04-04 08:28:52 -06:00
def enable(team: Team): Funit =
2019-12-02 21:31:35 -07:00
teamRepo.enable(team).void >>- (indexer ! InsertTeam(team))
2013-04-04 08:28:52 -06:00
def disable(team: Team): Funit =
2019-12-02 21:31:35 -07:00
teamRepo.disable(team).void >>- (indexer ! RemoveTeam(team.id))
2013-04-04 08:28:52 -06:00
// delete for ever, with members but not forums
2013-04-04 08:28:52 -06:00
def delete(team: Team): Funit =
2019-12-03 09:52:11 -07:00
teamRepo.coll.delete.one($id(team.id)) >>
2019-12-02 21:31:35 -07:00
memberRepo.removeByteam(team.id) >>-
2013-04-04 08:28:52 -06:00
(indexer ! RemoveTeam(team.id))
2019-07-08 11:59:39 -06:00
def syncBelongsTo(teamId: Team.ID, userId: User.ID): Boolean =
cached.syncTeamIds(userId) contains teamId
2019-07-08 11:59:39 -06:00
def belongsTo(teamId: Team.ID, userId: User.ID): Fu[Boolean] =
2020-01-15 18:26:38 -07:00
cached.teamIds(userId) dmap (_ contains teamId)
2013-04-09 12:58:34 -06:00
def leads(teamId: Team.ID, userId: User.ID): Fu[Boolean] =
teamRepo.leads(teamId, userId)
2019-10-02 04:20:44 -06:00
def filterExistingIds(ids: Set[String]): Fu[Set[Team.ID]] =
2019-12-02 21:31:35 -07:00
teamRepo.coll.distinctEasy[Team.ID, Set]("_id", $doc("_id" $in ids))
2019-10-02 04:20:44 -06:00
2019-10-02 10:50:09 -06:00
def autocomplete(term: String, max: Int): Fu[List[Team]] =
2019-12-13 07:30:20 -07:00
teamRepo.coll.ext
.find(
$doc(
"name".$startsWith(java.util.regex.Pattern.quote(term), "i"),
"enabled" -> true
)
)
.sort($sort desc "nbMembers")
.list[Team](max, ReadPreference.secondaryPreferred)
2019-10-02 10:50:09 -06:00
2019-07-08 11:59:39 -06:00
def nbRequests(teamId: Team.ID) = cached.nbRequests get teamId
2015-04-12 00:18:47 -06:00
private[team] def recomputeNbMembers: Funit =
teamRepo.coll.ext
.find($empty, $id(true))
.cursor[Bdoc](ReadPreference.secondaryPreferred)
.foldWhileM({}) { (_, doc) =>
(doc.string("_id") ?? recomputeNbMembers) inject Cursor.Cont({})
}
private[team] def recomputeNbMembers(teamId: Team.ID): Funit =
memberRepo.countByTeam(teamId) flatMap { nb =>
teamRepo.coll.updateField($id(teamId), "nbMembers", nb).void
}
}