lila/modules/api/src/main/PersonalDataExport.scala

104 lines
3.3 KiB
Scala

package lila.api
import akka.stream.Materializer
import akka.stream.scaladsl._
import org.joda.time.DateTime
import org.joda.time.format.DateTimeFormat
import reactivemongo.akkastream.cursorProducer
import reactivemongo.api.ReadPreference
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext
import lila.chat.Chat
import lila.db.dsl._
import lila.game.Game
import lila.user.User
final class PersonalDataExport(
securityEnv: lila.security.Env,
msgEnv: lila.msg.Env,
forumEnv: lila.forum.Env,
gameEnv: lila.game.Env,
chatEnv: lila.chat.Env,
relationEnv: lila.relation.Env,
userRepo: lila.user.UserRepo,
mongoCacheApi: lila.memo.MongoCache.Api
)(implicit ec: ExecutionContext, mat: Materializer) {
private val lightPerSecond = 60
private val heavyPerSecond = 30
def apply(user: User): Source[String, _] = {
val intro =
Source.futureSource {
userRepo.currentOrPrevEmail(user.id) map { email =>
Source(
List(
textTitle(s"Personal data export for ${user.username}"),
"All dates are UTC",
bigSep,
s"Signup date: ${textDate(user.createdAt)}",
s"Last seen: ${user.seenAt ?? textDate}",
s"Public profile: ${user.profile.??(_.toString)}",
s"Email: ${email.??(_.value)}"
)
)
}
}
val connections =
Source(List(textTitle("Connections"))) concat
securityEnv.store.allSessions(user.id).documentSource().throttle(lightPerSecond, 1 second).map { s =>
s"${s.date.??(textDate)} ${s.ip} ${s.ua}"
}
val followedUsers =
Source.futureSource {
relationEnv.api.fetchFollowing(user.id) map { userIds =>
Source(List(textTitle("Followed players")) ++ userIds)
}
}
val forumPosts =
Source(List(textTitle("Forum posts"))) concat
forumEnv.postApi.allByUser(user.id).documentSource().throttle(heavyPerSecond, 1 second).map { p =>
s"${textDate(p.createdAt)}\n${p.text}$bigSep"
}
val privateMessages =
Source(List(textTitle("Direct messages"))) concat
msgEnv.api
.allMessagesOf(user.id)
.throttle(heavyPerSecond, 1 second)
.map { case (text, date) =>
s"${textDate(date)}\n$text$bigSep"
}
val gameChats =
Source(List(textTitle("Game chat messages"))) concat
gameEnv.gameRepo.coll
.find($doc(Game.BSONFields.playerUids -> user.id), $id(true).some)
.cursor[Bdoc](ReadPreference.secondaryPreferred)
.documentSource()
.map { doc => ~doc.string("_id") }
.throttle(heavyPerSecond, 1 second)
.mapAsyncUnordered(8) { id =>
chatEnv.api.userChat.findLinesBy(Chat.Id(id), user.id)
}
.mapConcat(identity)
val outro = Source(List(textTitle("That's all we got.")))
List(intro, connections, followedUsers, forumPosts, privateMessages, gameChats, outro)
.foldLeft(Source.empty[String])(_ concat _)
}
private val bigSep = "\n------------------------------------------\n"
private def textTitle(t: String) = s"\n${"=" * t.length}\n$t\n${"=" * t.length}\n"
private val englishDateTimeFormatter = DateTimeFormat forStyle "MS"
private def textDate(date: DateTime) = englishDateTimeFormatter print date
}