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 a58a961 commit 16790db
Show file tree
Hide file tree
Showing 17 changed files with 87 additions and 11 deletions.
9 changes: 7 additions & 2 deletions modules/api/src/main/AccountTermination.scala
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ final class AccountTermination(

lila.common.LilaScheduler.variableDelay(
"accountTermination.delete",
prev => _.Delay(if prev.isDefined then 1.second else 10.seconds),
delay = prev => _.Delay(if prev.isDefined then 1.second else 10.seconds),
timeout = _.AtMost(1.minute),
initialDelay = _.Delay(111.seconds)
):
Expand All @@ -138,9 +138,14 @@ final class AccountTermination(
singlePlayerGameIds <- gameRepo.deleteAllSinglePlayerOf(u.id)
_ <- analysisRepo.remove(singlePlayerGameIds)
_ <- deleteAllGameChats(u)
_ <- streamerApi.delete(u)
_ <- swissApi.onUserDelete(u.id)
_ <- teamApi.onUserDelete(u.id)
_ <- u.marks.clean.so:
securityStore.deleteAllSessionsOf(u.id)
yield
// a lot of work is done by modules listening to the following event:
Bus.pub(lila.core.user.UserDelete(u.id, del.erase))
Bus.pub(lila.core.user.UserDelete(u, del.erase))

private def deleteAllGameChats(u: User) = gameRepo
.docCursor(lila.game.Query.user(u.id), $id(true).some)
Expand Down
3 changes: 2 additions & 1 deletion modules/core/src/main/user.scala
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,8 @@ object user:

case class ChangeEmail(id: UserId, email: EmailAddress)

case class UserDelete(id: UserId, erase: Boolean)
case class UserDelete(user: User, erase: Boolean):
export user.id
object UserDelete extends bus.GivenChannel[UserDelete]("userDelete")

trait UserApi:
Expand Down
12 changes: 12 additions & 0 deletions modules/report/src/main/ReportApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,18 @@ final class ReportApi(
"atoms.reason" -> reason
)

def deleteAllBy(u: User) = for
reports <- coll.list[Report]($doc("atoms.by" -> u.id), 500)
_ <- reports.traverse: r =>
val newAtoms = r.atoms.map: a =>
if a.by.is(u)
then a.copy(by = UserId.ghost.into(ReporterId))
else a
coll.update.one($id(r.id), $set("atoms" -> newAtoms))
_ <- u.marks.clean.so:
coll.update.one($doc("user" -> u.id), $set("user" -> UserId.ghost)).void
yield ()

object inquiries:

private val workQueue = scalalib.actor.AsyncActorSequencer(
Expand Down
4 changes: 4 additions & 0 deletions modules/security/src/main/Store.scala
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ final class Store(val coll: Coll, cacheApi: lila.memo.CacheApi)(using Executor):
)
yield uncacheAllOf(userId)

def deleteAllSessionsOf(userId: UserId): Funit =
for _ <- coll.delete.one($doc("user" -> userId))
yield uncacheAllOf(userId)

private given BSONDocumentHandler[UserSession] = Macros.handler[UserSession]
def openSessions(userId: UserId, nb: Int): Fu[List[UserSession]] =
coll
Expand Down
5 changes: 4 additions & 1 deletion modules/shutup/src/main/ShutupApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@ final class ShutupApi(
private given BSONDocumentHandler[UserRecord] = Macros.handler
import PublicLine.given

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

def getPublicLines(userId: UserId): Fu[List[PublicLine]] =
coll
.find($doc("_id" -> userId), $doc("pub" -> 1).some)
.find($id(userId), $doc("pub" -> 1).some)
.one[Bdoc]
.map:
~_.flatMap(_.getAsOpt[List[PublicLine]]("pub"))
Expand Down
4 changes: 4 additions & 0 deletions modules/simul/src/main/Env.scala
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,9 @@ final class Env(
}
)

lila.common.Bus.sub[lila.core.user.UserDelete]: del =>
repo.anonymizeHost(del.id)
repo.anonymizePlayers(del.id)

final class SimulIsFeaturable(f: Simul => Boolean) extends (Simul => Boolean):
def apply(simul: Simul) = f(simul)
6 changes: 6 additions & 0 deletions modules/simul/src/main/SimulRepo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,9 @@ final private[simul] class SimulRepo(val coll: Coll, gameRepo: GameRepo)(using E
"createdAt" -> $doc("$lt" -> (nowInstant.minusMinutes(60)))
)
)

private[simul] def anonymizeHost(id: UserId) =
coll.update.one($doc("hostId" -> id), $set("hostId" -> UserId.ghost))

private[simul] def anonymizePlayers(id: UserId) =
coll.update.one($doc("pairings.player.user" -> id), $set("pairings.$.player.user" -> UserId.ghost))
3 changes: 3 additions & 0 deletions modules/storm/src/main/StormDay.scala
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ final class StormDayApi(coll: Coll, highApi: StormHighApi, userApi: lila.core.us
import StormDay.*
import StormBsonHandlers.given

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

def addRun(
data: StormForm.RunData,
user: Option[User],
Expand Down
7 changes: 3 additions & 4 deletions modules/streamer/src/main/StreamerApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,9 @@ final class StreamerApi(
coll
.find($id(user.id))
.one[Streamer]
.map(_.foreach: s =>
.flatMapz: s =>
s.youTube.foreach(tuber => ytApi.channelSubscribe(tuber.channelId, false))
coll.delete.one($id(user.id)).void)
coll.delete.one($id(user.id)).void

def create(u: User): Funit =
coll.insert.one(Streamer.make(u)).void.recover(lila.db.ignoreDuplicateKey)
Expand All @@ -159,9 +159,8 @@ final class StreamerApi(
def uploadPicture(s: Streamer, picture: PicfitApi.FilePart, by: User): Funit =
picfitApi
.uploadFile(s"streamer:${s.id}", picture, userId = by.id)
.flatMap { pic =>
.flatMap: pic =>
coll.update.one($id(s.id), $set("picture" -> pic.id)).void
}

// unapprove after 6 weeks if you never streamed (was originally 1 week)
def autoDemoteFakes: Funit =
Expand Down
3 changes: 2 additions & 1 deletion modules/study/src/main/ChapterRepo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ final class ChapterRepo(val coll: AsyncColl)(using Executor, akka.stream.Materia

def deleteByStudy(s: Study): Funit = coll(_.delete.one($studyId(s.id))).void

def deleteByStudyIds(ids: List[StudyId]): Funit = coll(_.delete.one($doc("studyId".$in(ids)))).void
def deleteByStudyIds(ids: List[StudyId]): Funit = ids.nonEmpty.so:
coll(_.delete.one($doc("studyId".$in(ids)))).void

// studyId is useful to ensure that the chapter belongs to the study
def byIdAndStudy(id: StudyChapterId, studyId: StudyId): Fu[Option[Chapter]] =
Expand Down
7 changes: 7 additions & 0 deletions modules/study/src/main/Env.scala
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,10 @@ final class Env(

lila.common.Bus.subscribeFun("studyAnalysisProgress"):
case lila.tree.StudyAnalysisProgress(analysis, complete) => serverEvalMerger(analysis, complete)

lila.common.Bus.sub[lila.core.user.UserDelete]: del =>
for
studyIds <- studyRepo.deleteByOwner(del.id)
_ <- chapterRepo.deleteByStudyIds(studyIds)
_ <- topicApi.userTopicsDelete(del.id)
yield ()
8 changes: 8 additions & 0 deletions modules/study/src/main/StudyRepo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,14 @@ final class StudyRepo(private[study] val coll: AsyncColl)(using
private[study] def isAdminMember(study: Study, userId: UserId): Fu[Boolean] =
coll(_.exists($id(study.id) ++ $doc(s"members.$userId.admin" -> true)))

private[study] def deleteByOwner(u: UserId): Fu[List[StudyId]] = for
c <- coll.get
ids <- c.distinctEasy[StudyId, List]("_id", selectOwnerId(u))
_ <- c.delete.one(selectOwnerId(u))
_ <- c.update.one($doc(F.likers -> u), $pull(F.likers -> u))
_ <- c.update.one($doc(F.uids -> u), $pull(F.uids -> u) ++ $unset(s"members.$u"))
yield ids

private def countLikes(studyId: StudyId): Fu[Option[(Study.Likes, Instant)]] =
coll:
_.aggregateWith[Bdoc](): framework =>
Expand Down
3 changes: 3 additions & 0 deletions modules/study/src/main/StudyTopic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ final class StudyTopicApi(topicRepo: StudyTopicRepo, userTopicRepo: StudyUserTop
)
})

def userTopicsDelete(userId: UserId) =
userTopicRepo.coll(_.delete.one($id(userId)))

def popular(nb: Int): Fu[StudyTopics] =
StudyTopics.from(
topicRepo
Expand Down
14 changes: 14 additions & 0 deletions modules/swiss/src/main/SwissApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,20 @@ final class SwissApi(
def idNames(ids: List[SwissId]): Fu[List[IdName]] =
mongo.swiss.find($inIds(ids), idNameProjection.some).cursor[IdName]().listAll()

def onUserDelete(u: UserId) = for
_ <- mongo.swiss.update.one($doc("winnerId" -> u), $set("winnerId" -> UserId.ghost), multi = true)
players <- mongo.player.list[SwissPlayer]($doc("u" -> u), _.priTemp) // no index!!!
swissIds = players.map(_.swissId).distinct
// here we use a single ghost ID for all swiss players and pairings,
// because the mapping of swiss player to swiss pairings must be preserved
ghostId = UserId(s"!${scalalib.ThreadLocalRandom.nextString(8)}")
newPlayers = players.map: p =>
p.copy(id = SwissPlayer.makeId(p.swissId, ghostId), userId = ghostId)
_ <- mongo.player.delete.one($inIds(players.map(_.id)))
_ <- mongo.player.insert.many(newPlayers)
_ <- mongo.pairing.update.one($inIds(swissIds) ++ $doc("p" -> u), $set("p.$" -> ghostId), multi = true)
yield ()

private def Sequencing[A <: Matchable: alleycats.Zero](
id: SwissId
)(fetch: SwissId => Fu[Option[Swiss]])(run: Swiss => Fu[A]): Fu[A] =
Expand Down
2 changes: 1 addition & 1 deletion modules/team/src/main/TeamApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ final class TeamApi(

import BSONHandlers.given

export teamRepo.filterHideForum
export teamRepo.{ filterHideForum, onUserDelete }

def team(id: TeamId) = teamRepo.byId(id)

Expand Down
5 changes: 5 additions & 0 deletions modules/team/src/main/TeamRepo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ final class TeamRepo(val coll: Coll)(using Executor):
coll.secondaryPreferred
.distinctEasy[TeamId, Set]("_id", $inIds(ids) ++ $doc("forum".$ne(Access.Everyone)))

def onUserDelete(userId: UserId): Funit = for
_ <- coll.update.one($doc("createdBy" -> userId), $set("createdBy" -> UserId.ghost))
_ <- coll.update.one($doc("leaders" -> userId), $pull("leaders" -> userId))
yield ()

private[team] val enabledSelect = $doc("enabled" -> true)

private[team] val sortPopular = $sort.desc("nbMembers")
3 changes: 2 additions & 1 deletion modules/user/src/main/NoteApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ final class NoteApi(coll: Coll)(using Executor) extends lila.core.user.NoteApi:
lila.common.Bus.sub[lila.core.user.UserDelete]: del =>
for
_ <- coll.delete.one($doc("from" -> del.id)) // no index, expensive!
_ <- coll.delete.one($doc("to" -> del.id))
maybeKeepModNotes = del.user.marks.dirty.so($doc("mod" -> false))
_ <- coll.delete.one($doc("to" -> del.id) ++ maybeKeepModNotes)
yield ()

def getForMyPermissions(user: User, max: Max = Max(30))(using me: Me): Fu[List[Note]] =
Expand Down

0 comments on commit 16790db

Please sign in to comment.