From 5da5acf3388480009faf30c2b053b0540d00fc17 Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Fri, 4 May 2018 00:28:52 +0200 Subject: [PATCH] GDPR erasure: private messages --- modules/message/src/main/Env.scala | 6 ++++++ modules/message/src/main/MessageApi.scala | 6 ++++++ modules/message/src/main/Post.scala | 2 ++ modules/message/src/main/Thread.scala | 14 +++++++++++--- modules/message/src/main/ThreadRepo.scala | 8 ++++++++ 5 files changed, 33 insertions(+), 3 deletions(-) diff --git a/modules/message/src/main/Env.scala b/modules/message/src/main/Env.scala index c42cb4e4dc..7a28879c5f 100644 --- a/modules/message/src/main/Env.scala +++ b/modules/message/src/main/Env.scala @@ -45,6 +45,12 @@ final class Env( blocks = blocks, getPref = getPref ) + + system.lilaBus.subscribe(system.actorOf(Props(new Actor { + def receive = { + case lila.user.User.GDPRErase(user) => api erase user + } + })), 'gdprErase) } object Env { diff --git a/modules/message/src/main/MessageApi.scala b/modules/message/src/main/MessageApi.scala index 6290a6da75..85593ea840 100644 --- a/modules/message/src/main/MessageApi.scala +++ b/modules/message/src/main/MessageApi.scala @@ -133,4 +133,10 @@ final class MessageApi( ) ) } + + def erase(user: User) = ThreadRepo.byAndForWithoutIndex(user) flatMap { threads => + lila.common.Future.applySequentially(threads) { thread => + coll.update($id(thread.id), thread erase user).void + } + } } diff --git a/modules/message/src/main/Post.scala b/modules/message/src/main/Post.scala index 438ca9c7eb..2d7f6dcae7 100644 --- a/modules/message/src/main/Post.scala +++ b/modules/message/src/main/Post.scala @@ -16,6 +16,8 @@ case class Post( def isUnRead = !isRead def similar(other: Post) = text == other.text && isByCreator == other.isByCreator + + def erase = copy(text = "") } object Post { diff --git a/modules/message/src/main/Thread.scala b/modules/message/src/main/Thread.scala index 81e31653e4..afa7810dac 100644 --- a/modules/message/src/main/Thread.scala +++ b/modules/message/src/main/Thread.scala @@ -11,9 +11,9 @@ case class Thread( createdAt: DateTime, updatedAt: DateTime, posts: List[Post], - creatorId: String, - invitedId: String, - visibleByUserIds: List[String], + creatorId: User.ID, + invitedId: User.ID, + visibleByUserIds: List[User.ID], mod: Option[Boolean] ) { @@ -89,6 +89,14 @@ case class Thread( def hasPostsWrittenBy(userId: User.ID) = posts exists (_.isByCreator == (creatorId == userId)) def endsWith(post: Post) = posts.lastOption ?? post.similar + + def erase(user: User) = copy( + posts = posts.map { + case p if p.isByCreator && user.id == creatorId => p.erase + case p if !p.isByCreator && user.id == invitedId => p.erase + case p => p + } + ) } object Thread { diff --git a/modules/message/src/main/ThreadRepo.scala b/modules/message/src/main/ThreadRepo.scala index 09fd75d9fc..0238810b6d 100644 --- a/modules/message/src/main/ThreadRepo.scala +++ b/modules/message/src/main/ThreadRepo.scala @@ -1,5 +1,6 @@ package lila.message +import reactivemongo.api.ReadPreference import lila.db.dsl._ import lila.user.User @@ -22,6 +23,13 @@ object ThreadRepo { def createdByUser(user: ID): Fu[List[Thread]] = coll.find(visibleByUserQuery(user) ++ $doc("creatorId" -> user)).list[Thread]() + // super heavy. For GDPR only. + private[message] def byAndForWithoutIndex(user: User): Fu[List[Thread]] = + coll.find($or( + $doc("creatorId" -> user.id), + $doc("invitedId" -> user.id) + )).list[Thread](999, readPreference = ReadPreference.secondaryPreferred) + def setReadFor(user: User)(thread: Thread): Funit = { val indexes = thread.unreadIndexesBy(user) indexes.nonEmpty ?? coll.update($id(thread.id), $doc("$set" -> indexes.foldLeft($empty) {