mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-04-05 02:49:32 +00:00
Delete the user along its data
This commit is contained in:
parent
3650a7d20c
commit
8df235e9db
@ -15,7 +15,7 @@ import docspell.backend.PasswordCrypt
|
||||
import docspell.backend.ops.OCollective._
|
||||
import docspell.common._
|
||||
import docspell.store.UpdateResult
|
||||
import docspell.store.queries.QCollective
|
||||
import docspell.store.queries.{QCollective, QUser}
|
||||
import docspell.store.queue.JobQueue
|
||||
import docspell.store.records._
|
||||
import docspell.store.usertask.{UserTask, UserTaskScope, UserTaskStore}
|
||||
@ -37,7 +37,11 @@ trait OCollective[F[_]] {
|
||||
|
||||
def update(s: RUser): F[AddResult]
|
||||
|
||||
def deleteUser(login: Ident, collective: Ident): F[AddResult]
|
||||
/** Deletes the user and all its data. */
|
||||
def deleteUser(login: Ident, collective: Ident): F[UpdateResult]
|
||||
|
||||
/** Return an excerpt of what would be deleted, when the user is deleted. */
|
||||
def getDeleteUserData(accountId: AccountId): F[DeleteUserData]
|
||||
|
||||
def insights(collective: Ident): F[InsightData]
|
||||
|
||||
@ -91,6 +95,9 @@ object OCollective {
|
||||
type EmptyTrash = REmptyTrashSetting.EmptyTrash
|
||||
val EmptyTrash = REmptyTrashSetting.EmptyTrash
|
||||
|
||||
type DeleteUserData = QUser.UserData
|
||||
val DeleteUserData = QUser.UserData
|
||||
|
||||
sealed trait PassResetResult
|
||||
object PassResetResult {
|
||||
case class Success(newPw: Password) extends PassResetResult
|
||||
@ -215,8 +222,13 @@ object OCollective {
|
||||
def update(s: RUser): F[AddResult] =
|
||||
store.add(RUser.update(s), RUser.exists(s.login))
|
||||
|
||||
def deleteUser(login: Ident, collective: Ident): F[AddResult] =
|
||||
store.transact(RUser.delete(login, collective)).attempt.map(AddResult.fromUpdate)
|
||||
def getDeleteUserData(accountId: AccountId): F[DeleteUserData] =
|
||||
store.transact(QUser.getUserData(accountId))
|
||||
|
||||
def deleteUser(login: Ident, collective: Ident): F[UpdateResult] =
|
||||
UpdateResult.fromUpdate(
|
||||
store.transact(QUser.deleteUserAndData(AccountId(collective, login)))
|
||||
)
|
||||
|
||||
def insights(collective: Ident): F[InsightData] =
|
||||
store.transact(QCollective.getInsights(collective))
|
||||
|
@ -66,6 +66,14 @@ object UserRoutes {
|
||||
ar <- backend.collective.deleteUser(id, user.account.collective)
|
||||
resp <- Ok(basicResult(ar, "User deleted."))
|
||||
} yield resp
|
||||
|
||||
case GET -> Root / Ident(username) / "deleteData" =>
|
||||
for {
|
||||
data <- backend.collective.getDeleteUserData(
|
||||
AccountId(user.account.collective, username)
|
||||
)
|
||||
resp <- Ok(DeleteUserData(data.ownedFolders.map(_.id), data.sentMails))
|
||||
} yield resp
|
||||
}
|
||||
}
|
||||
|
||||
|
131
modules/store/src/main/scala/docspell/store/queries/QUser.scala
Normal file
131
modules/store/src/main/scala/docspell/store/queries/QUser.scala
Normal file
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright 2020 Docspell Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.store.queries
|
||||
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.common._
|
||||
import docspell.store.qb.DML
|
||||
import docspell.store.qb.DSL._
|
||||
import docspell.store.records._
|
||||
|
||||
import doobie._
|
||||
|
||||
object QUser {
|
||||
private val logger = Logger.log4s[ConnectionIO](org.log4s.getLogger)
|
||||
|
||||
final case class UserData(
|
||||
ownedFolders: List[Ident],
|
||||
sentMails: Int
|
||||
)
|
||||
|
||||
def getUserData(accountId: AccountId): ConnectionIO[UserData] = {
|
||||
val folder = RFolder.as("f")
|
||||
val mail = RSentMail.as("m")
|
||||
val mitem = RSentMailItem.as("mi")
|
||||
val user = RUser.as("u")
|
||||
|
||||
for {
|
||||
uid <- loadUserId(accountId).map(_.getOrElse(Ident.unsafe("")))
|
||||
folders <- run(
|
||||
select(folder.name),
|
||||
from(folder),
|
||||
folder.owner === uid && folder.collective === accountId.collective
|
||||
).query[Ident].to[List]
|
||||
mails <- run(
|
||||
select(count(mail.id)),
|
||||
from(mail)
|
||||
.innerJoin(mitem, mail.id === mitem.sentMailId)
|
||||
.innerJoin(user, user.uid === mail.uid),
|
||||
user.login === accountId.user && user.cid === accountId.collective
|
||||
).query[Int].unique
|
||||
} yield UserData(folders, mails)
|
||||
}
|
||||
|
||||
def deleteUserAndData(accountId: AccountId): ConnectionIO[Int] =
|
||||
for {
|
||||
uid <- loadUserId(accountId).map(_.getOrElse(Ident.unsafe("")))
|
||||
_ <- logger.info(s"Remove user ${accountId.asString} (uid=${uid.id})")
|
||||
|
||||
n1 <- deleteUserFolders(uid)
|
||||
|
||||
n2 <- deleteUserSentMails(uid)
|
||||
_ <- logger.info(s"Removed $n2 sent mails")
|
||||
|
||||
n3 <- deleteRememberMe(accountId)
|
||||
_ <- logger.info(s"Removed $n3 remember me tokens")
|
||||
|
||||
n4 <- deleteTotp(uid)
|
||||
_ <- logger.info(s"Removed $n4 totp secrets")
|
||||
|
||||
n5 <- deleteMailSettings(uid)
|
||||
_ <- logger.info(s"Removed $n5 mail settings")
|
||||
|
||||
nu <- RUser.deleteById(uid)
|
||||
} yield nu + n1 + n2 + n3 + n4 + n5
|
||||
|
||||
def deleteUserFolders(uid: Ident): ConnectionIO[Int] = {
|
||||
val folder = RFolder.as("f")
|
||||
val member = RFolderMember.as("fm")
|
||||
for {
|
||||
folders <- run(
|
||||
select(folder.id),
|
||||
from(folder),
|
||||
folder.owner === uid
|
||||
).query[Ident].to[List]
|
||||
_ <- logger.info(s"Removing folders: ${folders.map(_.id)}")
|
||||
|
||||
ri <- folders.traverse(RItem.removeFolder)
|
||||
_ <- logger.info(s"Removed folders from items: $ri")
|
||||
rs <- folders.traverse(RSource.removeFolder)
|
||||
_ <- logger.info(s"Removed folders from sources: $rs")
|
||||
rf <- folders.traverse(RFolderMember.deleteAll)
|
||||
_ <- logger.info(s"Removed folders from members: $rf")
|
||||
|
||||
n1 <- DML.delete(member, member.user === uid)
|
||||
_ <- logger.info(s"Removed $n1 members for owning folders.")
|
||||
n2 <- DML.delete(folder, folder.owner === uid)
|
||||
_ <- logger.info(s"Removed $n2 folders.")
|
||||
|
||||
} yield n1 + n2 + ri.sum + rs.sum + rf.sum
|
||||
}
|
||||
|
||||
def deleteUserSentMails(uid: Ident): ConnectionIO[Int] = {
|
||||
val mail = RSentMail.as("m")
|
||||
for {
|
||||
ids <- run(select(mail.id), from(mail), mail.uid === uid).query[Ident].to[List]
|
||||
n1 <- ids.traverse(RSentMailItem.deleteMail)
|
||||
n2 <- ids.traverse(RSentMail.delete)
|
||||
} yield n1.sum + n2.sum
|
||||
}
|
||||
|
||||
def deleteRememberMe(id: AccountId): ConnectionIO[Int] =
|
||||
DML.delete(
|
||||
RRememberMe.T,
|
||||
RRememberMe.T.cid === id.collective && RRememberMe.T.username === id.user
|
||||
)
|
||||
|
||||
def deleteTotp(uid: Ident): ConnectionIO[Int] =
|
||||
DML.delete(RTotp.T, RTotp.T.userId === uid)
|
||||
|
||||
def deleteMailSettings(uid: Ident): ConnectionIO[Int] = {
|
||||
val smtp = RUserEmail.as("ms")
|
||||
val imap = RUserImap.as("mi")
|
||||
for {
|
||||
n1 <- DML.delete(smtp, smtp.uid === uid)
|
||||
n2 <- DML.delete(imap, imap.uid === uid)
|
||||
} yield n1 + n2
|
||||
}
|
||||
|
||||
private def loadUserId(id: AccountId): ConnectionIO[Option[Ident]] =
|
||||
run(
|
||||
select(RUser.T.uid),
|
||||
from(RUser.T),
|
||||
RUser.T.cid === id.collective && RUser.T.login === id.user
|
||||
).query[Ident].option
|
||||
|
||||
}
|
@ -64,4 +64,7 @@ object RFolderMember {
|
||||
|
||||
def deleteAll(folderId: Ident): ConnectionIO[Int] =
|
||||
DML.delete(T, T.folder === folderId)
|
||||
|
||||
def deleteMemberships(userId: Ident): ConnectionIO[Int] =
|
||||
DML.delete(T, T.user === userId)
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ object RRememberMe {
|
||||
val all = NonEmptyList.of[Column[_]](id, cid, username, created, uses)
|
||||
}
|
||||
|
||||
private val T = Table(None)
|
||||
val T = Table(None)
|
||||
def as(alias: String): Table =
|
||||
Table(Some(alias))
|
||||
|
||||
|
@ -168,4 +168,7 @@ object RUser {
|
||||
val t = Table(None)
|
||||
DML.delete(t, t.cid === coll && t.login === user)
|
||||
}
|
||||
|
||||
def deleteById(uid: Ident): ConnectionIO[Int] =
|
||||
DML.delete(T, T.uid === uid)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user