- Реализовано хранение сессий в базе данных SQLite с помощью connect-sqlite3 - Добавлены API для проверки статуса аутентификации - Обновлены клиентские скрипты для управления состоянием аутентификации - Добавлены проверки аутентификации на страницах входа и профиля - Улучшено управление состоянием аутентификации в localStorage
306 lines
10 KiB
JavaScript
306 lines
10 KiB
JavaScript
// DOM элементы
|
||
const avatarImage = document.getElementById("avatarImage");
|
||
const avatarPlaceholder = document.getElementById("avatarPlaceholder");
|
||
const avatarInput = document.getElementById("avatarInput");
|
||
const deleteAvatarBtn = document.getElementById("deleteAvatarBtn");
|
||
const usernameInput = document.getElementById("username");
|
||
const emailInput = document.getElementById("email");
|
||
const updateProfileBtn = document.getElementById("updateProfileBtn");
|
||
const currentPasswordInput = document.getElementById("currentPassword");
|
||
const newPasswordInput = document.getElementById("newPassword");
|
||
const confirmPasswordInput = document.getElementById("confirmPassword");
|
||
const changePasswordBtn = document.getElementById("changePasswordBtn");
|
||
const messageContainer = document.getElementById("messageContainer");
|
||
|
||
// Функция для показа сообщений
|
||
function showMessage(message, type = "success") {
|
||
messageContainer.textContent = message;
|
||
messageContainer.className = `message-container ${type}`;
|
||
messageContainer.style.display = "block";
|
||
|
||
setTimeout(() => {
|
||
messageContainer.style.display = "none";
|
||
}, 5000);
|
||
}
|
||
|
||
// Загрузка данных профиля
|
||
async function loadProfile() {
|
||
try {
|
||
const response = await fetch("/api/user");
|
||
if (!response.ok) {
|
||
throw new Error("Ошибка загрузки профиля");
|
||
}
|
||
|
||
const user = await response.json();
|
||
|
||
// Заполняем поля
|
||
usernameInput.value = user.username || "";
|
||
emailInput.value = user.email || "";
|
||
|
||
// Обрабатываем аватарку
|
||
if (user.avatar) {
|
||
avatarImage.src = user.avatar;
|
||
avatarImage.style.display = "block";
|
||
avatarPlaceholder.style.display = "none";
|
||
deleteAvatarBtn.style.display = "inline-block";
|
||
} else {
|
||
avatarImage.style.display = "none";
|
||
avatarPlaceholder.style.display = "inline-flex";
|
||
deleteAvatarBtn.style.display = "none";
|
||
}
|
||
} catch (error) {
|
||
console.error("Ошибка загрузки профиля:", error);
|
||
showMessage("Ошибка загрузки данных профиля", "error");
|
||
}
|
||
}
|
||
|
||
// Обработчик загрузки аватарки
|
||
avatarInput.addEventListener("change", async function (event) {
|
||
const file = event.target.files[0];
|
||
if (!file) return;
|
||
|
||
// Проверка размера файла (5MB)
|
||
if (file.size > 5 * 1024 * 1024) {
|
||
showMessage("Файл слишком большой. Максимальный размер: 5 МБ", "error");
|
||
return;
|
||
}
|
||
|
||
// Проверка типа файла
|
||
const allowedTypes = ["image/jpeg", "image/jpg", "image/png", "image/gif"];
|
||
if (!allowedTypes.includes(file.type)) {
|
||
showMessage(
|
||
"Недопустимый формат файла. Используйте JPG, PNG или GIF",
|
||
"error"
|
||
);
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const formData = new FormData();
|
||
formData.append("avatar", file);
|
||
|
||
const response = await fetch("/api/user/avatar", {
|
||
method: "POST",
|
||
body: formData,
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const error = await response.json();
|
||
throw new Error(error.error || "Ошибка загрузки аватарки");
|
||
}
|
||
|
||
const result = await response.json();
|
||
|
||
// Обновляем отображение аватарки
|
||
avatarImage.src = result.avatar + "?t=" + Date.now(); // Добавляем timestamp для обновления кэша
|
||
avatarImage.style.display = "block";
|
||
avatarPlaceholder.style.display = "none";
|
||
deleteAvatarBtn.style.display = "inline-block";
|
||
|
||
showMessage("Аватарка успешно загружена", "success");
|
||
} catch (error) {
|
||
console.error("Ошибка загрузки аватарки:", error);
|
||
showMessage(error.message, "error");
|
||
}
|
||
|
||
// Сбрасываем input для возможности повторной загрузки того же файла
|
||
event.target.value = "";
|
||
});
|
||
|
||
// Обработчик удаления аватарки
|
||
deleteAvatarBtn.addEventListener("click", async function () {
|
||
if (!confirm("Вы уверены, что хотите удалить аватарку?")) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch("/api/user/avatar", {
|
||
method: "DELETE",
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const error = await response.json();
|
||
throw new Error(error.error || "Ошибка удаления аватарки");
|
||
}
|
||
|
||
// Обновляем отображение
|
||
avatarImage.style.display = "none";
|
||
avatarPlaceholder.style.display = "inline-flex";
|
||
deleteAvatarBtn.style.display = "none";
|
||
|
||
showMessage("Аватарка успешно удалена", "success");
|
||
} catch (error) {
|
||
console.error("Ошибка удаления аватарки:", error);
|
||
showMessage(error.message, "error");
|
||
}
|
||
});
|
||
|
||
// Обработчик обновления профиля
|
||
updateProfileBtn.addEventListener("click", async function () {
|
||
const username = usernameInput.value.trim();
|
||
const email = emailInput.value.trim();
|
||
|
||
// Валидация
|
||
if (!username) {
|
||
showMessage("Логин не может быть пустым", "error");
|
||
return;
|
||
}
|
||
|
||
if (username.length < 3) {
|
||
showMessage("Логин должен быть не менее 3 символов", "error");
|
||
return;
|
||
}
|
||
|
||
if (email && !isValidEmail(email)) {
|
||
showMessage("Некорректный email адрес", "error");
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch("/api/user/profile", {
|
||
method: "PUT",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
},
|
||
body: JSON.stringify({
|
||
username,
|
||
email: email || null,
|
||
}),
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const error = await response.json();
|
||
throw new Error(error.error || "Ошибка обновления профиля");
|
||
}
|
||
|
||
const result = await response.json();
|
||
showMessage(result.message || "Профиль успешно обновлен", "success");
|
||
} catch (error) {
|
||
console.error("Ошибка обновления профиля:", error);
|
||
showMessage(error.message, "error");
|
||
}
|
||
});
|
||
|
||
// Обработчик изменения пароля
|
||
changePasswordBtn.addEventListener("click", async function () {
|
||
const currentPassword = currentPasswordInput.value;
|
||
const newPassword = newPasswordInput.value;
|
||
const confirmPassword = confirmPasswordInput.value;
|
||
|
||
// Валидация
|
||
if (!currentPassword) {
|
||
showMessage("Введите текущий пароль", "error");
|
||
return;
|
||
}
|
||
|
||
if (!newPassword) {
|
||
showMessage("Введите новый пароль", "error");
|
||
return;
|
||
}
|
||
|
||
if (newPassword.length < 6) {
|
||
showMessage("Новый пароль должен быть не менее 6 символов", "error");
|
||
return;
|
||
}
|
||
|
||
if (newPassword !== confirmPassword) {
|
||
showMessage("Новый пароль и подтверждение не совпадают", "error");
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch("/api/user/profile", {
|
||
method: "PUT",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
},
|
||
body: JSON.stringify({
|
||
currentPassword,
|
||
newPassword,
|
||
}),
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const error = await response.json();
|
||
throw new Error(error.error || "Ошибка изменения пароля");
|
||
}
|
||
|
||
const result = await response.json();
|
||
|
||
// Очищаем поля паролей
|
||
currentPasswordInput.value = "";
|
||
newPasswordInput.value = "";
|
||
confirmPasswordInput.value = "";
|
||
|
||
showMessage(result.message || "Пароль успешно изменен", "success");
|
||
} catch (error) {
|
||
console.error("Ошибка изменения пароля:", error);
|
||
showMessage(error.message, "error");
|
||
}
|
||
});
|
||
|
||
// Функция валидации email
|
||
function isValidEmail(email) {
|
||
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||
return re.test(email);
|
||
}
|
||
|
||
// Функция для проверки аутентификации
|
||
async function checkAuthentication() {
|
||
const isAuthenticated = localStorage.getItem('isAuthenticated');
|
||
|
||
if (isAuthenticated !== 'true') {
|
||
// Если пользователь не аутентифицирован, перенаправляем на страницу входа
|
||
window.location.href = "/";
|
||
return;
|
||
}
|
||
|
||
// Проверяем, что сессия на сервере еще действительна
|
||
try {
|
||
const response = await fetch("/api/auth/status");
|
||
if (!response.ok) {
|
||
// Если сессия недействительна, очищаем localStorage и перенаправляем
|
||
localStorage.removeItem('isAuthenticated');
|
||
localStorage.removeItem('username');
|
||
window.location.href = "/";
|
||
return;
|
||
}
|
||
|
||
const authData = await response.json();
|
||
if (!authData.authenticated) {
|
||
// Если сервер говорит, что пользователь не аутентифицирован
|
||
localStorage.removeItem('isAuthenticated');
|
||
localStorage.removeItem('username');
|
||
window.location.href = "/";
|
||
return;
|
||
}
|
||
} catch (error) {
|
||
console.error("Ошибка проверки аутентификации:", error);
|
||
// В случае ошибки сети, оставляем пользователя на странице
|
||
// но показываем предупреждение
|
||
console.warn("Не удалось проверить статус аутентификации");
|
||
}
|
||
}
|
||
|
||
// Функция для настройки обработчика выхода
|
||
function setupLogoutHandler() {
|
||
const logoutForms = document.querySelectorAll('form[action="/logout"]');
|
||
logoutForms.forEach(form => {
|
||
form.addEventListener('submit', function(e) {
|
||
// Очищаем localStorage перед выходом
|
||
localStorage.removeItem('isAuthenticated');
|
||
localStorage.removeItem('username');
|
||
});
|
||
});
|
||
}
|
||
|
||
// Загружаем профиль при загрузке страницы
|
||
document.addEventListener("DOMContentLoaded", function () {
|
||
// Проверяем аутентификацию при загрузке страницы
|
||
checkAuthentication();
|
||
loadProfile();
|
||
|
||
// Добавляем обработчик для кнопки выхода
|
||
setupLogoutHandler();
|
||
});
|