Обновлены функции поиска заметок для регистронезависимого поиска по содержимому. Добавлен новый API для выхода, который логирует действие и очищает сессию. Реализована очистка IndexedDB при разлогинивании и смене пользователя. Обновлены компоненты для управления состоянием аутентификации и очистки данных при регистрации и удалении аккаунта.

This commit is contained in:
Fovway 2025-11-05 21:28:27 +07:00
parent 80c42f8df0
commit 7367364d93
9 changed files with 80 additions and 12 deletions

View File

@ -857,10 +857,10 @@ app.get("/api/notes/search", requireApiAuth, (req, res) => {
let whereClause = "WHERE n.user_id = ? AND n.is_archived = 0";
let params = [req.session.userId];
// Поиск по тексту
// Поиск по тексту (регистронезависимый)
if (q && q.trim()) {
whereClause += " AND n.content LIKE ?";
params.push(`%${q.trim()}%`);
whereClause += " AND LOWER(n.content) LIKE ?";
params.push(`%${q.trim().toLowerCase()}%`);
}
// Поиск по тегу (регистронезависимый)
@ -2549,6 +2549,23 @@ app.post("/logout", (req, res) => {
});
});
// API для выхода (для фронтенда)
app.post("/api/logout", (req, res) => {
const userId = req.session.userId;
// Логируем выход
if (userId) {
logAction(userId, "logout", "Выход из системы");
}
req.session.destroy((err) => {
if (err) {
return res.status(500).json({ error: "Ошибка выхода" });
}
res.json({ success: true, message: "Выход выполнен успешно" });
});
});
// API для удаления аккаунта
app.delete("/api/user/delete-account", requireApiAuth, async (req, res) => {
const { password } = req.body;

View File

@ -82,7 +82,7 @@ define(['./workbox-9dc17825'], (function (workbox) { 'use strict';
"revision": "3ca0b8505b4bec776b69afdba2768812"
}, {
"url": "index.html",
"revision": "0.2vg2p27g3bg"
"revision": "0.mib567t8mr8"
}], {});
workbox.cleanupOutdatedCaches();
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {

View File

@ -1,4 +1,5 @@
import axios from "axios";
import { dbManager } from "../utils/indexedDB";
const axiosClient = axios.create({
baseURL: "/api",
@ -48,6 +49,10 @@ axiosClient.interceptors.response.use(
// Разлогиниваем только если это НЕ запрос с проверкой пароля
if (!isPasswordProtected) {
// Очищаем IndexedDB при автоматическом разлогинивании
dbManager.clearAll().catch((err) => {
console.error("Ошибка очистки IndexedDB при 401:", err);
});
localStorage.removeItem("isAuthenticated");
window.location.href = "/";
}

View File

@ -4,11 +4,13 @@ import { useAppSelector, useAppDispatch } from "../store/hooks";
import { setAuth, clearAuth } from "../store/slices/authSlice";
import { authApi } from "../api/authApi";
import { LoadingOverlay } from "./common/LoadingOverlay";
import { dbManager } from "../utils/indexedDB";
export const ProtectedRoute: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const isAuthenticated = useAppSelector((state) => state.auth.isAuthenticated);
const currentUserId = useAppSelector((state) => state.auth.userId);
const dispatch = useAppDispatch();
const [isChecking, setIsChecking] = useState(true);
@ -17,9 +19,17 @@ export const ProtectedRoute: React.FC<{ children: React.ReactNode }> = ({
try {
const authStatus = await authApi.checkStatus();
if (authStatus.authenticated) {
const newUserId = authStatus.userId!;
// Если пользователь изменился, очищаем IndexedDB
if (currentUserId && currentUserId !== newUserId) {
console.log(`[ProtectedRoute] User changed from ${currentUserId} to ${newUserId}, clearing IndexedDB`);
await dbManager.clearAll();
}
dispatch(
setAuth({
userId: authStatus.userId!,
userId: newUserId,
username: authStatus.username!,
})
);
@ -36,7 +46,7 @@ export const ProtectedRoute: React.FC<{ children: React.ReactNode }> = ({
// Всегда проверяем статус аутентификации при монтировании,
// независимо от начального состояния Redux (localStorage может быть устаревшим)
checkAuth();
}, [dispatch]);
}, [dispatch, currentUserId]);
if (isChecking) {
return <LoadingOverlay />;

View File

@ -6,6 +6,7 @@ import { authApi } from "../api/authApi";
import { useNotification } from "../hooks/useNotification";
import { ThemeToggle } from "../components/common/ThemeToggle";
import { Icon } from "@iconify/react";
import { dbManager } from "../utils/indexedDB";
const LoginPage: React.FC = () => {
const [username, setUsername] = useState("");
@ -15,6 +16,7 @@ const LoginPage: React.FC = () => {
const dispatch = useAppDispatch();
const { showNotification } = useNotification();
const isAuthenticated = useAppSelector((state) => state.auth.isAuthenticated);
const currentUserId = useAppSelector((state) => state.auth.userId);
const [searchParams] = useSearchParams();
useEffect(() => {
@ -48,9 +50,17 @@ const LoginPage: React.FC = () => {
if (data.success) {
// Получаем информацию о пользователе
const authStatus = await authApi.checkStatus();
const newUserId = authStatus.userId!;
// Если пользователь изменился, очищаем IndexedDB
if (currentUserId && currentUserId !== newUserId) {
console.log(`[Login] User changed from ${currentUserId} to ${newUserId}, clearing IndexedDB`);
await dbManager.clearAll();
}
dispatch(
setAuth({
userId: authStatus.userId!,
userId: newUserId,
username: authStatus.username!,
})
);

View File

@ -11,6 +11,7 @@ import { setAccentColor } from "../utils/colorUtils";
import { useNotification } from "../hooks/useNotification";
import { Modal } from "../components/common/Modal";
import { ThemeToggle } from "../components/common/ThemeToggle";
import { dbManager } from "../utils/indexedDB";
const ProfilePage: React.FC = () => {
const navigate = useNavigate();
@ -211,6 +212,8 @@ const ProfilePage: React.FC = () => {
setIsDeleting(true);
try {
await userApi.deleteAccount(deletePassword);
// Очищаем IndexedDB при удалении аккаунта
await dbManager.clearAll();
showNotification("Аккаунт успешно удален", "success");
dispatch(clearAuth());
setTimeout(() => {
@ -259,10 +262,13 @@ const ProfilePage: React.FC = () => {
onClick={async () => {
try {
await authApi.logout();
dispatch(clearAuth());
navigate("/");
} catch (error) {
console.error("Ошибка выхода:", error);
} finally {
// Очищаем IndexedDB в фоне (не блокируем выход)
dbManager.clearAll().catch((err) => {
console.error("Ошибка очистки IndexedDB при выходе:", err);
});
dispatch(clearAuth());
navigate("/");
}

View File

@ -6,6 +6,7 @@ import { authApi } from "../api/authApi";
import { useNotification } from "../hooks/useNotification";
import { ThemeToggle } from "../components/common/ThemeToggle";
import { Icon } from "@iconify/react";
import { dbManager } from "../utils/indexedDB";
const RegisterPage: React.FC = () => {
const [username, setUsername] = useState("");
@ -57,9 +58,15 @@ const RegisterPage: React.FC = () => {
if (data.success) {
// Получаем информацию о пользователе
const authStatus = await authApi.checkStatus();
const newUserId = authStatus.userId!;
// При регистрации нового пользователя всегда очищаем IndexedDB
// (на случай, если в базе остались данные предыдущего пользователя)
await dbManager.clearAll();
dispatch(
setAuth({
userId: authStatus.userId!,
userId: newUserId,
username: authStatus.username!,
})
);

View File

@ -553,10 +553,13 @@ const SettingsPage: React.FC = () => {
onClick={async () => {
try {
await authApi.logout();
dispatch(clearAuth());
navigate("/");
} catch (error) {
console.error("Ошибка выхода:", error);
} finally {
// Очищаем IndexedDB в фоне (не блокируем выход)
dbManager.clearAll().catch((err) => {
console.error("Ошибка очистки IndexedDB при выходе:", err);
});
dispatch(clearAuth());
navigate("/");
}

View File

@ -225,6 +225,16 @@ class IndexedDBManager {
});
}
/**
* Полная очистка всех данных из IndexedDB (заметки и очередь синхронизации)
*/
async clearAll(): Promise<void> {
await Promise.all([
this.clearAllNotes(),
this.clearSyncQueue(),
]);
}
// ===== Утилиты =====
async getPendingSyncCount(): Promise<number> {