forked from open-webui/open-webui
		
	Merge branch 'main' into account-settings
This commit is contained in:
		
						commit
						450b9b6aef
					
				
					 7 changed files with 154 additions and 9 deletions
				
			
		|  | @ -109,5 +109,21 @@ class AuthsTable: | ||||||
|         except: |         except: | ||||||
|             return None |             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) | Auths = AuthsTable(DB) | ||||||
|  |  | ||||||
|  | @ -153,5 +153,14 @@ class ChatTable: | ||||||
|         except: |         except: | ||||||
|             return False |             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) | Chats = ChatTable(DB) | ||||||
|  |  | ||||||
|  | @ -8,6 +8,8 @@ from utils.utils import decode_token | ||||||
| from utils.misc import get_gravatar_url | from utils.misc import get_gravatar_url | ||||||
| 
 | 
 | ||||||
| from apps.web.internal.db import DB | from apps.web.internal.db import DB | ||||||
|  | from apps.web.models.chats import Chats | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| #################### | #################### | ||||||
| # User DB Schema | # User DB Schema | ||||||
|  | @ -110,5 +112,21 @@ class UsersTable: | ||||||
|         except: |         except: | ||||||
|             return None |             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) | Users = UsersTable(DB) | ||||||
|  |  | ||||||
|  | @ -9,6 +9,8 @@ import time | ||||||
| import uuid | import uuid | ||||||
| 
 | 
 | ||||||
| from apps.web.models.users import UserModel, UserRoleUpdateForm, Users | from apps.web.models.users import UserModel, UserRoleUpdateForm, Users | ||||||
|  | from apps.web.models.auths import Auths | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| from utils.utils import ( | from utils.utils import ( | ||||||
|     get_password_hash, |     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, |             status_code=status.HTTP_401_UNAUTHORIZED, | ||||||
|             detail=ERROR_MESSAGES.INVALID_TOKEN, |             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 ''}" |     DEFAULT = lambda err="": f"Something went wrong :/\n{err if err else ''}" | ||||||
|     ENV_VAR_NOT_FOUND = "Required environment variable not found. Terminating now." |     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." |     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." |     EMAIL_TAKEN = "Uh-oh! This email is already registered. Sign in with your existing account or choose another email to start anew." | ||||||
|     USERNAME_TAKEN = ( |     USERNAME_TAKEN = ( | ||||||
|         "Uh-oh! This username is already registered. Please choose another username." |         "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 :/" |     NOT_FOUND = "We could not find what you're looking for :/" | ||||||
|     USER_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." |     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(); | 			if (!res.ok) throw await res.json(); | ||||||
| 			return res.json(); | 			return res.json(); | ||||||
| 		}) | 		}) | ||||||
| 		.catch((error) => { | 		.catch((err) => { | ||||||
| 			console.log(error); | 			console.log(err); | ||||||
|  | 			error = err.detail; | ||||||
| 			return null; | 			return null; | ||||||
| 		}); | 		}); | ||||||
| 
 | 
 | ||||||
|  | @ -56,3 +57,30 @@ export const getUsers = async (token: string) => { | ||||||
| 
 | 
 | ||||||
| 	return res ? res : []; | 	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 toast from 'svelte-french-toast'; | ||||||
| 
 | 
 | ||||||
| 	import { updateUserRole, getUsers } from '$lib/apis/users'; | 	import { updateUserRole, getUsers, deleteUserById } from '$lib/apis/users'; | ||||||
| 
 | 
 | ||||||
| 	let loaded = false; | 	let loaded = false; | ||||||
| 	let users = []; | 	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 () => { | 	onMount(async () => { | ||||||
| 		if ($user?.role !== 'admin') { | 		if ($user?.role !== 'admin') { | ||||||
| 			await goto('/'); | 			await goto('/'); | ||||||
|  | @ -55,7 +65,7 @@ | ||||||
| 									<th scope="col" class="px-6 py-3"> Name </th> | 									<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"> Email </th> | ||||||
| 									<th scope="col" class="px-6 py-3"> Role </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> | 								</tr> | ||||||
| 							</thead> | 							</thead> | ||||||
| 							<tbody> | 							<tbody> | ||||||
|  | @ -63,15 +73,16 @@ | ||||||
| 									<tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700"> | 									<tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700"> | ||||||
| 										<th | 										<th | ||||||
| 											scope="row" | 											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"> | 											<div class="flex flex-row"> | ||||||
| 												<img | 												<img | ||||||
| 													class=" rounded-full max-w-[30px] max-h-[30px] object-cover mr-4" | 													class=" rounded-full max-w-[30px] max-h-[30px] object-cover mr-4" | ||||||
| 													src={user.profile_image_url} | 													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> | 											</div> | ||||||
| 										</th> | 										</th> | ||||||
| 										<td class="px-6 py-4"> {user.email} </td> | 										<td class="px-6 py-4"> {user.email} </td> | ||||||
|  | @ -89,9 +100,29 @@ | ||||||
| 												}}>{user.role}</button | 												}}>{user.role}</button | ||||||
| 											> | 											> | ||||||
| 										</td> | 										</td> | ||||||
| 										<!-- <td class="px-6 py-4 text-center"> | 										<td class="px-6 py-4 text-center flex justify-center"> | ||||||
| 											<button class="  text-white underline"> Edit </button> | 											<button | ||||||
| 										</td> --> | 												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> | 									</tr> | ||||||
| 								{/each} | 								{/each} | ||||||
| 							</tbody> | 							</tbody> | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Timothy Jaeryang Baek
						Timothy Jaeryang Baek