diff --git a/backend/modules/user/controller/user_controller.py b/backend/modules/user/controller/user_controller.py index 716b5983d..c734b6ef0 100644 --- a/backend/modules/user/controller/user_controller.py +++ b/backend/modules/user/controller/user_controller.py @@ -79,6 +79,26 @@ def get_user_identity_route( """ return user_repository.get_user_identity(current_user.id) +@user_router.delete( + "/user_data", + dependencies=[Depends(AuthBearer())], + tags=["User"], +) +async def delete_user_data_route( + current_user: UserIdentity = Depends(get_current_user), +): + """ + Delete a user. + + - `user_id`: The ID of the user to delete. + + This endpoint deletes a user from the system. + """ + + user_repository.delete_user_data(current_user.id) + + return {"message": "User deleted successfully"} + @user_router.get( "/user/credits", dependencies=[Depends(AuthBearer())], diff --git a/backend/modules/user/repository/users.py b/backend/modules/user/repository/users.py index cdd181909..a65d3188e 100644 --- a/backend/modules/user/repository/users.py +++ b/backend/modules/user/repository/users.py @@ -76,6 +76,30 @@ class Users(UsersInterface): ).execute() return response.data[0]["email"] + def delete_user_data(self, user_id): + response = ( + self.db.from_("brains_users") + .select("brain_id") + .filter("rights", "eq", "Owner") + .filter("user_id", "eq", str(user_id)) + .execute() + ) + brain_ids = [row["brain_id"] for row in response.data] + + for brain_id in brain_ids: + self.db.table("brains").delete().filter("brain_id", "eq", brain_id).execute() + + for brain_id in brain_ids: + self.db.table("brains_vectors").delete().filter("brain_id", "eq", brain_id).execute() + + for brain_id in brain_ids: + self.db.table("chat_history").delete().filter("brain_id", "eq", brain_id).execute() + + self.db.table("user_settings").delete().filter("user_id", "eq", str(user_id)).execute() + self.db.table("user_identity").delete().filter("user_id", "eq", str(user_id)).execute() + self.db.table("users").delete().filter("id", "eq", str(user_id)).execute() + + def get_user_credits(self, user_id): user_usage_instance = user_usage.UserUsage(id=user_id) diff --git a/backend/modules/user/repository/users_interface.py b/backend/modules/user/repository/users_interface.py index a188b455b..5c0e2b47c 100644 --- a/backend/modules/user/repository/users_interface.py +++ b/backend/modules/user/repository/users_interface.py @@ -46,6 +46,15 @@ class UsersInterface(ABC): pass @abstractmethod + def delete_user_data(self, user_id: str): + """ + Delete a user. + + - `user_id`: The ID of the user to delete. + + This endpoint deletes a user from the system. + """ + @abstractmethod def get_user_credits(self, user_id: UUID) -> int: """ Get user remaining credits diff --git a/frontend/app/user/page.module.scss b/frontend/app/user/page.module.scss index b9b7f737c..84203a00f 100644 --- a/frontend/app/user/page.module.scss +++ b/frontend/app/user/page.module.scss @@ -7,3 +7,14 @@ flex-direction: column; gap: Spacings.$spacing05; } + +.modal_wrapper { + display: flex; + flex-direction: column; + gap: Spacings.$spacing05; + + .buttons { + display: flex; + justify-content: space-between; + } +} diff --git a/frontend/app/user/page.tsx b/frontend/app/user/page.tsx index 356b1dc64..ee2a802d0 100644 --- a/frontend/app/user/page.tsx +++ b/frontend/app/user/page.tsx @@ -1,7 +1,9 @@ "use client"; +import { useState } from "react"; import { useTranslation } from "react-i18next"; +import { useUserApi } from "@/lib/api/user/useUserApi"; import PageHeader from "@/lib/components/PageHeader/PageHeader"; import { Modal } from "@/lib/components/ui/Modal/Modal"; import QuivrButton from "@/lib/components/ui/QuivrButton/QuivrButton"; @@ -18,7 +20,10 @@ import { useLogoutModal } from "../../lib/hooks/useLogoutModal"; const UserPage = (): JSX.Element => { const { session } = useSupabase(); const { userData } = useUserData(); + const { deleteUserData } = useUserApi(); const { t } = useTranslation(["translation", "logout"]); + const [deleteAccountModalOpened, setDeleteAccountModalOpened] = + useState(false); const { handleLogout, isLoggingOut, @@ -26,14 +31,24 @@ const UserPage = (): JSX.Element => { setIsLogoutModalOpened, } = useLogoutModal(); - const button: ButtonType = { - label: "Logout", - color: "dangerous", - onClick: () => { - setIsLogoutModalOpened(true); + const buttons: ButtonType[] = [ + { + label: "Logout", + color: "dangerous", + onClick: () => { + setIsLogoutModalOpened(true); + }, + iconName: "logout", }, - iconName: "logout", - }; + { + label: "Delete Account", + color: "dangerous", + onClick: () => { + setDeleteAccountModalOpened(true); + }, + iconName: "delete", + }, + ]; if (!session || !userData) { redirectToLogin(); @@ -42,7 +57,7 @@ const UserPage = (): JSX.Element => { return ( <>
- +
@@ -55,11 +70,9 @@ const UserPage = (): JSX.Element => { size="auto" CloseTrigger={
} > -
-

- {t("areYouSure", { ns: "logout" })} -

-
+
+

{t("areYouSure", { ns: "logout" })}

+
setIsLogoutModalOpened(false)} color="primary" @@ -76,6 +89,34 @@ const UserPage = (): JSX.Element => {
+ } + > +
+

Are you sure you want to delete your account ?

+
+ setDeleteAccountModalOpened(false)} + color="primary" + label={t("cancel", { ns: "logout" })} + iconName="close" + > + { + void deleteUserData(); + void handleLogout(); + }} + label="Delete Account" + iconName="logout" + > +
+
+
); }; diff --git a/frontend/lib/api/user/useUserApi.ts b/frontend/lib/api/user/useUserApi.ts index 8528fe812..690272339 100644 --- a/frontend/lib/api/user/useUserApi.ts +++ b/frontend/lib/api/user/useUserApi.ts @@ -1,6 +1,7 @@ import { useAxios } from "@/lib/hooks"; import { + deleteUserData, getUser, getUserCredits, getUserIdentity, @@ -18,6 +19,7 @@ export const useUserApi = () => { ) => updateUserIdentity(userIdentityUpdatableProperties, axiosInstance), getUserIdentity: async () => getUserIdentity(axiosInstance), getUser: async () => getUser(axiosInstance), + deleteUserData: async () => deleteUserData(axiosInstance), getUserCredits: async () => getUserCredits(axiosInstance), }; }; diff --git a/frontend/lib/api/user/user.ts b/frontend/lib/api/user/user.ts index 6604d8b96..d155d4fa5 100644 --- a/frontend/lib/api/user/user.ts +++ b/frontend/lib/api/user/user.ts @@ -31,7 +31,7 @@ export type UserIdentityUpdatableProperties = { }; export type UserIdentity = { - user_id: UUID; + id: UUID; onboarded: boolean; username: string; }; @@ -54,6 +54,12 @@ export const getUser = async ( axiosInstance: AxiosInstance ): Promise => (await axiosInstance.get("/user")).data; +export const deleteUserData = async ( + axiosInstance: AxiosInstance +): Promise => { + await axiosInstance.delete(`/user_data`); +}; + export const getUserCredits = async ( axiosInstance: AxiosInstance ): Promise => (await axiosInstance.get("/user/credits")).data; diff --git a/frontend/lib/hooks/useLogoutModal.ts b/frontend/lib/hooks/useLogoutModal.ts index d8844b18b..bb870b39f 100644 --- a/frontend/lib/hooks/useLogoutModal.ts +++ b/frontend/lib/hooks/useLogoutModal.ts @@ -30,10 +30,6 @@ export const useLogoutModal = () => { text: t("error", { errorMessage: error.message, ns: "logout" }), }); } else { - publish({ - variant: "success", - text: t("loggedOut", { ns: "logout" }), - }); window.location.href = "/"; } setIsLoggingOut(false);