New endpoint that is marking user for deletion

This commit is contained in:
shepilov
2025-04-16 18:38:28 +02:00
committed by Anton Shepilov
parent 8789b91a7a
commit 7580b37671
11 changed files with 75 additions and 5 deletions
+1
View File
@@ -10,6 +10,7 @@
<env name="PUBSUB_TYPE" value="local" />
<env name="SEARCH_DRIVER" value="mongodb" />
<env name="STORAGE_LOCAL_PATH" value="/tmp" />
<env name="ADMIN_ENDPOINT_SECRET" value="123" />
</envs>
<scope-kind value="DIRECTORY" />
<test-directory value="$PROJECT_DIR$/tdrive/backend/node/test/e2e" />
@@ -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,
@@ -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();
};
@@ -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;
@@ -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,
@@ -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" },
@@ -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;
@@ -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: {
+1
View File
@@ -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,
@@ -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;
@@ -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);
});
});
});