forked from open-webui/open-webui
		
	Merge pull request #314 from ollama-webui/delete-user
feat: delete user
This commit is contained in:
		
						commit
						3fce09eb3d
					
				
					 7 changed files with 154 additions and 9 deletions
				
			
		|  | @ -109,5 +109,21 @@ class AuthsTable: | |||
|         except: | ||||
|             return None | ||||
| 
 | ||||
|     def delete_auth_by_id(self, id: str) -> Optional[UserModel]: | ||||
|         try: | ||||
|             # Delete User | ||||
|             result = Users.delete_user_by_id(id) | ||||
| 
 | ||||
|             if result: | ||||
|                 # Delete Auth | ||||
|                 query = Auth.delete().where(Auth.id == id) | ||||
|                 query.execute()  # Remove the rows, return number of rows removed. | ||||
| 
 | ||||
|                 return True | ||||
|             else: | ||||
|                 return False | ||||
|         except: | ||||
|             return False | ||||
| 
 | ||||
| 
 | ||||
| Auths = AuthsTable(DB) | ||||
|  |  | |||
|  | @ -153,5 +153,14 @@ class ChatTable: | |||
|         except: | ||||
|             return False | ||||
| 
 | ||||
|     def delete_chats_by_user_id(self, user_id: str) -> bool: | ||||
|         try: | ||||
|             query = Chat.delete().where(Chat.user_id == user_id) | ||||
|             query.execute()  # Remove the rows, return number of rows removed. | ||||
| 
 | ||||
|             return True | ||||
|         except: | ||||
|             return False | ||||
| 
 | ||||
| 
 | ||||
| Chats = ChatTable(DB) | ||||
|  |  | |||
|  | @ -8,6 +8,8 @@ from utils.utils import decode_token | |||
| from utils.misc import get_gravatar_url | ||||
| 
 | ||||
| from apps.web.internal.db import DB | ||||
| from apps.web.models.chats import Chats | ||||
| 
 | ||||
| 
 | ||||
| #################### | ||||
| # User DB Schema | ||||
|  | @ -110,5 +112,21 @@ class UsersTable: | |||
|         except: | ||||
|             return None | ||||
| 
 | ||||
|     def delete_user_by_id(self, id: str) -> bool: | ||||
|         try: | ||||
|             # Delete User Chats | ||||
|             result = Chats.delete_chats_by_user_id(id) | ||||
| 
 | ||||
|             if result: | ||||
|                 # Delete User | ||||
|                 query = User.delete().where(User.id == id) | ||||
|                 query.execute()  # Remove the rows, return number of rows removed. | ||||
| 
 | ||||
|                 return True | ||||
|             else: | ||||
|                 return False | ||||
|         except: | ||||
|             return False | ||||
| 
 | ||||
| 
 | ||||
| Users = UsersTable(DB) | ||||
|  |  | |||
|  | @ -9,6 +9,8 @@ import time | |||
| import uuid | ||||
| 
 | ||||
| from apps.web.models.users import UserModel, UserRoleUpdateForm, Users | ||||
| from apps.web.models.auths import Auths | ||||
| 
 | ||||
| 
 | ||||
