diff --git a/api/routers/__pycache__/user.cpython-313.pyc b/api/routers/__pycache__/user.cpython-313.pyc index 51ca486..bc1d435 100644 Binary files a/api/routers/__pycache__/user.cpython-313.pyc and b/api/routers/__pycache__/user.cpython-313.pyc differ diff --git a/api/routers/user.py b/api/routers/user.py index 36025bc..2349729 100644 --- a/api/routers/user.py +++ b/api/routers/user.py @@ -8,8 +8,10 @@ router = APIRouter() @router.get("/", name="userInfo", summary="Получение информации о пользователе") -async def get_user(): - return +async def get_user(request_data: dict = Depends(requestDict)): + userId = int(request_data.get("query").get("userId")) + logger.info(f"Получение информации о пользователе {userId}") + return await UserHandler.get(userId) @router.post("/", summary="Правка данных пользователя") @@ -17,13 +19,14 @@ async def manage_user(request_data: dict = Depends(requestDict)): response = {"status": "error"} userData = request_data.get("body").get("userData", {}) action = request_data.get("body").get("action") - userId = request_data.get("body").get("userId") + userId = int(request_data.get("body").get("userId")) match action: case "create": result = await UserHandler.add(userData, userId) if result: response["status"] = "ok" case "update": + logger.info(f"Обновление данных пользователя {userId}") result = await UserHandler.edit(userData, user_id=userId) if "error" not in result: response["status"] = "ok" @@ -75,6 +78,20 @@ async def authenticationPage(request: Request): return await render(request) +@router.post("/check", summary="Проверка авторизации") +async def check_authentication(request_data: dict = Depends(requestDict)): + try: + userId = int(request_data.get("body").get("userId")) + logger.info(f"Проверка авторизации пользователя {userId}") + result = await UserHandler.checkActive(userId) + if result: + return {"status": "ok"} + else: + return {"status": "error"} + except: + return {"status": "error"} + + @router.post("/login") async def authentication( request_data: dict = Depends(requestDict), diff --git a/api/static/js/api.js b/api/static/js/api.js index c2783f7..f5148cd 100644 --- a/api/static/js/api.js +++ b/api/static/js/api.js @@ -19,7 +19,7 @@ function hideLoader() { } export async function apiRequest(url, payload = {}, method = 'POST') { - method = method.toUpperCase(); + method = typeof method === 'string' ? method.toUpperCase() : 'POST'; let finalUrl = url; let options = { diff --git a/api/static/js/cookies.js b/api/static/js/cookies.js index f5e774b..3407915 100644 --- a/api/static/js/cookies.js +++ b/api/static/js/cookies.js @@ -26,6 +26,9 @@ export async function setCookie(name, value, days = 180) { const expires = new Date(Date.now() + days * 864e5).toUTCString(); const encodedName = encodeURIComponent(name); + document.cookie = `${encodedName}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/;`; + document.cookie = `${encodedName}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; Secure;`; + // ---------- 1. Пытаемся установить безопасную куку ---------- let secureCookie = `${encodedName}=${cookieValue}; expires=${expires}; path=/; Secure; SameSite=Lax`; document.cookie = secureCookie; @@ -45,6 +48,7 @@ export async function setCookie(name, value, days = 180) { return false; // безопасную куку установить не удалось } + export async function getCookie(name) { const cookies = document.cookie ? document.cookie.split('; ') : []; diff --git a/api/static/js/index.js b/api/static/js/index.js index 3df0e8d..0ad78c1 100644 --- a/api/static/js/index.js +++ b/api/static/js/index.js @@ -67,6 +67,19 @@ const predefinedSpecs = { async function getCookieData() { accessData = await getCookie('toolbox_access'); userData = await getCookie('toolbox_user'); + await checkActiveUser(); +} + +async function checkActiveUser() { + const activeCookie = loadFromStorage('active'); + if (!activeCookie || !activeCookie.active || !activeCookie.datetime || Date.now() - activeCookie.datetime > 12 * 60 * 60 * 1000) { + const checkActive = await apiRequest('/user/check', { userId: userData.id }); + if (checkActive.status === 'ok') { + saveToStorage('active', { active: true, datetime: Date.now() }); + } else { + window.clientManager?.initLogout(); + } + } } async function openTab(event, tabId) { @@ -7209,6 +7222,7 @@ document.addEventListener('DOMContentLoaded', async () => { if (!accessData || !userData) { console.warn('Access data or user data not found'); + console.log(accessData, userData); return; } diff --git a/api/static/js/user.js b/api/static/js/user.js index 44a5832..f8cb1c7 100644 --- a/api/static/js/user.js +++ b/api/static/js/user.js @@ -1,4 +1,5 @@ -import { getCookie, deleteCookie } from '/static/js/cookies.js'; +import { apiRequest } from './api.js'; +import { getCookie, deleteCookie, setCookie } from './cookies.js'; class ClientManager { constructor() { @@ -13,11 +14,9 @@ class ClientManager { this.userData = await getCookie('toolbox_user'); this.accessData = await getCookie('toolbox_access'); - if (!this.userData || !this.accessData) { console.warn('User data or access data not found in cookie'); - this.clearUserCookie(); - window.location.href = '/user/login'; + this.initLogout(); } // Вставляем данные пользователя в DOM @@ -35,8 +34,7 @@ class ClientManager { renderUserInfo() { if (!this.userData) { - // Если нет данных, показываем заглушку - this.renderFallbackUser(); + this.initLogout(); return; } @@ -64,37 +62,6 @@ class ClientManager { } - renderFallbackUser() { - const userCard = document.querySelector('.client-card'); - if (userCard) { - userCard.innerHTML = ` -
-
- Гость - -
-
-
Гость
-
- - - Не авторизован - -
-
-
- - - Войти - -
-
- `; - } - } - initLogoutHandler() { const logoutBtn = document.getElementById('clientLogoutBtn'); if (logoutBtn) { @@ -105,12 +72,18 @@ class ClientManager { } } + initLogout() { + const logoutBtn = document.getElementById('clientLogoutBtn'); + if (logoutBtn) { + this.handleLogout(logoutBtn); + } + } + handleLogout(button) { // Анимация нажатия button.style.transform = 'scale(0.95)'; // Добавляем иконку загрузки - const originalContent = button.innerHTML; button.innerHTML = ` Выход... @@ -162,6 +135,220 @@ class ClientManager { }; } } + + userProfile() { + const avatarBtn = document.getElementById('userProfile'); + + async function openEditUserModal(user = null) { + const isNew = !user; + const modalId = `profile-edit-user-modal`; + + // Удаляем старую модалку, если есть + const existingModal = document.getElementById(modalId); + if (existingModal) { + existingModal.remove(); + } + + const modalHTML = ` + + `; + + document.body.insertAdjacentHTML('beforeend', modalHTML); + const modal = new bootstrap.Modal(document.getElementById(modalId)); + let photoFile = null; + let removePhoto = false; + + // Обработчики для фото + const changePhotoBtn = document.getElementById(`${modalId}-change-photo-btn`); + const photoInput = document.getElementById(`${modalId}-photo-input`); + const photoPreview = document.getElementById(`${modalId}-photo-preview`); + + changePhotoBtn.addEventListener('click', () => { + photoInput.click(); + }); + + photoInput.addEventListener('change', (e) => { + const file = e.target.files[0]; + if (file) { + if (file.type.startsWith('image/')) { + photoFile = file; + const reader = new FileReader(); + reader.onload = (e) => { + photoPreview.src = e.target.result; + }; + reader.readAsDataURL(file); + removePhoto = false; + } else { + showConfirmationModal('Ошибка', 'Пожалуйста, выберите файл изображения', () => { }); + photoInput.value = ''; + } + } + }); + + if (document.getElementById(`${modalId}-remove-photo-btn`)) { + document.getElementById(`${modalId}-remove-photo-btn`).addEventListener('click', () => { + photoPreview.src = 'static/images/users/default.png'; + photoFile = null; + removePhoto = true; + }); + } + + // Обработчик сохранения + document.getElementById(`${modalId}-save-btn`).addEventListener('click', async () => { + const form = document.getElementById(`${modalId}-form`); + if (!form.checkValidity()) { + form.reportValidity(); + return; + } + + const formUserData = { + login: document.getElementById(`${modalId}-login`).value.trim(), + username: document.getElementById(`${modalId}-username`).value.trim(), + }; + + // Добавляем пароль, если он указан + const password = document.getElementById(`${modalId}-password`).value; + if (password || isNew) { + formUserData.password = password || ''; + } + + // Определяем действие + let action = isNew ? 'create' : 'update'; + + // Обработка фото + if (photoFile) { + if (photoFile.size > 5 * 1024 * 1024) { + showInfo('Фото больше 5 МБ, оно не подходит', 'warning'); + return; + } + formUserData.photo = await new Promise((resolve, reject) => { + const reader = new FileReader(); + + reader.onload = () => resolve(reader.result); + reader.onerror = () => reject(new Error('Ошибка чтения файла')); + + reader.readAsDataURL(photoFile); + }); + + } else if (removePhoto) { + formUserData.photo = ''; + } + + let changedUserData = {}; + if (isNew) { + changedUserData = formUserData; + } else { + changedUserData = Object.keys(formUserData).reduce((acc, key) => { + if (formUserData[key] !== user[key]) { + acc[key] = formUserData[key]; + } + return acc; + }, {}); + } + + if (Object.keys(changedUserData).length === 0) { + showInfo('Нет изменений', 'info'); + return; + } + + // Обновляем данные + if (!isNew) { + changedUserData.id = user.id; + } + + const result = await apiRequest('/user/', { action, userData: changedUserData, userId: user.id }); + + if (result && result.status === 'ok') { + const newUserData = await apiRequest('/user/', { userId: user.id }, 'get'); + await setCookie('toolbox_user', JSON.stringify(newUserData)); + const checkData = await getCookie('toolbox_user'); + if (window.clientManager) { + window.clientManager.userData = newUserData; + window.clientManager.renderUserInfo(); + } + if (modal) { modal.hide(); } + } else { + const errorMessageDiv = document.getElementById(`${modalId}-error-message`); + errorMessageDiv.textContent = result.message; + errorMessageDiv.hidden = false; + } + }); + + modal.show(); + + // Удаляем модалку после скрытия + modal._element.addEventListener('hidden.bs.modal', () => { + modal._element.remove(); + }); + } + + avatarBtn.addEventListener('click', async () => { + await openEditUserModal(this.userData); + }); + } } // Инициализация при загрузке документа @@ -174,6 +361,8 @@ document.addEventListener('DOMContentLoaded', () => { elements.forEach((el, index) => { el.style.animationDelay = `${index * 0.1}s`; }); + + window.clientManager.userProfile(); }); // Экспорт для использования в других модулях diff --git a/api/templates/index.html b/api/templates/index.html index 3979a06..71daa20 100644 --- a/api/templates/index.html +++ b/api/templates/index.html @@ -32,7 +32,7 @@
-
+
Загрузка... diff --git a/db/handlers/__pycache__/user.cpython-313.pyc b/db/handlers/__pycache__/user.cpython-313.pyc index 3287e26..d74595c 100644 Binary files a/db/handlers/__pycache__/user.cpython-313.pyc and b/db/handlers/__pycache__/user.cpython-313.pyc differ diff --git a/db/handlers/user.py b/db/handlers/user.py index 88b2855..c13d980 100644 --- a/db/handlers/user.py +++ b/db/handlers/user.py @@ -109,6 +109,20 @@ class UserHandler: else: userData["photo"] = "static/images/users/default.png" deleteImage(user.photo) + if "login" in userData: + uniqueLogin = await CRUD.read( + select(User).where(User.login == userData["login"]) + ) + if uniqueLogin and uniqueLogin.id != user.id: + logger.error("Пользователь с таким логином уже существует") + return {"error": "Пользователь с таким логином уже существует"} + if "username" in userData: + uniqueUserName = await CRUD.read( + select(User).where(User.username == userData["username"]) + ) + if uniqueUserName and uniqueUserName.id != user.id: + logger.error("Пользователь с таким именем уже существует") + return {"error": "Пользователь с таким именем уже существует"} try: userData.pop("id") editedUser = await user.edit(**userData) @@ -156,7 +170,9 @@ class UserHandler: if not user: logger.error("Пользователь с таким id не найден") return {} - return user.toDict() + userdata = user.toDict() + userdata.pop("hashed_password") + return userdata async def delete(id: int, user_id: int = None) -> dict: userRecordsCount = await StocksRecordsHandler.getUserRecords(id) @@ -202,12 +218,26 @@ class UserHandler: ) return True + async def checkActive(id: int) -> bool: + query = select(User).where(User.id == id) + user = await CRUD.read(query) + if not user: + logger.error("Пользователь с таким id не найден") + return False + return user.is_active + async def auth(login: str, password: str) -> dict: query = select(User).where(User.login == login) user = await CRUD.read(query) if not user: logger.error(f"Пользователь с логином {login} не найден") return {} + if not user.is_active: + logger.error(f"Пользователь {user.username} не активен") + await ServiceRecordsHandler.add( + user.id, {"Пользователь не активен": user.username} + ) + return {} if not pwd_verify(password, user.hashed_password): logger.error(f"Неверный пароль пользователя {user.username}") await ServiceRecordsHandler.add(