Skip to content

Commit

Permalink
feat(user): Delete User Data from frontend (#2476)
Browse files Browse the repository at this point in the history
# Description

Please include a summary of the changes and the related issue. Please
also include relevant motivation and context.

## Checklist before requesting a review

Please delete options that are not relevant.

- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented hard-to-understand areas
- [ ] I have ideally added tests that prove my fix is effective or that
my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged

## Screenshots (if appropriate):

---------

Co-authored-by: Stan Girard <[email protected]>
  • Loading branch information
Zewed and StanGirard committed May 2, 2024
1 parent 699097f commit 8d54187
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 18 deletions.
20 changes: 20 additions & 0 deletions backend/modules/user/controller/user_controller.py
Expand Up @@ -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())],
Expand Down
24 changes: 24 additions & 0 deletions backend/modules/user/repository/users.py
Expand Up @@ -76,6 +76,30 @@ def get_user_email_by_user_id(self, user_id):
).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)

Expand Down
9 changes: 9 additions & 0 deletions backend/modules/user/repository/users_interface.py
Expand Up @@ -46,6 +46,15 @@ def get_user_email_by_user_id(self, user_id: UUID) -> str:
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
Expand Down
11 changes: 11 additions & 0 deletions frontend/app/user/page.module.scss
Expand Up @@ -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;
}
}
67 changes: 54 additions & 13 deletions 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";
Expand All @@ -18,22 +20,35 @@ 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,
isLogoutModalOpened,
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();
Expand All @@ -42,7 +57,7 @@ const UserPage = (): JSX.Element => {
return (
<>
<div className={styles.page_header}>
<PageHeader iconName="user" label="Profile" buttons={[button]} />
<PageHeader iconName="user" label="Profile" buttons={buttons} />
</div>
<div className={styles.user_page_container}>
<div className={styles.content_wrapper}>
Expand All @@ -55,11 +70,9 @@ const UserPage = (): JSX.Element => {
size="auto"
CloseTrigger={<div />}
>
<div className="text-center flex flex-col items-center gap-5">
<h2 className="text-lg font-medium mb-5">
{t("areYouSure", { ns: "logout" })}
</h2>
<div className="flex gap-5 items-center justify-center">
<div className={styles.modal_wrapper}>
<h2>{t("areYouSure", { ns: "logout" })}</h2>
<div className={styles.buttons}>
<QuivrButton
onClick={() => setIsLogoutModalOpened(false)}
color="primary"
Expand All @@ -76,6 +89,34 @@ const UserPage = (): JSX.Element => {
</div>
</div>
</Modal>
<Modal
isOpen={deleteAccountModalOpened}
setOpen={setDeleteAccountModalOpened}
size="auto"
CloseTrigger={<div />}
>
<div className={styles.modal_wrapper}>
<h2>Are you sure you want to delete your account ?</h2>
<div className={styles.buttons}>
<QuivrButton
onClick={() => setDeleteAccountModalOpened(false)}
color="primary"
label={t("cancel", { ns: "logout" })}
iconName="close"
></QuivrButton>
<QuivrButton
isLoading={isLoggingOut}
color="dangerous"
onClick={() => {
void deleteUserData();
void handleLogout();
}}
label="Delete Account"
iconName="logout"
></QuivrButton>
</div>
</div>
</Modal>
</>
);
};
Expand Down
2 changes: 2 additions & 0 deletions frontend/lib/api/user/useUserApi.ts
@@ -1,6 +1,7 @@
import { useAxios } from "@/lib/hooks";

import {
deleteUserData,
getUser,
getUserCredits,
getUserIdentity,
Expand All @@ -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),
};
};
8 changes: 7 additions & 1 deletion frontend/lib/api/user/user.ts
Expand Up @@ -31,7 +31,7 @@ export type UserIdentityUpdatableProperties = {
};

export type UserIdentity = {
user_id: UUID;
id: UUID;
onboarded: boolean;
username: string;
};
Expand All @@ -54,6 +54,12 @@ export const getUser = async (
axiosInstance: AxiosInstance
): Promise<UserStats> => (await axiosInstance.get<UserStats>("/user")).data;

export const deleteUserData = async (
axiosInstance: AxiosInstance
): Promise<void> => {
await axiosInstance.delete(`/user_data`);
};

export const getUserCredits = async (
axiosInstance: AxiosInstance
): Promise<number> => (await axiosInstance.get<number>("/user/credits")).data;
4 changes: 0 additions & 4 deletions frontend/lib/hooks/useLogoutModal.ts
Expand Up @@ -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);
Expand Down

0 comments on commit 8d54187

Please sign in to comment.