🚧 back: user deletion wip

This commit is contained in:
Eric Doughty-Papassideris
2025-03-04 17:50:29 +01:00
committed by Anton Shepilov
parent 99d78ad5ac
commit 658e8de6d4
4 changed files with 110 additions and 66 deletions
@@ -4,45 +4,13 @@ import type { ExecutionContext } from "../../../../platform/framework/api/crud-s
import { adminLogger, buildUserDeletionRepositories } from "../utils";
// /**
// * Create all repositories required for deleting a user
// * @deprecated Do not use this outside of this file, it is exported exclusively for e2e tests
// */
// export async function buildUserDeletionRepositories(
// db: DatabaseServiceAPI,
// search: SearchServiceAPI,
// ) {
// return {
// driveFile: await db.getRepository<DriveFile>(DriveFileType, DriveFile),
// file: await db.getRepository<File>(FileType, File),
// fileVersion: await db.getRepository<FileVersion>(FileVersionType, FileVersion),
// user: await db.getRepository<User>(UserType, User),
// companyUser: await db.getRepository<CompanyUser>(CompanyUserType, CompanyUser),
// externalUser: await db.getRepository<ExternalUser>(ExternalUserType, ExternalUser),
// // company: await db.getRepository<Company>(CompanyType, Company),
// // missed_drive_files
// // session
// // user_online
// search: {
// driveFile: await search.getRepository<DriveFile>(DriveFileType, DriveFile),
// user: await search.getRepository<User>(UserType, User),
// },
// };
// }
export class AdminDeleteUserController {
private constructor(
private readonly repos: Awaited<ReturnType<typeof buildUserDeletionRepositories>>,
) {}
public static async create() {
return new AdminDeleteUserController(
await buildUserDeletionRepositories(gr.database, gr.platformServices.search),
);
private _repos: Awaited<ReturnType<typeof buildUserDeletionRepositories>>;
private async getRepos() {
if (!this._repos)
this._repos = await buildUserDeletionRepositories(gr.database, gr.platformServices.search);
return this._repos;
}
// fisherYattesShuffleInPlace
/** Begin or forward the deletion process of a user */
async deleteUser(userId: string): Promise<"failed" | "deleting" | "done"> {
@@ -51,7 +19,7 @@ export class AdminDeleteUserController {
user: { server_request: true },
company: { id: "// TODO: REPLACE WITH COMPANY ID" },
} as unknown as ExecutionContext);
const existingUser = await this.repos.user.findOne({ id: userId });
const existingUser = await (await this.getRepos()).user.findOne({ id: userId });
if (existingUser?.deleted) {
if (existingUser.delete_process_started_epoch > 0) return "deleting";
}
@@ -65,7 +33,9 @@ export class AdminDeleteUserController {
/** Get an array of user IDs that are incompletely deleted */
async listUsersPendingDeletion() {
return (await this.repos.user.find({}, { $gt: [["delete_process_started_epoch", 0]] }))
return (
await (await this.getRepos()).user.find({}, { $gt: [["delete_process_started_epoch", 0]] })
)
.getEntities()
.map(({ id }) => id);
}
@@ -8,14 +8,13 @@ import {
descendDriveItemsDepthFirstRandomOrder,
loadRawVersionsOfItemForDeletion,
runInBatchesAreAllTrue,
TUserDeletionRepos,
} from "./utils";
export default class AdminServiceImpl implements AdminServiceAPI {
version: "1";
private _repos;
private get repos(): TUserDeletionRepos {
private get repos(): ReturnType<typeof buildUserDeletionRepositories> {
return (this._repos ||= buildUserDeletionRepositories());
}
@@ -35,8 +34,9 @@ export default class AdminServiceImpl implements AdminServiceAPI {
Promise.all(
paths.map(path => {
try {
return gr.platformServices.storage.remove(path);
return gr.platformServices.storage.remove("x" + path);
} catch (err) {
console.log(err);
logger.error({ err, path }, "Error deleting storage item");
return false;
}
@@ -71,12 +71,16 @@ export default class AdminServiceImpl implements AdminServiceAPI {
*/
const deleteUserLogger = adminLogger.child({ adminOp: "DeleteUser", user });
const result = await descendDriveItemsDepthFirstRandomOrder(
this.repos,
await this.repos,
"user_" + user.id,
async (item, children, _parents) => {
let canDeleteItem = true;
if (!item.is_directory) {
const versionsAssets = await loadRawVersionsOfItemForDeletion(this.repos, item, true);
const versionsAssets = await loadRawVersionsOfItemForDeletion(
await this.repos,
item,
true,
);
for (const { version, file, paths } of versionsAssets) {
if (paths.length > 0 && !(await this.deleteS3Paths(deleteUserLogger, paths))) {
deleteUserLogger.error({ paths }, "Failed to delete paths");
@@ -84,7 +88,7 @@ export default class AdminServiceImpl implements AdminServiceAPI {
} else {
try {
if (file) {
const result = await this.repos.file.remove(file);
const result = await (await this.repos).file.remove(file);
if (!result)
// No error but nothing deleted, just move on
deleteUserLogger.warn({ file, result }, "Failed to delete file");
@@ -96,7 +100,7 @@ export default class AdminServiceImpl implements AdminServiceAPI {
if (canDeleteItem && version)
try {
if (version) {
const result = await this.repos.fileVersion.remove(version);
const result = await (await this.repos).fileVersion.remove(version);
if (!result)
// No error but nothing deleted, just move on
deleteUserLogger.warn({ version, result }, "Failed to delete version");
@@ -109,18 +113,18 @@ export default class AdminServiceImpl implements AdminServiceAPI {
}
}
if (canDeleteItem) {
if (children.every(x => !!x)) {
if (children === undefined || children.every(x => !!x)) {
if (item.is_directory && children.length == 0)
deleteUserLogger.warn({ item }, "Deleting empty directory");
try {
await this.repos.search.driveFile.service.remove([item as never]);
await (await this.repos).search.driveFile.service.remove([item as never]);
} catch (err) {
canDeleteItem = false;
deleteUserLogger.error({ err, item }, "Error deleting drive item search entry");
}
if (canDeleteItem)
try {
const result = await this.repos.driveFile.remove(item);
const result = await (await this.repos).driveFile.remove(item);
if (!result)
// No error but nothing deleted, just move on
deleteUserLogger.warn({ item, result }, "Failed to delete drive item");
@@ -141,9 +145,9 @@ export default class AdminServiceImpl implements AdminServiceAPI {
);
if (!result.every(x => !!x)) return false;
//TODO: error checking and such
await this.repos.search.user.service.remove([user as never]);
await (await this.repos).search.user.service.remove([user as never]);
user.delete_process_started_epoch = 0;
await this.repos.user.save(user);
await (await this.repos).user.save(user);
return true;
}
}
@@ -158,6 +158,7 @@ export class UserServiceImpl {
const user = await this.get(pk);
if (context.user.server_request || context.user.id === user.id) {
const userCopy = { ...user } as User;
//We keep a part of the user id as new name
const partialId = user.id.toString().split("-")[0];
@@ -177,6 +178,8 @@ export class UserServiceImpl {
localEventBus.publish<ResourceEventsPayload>("user:deleted", {
user: user,
});
await gr.platformServices.admin.deleteUser(userCopy);
}
}