| from utils.utils import ( | ||||
|     get_password_hash, | ||||
|  | @ -73,3 +75,42 @@ async def update_user_role(form_data: UserRoleUpdateForm, cred=Depends(bearer_sc | |||
|             status_code=status.HTTP_401_UNAUTHORIZED, | ||||
|             detail=ERROR_MESSAGES.INVALID_TOKEN, | ||||
|         ) | ||||
| 
 | ||||
| 
 | ||||
| ############################ | ||||
| # DeleteUserById | ||||
| ############################ | ||||
| 
 | ||||
| 
 | ||||
| @router.delete("/{user_id}", response_model=bool) | ||||
| async def delete_user_by_id(user_id: str, cred=Depends(bearer_scheme)): | ||||
|     token = cred.credentials | ||||
|     user = Users.get_user_by_token(token) | ||||
| 
 | ||||
|     if user: | ||||
|         if user.role == "admin": | ||||
|             if user.id != user_id: | ||||
|                 result = Auths.delete_auth_by_id(user_id) | ||||
| 
 | ||||
|                 if result: | ||||
|                     return True | ||||
|                 else: | ||||
|                     raise HTTPException( | ||||
|                         status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | ||||
|                         detail=ERROR_MESSAGES.DELETE_USER_ERROR, | ||||
|                     ) | ||||
|             else: | ||||
|                 raise HTTPException( | ||||
|                     status_code=status.HTTP_403_FORBIDDEN, | ||||
|                     detail=ERROR_MESSAGES.ACTION_PROHIBITED, | ||||
|                 ) | ||||
|         else: | ||||
|             raise HTTPException( | ||||
|                 status_code=status.HTTP_403_FORBIDDEN, | ||||
|                 detail=ERROR_MESSAGES.ACCESS_PROHIBITED, | ||||
|             ) | ||||
|     else: | ||||
|         raise HTTPException( | ||||
|             status_code=status.HTTP_401_UNAUTHORIZED, | ||||
|             detail=ERROR_MESSAGES.INVALID_TOKEN, | ||||
|         ) | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ class ERROR_MESSAGES(str, Enum): | |||
|     DEFAULT = lambda err="": f"Something went wrong :/\n{err if err else ''}" | ||||
|     ENV_VAR_NOT_FOUND = "Required environment variable not found. Terminating now." | ||||
|     CREATE_USER_ERROR = "Oops! Something went wrong while creating your account. Please try again later. If the issue persists, contact support for assistance." | ||||
|     DELETE_USER_ERROR = "Oops! Something went wrong. We encountered an issue while trying to delete the user. Please give it another shot." | ||||
|     EMAIL_TAKEN = "Uh-oh! This email is already registered. Sign in with your existing account or choose another email to start anew." | ||||
|     USERNAME_TAKEN = ( | ||||
|         "Uh-oh! This username is already registered. Please choose another username." | ||||
|  | @ -27,4 +28,5 @@ class ERROR_MESSAGES(str, Enum): | |||
|     ) | ||||
|     NOT_FOUND = "We could not find what you're looking for :/" | ||||
|     USER_NOT_FOUND = "We could not find what you're looking for :/" | ||||
| 
 | ||||
|     MALICIOUS = "Unusual activities detected, please try again in a few minutes." | ||||
|  |  | |||
|  | @ -45,8 +45,9 @@ export const getUsers = async (token: string) => { | |||
| 			if (!res.ok) throw await res.json(); | ||||
| 			return res.json(); | ||||
| 		}) | ||||
| 		.catch((error) => { | ||||
| 			console.log(error); | ||||
| 		.catch((err) => { | ||||
| 			console.log(err); | ||||
| 			error = err.detail; | ||||
| 			return null; | ||||
| 		}); | ||||
| 
 | ||||
|  | @ -56,3 +57,30 @@ export const getUsers = async (token: string) => { | |||
| 
 | ||||
| 	return res ? res : []; | ||||
| }; | ||||
| 
 | ||||
| export const deleteUserById = async (token: string, userId: string) => { | ||||
| 	let error = null; | ||||
| 
 | ||||
| 	const res = await fetch(`${WEBUI_API_BASE_URL}/users/${userId}`, { | ||||
| 		method: 'DELETE', | ||||
| 		headers: { | ||||
| 			'Content-Type': 'application/json', | ||||
| 			Authorization: `Bearer ${token}` | ||||
| 		} | ||||
| 	}) | ||||
| 		.then(async (res) => { | ||||
| 			if (!res.ok) throw await res.json(); | ||||
| 			return res.json(); | ||||
| 		}) | ||||
| 		.catch((err) => { | ||||
| 			console.log(err); | ||||
| 			error = err.detail; | ||||
| 			return null; | ||||
| 		}); | ||||
| 
 | ||||
| 	if (error) { | ||||
| 		throw error; | ||||
| 	} | ||||
| 
 | ||||
| 	return res; | ||||
| }; | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ | |||
| 
 | ||||
| 	import toast from 'svelte-french-toast'; | ||||
| 
 | ||||
| 	import { updateUserRole, getUsers } from '$lib/apis/users'; | ||||
| 	import { updateUserRole, getUsers, deleteUserById } from '$lib/apis/users'; | ||||
| 
 | ||||
| 	let loaded = false; | ||||
| 	let users = []; | ||||
|  | @ -22,6 +22,16 @@ | |||
| 		} | ||||
| 	}; | ||||
| 
 | ||||
| 	const deleteUserHandler = async (id) => { | ||||
| 		const res = await deleteUserById(localStorage.token, id).catch((error) => { | ||||
| 			toast.error(error); | ||||
| 			return null; | ||||
| 		}); | ||||
| 		if (res) { | ||||
| 			users = await getUsers(localStorage.token); | ||||
| 		} | ||||
| 	}; | ||||
| 
 | ||||
| 	onMount(async () => { | ||||
| 		if ($user?.role !== 'admin') { | ||||
| 			await goto('/'); | ||||
|  | @ -55,7 +65,7 @@ | |||
| 									<th scope="col" class="px-6 py-3"> Name </th> | ||||
| 									<th scope="col" class="px-6 py-3"> Email </th> | ||||
| 									<th scope="col" class="px-6 py-3"> Role </th> | ||||
| 									<!-- <th scope="col" class="px-6 py-3"> Action </th> --> | ||||
| 									<th scope="col" class="px-6 py-3"> Action </th> | ||||
| 								</tr> | ||||
| 							</thead> | ||||
| 							<tbody> | ||||
|  | @ -63,15 +73,16 @@ | |||
| 									<tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700"> | ||||
| 										<th | ||||
| 											scope="row" | ||||
| 											class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white" | ||||
| 											class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white w-fit" | ||||
| 										> | ||||
| 											<div class="flex flex-row"> | ||||
| 												<img | ||||
| 													class=" rounded-full max-w-[30px] max-h-[30px] object-cover mr-4" | ||||
| 													src={user.profile_image_url} | ||||
| 													alt="user" | ||||
| 												/> | ||||
| 
 | ||||
| 												<div class=" font-semibold md:self-center">{user.name}</div> | ||||
| 												<div class=" font-semibold self-center">{user.name}</div> | ||||
| 											</div> | ||||
| 										</th> | ||||
| 										<td class="px-6 py-4"> {user.email} </td> | ||||
|  | @ -89,9 +100,29 @@ | |||
| 												}}>{user.role}</button | ||||
| 											> | ||||
| 										</td> | ||||
| 										<!-- <td class="px-6 py-4 text-center"> | ||||
| 											<button class="  text-white underline"> Edit </button> | ||||
| 										</td> --> | ||||
| 										<td class="px-6 py-4 text-center flex justify-center"> | ||||
| 											<button | ||||
| 												class="self-center w-fit text-sm p-1.5 border dark:border-gray-600 rounded-xl flex" | ||||
| 												on:click={async () => { | ||||
| 													deleteUserHandler(user.id); | ||||
| 												}} | ||||
| 											> | ||||
| 												<svg | ||||
| 													xmlns="http://www.w3.org/2000/svg" | ||||
| 													fill="none" | ||||
| 													viewBox="0 0 24 24" | ||||
| 													stroke-width="1.5" | ||||
| 													stroke="currentColor" | ||||
| 													class="w-4 h-4" | ||||
| 												> | ||||
| 													<path | ||||
| 														stroke-linecap="round" | ||||
| 														stroke-linejoin="round" | ||||
| 														d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" | ||||
| 													/> | ||||
| 												</svg> | ||||
| 											</button> | ||||
| 										</td> | ||||
| 									</tr> | ||||
| 								{/each} | ||||
| 							</tbody> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Timothy Jaeryang Baek
						Timothy Jaeryang Baek