Добавлена функция удаления аккаунта пользователя

- Реализован новый API-эндпоинт для удаления аккаунта с подтверждением пароля.
- Добавлено модальное окно для подтверждения удаления аккаунта на странице профиля.
- Обновлены стили и логика для предпросмотра заметок с учетом текущей темы.
- Улучшены обработчики событий для кнопки удаления аккаунта и модального окна.
This commit is contained in:
Fovway 2025-10-28 01:03:16 +07:00
parent 59992e54dd
commit 155f4303d5
5 changed files with 677 additions and 9 deletions

View File

@ -1170,13 +1170,16 @@ function togglePreview() {
const contentWithTags = makeTagsClickable(htmlContent); const contentWithTags = makeTagsClickable(htmlContent);
notePreviewContent.innerHTML = contentWithTags; notePreviewContent.innerHTML = contentWithTags;
// Применяем текущую тему к предпросмотру
applyThemeToPreview();
// Инициализируем lazy loading для изображений в превью // Инициализируем lazy loading для изображений в превью
setTimeout(() => { setTimeout(() => {
initLazyLoading(); initLazyLoading();
}, 0); }, 0);
} else { } else {
notePreviewContent.innerHTML = notePreviewContent.innerHTML =
'<p style="color: #999; font-style: italic;">Нет содержимого для предпросмотра</p>'; '<p style="color: var(--text-muted); font-style: italic;">Нет содержимого для предпросмотра</p>';
} }
// Меняем иконку кнопки // Меняем иконку кнопки
@ -1194,6 +1197,98 @@ function togglePreview() {
} }
} }
// Функция применения темы к предпросмотру
function applyThemeToPreview() {
if (!notePreviewContainer || notePreviewContainer.style.display === "none") {
return;
}
const currentTheme = document.documentElement.getAttribute("data-theme");
// Применяем тему к контейнеру предпросмотра
if (currentTheme === "dark") {
notePreviewContainer.setAttribute("data-theme", "dark");
} else {
notePreviewContainer.removeAttribute("data-theme");
}
// Обновляем стили для элементов внутри предпросмотра
const previewElements = notePreviewContent.querySelectorAll("*");
previewElements.forEach((element) => {
// Применяем тему к элементам кода
if (element.tagName === "CODE" || element.tagName === "PRE") {
if (currentTheme === "dark") {
element.style.backgroundColor = "var(--bg-quaternary)";
element.style.color = "#e6e6e6";
element.style.border = "1px solid var(--border-primary)";
} else {
element.style.backgroundColor = "var(--bg-quaternary)";
element.style.color = "var(--text-primary)";
element.style.border = "1px solid var(--border-primary)";
}
}
// Применяем тему к цитатам
if (element.tagName === "BLOCKQUOTE") {
if (currentTheme === "dark") {
element.style.backgroundColor = "var(--bg-tertiary)";
element.style.borderLeftColor = "var(--accent-color, #4a9eff)";
element.style.color = "var(--text-secondary)";
} else {
element.style.backgroundColor = "var(--bg-tertiary)";
element.style.borderLeftColor = "var(--accent-color, #007bff)";
element.style.color = "var(--text-secondary)";
}
}
});
}
// Функция применения темы к предпросмотру в режиме редактирования
function applyThemeToEditPreview(editPreviewContainer, editPreviewContent) {
if (!editPreviewContainer || editPreviewContainer.style.display === "none") {
return;
}
const currentTheme = document.documentElement.getAttribute("data-theme");
// Применяем тему к контейнеру предпросмотра редактирования
if (currentTheme === "dark") {
editPreviewContainer.setAttribute("data-theme", "dark");
} else {
editPreviewContainer.removeAttribute("data-theme");
}
// Обновляем стили для элементов внутри предпросмотра редактирования
const previewElements = editPreviewContent.querySelectorAll("*");
previewElements.forEach((element) => {
// Применяем тему к элементам кода
if (element.tagName === "CODE" || element.tagName === "PRE") {
if (currentTheme === "dark") {
element.style.backgroundColor = "var(--bg-quaternary)";
element.style.color = "#e6e6e6";
element.style.border = "1px solid var(--border-primary)";
} else {
element.style.backgroundColor = "var(--bg-quaternary)";
element.style.color = "var(--text-primary)";
element.style.border = "1px solid var(--border-primary)";
}
}
// Применяем тему к цитатам
if (element.tagName === "BLOCKQUOTE") {
if (currentTheme === "dark") {
element.style.backgroundColor = "var(--bg-tertiary)";
element.style.borderLeftColor = "var(--accent-color, #4a9eff)";
element.style.color = "var(--text-secondary)";
} else {
element.style.backgroundColor = "var(--bg-tertiary)";
element.style.borderLeftColor = "var(--accent-color, #007bff)";
element.style.color = "var(--text-secondary)";
}
}
});
}
// Обработчик выбора файлов // Обработчик выбора файлов
imageInput.addEventListener("change", function (event) { imageInput.addEventListener("change", function (event) {
const files = Array.from(event.target.files); const files = Array.from(event.target.files);
@ -2566,13 +2661,19 @@ function addNoteEventListeners() {
const contentWithTags = makeTagsClickable(htmlContent); const contentWithTags = makeTagsClickable(htmlContent);
editPreviewContent.innerHTML = contentWithTags; editPreviewContent.innerHTML = contentWithTags;
// Применяем текущую тему к предпросмотру редактирования
applyThemeToEditPreview(
editPreviewContainer,
editPreviewContent
);
// Инициализируем lazy loading для изображений в превью // Инициализируем lazy loading для изображений в превью
setTimeout(() => { setTimeout(() => {
initLazyLoading(); initLazyLoading();
}, 0); }, 0);
} else { } else {
editPreviewContent.innerHTML = editPreviewContent.innerHTML =
'<p style="color: #999; font-style: italic;">Нет содержимого для предпросмотра</p>'; '<p style="color: var(--text-muted); font-style: italic;">Нет содержимого для предпросмотра</p>';
} }
// Меняем иконку кнопки // Меняем иконку кнопки
@ -4068,6 +4169,11 @@ function applyTheme(theme) {
); );
} }
} }
// Применяем тему к предпросмотру, если он открыт
if (isPreviewMode) {
applyThemeToPreview();
}
} }
// Инициализируем переключатель темы при загрузке страницы // Инициализируем переключатель темы при загрузке страницы

