diff --git a/.run/Run all e2e [MongoDB].run.xml b/.run/Run all e2e [MongoDB].run.xml
index 8464490f..3943c677 100644
--- a/.run/Run all e2e [MongoDB].run.xml
+++ b/.run/Run all e2e [MongoDB].run.xml
@@ -10,6 +10,7 @@
+
diff --git a/tdrive/backend/node/src/core/platform/services/admin/controller/delete-user-controller.ts b/tdrive/backend/node/src/core/platform/services/admin/controller/delete-user-controller.ts
index a70bb76a..e757e4e8 100644
--- a/tdrive/backend/node/src/core/platform/services/admin/controller/delete-user-controller.ts
+++ b/tdrive/backend/node/src/core/platform/services/admin/controller/delete-user-controller.ts
@@ -1,4 +1,3 @@
-import { UserPrimaryKey } from "src/services/user/entities/user";
import gr from "../../../../../services/global-resolver";
import type { ExecutionContext } from "../../../../platform/framework/api/crud-service";
@@ -13,6 +12,25 @@ export class AdminDeleteUserController {
return this._repos;
}
+ async markToDelete(
+ userId: string,
+ ): Promise<{ status: "failed" | "deleting" | "done"; userId?: string }> {
+ try {
+ await gr.services.console.getClient().userWasDeletedForceLogout(userId);
+ await gr.services.users.markToDeleted({ id: userId });
+ } catch (err) {
+ adminLogger.error({ err, userId }, "[DELETE USER] Error dustin updating user ");
+ return {
+ status: "failed",
+ userId,
+ };
+ }
+ return {
+ status: "done",
+ userId,
+ };
+ }
+
/** Begin or forward the deletion process of a user, if `deleteData` is false, only anonymises the user entry */
async deleteUser(
userId: string,
diff --git a/tdrive/backend/node/src/core/platform/services/admin/web/delete-user-routes.ts b/tdrive/backend/node/src/core/platform/services/admin/web/delete-user-routes.ts
index 956bf8c1..9520d8f5 100644
--- a/tdrive/backend/node/src/core/platform/services/admin/web/delete-user-routes.ts
+++ b/tdrive/backend/node/src/core/platform/services/admin/web/delete-user-routes.ts
@@ -62,6 +62,19 @@ const routes: FastifyPluginCallback = async (fastify: FastifyInstance, _opts, ne
return { id: userId };
});
+
+ fastify.post(`${urlRoot}/mark`, async (request, reply) => {
+ if (!authenticateAdminQuery(request, reply)) return false;
+
+ const { email } = request.body as TUserGetIdByEmailBody;
+ const userId = await controller.getUserIdByEmail(email);
+
+ if (!userId) {
+ return reply.status(404).send({ message: "User not found" });
+ }
+
+ return await controller.markToDelete(userId);
+ });
}
next();
};
diff --git a/tdrive/backend/node/src/services/user/entities/user.ts b/tdrive/backend/node/src/services/user/entities/user.ts
index 73081359..846ba9e2 100644
--- a/tdrive/backend/node/src/services/user/entities/user.ts
+++ b/tdrive/backend/node/src/services/user/entities/user.ts
@@ -75,6 +75,10 @@ export default class User {
@Column("password", "string")
password: string;
+ //just mark user as deleted not to be able to login
+ @Column("marked_to_delete", "tdrive_boolean")
+ marked_to_delete: boolean;
+
@Column("deleted", "tdrive_boolean")
deleted: boolean;
diff --git a/tdrive/backend/node/src/services/user/services/users/service.ts b/tdrive/backend/node/src/services/user/services/users/service.ts
index cc83de3c..4c2c6a9c 100644
--- a/tdrive/backend/node/src/services/user/services/users/service.ts
+++ b/tdrive/backend/node/src/services/user/services/users/service.ts
@@ -201,6 +201,12 @@ export class UserServiceImpl {
}
}
+ async markToDeleted(pk: UserPrimaryKey) {
+ const user = await this.get(pk);
+ user.marked_to_delete = true;
+ await this.save(user);
+ }
+
async search(
pagination: Pagination,
options?: SearchUserOptions,
diff --git a/tdrive/backend/node/src/services/user/web/schemas.ts b/tdrive/backend/node/src/services/user/web/schemas.ts
index 976b8667..1ea9e87e 100644
--- a/tdrive/backend/node/src/services/user/web/schemas.ts
+++ b/tdrive/backend/node/src/services/user/web/schemas.ts
@@ -15,6 +15,7 @@ export const userObjectSchema = {
last_name: { type: "string" },
created_at: { type: "number" },
deleted: { type: "boolean" },
+ marked_to_delete: { type: "boolean" },
delete_process_started_epoch: { type: "number" },
status: { type: "string" },
diff --git a/tdrive/backend/node/src/services/user/web/types.ts b/tdrive/backend/node/src/services/user/web/types.ts
index 88a64c6a..6d3e2ebd 100644
--- a/tdrive/backend/node/src/services/user/web/types.ts
+++ b/tdrive/backend/node/src/services/user/web/types.ts
@@ -56,6 +56,7 @@ export interface UserObject {
full_name: string;
created_at: number;
deleted: boolean;
+ marked_to_delete: boolean;
status: string; //Single string for the status
last_activity: number;
last_seen?: number;
diff --git a/tdrive/backend/node/src/services/workspaces/web/controllers/workspace-users.ts b/tdrive/backend/node/src/services/workspaces/web/controllers/workspace-users.ts
index bfc214ff..05acbf65 100644
--- a/tdrive/backend/node/src/services/workspaces/web/controllers/workspace-users.ts
+++ b/tdrive/backend/node/src/services/workspaces/web/controllers/workspace-users.ts
@@ -82,6 +82,7 @@ export class WorkspaceUsersCrudController
full_name: [user.first_name, user.last_name].join(" "),
created_at: user.creation_date,
deleted: Boolean(user.deleted),
+ marked_to_delete: Boolean(user.marked_to_delete),
status: user.status_icon,
last_activity: user.last_activity,
cache: {
diff --git a/tdrive/backend/node/src/utils/users.ts b/tdrive/backend/node/src/utils/users.ts
index 648ca88d..0a036960 100644
--- a/tdrive/backend/node/src/utils/users.ts
+++ b/tdrive/backend/node/src/utils/users.ts
@@ -30,6 +30,7 @@ export async function formatUser(
full_name: [user.first_name, user.last_name].join(" "),
created_at: user.creation_date,
deleted: Boolean(user.deleted),
+ marked_to_delete: Boolean(user.marked_to_delete),
delete_process_started_epoch: user.delete_process_started_epoch,
status: user.status_icon,
last_activity: user.last_activity,
diff --git a/tdrive/backend/node/test/e2e/setup/index.ts b/tdrive/backend/node/test/e2e/setup/index.ts
index e3f37239..8a31dd4c 100644
--- a/tdrive/backend/node/test/e2e/setup/index.ts
+++ b/tdrive/backend/node/test/e2e/setup/index.ts
@@ -30,9 +30,11 @@ type TokenPayload = {
export type User = {
id: string;
+ email?: string;
first_name?: string;
isWorkspaceModerator?: boolean;
deleted?: boolean,
+ marked_to_delete?: boolean;
delete_process_started_epoch?: number,
preferences?: {
language?: string;
diff --git a/tdrive/backend/node/test/e2e/users/users.deletion.spec.ts b/tdrive/backend/node/test/e2e/users/users.deletion.spec.ts
index c8ebb853..f49f9066 100644
--- a/tdrive/backend/node/test/e2e/users/users.deletion.spec.ts
+++ b/tdrive/backend/node/test/e2e/users/users.deletion.spec.ts
@@ -301,10 +301,32 @@ describe("The users deletion API", () => {
});
it("should have no objects left other than user (and untestable files)", async () => {
- expect(await (await Promise.all(
- Object.entries(listByUserIn)
- .flatMap(async ([key, fn]) => (await fn(myUserId)).getEntities().map(e => ({"": e.constructor.name, ...e})))
- )).flat()).toHaveLength(0);
+ expect((await Promise.all(
+ Object.entries(listByUserIn)
+ .flatMap(async ([_, fn]) => (await fn(myUserId)).getEntities().map(e => ({"": e.constructor.name, ...e})))
+ )).flat()).toHaveLength(0);
});
+
+ it("'existing user with to_delete flag' should return 404 if user is not found", async () => {
+ const secret = adminConfig.endpointSecret;
+ const email = "notexistingemail@test.com";
+ const response = await platform!.app.inject({
+ method: "POST",
+ url: `/admin/api/user/delete/mark`,
+ body: { secret, email },
+ });
+ expect(response.statusCode).toBe(404);
+ });
+
+ it("'existing user with to_delete flag' should return 200", async () => {
+ const user = await UserApi.getInstance(platform);
+ const response = await platform!.app.inject({
+ method: "POST",
+ url: `/admin/api/user/delete/mark`,
+ body: { secret: adminConfig.endpointSecret, email: (await user.getUser()).email },
+ });
+ expect(response.statusCode).toBe(200);
+ });
+
});
});