diff --git a/backend/apps/web/models/auths.py b/backend/apps/web/models/auths.py index 68b284ea..00c66a2c 100644 --- a/backend/apps/web/models/auths.py +++ b/backend/apps/web/models/auths.py @@ -75,26 +75,20 @@ class SignupForm(BaseModel): class AuthsTable: - def __init__(self, db): self.db = db self.db.create_tables([Auth]) - def insert_new_auth(self, - email: str, - password: str, - name: str, - role: str = "pending") -> Optional[UserModel]: + def insert_new_auth( + self, email: str, password: str, name: str, role: str = "pending" + ) -> Optional[UserModel]: print("insert_new_auth") id = str(uuid.uuid4()) - auth = AuthModel(**{ - "id": id, - "email": email, - "password": password, - "active": True - }) + auth = AuthModel( + **{"id": id, "email": email, "password": password, "active": True} + ) result = Auth.create(**auth.model_dump()) user = Users.insert_new_user(id, name, email, role) @@ -104,8 +98,7 @@ class AuthsTable: else: return None - def authenticate_user(self, email: str, - password: str) -> Optional[UserModel]: + def authenticate_user(self, email: str, password: str) -> Optional[UserModel]: print("authenticate_user", email) try: auth = Auth.get(Auth.email == email, Auth.active == True) @@ -129,6 +122,15 @@ class AuthsTable: except: return False + def update_email_by_id(self, id: str, email: str) -> bool: + try: + query = Auth.update(email=email).where(Auth.id == id) + result = query.execute() + + return True if result == 1 else False + except: + return False + def delete_auth_by_id(self, id: str) -> bool: try: # Delete User @@ -137,8 +139,7 @@ class AuthsTable: if result: # Delete Auth query = Auth.delete().where(Auth.id == id) - query.execute( - ) # Remove the rows, return number of rows removed. + query.execute() # Remove the rows, return number of rows removed. return True else: diff --git a/backend/apps/web/models/users.py b/backend/apps/web/models/users.py index dc480882..f86697f4 100644 --- a/backend/apps/web/models/users.py +++ b/backend/apps/web/models/users.py @@ -44,17 +44,21 @@ class UserRoleUpdateForm(BaseModel): role: str -class UsersTable: +class UserUpdateForm(BaseModel): + name: str + email: str + profile_image_url: str + password: Optional[str] = None + +class UsersTable: def __init__(self, db): self.db = db self.db.create_tables([User]) - def insert_new_user(self, - id: str, - name: str, - email: str, - role: str = "pending") -> Optional[UserModel]: + def insert_new_user( + self, id: str, name: str, email: str, role: str = "pending" + ) -> Optional[UserModel]: user = UserModel( **{ "id": id, @@ -63,7 +67,8 @@ class UsersTable: "role": role, "profile_image_url": get_gravatar_url(email), "timestamp": int(time.time()), - }) + } + ) result = User.create(**user.model_dump()) if result: return user @@ -93,8 +98,7 @@ class UsersTable: def get_num_users(self) -> Optional[int]: return User.select().count() - def update_user_role_by_id(self, id: str, - role: str) -> Optional[UserModel]: + def update_user_role_by_id(self, id: str, role: str) -> Optional[UserModel]: try: query = User.update(role=role).where(User.id == id) query.execute() @@ -104,6 +108,16 @@ class UsersTable: except: return None + def update_user_by_id(self, id: str, updated: dict) -> Optional[UserModel]: + try: + query = User.update(**updated).where(User.id == id) + query.execute() + + user = User.get(User.id == id) + return UserModel(**model_to_dict(user)) + except: + return None + def delete_user_by_id(self, id: str) -> bool: try: # Delete User Chats @@ -112,8 +126,7 @@ class UsersTable: if result: # Delete User query = User.delete().where(User.id == id) - query.execute( - ) # Remove the rows, return number of rows removed. + query.execute() # Remove the rows, return number of rows removed. return True else: diff --git a/backend/apps/web/routers/users.py b/backend/apps/web/routers/users.py index 3281ac65..f478003c 100644 --- a/backend/apps/web/routers/users.py +++ b/backend/apps/web/routers/users.py @@ -8,10 +8,10 @@ from pydantic import BaseModel import time import uuid -from apps.web.models.users import UserModel, UserRoleUpdateForm, Users +from apps.web.models.users import UserModel, UserUpdateForm, UserRoleUpdateForm, Users from apps.web.models.auths import Auths -from utils.utils import get_current_user +from utils.utils import get_current_user, get_password_hash from constants import ERROR_MESSAGES router = APIRouter() @@ -22,9 +22,7 @@ router = APIRouter() @router.get("/", response_model=List[UserModel]) -async def get_users(skip: int = 0, - limit: int = 50, - user=Depends(get_current_user)): +async def get_users(skip: int = 0, limit: int = 50, user=Depends(get_current_user)): if user.role != "admin": raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, @@ -34,25 +32,58 @@ async def get_users(skip: int = 0, ############################ -# UpdateUserRole +# UpdateUserById ############################ -@router.post("/update/role", response_model=Optional[UserModel]) -async def update_user_role(form_data: UserRoleUpdateForm, - user=Depends(get_current_user)): - if user.role != "admin": +@router.post("/{user_id}/update", response_model=Optional[UserModel]) +async def update_user_by_id( + user_id: str, form_data: UserUpdateForm, session_user=Depends(get_current_user) +): + if session_user.role != "admin": raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED, ) - if user.id != form_data.id: - return Users.update_user_role_by_id(form_data.id, form_data.role) + user = Users.get_user_by_id(user_id) + + if user: + if form_data.email != user.email: + email_user = Users.get_user_by_email(form_data.email) + if email_user: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=ERROR_MESSAGES.EMAIL_TAKEN, + ) + + if form_data.password: + hashed = get_password_hash(form_data.password) + print(hashed) + Auths.update_user_password_by_id(user_id, hashed) + + Auths.update_email_by_id(user_id, form_data.email) + updated_user = Users.update_user_by_id( + user_id, + { + "name": form_data.name, + "email": form_data.email, + "profile_image_url": form_data.profile_image_url, + }, + ) + + if updated_user: + return updated_user + else: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=ERROR_MESSAGES.DEFAULT(), + ) + else: raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail=ERROR_MESSAGES.ACTION_PROHIBITED, + status_code=status.HTTP_400_BAD_REQUEST, + detail=ERROR_MESSAGES.USER_NOT_FOUND, ) diff --git a/package-lock.json b/package-lock.json index 166f5ce8..a234d4e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.1", "dependencies": { "@sveltejs/adapter-node": "^1.3.1", + "dayjs": "^1.11.10", "file-saver": "^2.0.5", "highlight.js": "^11.9.0", "idb": "^7.1.1", @@ -1577,6 +1578,11 @@ "node": ">=4" } }, + "node_modules/dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -4940,6 +4946,11 @@ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true }, + "dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", diff --git a/package.json b/package.json index 456b5d06..85373805 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "type": "module", "dependencies": { "@sveltejs/adapter-node": "^1.3.1", + "dayjs": "^1.11.10", "file-saver": "^2.0.5", "highlight.js": "^11.9.0", "idb": "^7.1.1", diff --git a/src/lib/apis/users/index.ts b/src/lib/apis/users/index.ts index 3fca8b99..3faeb8c4 100644 --- a/src/lib/apis/users/index.ts +++ b/src/lib/apis/users/index.ts @@ -84,3 +84,43 @@ export const deleteUserById = async (token: string, userId: string) => { return res; }; + +type UserUpdateForm = { + profile_image_url: string; + email: string; + name: string; + password: string; +}; + +export const updateUserById = async (token: string, userId: string, user: UserUpdateForm) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/users/${userId}/update`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` + }, + body: JSON.stringify({ + profile_image_url: user.profile_image_url, + email: user.email, + name: user.name, + password: user.password !== '' ? user.password : undefined + }) + }) + .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; +}; diff --git a/src/lib/components/admin/EditUserModal.svelte b/src/lib/components/admin/EditUserModal.svelte new file mode 100644 index 00000000..09005b30 --- /dev/null +++ b/src/lib/components/admin/EditUserModal.svelte @@ -0,0 +1,172 @@ + + + +
+
+
Edit User
+ +
+
+ +
+
+
{ + submitHandler(); + }} + > +
+
+ User profile +
+ +
+
{selectedUser.name}
+ +
+ Created at {dayjs(selectedUser.timestamp * 1000).format('MMMM DD, YYYY')} +
+
+
+ +
+ +
+
+
Email
+ +
+ +
+
+ +
+
Name
+ +
+ +
+
+ +
+
New Password
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+
+ + diff --git a/src/lib/components/common/Modal.svelte b/src/lib/components/common/Modal.svelte index a8ad70e8..0fb3f8bd 100644 --- a/src/lib/components/common/Modal.svelte +++ b/src/lib/components/common/Modal.svelte @@ -3,8 +3,18 @@ import { fade, blur } from 'svelte/transition'; export let show = true; + export let size = 'md'; + let mounted = false; + const sizeToWidth = (size) => { + if (size === 'sm') { + return 'w-[30rem]'; + } else { + return 'w-[40rem]'; + } + }; + onMount(() => { mounted = true; }); @@ -28,7 +38,9 @@ }} >
{ e.stopPropagation(); diff --git a/src/routes/(app)/admin/+page.svelte b/src/routes/(app)/admin/+page.svelte index 1d7ec44f..92ce7ad2 100644 --- a/src/routes/(app)/admin/+page.svelte +++ b/src/routes/(app)/admin/+page.svelte @@ -8,11 +8,15 @@ import { updateUserRole, getUsers, deleteUserById } from '$lib/apis/users'; import { getSignUpEnabledStatus, toggleSignUpEnabledStatus } from '$lib/apis/auths'; + import EditUserModal from '$lib/components/admin/EditUserModal.svelte'; let loaded = false; let users = []; + let selectedUser = null; + let signUpEnabled = true; + let showEditUserModal = false; const updateRoleHandler = async (id, role) => { const res = await updateUserRole(localStorage.token, id, role).catch((error) => { @@ -25,6 +29,17 @@ } }; + const editUserPasswordHandler = async (id, password) => { + const res = await deleteUserById(localStorage.token, id).catch((error) => { + toast.error(error); + return null; + }); + if (res) { + users = await getUsers(localStorage.token); + toast.success('Successfully updated'); + } + }; + const deleteUserHandler = async (id) => { const res = await deleteUserById(localStorage.token, id).catch((error) => { toast.error(error); @@ -51,6 +66,17 @@ }); +{#key selectedUser} + { + users = await getUsers(localStorage.token); + }} + /> +{/key} +
@@ -154,7 +180,28 @@ }}>{user.role} - + + +