Support delete own account and other tweaks (#614)

Co-authored-by: Esteban Maya Cadavid <emaya@trueblue.com>
This commit is contained in:
Alejandra
2024-02-27 15:47:42 -05:00
committed by GitHub
parent e44777f919
commit 2346b81c51
7 changed files with 30 additions and 24 deletions

View File

@@ -1,7 +1,7 @@
from typing import Any from typing import Any
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from sqlmodel import func, select from sqlmodel import func, select, delete
from app import crud from app import crud
from app.api.deps import ( from app.api.deps import (
@@ -21,6 +21,7 @@ from app.models import (
UsersOut, UsersOut,
UserUpdate, UserUpdate,
UserUpdateMe, UserUpdateMe,
Item
) )
from app.utils import send_new_account_email from app.utils import send_new_account_email
@@ -194,12 +195,14 @@ def delete_user(
user = session.get(User, user_id) user = session.get(User, user_id)
if not user: if not user:
raise HTTPException(status_code=404, detail="User not found") raise HTTPException(status_code=404, detail="User not found")
if not current_user.is_superuser:
raise HTTPException(status_code=400, detail="Not enough permissions") if (user == current_user and not current_user.is_superuser) or (user != current_user and current_user.is_superuser):
if user == current_user: statement = delete(Item).where(Item.owner_id == user_id)
raise HTTPException( session.exec(statement)
status_code=400, detail="Users are not allowed to delete themselves"
)
session.delete(user) session.delete(user)
session.commit() session.commit()
return Message(message="User deleted successfully") return Message(message="User deleted successfully")
elif user == current_user and current_user.is_superuser:
raise HTTPException(
status_code=400, detail="Super users are not allowed to delete themselves"
)

View File

@@ -2,7 +2,6 @@ import React from 'react';
import { Box, Drawer, DrawerBody, DrawerCloseButton, DrawerContent, DrawerOverlay, Flex, IconButton, Image, Text, useColorModeValue, useDisclosure } from '@chakra-ui/react'; import { Box, Drawer, DrawerBody, DrawerCloseButton, DrawerContent, DrawerOverlay, Flex, IconButton, Image, Text, useColorModeValue, useDisclosure } from '@chakra-ui/react';
import { FiLogOut, FiMenu } from 'react-icons/fi'; import { FiLogOut, FiMenu } from 'react-icons/fi';
import { useNavigate } from 'react-router-dom';
import Logo from '../../assets/images/fastapi-logo.svg'; import Logo from '../../assets/images/fastapi-logo.svg';
import useAuth from '../../hooks/useAuth'; import useAuth from '../../hooks/useAuth';
@@ -16,11 +15,9 @@ const Sidebar: React.FC = () => {
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
const { user } = useUserStore(); const { user } = useUserStore();
const { logout } = useAuth(); const { logout } = useAuth();
const navigate = useNavigate();
const handleLogout = async () => { const handleLogout = async () => {
logout() logout()
navigate('/login');
}; };

View File

@@ -3,17 +3,15 @@ import React from 'react';
import { Box, IconButton, Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/react'; import { Box, IconButton, Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/react';
import { FaUserAstronaut } from 'react-icons/fa'; import { FaUserAstronaut } from 'react-icons/fa';
import { FiLogOut, FiUser } from 'react-icons/fi'; import { FiLogOut, FiUser } from 'react-icons/fi';
import { Link, useNavigate } from 'react-router-dom'; import { Link } from 'react-router-dom';
import useAuth from '../../hooks/useAuth'; import useAuth from '../../hooks/useAuth';
const UserMenu: React.FC = () => { const UserMenu: React.FC = () => {
const navigate = useNavigate();
const { logout } = useAuth(); const { logout } = useAuth();
const handleLogout = async () => { const handleLogout = async () => {
logout() logout()
navigate('/login');
}; };
return ( return (

View File

@@ -2,7 +2,10 @@ import React, { useState } from 'react';
import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, Button } from '@chakra-ui/react'; import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, Button } from '@chakra-ui/react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { ApiError } from '../../client';
import useAuth from '../../hooks/useAuth';
import useCustomToast from '../../hooks/useCustomToast'; import useCustomToast from '../../hooks/useCustomToast';
import { useUserStore } from '../../store/user-store';
interface DeleteProps { interface DeleteProps {
isOpen: boolean; isOpen: boolean;
@@ -12,18 +15,19 @@ interface DeleteProps {
const DeleteConfirmation: React.FC<DeleteProps> = ({ isOpen, onClose }) => { const DeleteConfirmation: React.FC<DeleteProps> = ({ isOpen, onClose }) => {
const showToast = useCustomToast(); const showToast = useCustomToast();
const cancelRef = React.useRef<HTMLButtonElement | null>(null); const cancelRef = React.useRef<HTMLButtonElement | null>(null);
const [isLoading, setIsLoading] = useState(false); const { handleSubmit, formState: { isSubmitting } } = useForm();
const { handleSubmit } = useForm(); const { user, deleteUser } = useUserStore();
const { logout } = useAuth();
const onSubmit = async () => { const onSubmit = async () => {
setIsLoading(true);
try { try {
// TODO: Delete user account when API is ready await deleteUser(user!.id);
logout();
onClose(); onClose();
showToast('Success', 'Your account has been successfully deleted.', 'success');
} catch (err) { } catch (err) {
showToast('An error occurred', 'An error occurred while deleting your account.', 'error'); const errDetail = (err as ApiError).body.detail;
} finally { showToast('Something went wrong.', `${errDetail}`, 'error');
setIsLoading(false);
} }
} }
@@ -47,7 +51,7 @@ const DeleteConfirmation: React.FC<DeleteProps> = ({ isOpen, onClose }) => {
</AlertDialogBody> </AlertDialogBody>
<AlertDialogFooter gap={3}> <AlertDialogFooter gap={3}>
<Button bg='ui.danger' color='white' _hover={{ opacity: 0.8 }} type='submit' isLoading={isLoading}> <Button bg='ui.danger' color='white' _hover={{ opacity: 0.8 }} type='submit' isLoading={isSubmitting}>
Confirm Confirm
</Button> </Button>
<Button ref={cancelRef} onClick={onClose} isDisabled={isLoading}> <Button ref={cancelRef} onClick={onClose} isDisabled={isLoading}>

View File

@@ -2,11 +2,14 @@ import { useUserStore } from '../store/user-store';
import { Body_login_login_access_token as AccessToken, LoginService } from '../client'; import { Body_login_login_access_token as AccessToken, LoginService } from '../client';
import { useUsersStore } from '../store/users-store'; import { useUsersStore } from '../store/users-store';
import { useItemsStore } from '../store/items-store'; import { useItemsStore } from '../store/items-store';
import { useNavigate } from 'react-router-dom';
const useAuth = () => { const useAuth = () => {
const { user, getUser, resetUser } = useUserStore(); const { user, getUser, resetUser } = useUserStore();
const { resetUsers } = useUsersStore(); const { resetUsers } = useUsersStore();
const { resetItems } = useItemsStore(); const { resetItems } = useItemsStore();
const navigate = useNavigate();
const login = async (data: AccessToken) => { const login = async (data: AccessToken) => {
const response = await LoginService.loginAccessToken({ const response = await LoginService.loginAccessToken({
@@ -21,6 +24,7 @@ const useAuth = () => {
resetUser(); resetUser();
resetUsers(); resetUsers();
resetItems(); resetItems();
navigate('/login');
}; };
const isLoggedIn = () => { const isLoggedIn = () => {

View File

@@ -14,7 +14,7 @@ export const useItemsStore = create<ItemsStore>((set) => ({
items: [], items: [],
getItems: async () => { getItems: async () => {
const itemsResponse = await ItemsService.readItems({ skip: 0, limit: 10 }); const itemsResponse = await ItemsService.readItems({ skip: 0, limit: 10 });
set({ items: itemsResponse }); set({ items: itemsResponse.data });
}, },
addItem: async (item: ItemCreate) => { addItem: async (item: ItemCreate) => {
const itemResponse = await ItemsService.createItem({ requestBody: item }); const itemResponse = await ItemsService.createItem({ requestBody: item });

View File

@@ -14,7 +14,7 @@ export const useUsersStore = create<UsersStore>((set) => ({
users: [], users: [],
getUsers: async () => { getUsers: async () => {
const usersResponse = await UsersService.readUsers({ skip: 0, limit: 10 }); const usersResponse = await UsersService.readUsers({ skip: 0, limit: 10 });
set({ users: usersResponse }); set({ users: usersResponse.data });
}, },
addUser: async (user: UserCreate) => { addUser: async (user: UserCreate) => {
const userResponse = await UsersService.createUser({ requestBody: user }); const userResponse = await UsersService.createUser({ requestBody: user });