lila/app/controllers/User.scala

318 lines
12 KiB
Scala
Raw Normal View History

2013-03-20 08:23:41 -06:00
package controllers
2016-02-28 19:45:02 -07:00
import play.api.libs.json._
2017-01-15 05:26:08 -07:00
import play.api.mvc._
import scala.concurrent.duration._
2013-09-11 04:38:16 -06:00
2017-01-15 05:26:08 -07:00
import lila.api.BodyContext
2013-03-20 08:23:41 -06:00
import lila.app._
import lila.app.mashup.GameFilterMenu
2017-02-15 17:53:15 -07:00
import lila.common.{ IpAddress, HTTPRequest }
import lila.common.paginator.Paginator
2017-01-15 05:26:08 -07:00
import lila.game.{ GameRepo, Game => GameModel }
import lila.rating.PerfType
2014-02-17 02:12:19 -07:00
import lila.user.{ User => UserModel, UserRepo }
2013-06-28 05:52:52 -06:00
import views._
2013-03-20 08:23:41 -06:00
object User extends LilaController {
2013-05-08 09:41:12 -06:00
private def env = Env.user
2014-04-17 06:10:08 -06:00
private def relationApi = Env.relation.api
private def userGameSearch = Env.gameSearch.userGameSearch
2013-05-08 09:41:12 -06:00
def tv(username: String) = Open { implicit ctx =>
OptionFuResult(UserRepo named username) { user =>
(GameRepo lastPlayedPlaying user) orElse
2014-12-23 17:34:13 -07:00
(GameRepo lastPlayed user) flatMap {
2014-12-20 07:01:19 -07:00
_.fold(fuccess(Redirect(routes.User.show(username)))) { pov =>
Round.watch(pov, userTv = user.some)
}
}
}
}
def studyTv(username: String) = Open { implicit ctx =>
2017-02-14 06:23:54 -07:00
OptionResult(UserRepo named username) { user =>
Redirect {
lila.relation.Env.current currentlyStudying user.id match {
case None => routes.Study.byOwnerDefault(user.id)
2017-02-14 06:23:54 -07:00
case Some(studyId) => routes.Study.show(studyId)
}
}
}
}
def show(username: String) = OpenBody { implicit ctx =>
2016-03-18 09:14:56 -06:00
filter(username, none, 1)
}
2014-02-17 02:12:19 -07:00
def showMini(username: String) = Open { implicit ctx =>
OptionFuResult(UserRepo named username) { user =>
if (user.enabled) for {
blocked <- ctx.userId ?? { relationApi.fetchBlocks(user.id, _) }
crosstable <- ctx.userId ?? { Env.game.crosstableApi(user.id, _) }
followable <- ctx.isAuth ?? { Env.pref.api.followable(user.id) }
relation <- ctx.userId ?? { relationApi.fetchRelation(_, user.id) }
res <- negotiate(
html = GameRepo lastPlayedPlaying user map { pov =>
Ok(html.user.mini(user, pov, blocked, followable, relation, crosstable))
.withHeaders(CACHE_CONTROL -> "max-age=5")
},
api = _ => {
import lila.game.JsonView.crosstableWrites
fuccess(Ok(Json.obj(
"crosstable" -> crosstable,
"perfs" -> lila.user.JsonView.perfs(user, user.best8Perfs)
)))
}
)
} yield res
2016-11-07 15:16:58 -07:00
else fuccess(Ok(html.user.miniClosed(user)))
2013-12-24 06:58:54 -07:00
}
2013-05-20 22:12:10 -06:00
}
2013-05-08 09:41:12 -06:00
def showFilter(username: String, filterName: String, page: Int) = OpenBody { implicit ctx =>
filter(username, filterName.some, page)
2013-05-20 22:12:10 -06:00
}
2014-02-17 02:12:19 -07:00
def online = Open { implicit req =>
2016-02-26 18:20:27 -07:00
val max = 50
2015-06-04 15:37:31 -06:00
negotiate(
2016-02-26 18:20:27 -07:00
html = notFound,
api = _ => env.cached.top50Online.get map { list =>
Ok(Json.toJson(list.take(getInt("nb").fold(10)(_ min max)).map(env.jsonView(_))))
}
)
}
2014-04-22 17:22:57 -06:00
private def filter(
username: String,
filterOption: Option[String],
page: Int,
status: Results.Status = Results.Ok
)(implicit ctx: BodyContext[_]) =
2013-05-08 09:41:12 -06:00
Reasonable(page) {
2014-02-17 02:12:19 -07:00
OptionFuResult(UserRepo named username) { u =>
2015-05-19 09:59:12 -06:00
if (u.enabled || isGranted(_.UserSpy)) negotiate(
html = {
if (lila.common.HTTPRequest.isSynchronousHttp(ctx.req)) userShow(u, filterOption, page)
else userGames(u, filterOption, page) map {
case (filterName, pag) => html.user.games(u, pag, filterName)
}
2016-03-10 20:08:34 -07:00
}.map { status(_) }.mon(_.http.response.user.show.website),
api = _ => userGames(u, filterOption, page).flatMap {
2017-01-09 01:49:16 -07:00
case (filterName, pag) => Env.api.userGameApi.jsPaginator(pag) map { res =>
Ok(res ++ Json.obj("filter" -> filterName))
}
}.mon(_.http.response.user.show.mobile)
)
2015-05-19 09:59:12 -06:00
else negotiate(
html = fuccess(NotFound(html.user.disabled(u))),
api = _ => fuccess(NotFound(jsonError("No such user, or account closed")))
)
}
2013-05-04 17:12:53 -06:00
}
2013-03-20 07:24:47 -06:00
2015-09-16 09:32:02 -06:00
private def userShow(u: UserModel, filterOption: Option[String], page: Int)(implicit ctx: BodyContext[_]) = for {
info Env.current.userInfo(u, ctx)
filters = GameFilterMenu(info, ctx.me, filterOption)
2014-11-17 18:54:16 -07:00
pag <- GameFilterMenu.paginatorOf(
userGameSearch = userGameSearch,
2014-11-17 18:54:16 -07:00
user = u,
info = info.some,
filter = filters.current,
me = ctx.me,
page = page
)(ctx.body)
_ <- Env.user.lightUserApi preloadMany pag.currentPageResults.flatMap(_.userIds)
2017-01-26 09:56:30 -07:00
_ <- Env.tournament.cached.nameCache preloadMany pag.currentPageResults.flatMap(_.tournamentId)
2017-02-05 04:19:53 -07:00
_ <- Env.team.cached.nameCache preloadMany info.teamIds
relation <- ctx.userId ?? { relationApi.fetchRelation(_, u.id) }
2014-04-22 17:22:57 -06:00
notes <- ctx.me ?? { me =>
relationApi fetchFriends me.id flatMap { env.noteApi.get(u, me, _, isGranted(_.ModNote)) }
2014-04-22 17:22:57 -06:00
}
2014-05-03 01:39:20 -06:00
followable <- ctx.isAuth ?? { Env.pref.api followable u.id }
blocked <- ctx.userId ?? { relationApi.fetchBlocks(u.id, _) }
searchForm = GameFilterMenu.searchForm(userGameSearch, filters.current)(ctx.body)
} yield html.user.show(u, info, pag, filters, searchForm, relation, notes, followable, blocked)
2013-05-08 09:41:12 -06:00
2017-02-15 17:53:15 -07:00
private val UserGamesRateLimitPerIP = new lila.memo.RateLimit[IpAddress](
credits = 500,
duration = 10 minutes,
2016-09-01 15:54:43 -06:00
name = "user games web/mobile per IP",
key = "user_games.web.ip"
)
implicit val userGamesDefault =
ornicar.scalalib.Zero.instance[Fu[Paginator[GameModel]]](fuccess(Paginator.empty[GameModel]))
private def userGames(
u: UserModel,
filterOption: Option[String],
page: Int
)(implicit ctx: BodyContext[_]): Fu[(String, Paginator[GameModel])] = {
import lila.app.mashup.GameFilter.{ All, Playing }
filterOption.fold({
Env.simul isHosting u.id map (_.fold(Playing, All).name)
})(fuccess) flatMap { filterName =>
UserGamesRateLimitPerIP(HTTPRequest lastRemoteAddress ctx.req, cost = page, msg = s"on ${u.username}") {
2016-08-31 12:38:04 -06:00
lila.mon.http.userGames.cost(page)
GameFilterMenu.paginatorOf(
userGameSearch = userGameSearch,
user = u,
info = none,
filter = GameFilterMenu.currentOf(GameFilterMenu.all, filterName),
me = ctx.me,
page = page
)(ctx.body)
} map { filterName -> _ }
}
}
2014-08-02 11:33:46 -06:00
def list = Open { implicit ctx =>
val nb = 10
for {
2016-01-19 10:01:57 -07:00
leaderboards <- env.cached.leaderboards
nbAllTime env.cached topNbGame nb
nbDay fuccess(Nil)
// Env.game.cached activePlayerUidsDay nb map {
// _ flatMap { pair =>
// env lightUser pair.userId map { UserModel.LightCount(_, pair.nb) }
// }
// }
2016-10-17 09:35:24 -06:00
tourneyWinners Env.tournament.winners.all.map(_.top)
online env.cached.top50Online.get
2017-01-25 05:18:04 -07:00
_ <- Env.user.lightUserApi preloadMany tourneyWinners.map(_.userId)
2015-05-29 03:51:12 -06:00
res <- negotiate(
html = fuccess(Ok(html.user.list(
tourneyWinners = tourneyWinners,
online = online,
2016-01-19 10:01:57 -07:00
leaderboards = leaderboards,
nbDay = nbDay,
nbAllTime = nbAllTime
))),
2015-05-29 03:51:12 -06:00
api = _ => fuccess {
2016-02-28 19:45:02 -07:00
implicit val lpWrites = OWrites[UserModel.LightPerf](env.jsonView.lightPerfIsOnline)
2015-05-29 03:51:12 -06:00
Ok(Json.obj(
2016-01-19 10:01:57 -07:00
"bullet" -> leaderboards.bullet,
"blitz" -> leaderboards.blitz,
"classical" -> leaderboards.classical,
"crazyhouse" -> leaderboards.crazyhouse,
"chess960" -> leaderboards.chess960,
"kingOfTheHill" -> leaderboards.kingOfTheHill,
"threeCheck" -> leaderboards.threeCheck,
"antichess" -> leaderboards.antichess,
"atomic" -> leaderboards.atomic,
"horde" -> leaderboards.horde,
"racingKings" -> leaderboards.racingKings
))
}
)
2015-05-29 03:51:12 -06:00
} yield res
2013-05-08 09:41:12 -06:00
}
2013-03-20 08:23:41 -06:00
def top200(perfKey: String) = Open { implicit ctx =>
PerfType(perfKey).fold(notFound) { perfType =>
2016-02-23 19:45:38 -07:00
env.cached top200Perf perfType.id map { users =>
Ok(html.user.top200(perfType, users))
}
}
}
def topWeek = Open { implicit ctx =>
negotiate(
html = notFound,
2017-01-26 11:46:19 -07:00
api = _ => env.cached.topWeek(()).map { users =>
Ok(Json toJson users.map(env.jsonView.lightPerfIsOnline))
}
)
}
def mod(username: String) = Secure(_.UserSpy) { implicit ctx => me =>
OptionFuOk(UserRepo named username) { user =>
for {
email <- (!isGranted(_.SetEmail, user) ?? UserRepo.email(user.id))
spy <- Env.security userSpy user.id
2017-01-26 05:38:44 -07:00
assess <- Env.mod.assessApi.getPlayerAggregateAssessmentWithGames(user.id)
history <- Env.mod.logApi.userHistory(user.id)
charges <- Env.plan.api.recentChargesOf(user)
reports <- Env.report.api.byAndAbout(user, 20)
pref <- Env.pref.api.getPref(user)
bans <- Env.playban.api bans spy.usersSharingIp.map(_.id)
notes <- Env.user.noteApi.byUserIdsForMod(spy.otherUsers.map(_.user.id))
2017-01-26 05:38:44 -07:00
_ <- Env.user.lightUserApi preloadMany {
reports.userIds ::: assess.??(_.games).flatMap(_.userIds)
}
} yield html.user.mod(user, email, spy, assess, bans, history, charges, reports, pref, notes)
}
}
def writeNote(username: String) = AuthBody { implicit ctx => me =>
OptionFuResult(UserRepo named username) { user =>
2014-04-22 17:22:57 -06:00
implicit val req = ctx.body
env.forms.note.bindFromRequest.fold(
err => filter(username, none, 1, Results.BadRequest),
data => env.noteApi.write(user, data.text, me, data.mod && isGranted(_.ModNote)) inject
Redirect(routes.User.show(username).url + "?note")
2014-04-22 17:22:57 -06:00
)
}
}
def opponents = Auth { implicit ctx => me =>
for {
ops <- Env.game.bestOpponents(me.id)
followables <- Env.pref.api.followables(ops map (_._1.id))
relateds <- ops.zip(followables).map {
case ((u, nb), followable) => relationApi.fetchRelation(me.id, u.id) map {
lila.relation.Related(u, nb.some, followable, _)
}
}.sequenceFu
} yield html.user.opponents(me, relateds)
}
2015-12-23 20:41:28 -07:00
def perfStat(username: String, perfKey: String) = Open { implicit ctx =>
2015-12-23 20:57:20 -07:00
OptionFuResult(UserRepo named username) { u =>
if ((u.disabled || (u.lame && !ctx.is(u))) && !isGranted(_.UserSpy)) notFound
else PerfType(perfKey).fold(notFound) { perfType =>
2015-12-25 23:52:06 -07:00
for {
perfStat <- Env.perfStat.get(u, perfType)
ranks <- Env.user.cached.ranking.getAll(u.id)
2015-12-26 02:41:38 -07:00
distribution <- u.perfs(perfType).established ?? {
Env.user.cached.ratingDistribution(perfType) map some
2015-12-26 02:41:38 -07:00
}
2017-01-26 04:44:53 -07:00
_ <- Env.user.lightUserApi preloadMany { u.id :: perfStat.userIds.map(_.value) }
2015-12-25 23:52:06 -07:00
data = Env.perfStat.jsonView(u, perfStat, ranks get perfType.key, distribution)
2015-12-26 03:11:47 -07:00
response <- negotiate(
html = Ok(html.user.perfStat(u, ranks, perfType, data)).fuccess,
2017-01-26 04:44:53 -07:00
api = _ => getBool("graph").?? {
Env.history.ratingChartApi.singlePerf(u, perfType).map(_.some)
} map {
_.fold(data) { graph => data + ("graph" -> graph) }
} map { Ok(_) }
)
2015-12-26 03:11:47 -07:00
} yield response
2015-12-23 20:41:28 -07:00
}
}
}
2014-02-17 02:12:19 -07:00
def autocomplete = Open { implicit ctx =>
get("term", ctx.req).filter(_.nonEmpty) match {
case None => BadRequest("No search term provided").fuccess
case Some(term) => JsonOk {
ctx.me.ifTrue(getBool("friend")) match {
case None => UserRepo usernamesLike term
case Some(follower) =>
Env.relation.api.searchFollowedBy(follower, term, 10) flatMap {
case Nil => UserRepo usernamesLike term
case userIds => UserRepo usernamesByIds userIds
}
}
}
2013-05-06 15:52:48 -06:00
}
}
2016-08-26 06:46:42 -06:00
def myself = Auth { ctx => me =>
fuccess(Redirect(routes.User.show(me.username)))
2016-08-26 06:46:42 -06:00
}
2013-03-20 08:23:41 -06:00
}