View File

@ -189,6 +189,61 @@
<button id="changePasswordBtn" class="btnSave"> <button id="changePasswordBtn" class="btnSave">
Изменить пароль Изменить пароль
</button> </button>
<hr class="separator" />
<button id="deleteAccountBtn" class="btn-danger">
<span class="iconify" data-icon="mdi:account-remove"></span>
Удалить аккаунт
</button>
<p style="color: #666; font-size: 14px; margin-bottom: 15px">
Удаление аккаунта - это необратимое действие. Все ваши заметки,
изображения и данные будут удалены навсегда.
</p>
</div>
</div>
</div>
<!-- Модальное окно подтверждения удаления аккаунта -->
<div id="deleteAccountModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3 style="color: #dc3545">Удаление аккаунта</h3>
<span class="modal-close" id="deleteAccountModalClose">&times;</span>
</div>
<div class="modal-body">
<p style="color: #dc3545; font-weight: bold; margin-bottom: 15px">
⚠️ ВНИМАНИЕ: Это действие нельзя отменить!
</p>
<p style="margin-bottom: 20px">
Вы действительно хотите удалить свой аккаунт? Все ваши заметки,
изображения, настройки и данные будут удалены навсегда.
</p>
<div style="margin-bottom: 15px">
<label
for="deleteAccountPassword"
style="display: block; margin-bottom: 5px; font-weight: bold"
>
Введите пароль для подтверждения:
</label>
<input
type="password"
id="deleteAccountPassword"
placeholder="Пароль от аккаунта"
class="modal-password-input"
/>
</div>
</div>
<div class="modal-footer">
<button
id="confirmDeleteAccount"
class="btn-danger"
style="margin-right: 10px"
>
<span class="iconify" data-icon="mdi:account-remove"></span> Удалить
аккаунт
</button>
<button id="cancelDeleteAccount" class="btn-secondary">Отмена</button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -688,6 +688,99 @@ function setupLogoutHandler() {
}); });
} }
// Функция для инициализации обработчика удаления аккаунта
function initDeleteAccountHandler() {
const deleteAccountBtn = document.getElementById("deleteAccountBtn");
const modal = document.getElementById("deleteAccountModal");
const closeBtn = document.getElementById("deleteAccountModalClose");
const cancelBtn = document.getElementById("cancelDeleteAccount");
const confirmBtn = document.getElementById("confirmDeleteAccount");
const passwordInput = document.getElementById("deleteAccountPassword");
// Открытие модального окна
deleteAccountBtn.addEventListener("click", () => {
// Очищаем поле пароля и открываем модальное окно
passwordInput.value = "";
modal.style.display = "block";
passwordInput.focus();
});
// Закрытие модального окна
function closeModal() {
modal.style.display = "none";
passwordInput.value = "";
}
closeBtn.addEventListener("click", closeModal);
cancelBtn.addEventListener("click", closeModal);
// Закрытие при клике вне модального окна
window.addEventListener("click", (e) => {
if (e.target === modal) {
closeModal();
}
});
// Подтверждение удаления
confirmBtn.addEventListener("click", async () => {
const password = passwordInput.value.trim();
if (!password) {
showNotification("Введите пароль", "warning");
passwordInput.focus();
return;
}
// Блокируем кнопку во время выполнения
confirmBtn.disabled = true;
confirmBtn.innerHTML =
'<span class="iconify" data-icon="mdi:loading"></span> Удаление...';
try {
const response = await fetch("/api/user/delete-account", {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ password }),
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || "Ошибка удаления аккаунта");
}
// Успешное удаление
showNotification("Аккаунт успешно удален", "success");
// Очищаем localStorage
localStorage.removeItem("isAuthenticated");
localStorage.removeItem("username");
// Перенаправляем на главную страницу
setTimeout(() => {
window.location.href = "/";
}, 2000);
} catch (error) {
console.error("Ошибка:", error);
showNotification(error.message || "Ошибка удаления аккаунта", "error");
} finally {
// Разблокируем кнопку
confirmBtn.disabled = false;
confirmBtn.innerHTML =
'<span class="iconify" data-icon="mdi:account-remove"></span> Удалить аккаунт';
}
});
// Обработка Enter в поле пароля
passwordInput.addEventListener("keypress", (e) => {
if (e.key === "Enter") {
confirmBtn.click();
}
});
}
// Загружаем профиль при загрузке страницы // Загружаем профиль при загрузке страницы
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {
// Проверяем аутентификацию при загрузке страницы // Проверяем аутентификацию при загрузке страницы
@ -699,4 +792,7 @@ document.addEventListener("DOMContentLoaded", function () {
// Добавляем обработчик для кнопки выхода // Добавляем обработчик для кнопки выхода
setupLogoutHandler(); setupLogoutHandler();
// Инициализируем обработчик удаления аккаунта
initDeleteAccountHandler();
}); });

