mirror of
https://github.com/lichess-org/lila.git
synced 2026-05-26 13:51:00 +00:00
mobile friend list API WIP
This commit is contained in:
@@ -10,7 +10,6 @@ import lila.core.LightUser
|
||||
import lila.core.perf.UserWithPerfs
|
||||
import lila.rating.UserPerfsExt.bestRatedPerf
|
||||
import lila.relation.Related
|
||||
import lila.relation.RelationStream.Direction
|
||||
|
||||
final class Relation(env: Env, apiC: => Api) extends LilaController(env):
|
||||
|
||||
@@ -101,14 +100,21 @@ final class Relation(env: Env, apiC: => Api) extends LilaController(env):
|
||||
)
|
||||
yield res
|
||||
|
||||
def apiFollowing = Scoped(_.Follow.Read, _.Web.Mobile) { ctx ?=> me ?=>
|
||||
def apiFollowing = Scoped(_.Follow.Read, _.Web.Mobile) { ctx ?=> _ ?=>
|
||||
apiC.jsonDownload:
|
||||
env.relation.stream
|
||||
.follow(me, Direction.Following, MaxPerSecond(30))
|
||||
.mapAsync(1): ids =>
|
||||
env.user.api.listWithPerfs(ids.toList, includeClosed = false)
|
||||
.mapConcat(identity)
|
||||
.map(env.api.userApi.one(_, None))
|
||||
getInt("recentlySeen").map(_.squeeze(1, 100)) match
|
||||
case None =>
|
||||
env.relation.stream
|
||||
.follow(MaxPerSecond(30))
|
||||
.mapAsync(1): ids =>
|
||||
env.user.api.listWithPerfs(ids.toList, includeClosed = false)
|
||||
.mapConcat(identity)
|
||||
.map(env.api.userApi.one(_, None))
|
||||
case Some(nb) =>
|
||||
import env.user.lightUserApi.reader
|
||||
env.relation.stream
|
||||
.recentlySeen(nb, env.user.lightUserApi.projection)
|
||||
.map(env.api.userApi.recentlySeen)
|
||||
}
|
||||
|
||||
// for lichobile, remove at some point
|
||||
|
||||
@@ -26,6 +26,8 @@ final class UserApi(
|
||||
streamerApi: lila.streamer.StreamerApi,
|
||||
liveStreamApi: lila.streamer.LiveApi,
|
||||
gameProxyRepo: lila.round.GameProxyRepo,
|
||||
playingUsers: lila.round.PlayingUsers,
|
||||
isOnline: lila.core.socket.IsOnline,
|
||||
trophyApi: lila.user.TrophyApi,
|
||||
shieldApi: lila.tournament.TournamentShieldApi,
|
||||
revolutionApi: lila.tournament.RevolutionApi,
|
||||
@@ -164,6 +166,13 @@ final class UserApi(
|
||||
val roleTrophies = trophyApi.roleBasedTrophies(u)
|
||||
UserApi.TrophiesAndAwards(userCache.rankingsOf(u.id), trophies ::: roleTrophies, shields, revols)
|
||||
|
||||
def recentlySeen(u: LightUser, seenAt: Option[Instant]): JsObject =
|
||||
lila.common.Json.lightUser
|
||||
.writeNoId(u)
|
||||
.add("seenAt", seenAt)
|
||||
.add("online", isOnline.exec(u.id))
|
||||
.add("playing", playingUsers(u.id))
|
||||
|
||||
private def trophiesJson(all: UserApi.TrophiesAndAwards)(using Lang): JsArray =
|
||||
JsArray:
|
||||
all.ranks.toList
|
||||
|
||||
@@ -4,34 +4,59 @@ import akka.stream.scaladsl.*
|
||||
import reactivemongo.akkastream.cursorProducer
|
||||
|
||||
import lila.db.dsl.{ *, given }
|
||||
import lila.core.user.UserRepo
|
||||
import reactivemongo.api.bson.BSONDocumentReader
|
||||
import lila.core.LightUser
|
||||
|
||||
final class RelationStream(colls: Colls)(using akka.stream.Materializer):
|
||||
|
||||
import RelationStream.*
|
||||
final class RelationStream(colls: Colls, userRepo: UserRepo)(using akka.stream.Materializer):
|
||||
|
||||
private val coll = colls.relation
|
||||
|
||||
def follow(userId: UserId, direction: Direction, perSecond: MaxPerSecond): Source[Seq[UserId], ?] =
|
||||
def follow(perSecond: MaxPerSecond)(using me: Me): Source[Seq[UserId], ?] =
|
||||
coll
|
||||
.find(
|
||||
$doc(selectField(direction) -> userId, "r" -> lila.core.relation.Relation.Follow),
|
||||
$doc(projectField(direction) -> true, "_id" -> false).some
|
||||
$doc(F.from -> me.userId, "r" -> lila.core.relation.Relation.Follow),
|
||||
$doc(F.to -> true, "_id" -> false).some
|
||||
)
|
||||
.batchSize(perSecond.value)
|
||||
.cursor[Bdoc](ReadPref.sec)
|
||||
.documentSource()
|
||||
.grouped(perSecond.value)
|
||||
.map(_.flatMap(_.getAsOpt[UserId](projectField(direction))))
|
||||
.map(_.flatMap(_.getAsOpt[UserId](F.to)))
|
||||
.throttle(1, 1.second)
|
||||
|
||||
private def selectField(d: Direction) = d match
|
||||
case Direction.Following => "u1"
|
||||
case Direction.Followers => "u2"
|
||||
private def projectField(d: Direction) = d match
|
||||
case Direction.Following => "u2"
|
||||
case Direction.Followers => "u1"
|
||||
def recentlySeen(nb: Int, projection: Bdoc)(using
|
||||
reader: BSONDocumentReader[LightUser],
|
||||
me: Me
|
||||
): Source[(LightUser, Option[Instant]), ?] =
|
||||
coll
|
||||
.aggregateWith[Bdoc](readPreference = ReadPref.sec): framework =>
|
||||
import framework.*
|
||||
List(
|
||||
Match($doc(F.from -> me.userId, "r" -> lila.core.relation.Relation.Follow)),
|
||||
PipelineOperator(
|
||||
$lookup.simple(
|
||||
from = userRepo.coll,
|
||||
as = "user",
|
||||
local = F.to,
|
||||
foreign = "_id",
|
||||
pipe = List(
|
||||
$doc("$match" -> $doc("enabled" -> true)),
|
||||
$doc("$sort" -> $doc("seenAt" -> -1)),
|
||||
$doc("$project" -> (projection ++ $doc("seenAt" -> true))),
|
||||
$doc("$limit" -> nb)
|
||||
)
|
||||
)
|
||||
),
|
||||
ReplaceRootField("user.0")
|
||||
)
|
||||
.documentSource()
|
||||
.mapConcat: doc =>
|
||||
for
|
||||
user <- doc.asOpt[LightUser]
|
||||
seenAt = doc.getAsOpt[Instant]("seenAt")
|
||||
yield (user, seenAt)
|
||||
|
||||
object RelationStream:
|
||||
|
||||
enum Direction:
|
||||
case Following, Followers
|
||||
private object F:
|
||||
val from = "u1"
|
||||
val to = "u2"
|
||||
|
||||
@@ -45,7 +45,7 @@ final class LightUserApi(repo: UserRepo, cacheApi: CacheApi)(using Executor)
|
||||
if id.isGhost then fuccess(LightUser.ghost.some)
|
||||
else
|
||||
repo.coll
|
||||
.find($id(id), projection)
|
||||
.find($id(id), projection.some)
|
||||
.one[LightUser]
|
||||
.recover:
|
||||
case _: exceptions.BSONValueNotFoundException => LightUser.ghost.some
|
||||
@@ -55,7 +55,7 @@ final class LightUserApi(repo: UserRepo, cacheApi: CacheApi)(using Executor)
|
||||
expireAfter = Syncache.ExpireAfter.Write(10.minutes)
|
||||
)
|
||||
|
||||
private given BSONDocumentReader[LightUser] with
|
||||
given reader: BSONDocumentReader[LightUser] with
|
||||
def readDocument(doc: BSONDocument) =
|
||||
doc
|
||||
.getAsTry[UserName](F.username)
|
||||
@@ -81,11 +81,11 @@ final class LightUserApi(repo: UserRepo, cacheApi: CacheApi)(using Executor)
|
||||
patronColor = patronColor
|
||||
)
|
||||
|
||||
private val projection =
|
||||
val projection =
|
||||
$doc(
|
||||
F.id -> false,
|
||||
F.username -> true,
|
||||
F.title -> true,
|
||||
F.plan -> true,
|
||||
F.flair -> true
|
||||
).some
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user