Skip to content

Commit 38858ce

Browse files
committed
implement relation API endpoints - closes lichess-org#4398
See https://lichess.org/api#tag/Relations
1 parent ab6f131 commit 38858ce

File tree

5 files changed

+70
-1
lines changed

5 files changed

+70
-1
lines changed

app/controllers/Relation.scala

+21
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package controllers
22

3+
import play.api.libs.iteratee._
34
import play.api.libs.json.Json
5+
import play.api.mvc._
46

57
import lila.api.Context
68
import lila.app._
79
import lila.common.paginator.{ Paginator, AdapterLike, PaginatorJson }
10+
import lila.common.{ HTTPRequest, MaxPerSecond }
811
import lila.relation.Related
12+
import lila.relation.RelationStream._
913
import lila.user.{ User => UserModel, UserRepo }
1014
import views._
1115

@@ -76,6 +80,23 @@ object Relation extends LilaController {
7680
}
7781
}
7882

83+
def apiFollowing(name: String) = apiRelation(name, Direction.Following)
84+
85+
def apiFollowers(name: String) = apiRelation(name, Direction.Followers)
86+
87+
private def apiRelation(name: String, direction: Direction) = Action.async { req =>
88+
UserRepo.named(name) flatMap {
89+
_ ?? { user =>
90+
import Api.limitedDefault
91+
Api.GlobalLinearLimitPerIP(HTTPRequest lastRemoteAddress req) {
92+
Api.jsonStream {
93+
env.stream.follow(user, direction, MaxPerSecond(20)) &> Enumeratee.map(Env.api.userApi.one)
94+
} |> fuccess
95+
}
96+
}
97+
}
98+
}
99+
79100
private def jsonRelatedPaginator(pag: Paginator[Related]) = {
80101
import lila.user.JsonView.nameWrites
81102
import lila.relation.JsonView.relatedWrites

build.sbt

+1-1
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ lazy val plan = module("plan", Seq(common, user, notifyModule)).settings(
329329
)
330330

331331
lazy val relation = module("relation", Seq(common, db, memo, hub, user, game, pref)).settings(
332-
libraryDependencies ++= provided(play.api, reactivemongo.driver)
332+
libraryDependencies ++= provided(play.api, reactivemongo.driver, reactivemongo.iteratees)
333333
)
334334

335335
lazy val pref = module("pref", Seq(common, db, user)).settings(

conf/routes

+2
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,8 @@ GET /api controllers.Api.index
494494
POST /api/users controllers.Api.usersByIds
495495
GET /api/user/:name controllers.Api.user(name: String)
496496
GET /api/user/:name/activity controllers.Api.activity(name: String)
497+
GET /api/user/:name/following controllers.Relation.apiFollowing(name: String)
498+
GET /api/user/:name/followers controllers.Relation.apiFollowers(name: String)
497499
GET /api/game/:id controllers.Api.game(id: String)
498500
GET /api/games/team/:teamId controllers.Api.gamesVsTeam(teamId: String)
499501
GET /api/tournament controllers.Api.currentTournaments

modules/relation/src/main/Env.scala

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ final class Env(
3939
maxBlock = MaxBlock
4040
)
4141

42+
lazy val stream = new RelationStream(coll = coll)(system)
43+
4244
val online = new OnlineDoing(
4345
api,
4446
lightUser = lightUserApi.sync,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package lila.relation
2+
3+
import play.api.libs.iteratee._
4+
import reactivemongo.api.ReadPreference
5+
import reactivemongo.play.iteratees.cursorProducer
6+
import scala.concurrent.duration._
7+
8+
import lila.common.MaxPerSecond
9+
import lila.db.dsl._
10+
import lila.user.{ User, UserRepo }
11+
12+
final class RelationStream(coll: Coll)(implicit system: akka.actor.ActorSystem) {
13+
14+
import RelationStream._
15+
16+
def follow(user: User, direction: Direction, perSecond: MaxPerSecond): Enumerator[User] = {
17+
val field = direction match {
18+
case Direction.Following => "u2"
19+
case Direction.Followers => "u1"
20+
}
21+
val projection = $doc(field -> true, "_id" -> false)
22+
val query = direction match {
23+
case Direction.Following => coll.find($doc("u1" -> user.id, "r" -> Follow), projection)
24+
case Direction.Followers => coll.find($doc("u2" -> user.id, "r" -> Follow), projection)
25+
}
26+
query.copy(options = query.options.batchSize(perSecond.value))
27+
.cursor[Bdoc](readPreference = ReadPreference.secondaryPreferred)
28+
.bulkEnumerator() &>
29+
lila.common.Iteratee.delay(1 second) &>
30+
Enumeratee.mapM { docs =>
31+
UserRepo usersFromSecondary docs.toSeq.flatMap(_.getAs[User.ID](field))
32+
} &>
33+
Enumeratee.mapConcat(_.toSeq)
34+
}
35+
}
36+
37+
object RelationStream {
38+
39+
sealed trait Direction
40+
object Direction {
41+
case object Following extends Direction
42+
case object Followers extends Direction
43+
}
44+
}

0 commit comments

Comments
 (0)