View File

@ -1957,12 +1957,13 @@ textarea:focus {
.note-preview-container { .note-preview-container {
margin: 15px 0; margin: 15px 0;
padding: 15px; padding: 15px;
background: #f8f9fa; background: var(--bg-tertiary);
border: 2px solid #007bff; border: 2px solid var(--accent-color, #007bff);
border-radius: 8px; border-radius: 8px;
min-height: 200px; min-height: 200px;
max-height: 600px; max-height: 600px;
overflow-y: auto; overflow-y: auto;
transition: background-color 0.3s ease, border-color 0.3s ease;
} }
.note-preview-header { .note-preview-header {
@ -1971,17 +1972,20 @@ textarea:focus {
align-items: center; align-items: center;
margin-bottom: 15px; margin-bottom: 15px;
padding-bottom: 10px; padding-bottom: 10px;
border-bottom: 2px solid #dee2e6; border-bottom: 2px solid var(--border-primary);
font-weight: 600; font-weight: 600;
color: #007bff; color: var(--accent-color, #007bff);
font-size: 16px; font-size: 16px;
transition: color 0.3s ease, border-color 0.3s ease;
} }
.note-preview-content { .note-preview-content {
background: white; background: var(--bg-secondary);
color: var(--text-primary);
padding: 15px; padding: 15px;
border-radius: 6px; border-radius: 6px;
min-height: 150px; min-height: 150px;
transition: background-color 0.3s ease, color 0.3s ease;
} }
.note-preview-content h1, .note-preview-content h1,
@ -1992,32 +1996,63 @@ textarea:focus {
.note-preview-content h6 { .note-preview-content h6 {
margin-top: 0.5em; margin-top: 0.5em;
margin-bottom: 0.5em; margin-bottom: 0.5em;
color: var(--text-primary);
transition: color 0.3s ease;
} }
.note-preview-content p { .note-preview-content p {
margin: 0.5em 0; margin: 0.5em 0;
color: var(--text-primary);
transition: color 0.3s ease;
} }
.note-preview-content ul, .note-preview-content ul,
.note-preview-content ol { .note-preview-content ol {
margin: 0.5em 0; margin: 0.5em 0;
padding-left: 2em; padding-left: 2em;
color: var(--text-primary);
transition: color 0.3s ease;
}
.note-preview-content li {
color: var(--text-primary);
transition: color 0.3s ease;
}
.note-preview-content a {
color: var(--accent-color, #007bff);
text-decoration: none;
transition: color 0.3s ease;
}
.note-preview-content a:hover {
text-decoration: underline;
} }
.note-preview-content code { .note-preview-content code {
background: var(--bg-tertiary); background: var(--bg-quaternary);
color: var(--text-primary); color: var(--text-primary);
padding: 2px 6px; padding: 2px 6px;
border-radius: 4px; border-radius: 4px;
font-family: "Courier New", monospace; font-family: "Courier New", monospace;
transition: background-color 0.3s ease, color 0.3s ease;
} }
.note-preview-content pre { .note-preview-content pre {
background: var(--bg-tertiary); background: var(--bg-quaternary);
color: var(--text-primary); color: var(--text-primary);
padding: 10px; padding: 10px;
border-radius: 6px; border-radius: 6px;
overflow-x: auto; overflow-x: auto;
border: 1px solid var(--border-primary);
transition: background-color 0.3s ease, color 0.3s ease,
border-color 0.3s ease;
}
.note-preview-content pre code {
background: transparent;
padding: 0;
border-radius: 0;
} }
.note-preview-content blockquote { .note-preview-content blockquote {
@ -2028,6 +2063,13 @@ textarea:focus {
background-color: var(--bg-tertiary); background-color: var(--bg-tertiary);
padding: 8px 15px; padding: 8px 15px;
border-radius: 0 4px 4px 0; border-radius: 0 4px 4px 0;
transition: color 0.3s ease, background-color 0.3s ease,
border-color 0.3s ease;
}
.note-preview-content blockquote p {
color: var(--text-secondary);
margin: 0;
} }
.note-preview-content img { .note-preview-content img {
@ -2035,6 +2077,192 @@ textarea:focus {
height: auto; height: auto;
border-radius: 6px; border-radius: 6px;
margin: 10px 0; margin: 10px 0;
box-shadow: 0 2px 4px var(--shadow-light);
transition: box-shadow 0.3s ease;
}
.note-preview-content table {
width: 100%;
border-collapse: collapse;
margin: 10px 0;
background: var(--bg-secondary);
border-radius: 6px;
overflow: hidden;
box-shadow: 0 1px 3px var(--shadow-light);
transition: background-color 0.3s ease, box-shadow 0.3s ease;
}
.note-preview-content th,
.note-preview-content td {
padding: 8px 12px;
text-align: left;
border-bottom: 1px solid var(--border-primary);
color: var(--text-primary);
transition: color 0.3s ease, border-color 0.3s ease;
}
.note-preview-content th {
background: var(--bg-tertiary);
font-weight: bold;
color: var(--text-primary);
transition: background-color 0.3s ease, color 0.3s ease;
}
.note-preview-content tr:hover {
background: var(--bg-quaternary);
transition: background-color 0.3s ease;
}
.note-preview-content hr {
border: none;
height: 1px;
background: var(--border-primary);
margin: 20px 0;
transition: background-color 0.3s ease;
}
/* Специальные стили для темной темы в предпросмотре */
[data-theme="dark"] .note-preview-container {
background: var(--bg-tertiary);
border-color: var(--accent-color, #4a9eff);
}
[data-theme="dark"] .note-preview-header {
color: var(--accent-color, #4a9eff);
border-bottom-color: var(--border-primary);
}
[data-theme="dark"] .note-preview-content {
background: var(--bg-secondary);
color: var(--text-primary);
}
[data-theme="dark"] .note-preview-content code {
background: var(--bg-quaternary);
color: #e6e6e6;
border: 1px solid var(--border-primary);
}
[data-theme="dark"] .note-preview-content pre {
background: var(--bg-quaternary);
color: #e6e6e6;
border: 1px solid var(--border-primary);
}
[data-theme="dark"] .note-preview-content pre code {
background: transparent;
border: none;
color: #e6e6e6;
}
[data-theme="dark"] .note-preview-content blockquote {
background-color: var(--bg-tertiary);
border-left-color: var(--accent-color, #4a9eff);
color: var(--text-secondary);
}
[data-theme="dark"] .note-preview-content table {
background: var(--bg-secondary);
box-shadow: 0 1px 3px var(--shadow-light);
}
[data-theme="dark"] .note-preview-content th {
background: var(--bg-tertiary);
color: var(--text-primary);
}
[data-theme="dark"] .note-preview-content th,
[data-theme="dark"] .note-preview-content td {
border-bottom-color: var(--border-primary);
color: var(--text-primary);
}
[data-theme="dark"] .note-preview-content tr:hover {
background: var(--bg-quaternary);
}
[data-theme="dark"] .note-preview-content img {
box-shadow: 0 2px 4px var(--shadow-light);
}
/* Стили для чекбоксов в предпросмотре */
.note-preview-content input[type="checkbox"] {
cursor: pointer;
margin-right: 8px;
width: 18px;
height: 18px;
accent-color: var(--accent-color, #007bff);
vertical-align: middle;
position: relative;
top: -1px;
}
/* Стили для элементов списка с чекбоксами в предпросмотре */
.note-preview-content .task-list-item {
list-style-type: none;
margin-left: -20px;
padding: 0;
line-height: 1.5;
transition: all 0.3s ease;
}
.note-preview-content .task-list-item:has(input[type="checkbox"]:checked) {
opacity: 0.65;
}
.note-preview-content .task-list-item input[type="checkbox"]:checked ~ * {
text-decoration: line-through;
color: var(--text-muted);
}
.note-preview-content .task-list-item:hover {
background-color: rgba(0, 123, 255, 0.05);
border-radius: 4px;
padding-left: 4px;
margin-left: -24px;
}
/* Альтернативный вариант для перечеркивания всего текста в элементе */
.note-preview-content input[type="checkbox"]:checked {
text-decoration: none;
}
.note-preview-content input[type="checkbox"]:checked + * {
text-decoration: line-through;
color: var(--text-muted);
}
/* Если marked.js не добавляет класс task-list-item, используем :has селектор */
.note-preview-content li:has(input[type="checkbox"]) {
list-style-type: none;
margin-left: -20px;
padding: 0;
line-height: 1.5;
transition: all 0.3s ease;
}
.note-preview-content li:has(input[type="checkbox"]:checked) {
opacity: 0.65;
}
.note-preview-content
li:has(input[type="checkbox"])
input[type="checkbox"]:checked {
text-decoration: none;
}
.note-preview-content li:has(input[type="checkbox"]:checked) label,
.note-preview-content li:has(input[type="checkbox"]:checked) span,
.note-preview-content li:has(input[type="checkbox"]:checked) p {
text-decoration: line-through;
color: var(--text-muted);
}
.note-preview-content li:has(input[type="checkbox"]):hover {
background-color: rgba(0, 123, 255, 0.05);
border-radius: 4px;
padding-left: 4px;
margin-left: -24px;
} }
.clear-images-btn { .clear-images-btn {
@ -2621,6 +2849,56 @@ textarea:focus {
opacity: 0.6; opacity: 0.6;
} }
/* Стили для опасной зоны */
.danger-zone {
background-color: var(--bg-secondary);
border: 2px solid #dc3545;
border-radius: 8px;
padding: 20px;
margin-top: 20px;
position: relative;
}
.danger-zone::before {
content: "⚠️";
position: absolute;
top: -10px;
left: 20px;
background-color: var(--bg-primary);
padding: 0 10px;
font-size: 16px;
}
.danger-zone h3 {
color: #dc3545;
margin-top: 0;
margin-bottom: 15px;
font-size: 18px;
font-weight: bold;
}
.danger-zone p {
color: var(--text-secondary);
font-size: 14px;
line-height: 1.5;
margin-bottom: 15px;
}
.danger-zone .btn-danger {
background-color: #dc3545;
border-color: #dc3545;
font-weight: 600;
padding: 12px 24px;
font-size: 15px;
}
.danger-zone .btn-danger:hover {
background-color: #c82333;
border-color: #bd2130;
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(220, 53, 69, 0.3);
}
.btn-secondary { .btn-secondary {
background-color: var(--bg-tertiary); background-color: var(--bg-tertiary);
color: var(--text-primary); color: var(--text-primary);

133
server.js
View File

@ -2109,6 +2109,139 @@ app.post("/logout", (req, res) => {
}); });
}); });
// API для удаления аккаунта
app.delete("/api/user/delete-account", requireApiAuth, async (req, res) => {
const { password } = req.body;
const userId = req.session.userId;
if (!password) {
return res.status(400).json({ error: "Пароль обязателен" });
}
try {
// Получаем пользователя и проверяем пароль
const getUserSql = "SELECT id, password FROM users WHERE id = ?";
db.get(getUserSql, [userId], async (err, user) => {
if (err) {
console.error("Ошибка получения пользователя:", err.message);
return res.status(500).json({ error: "Ошибка сервера" });
}
if (!user) {
return res.status(404).json({ error: "Пользователь не найден" });
}
// Проверяем пароль
const isPasswordValid = await bcrypt.compare(password, user.password);
if (!isPasswordValid) {
return res.status(401).json({ error: "Неверный пароль" });
}
// Получаем аватарку пользователя перед удалением
const getAvatarSql = "SELECT avatar FROM users WHERE id = ?";
db.get(getAvatarSql, [userId], (err, userData) => {
if (err) {
console.error("Ошибка получения аватарки:", err.message);
return res.status(500).json({ error: "Ошибка получения аватарки" });
}
// Удаляем файл аватарки, если он существует
if (userData && userData.avatar) {
const avatarPath = path.join(
__dirname,
"public",
"uploads",
userData.avatar
);
fs.unlink(avatarPath, (err) => {
if (err && err.code !== "ENOENT") {
console.error("Ошибка удаления файла аватарки:", err.message);
}
});
}
// Начинаем транзакцию для удаления всех данных пользователя
db.serialize(() => {
db.run("BEGIN TRANSACTION");
// Удаляем все изображения заметок пользователя (через JOIN с notes)
const deleteImagesSql = `
DELETE FROM note_images
WHERE note_id IN (SELECT id FROM notes WHERE user_id = ?)
`;
db.run(deleteImagesSql, [userId], (err) => {
if (err) {
console.error("Ошибка удаления изображений:", err.message);
db.run("ROLLBACK");
return res
.status(500)
.json({ error: "Ошибка удаления изображений" });
}
});
// Удаляем все заметки пользователя (CASCADE удалит связанные изображения)
db.run("DELETE FROM notes WHERE user_id = ?", [userId], (err) => {
if (err) {
console.error("Ошибка удаления заметок:", err.message);
db.run("ROLLBACK");
return res.status(500).json({ error: "Ошибка удаления заметок" });
}
});
// Удаляем логи пользователя
db.run(
"DELETE FROM action_logs WHERE user_id = ?",
[userId],
(err) => {
if (err) {
console.error("Ошибка удаления логов:", err.message);
db.run("ROLLBACK");
return res.status(500).json({ error: "Ошибка удаления логов" });
}
}
);
// Удаляем пользователя из базы данных
db.run("DELETE FROM users WHERE id = ?", [userId], (err) => {
if (err) {
console.error("Ошибка удаления пользователя:", err.message);
db.run("ROLLBACK");
return res
.status(500)
.json({ error: "Ошибка удаления пользователя" });
}
// Подтверждаем транзакцию
db.run("COMMIT", (err) => {
if (err) {
console.error("Ошибка подтверждения транзакции:", err.message);
return res
.status(500)
.json({ error: "Ошибка подтверждения транзакции" });
}
// Уничтожаем сессию
req.session.destroy((err) => {
if (err) {
console.error("Ошибка уничтожения сессии:", err.message);
}
res.json({
message: "Аккаунт успешно удален",
success: true,
});
});
});
});
});
});
});
} catch (error) {
console.error("Ошибка удаления аккаунта:", error);
res.status(500).json({ error: "Ошибка сервера" });
}
});
// Запуск сервера // Запуск сервера
app.listen(PORT, () => { app.listen(PORT, () => {
console.log(`🚀 Сервер запущен на порту ${PORT}`); console.log(`🚀 Сервер запущен на порту ${PORT}`);