Skip to content

Commit

Permalink
account termination WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
ornicar committed Jan 16, 2025
1 parent 16790db commit dfe1ac0
Show file tree
Hide file tree
Showing 9 changed files with 83 additions and 10 deletions.
14 changes: 9 additions & 5 deletions modules/api/src/main/AccountTermination.scala
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ final class AccountTermination(
selfClose = me.is(u)
teacherClose = !selfClose && !Granter(_.CloseAccount) && Granter(_.Teacher)
modClose = !selfClose && Granter(_.CloseAccount)
tos = u.lameOrTroll || u.marks.alt || modClose
_ <- userRepo.disable(u, keepEmail = tos || playbanned)
tos = u.marks.dirty || modClose || playbanned
_ <- userRepo.disable(u, keepEmail = tos)
_ <- roundApi.resignAllGamesOf(u.id)
_ <- relationApi.unfollowAll(u.id)
_ <- relationApi.removeAllFollowers(u.id)
Expand Down Expand Up @@ -133,18 +133,22 @@ final class AccountTermination(
else doDeleteNow(user, del).inject(user.some)

private def doDeleteNow(u: User, del: UserDelete): Funit = for
_ <- activityWrite.deleteAll(u)
tos = u.lameOrTroll || u.marks.alt
playbanned <- playbanApi.hasCurrentPlayban(u.id)
closedByMod <- modLogApi.closedByMod(u)
tos = u.marks.dirty || closedByMod || playbanned
_ <- if tos then userRepo.deleteWithTosViolation(u) else userRepo.deleteFully(u)
_ <- activityWrite.deleteAll(u)
singlePlayerGameIds <- gameRepo.deleteAllSinglePlayerOf(u.id)
_ <- analysisRepo.remove(singlePlayerGameIds)
_ <- deleteAllGameChats(u)
_ <- streamerApi.delete(u)
_ <- swissApi.onUserDelete(u.id)
_ <- teamApi.onUserDelete(u.id)
_ <- ublogApi.onAccountDelete(u)
_ <- u.marks.clean.so:
securityStore.deleteAllSessionsOf(u.id)
yield
// a lot of work is done by modules listening to the following event:
// a lot of deletion is done by modules listening to the following event:
Bus.pub(lila.core.user.UserDelete(u, del.erase))

private def deleteAllGameChats(u: User) = gameRepo
Expand Down
10 changes: 5 additions & 5 deletions modules/tournament/src/main/LeaderboardApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -69,19 +69,19 @@ final class LeaderboardApi(
yield entries.map(_.tourId)

def byPlayerStream(
user: User,
userId: UserId,
withPerformance: Boolean,
perSecond: MaxPerSecond,
nb: Int
): Source[TourEntry, ?] =
repo.coll
.aggregateWith[Bdoc](): fw =>
aggregateByPlayer(user, fw, fw.Descending("d"), withPerformance, nb, offset = 0).toList
aggregateByPlayer(userId, fw, fw.Descending("d"), withPerformance, nb, offset = 0).toList
.documentSource()
.mapConcat(readTourEntry)

private def aggregateByPlayer(
user: User,
userId: UserId,
framework: repo.coll.AggregationFramework.type,
sort: framework.SortOrder,
withPerformance: Boolean,
Expand All @@ -91,7 +91,7 @@ final class LeaderboardApi(
import framework.*
NonEmptyList
.of(
Match($doc("u" -> user.id)),
Match($doc("u" -> userId)),
Sort(sort),
Skip(offset),
Limit(nb),
Expand Down Expand Up @@ -132,7 +132,7 @@ final class LeaderboardApi(
.aggregateList(length, _.sec): framework =>
import framework.*
val sort = if sortBest then framework.Ascending("w") else framework.Descending("d")
val pipe = aggregateByPlayer(user, framework, sort, false, length, offset)
val pipe = aggregateByPlayer(user.id, framework, sort, false, length, offset)
pipe.head -> pipe.tail
.map(_.flatMap(readTourEntry))
)
Expand Down
3 changes: 3 additions & 0 deletions modules/tournament/src/main/PairingRepo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,6 @@ final class PairingRepo(coll: Coll)(using Executor, Materializer):
"b2" -> SumField("b2")
)
)

private[tournament] def anonymize(tourId: TourId, userId: UserId)(ghostId: UserId) =
coll.update.one($doc("tid" -> tourId, "u" -> userId), $set("u.$" -> ghostId)).void
3 changes: 3 additions & 0 deletions modules/tournament/src/main/PlayerRepo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -335,3 +335,6 @@ final class PlayerRepo(private[tournament] val coll: Coll)(using Executor):
.sort($sort.desc("m"))
.batchSize(batchSize)
.cursor[Player](readPref)

private[tournament] def anonymize(tourId: TourId, userId: UserId)(ghostId: UserId) =
coll.update.one($doc("tid" -> tourId, "uid" -> userId), $set("uid" -> ghostId)).void
15 changes: 15 additions & 0 deletions modules/tournament/src/main/TournamentApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,21 @@ final class TournamentApi(
}
else tournamentRepo.setSchedule(tourId, none)

def onUserDelete(u: UserId) =
leaderboard
.byPlayerStream(u, withPerformance = false, perSecond = MaxPerSecond(100), nb = Int.MaxValue)
.mapAsync(1): result =>
import result.tour
for
_ <- tournamentRepo.anonymize(tour, u)
// here we use a single ghost ID for all arena players and pairings,
// because the mapping of arena player to arena pairings must be preserved
ghostId = UserId(s"!${scalalib.ThreadLocalRandom.nextString(8)}")
_ <- playerRepo.anonymize(tour.id, u)(ghostId)
_ <- pairingRepo.anonymize(tour.id, u)(ghostId)
yield ()
.runWith(Sink.ignore)

private def playerPovs(tour: Tournament, userId: UserId, nb: Int): Fu[List[LightPov]] =
pairingRepo.recentIdsByTourAndUserId(tour.id, userId, nb).flatMap(gameRepo.light.gamesFromPrimary).map {
_.flatMap { LightPov(_, userId) }
Expand Down
5 changes: 5 additions & 0 deletions modules/tournament/src/main/TournamentRepo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,11 @@ final class TournamentRepo(val coll: Coll, playerCollName: CollName)(using Execu
.cursor[Tournament](ReadPref.sec)
.list(500)

def anonymize(tour: Tournament, u: UserId) = for
_ <- tour.winnerId.has(u).so(coll.updateField($id(tour.id), "winner", UserId.ghost).void)
_ <- tour.createdBy.is(u).so(coll.updateField($id(tour.id), "createdBy", UserId.ghost).void)
yield ()

private[tournament] def sortedCursor(
owner: User,
status: List[Status],
Expand Down
5 changes: 5 additions & 0 deletions modules/ublog/src/main/UblogApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,11 @@ final class UblogApi(
_ <- blog.filter(_.visible).so(b => setTier(b.id, UblogRank.Tier.HIDDEN))
yield ()

def onAccountDelete(user: User) = for
_ <- colls.blog.delete.one($id(UblogBlog.Id.User(user.id)))
_ <- colls.post.delete.one($doc("blog" -> UblogBlog.Id.User(user.id)))
yield ()

def postCursor(user: User): AkkaStreamCursor[UblogPost] =
colls.post.find($doc("blog" -> s"user:${user.id}")).cursor[UblogPost](ReadPref.priTemp)

Expand Down
3 changes: 3 additions & 0 deletions modules/user/src/main/TrophyApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ final class TrophyApi(
private given BSONHandler[TrophyKind] = BSONStringHandler.as[TrophyKind](kindCache.sync, _._id)
private given BSONDocumentHandler[Trophy] = Macros.handler[Trophy]

lila.common.Bus.sub[lila.core.user.UserDelete]: del =>
coll.delete.one($doc("user" -> del.id))

def findByUser(user: User, max: Int = 50): Fu[List[Trophy]] =
coll.list[Trophy]($doc("user" -> user.id), max).map(_.filter(_.kind != TrophyKind.Unknown))

Expand Down
35 changes: 35 additions & 0 deletions modules/user/src/main/UserRepo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,41 @@ final class UserRepo(c: Coll)(using Executor) extends lila.core.user.UserRepo(c)
)
.void

def deleteWithTosViolation(user: User) =
import F.*
coll.update.one(
$id(user.id),
$unset(
profile,
roles,
toints,
"time",
kid,
lang,
title,
plan,
totpSecret,
changedCase,
blind,
salt,
bpass,
"mustConfirmEmail",
colorIt
) ++ $set(s"${F.delete}.done" -> true)
)

def deleteFully(user: User) = for
lockEmail <- emailOrPrevious(user.id)
_ <- coll.update.one(
$id(user.id),
$doc(
"prevEmail" -> lockEmail,
"createdAt" -> user.createdAt,
s"${F.delete}.done" -> true
)
)
yield ()

def findNextToDelete(delay: FiniteDuration): Fu[Option[(User, UserDelete)]] =
coll
.find:
Expand Down

0 comments on commit dfe1ac0

Please sign in to comment.