Compare commits
No commits in common. "82094e46b5cef1012ac67ab2f24631560fa5451f" and "80c42f8df01a1d4f5512d9109796ed391d528cbb" have entirely different histories.
82094e46b5
...
80c42f8df0
@ -857,10 +857,10 @@ app.get("/api/notes/search", requireApiAuth, (req, res) => {
|
|||||||
let whereClause = "WHERE n.user_id = ? AND n.is_archived = 0";
|
let whereClause = "WHERE n.user_id = ? AND n.is_archived = 0";
|
||||||
let params = [req.session.userId];
|
let params = [req.session.userId];
|
||||||
|
|
||||||
// Поиск по тексту (регистронезависимый)
|
// Поиск по тексту
|
||||||
if (q && q.trim()) {
|
if (q && q.trim()) {
|
||||||
whereClause += " AND LOWER(n.content) LIKE ?";
|
whereClause += " AND n.content LIKE ?";
|
||||||
params.push(`%${q.trim().toLowerCase()}%`);
|
params.push(`%${q.trim()}%`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Поиск по тегу (регистронезависимый)
|
// Поиск по тегу (регистронезависимый)
|
||||||
@ -2549,23 +2549,6 @@ 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 для удаления аккаунта
|
// API для удаления аккаунта
|
||||||
app.delete("/api/user/delete-account", requireApiAuth, async (req, res) => {
|
app.delete("/api/user/delete-account", requireApiAuth, async (req, res) => {
|
||||||
const { password } = req.body;
|
const { password } = req.body;
|
||||||
|
|||||||
@ -82,7 +82,7 @@ define(['./workbox-9dc17825'], (function (workbox) { 'use strict';
|
|||||||
"revision": "3ca0b8505b4bec776b69afdba2768812"
|
"revision": "3ca0b8505b4bec776b69afdba2768812"
|
||||||
}, {
|
}, {
|
||||||
"url": "index.html",
|
"url": "index.html",
|
||||||
"revision": "0.hpmojmu6e28"
|
"revision": "0.2vg2p27g3bg"
|
||||||
}], {});
|
}], {});
|
||||||
workbox.cleanupOutdatedCaches();
|
workbox.cleanupOutdatedCaches();
|
||||||
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
|
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { dbManager } from "../utils/indexedDB";
|
|
||||||
|
|
||||||
const axiosClient = axios.create({
|
const axiosClient = axios.create({
|
||||||
baseURL: "/api",
|
baseURL: "/api",
|
||||||
@ -49,10 +48,6 @@ axiosClient.interceptors.response.use(
|
|||||||
|
|
||||||
// Разлогиниваем только если это НЕ запрос с проверкой пароля
|
// Разлогиниваем только если это НЕ запрос с проверкой пароля
|
||||||
if (!isPasswordProtected) {
|
if (!isPasswordProtected) {
|
||||||
// Очищаем IndexedDB при автоматическом разлогинивании
|
|
||||||
dbManager.clearAll().catch((err) => {
|
|
||||||
console.error("Ошибка очистки IndexedDB при 401:", err);
|
|
||||||
});
|
|
||||||
localStorage.removeItem("isAuthenticated");
|
localStorage.removeItem("isAuthenticated");
|
||||||
window.location.href = "/";
|
window.location.href = "/";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -630,21 +630,18 @@ export const offlineNotesApi = {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Сервер возвращает объект с полем images
|
|
||||||
const uploadedImages = Array.isArray(data) ? data : (data?.images || []);
|
|
||||||
|
|
||||||
const note = await dbManager.getNote(noteId);
|
const note = await dbManager.getNote(noteId);
|
||||||
if (note) {
|
if (note) {
|
||||||
const updatedNote: Note = {
|
const updatedNote: Note = {
|
||||||
...note,
|
...note,
|
||||||
images: [...(note.images || []), ...uploadedImages],
|
images: [...note.images, ...data],
|
||||||
syncStatus: "synced",
|
syncStatus: "synced",
|
||||||
};
|
};
|
||||||
await dbManager.saveNote(updatedNote);
|
await dbManager.saveNote(updatedNote);
|
||||||
store.dispatch(updateNote(updatedNote));
|
store.dispatch(updateNote(updatedNote));
|
||||||
}
|
}
|
||||||
|
|
||||||
return uploadedImages;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error uploading images:", error);
|
console.error("Error uploading images:", error);
|
||||||
throw error;
|
throw error;
|
||||||
@ -727,21 +724,18 @@ export const offlineNotesApi = {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Сервер возвращает объект с полем files
|
|
||||||
const uploadedFiles = Array.isArray(data) ? data : (data?.files || []);
|
|
||||||
|
|
||||||
const note = await dbManager.getNote(noteId);
|
const note = await dbManager.getNote(noteId);
|
||||||
if (note) {
|
if (note) {
|
||||||
const updatedNote: Note = {
|
const updatedNote: Note = {
|
||||||
...note,
|
...note,
|
||||||
files: [...(note.files || []), ...uploadedFiles],
|
files: [...note.files, ...data],
|
||||||
syncStatus: "synced",
|
syncStatus: "synced",
|
||||||
};
|
};
|
||||||
await dbManager.saveNote(updatedNote);
|
await dbManager.saveNote(updatedNote);
|
||||||
store.dispatch(updateNote(updatedNote));
|
store.dispatch(updateNote(updatedNote));
|
||||||
}
|
}
|
||||||
|
|
||||||
return uploadedFiles;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error uploading files:", error);
|
console.error("Error uploading files:", error);
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@ -4,13 +4,11 @@ import { useAppSelector, useAppDispatch } from "../store/hooks";
|
|||||||
import { setAuth, clearAuth } from "../store/slices/authSlice";
|
import { setAuth, clearAuth } from "../store/slices/authSlice";
|
||||||
import { authApi } from "../api/authApi";
|
import { authApi } from "../api/authApi";
|
||||||
import { LoadingOverlay } from "./common/LoadingOverlay";
|
import { LoadingOverlay } from "./common/LoadingOverlay";
|
||||||
import { dbManager } from "../utils/indexedDB";
|
|
||||||
|
|
||||||
export const ProtectedRoute: React.FC<{ children: React.ReactNode }> = ({
|
export const ProtectedRoute: React.FC<{ children: React.ReactNode }> = ({
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const isAuthenticated = useAppSelector((state) => state.auth.isAuthenticated);
|
const isAuthenticated = useAppSelector((state) => state.auth.isAuthenticated);
|
||||||
const currentUserId = useAppSelector((state) => state.auth.userId);
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const [isChecking, setIsChecking] = useState(true);
|
const [isChecking, setIsChecking] = useState(true);
|
||||||
|
|
||||||
@ -19,17 +17,9 @@ export const ProtectedRoute: React.FC<{ children: React.ReactNode }> = ({
|
|||||||
try {
|
try {
|
||||||
const authStatus = await authApi.checkStatus();
|
const authStatus = await authApi.checkStatus();
|
||||||
if (authStatus.authenticated) {
|
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(
|
dispatch(
|
||||||
setAuth({
|
setAuth({
|
||||||
userId: newUserId,
|
userId: authStatus.userId!,
|
||||||
username: authStatus.username!,
|
username: authStatus.username!,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -46,7 +36,7 @@ export const ProtectedRoute: React.FC<{ children: React.ReactNode }> = ({
|
|||||||
// Всегда проверяем статус аутентификации при монтировании,
|
// Всегда проверяем статус аутентификации при монтировании,
|
||||||
// независимо от начального состояния Redux (localStorage может быть устаревшим)
|
// независимо от начального состояния Redux (localStorage может быть устаревшим)
|
||||||
checkAuth();
|
checkAuth();
|
||||||
}, [dispatch, currentUserId]);
|
}, [dispatch]);
|
||||||
|
|
||||||
if (isChecking) {
|
if (isChecking) {
|
||||||
return <LoadingOverlay />;
|
return <LoadingOverlay />;
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import { authApi } from "../api/authApi";
|
|||||||
import { useNotification } from "../hooks/useNotification";
|
import { useNotification } from "../hooks/useNotification";
|
||||||
import { ThemeToggle } from "../components/common/ThemeToggle";
|
import { ThemeToggle } from "../components/common/ThemeToggle";
|
||||||
import { Icon } from "@iconify/react";
|
import { Icon } from "@iconify/react";
|
||||||
import { dbManager } from "../utils/indexedDB";
|
|
||||||
|
|
||||||
const LoginPage: React.FC = () => {
|
const LoginPage: React.FC = () => {
|
||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
@ -16,7 +15,6 @@ const LoginPage: React.FC = () => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { showNotification } = useNotification();
|
const { showNotification } = useNotification();
|
||||||
const isAuthenticated = useAppSelector((state) => state.auth.isAuthenticated);
|
const isAuthenticated = useAppSelector((state) => state.auth.isAuthenticated);
|
||||||
const currentUserId = useAppSelector((state) => state.auth.userId);
|
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -50,17 +48,9 @@ const LoginPage: React.FC = () => {
|
|||||||
if (data.success) {
|
if (data.success) {
|
||||||
// Получаем информацию о пользователе
|
// Получаем информацию о пользователе
|
||||||
const authStatus = await authApi.checkStatus();
|
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(
|
dispatch(
|
||||||
setAuth({
|
setAuth({
|
||||||
userId: newUserId,
|
userId: authStatus.userId!,
|
||||||
username: authStatus.username!,
|
username: authStatus.username!,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import { setAccentColor } from "../utils/colorUtils";
|
|||||||
import { useNotification } from "../hooks/useNotification";
|
import { useNotification } from "../hooks/useNotification";
|
||||||
import { Modal } from "../components/common/Modal";
|
import { Modal } from "../components/common/Modal";
|
||||||
import { ThemeToggle } from "../components/common/ThemeToggle";
|
import { ThemeToggle } from "../components/common/ThemeToggle";
|
||||||
import { dbManager } from "../utils/indexedDB";
|
|
||||||
|
|
||||||
const ProfilePage: React.FC = () => {
|
const ProfilePage: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -212,8 +211,6 @@ const ProfilePage: React.FC = () => {
|
|||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
try {
|
try {
|
||||||
await userApi.deleteAccount(deletePassword);
|
await userApi.deleteAccount(deletePassword);
|
||||||
// Очищаем IndexedDB при удалении аккаунта
|
|
||||||
await dbManager.clearAll();
|
|
||||||
showNotification("Аккаунт успешно удален", "success");
|
showNotification("Аккаунт успешно удален", "success");
|
||||||
dispatch(clearAuth());
|
dispatch(clearAuth());
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -262,13 +259,10 @@ const ProfilePage: React.FC = () => {
|
|||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
try {
|
try {
|
||||||
await authApi.logout();
|
await authApi.logout();
|
||||||
|
dispatch(clearAuth());
|
||||||
|
navigate("/");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Ошибка выхода:", error);
|
console.error("Ошибка выхода:", error);
|
||||||
} finally {
|
|
||||||
// Очищаем IndexedDB в фоне (не блокируем выход)
|
|
||||||
dbManager.clearAll().catch((err) => {
|
|
||||||
console.error("Ошибка очистки IndexedDB при выходе:", err);
|
|
||||||
});
|
|
||||||
dispatch(clearAuth());
|
dispatch(clearAuth());
|
||||||
navigate("/");
|
navigate("/");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import { authApi } from "../api/authApi";
|
|||||||
import { useNotification } from "../hooks/useNotification";
|
import { useNotification } from "../hooks/useNotification";
|
||||||
import { ThemeToggle } from "../components/common/ThemeToggle";
|
import { ThemeToggle } from "../components/common/ThemeToggle";
|
||||||
import { Icon } from "@iconify/react";
|
import { Icon } from "@iconify/react";
|
||||||
import { dbManager } from "../utils/indexedDB";
|
|
||||||
|
|
||||||
const RegisterPage: React.FC = () => {
|
const RegisterPage: React.FC = () => {
|
||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
@ -58,15 +57,9 @@ const RegisterPage: React.FC = () => {
|
|||||||
if (data.success) {
|
if (data.success) {
|
||||||
// Получаем информацию о пользователе
|
// Получаем информацию о пользователе
|
||||||
const authStatus = await authApi.checkStatus();
|
const authStatus = await authApi.checkStatus();
|
||||||
const newUserId = authStatus.userId!;
|
|
||||||
|
|
||||||
// При регистрации нового пользователя всегда очищаем IndexedDB
|
|
||||||
// (на случай, если в базе остались данные предыдущего пользователя)
|
|
||||||
await dbManager.clearAll();
|
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
setAuth({
|
setAuth({
|
||||||
userId: newUserId,
|
userId: authStatus.userId!,
|
||||||
username: authStatus.username!,
|
username: authStatus.username!,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@ -553,13 +553,10 @@ const SettingsPage: React.FC = () => {
|
|||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
try {
|
try {
|
||||||
await authApi.logout();
|
await authApi.logout();
|
||||||
|
dispatch(clearAuth());
|
||||||
|
navigate("/");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Ошибка выхода:", error);
|
console.error("Ошибка выхода:", error);
|
||||||
} finally {
|
|
||||||
// Очищаем IndexedDB в фоне (не блокируем выход)
|
|
||||||
dbManager.clearAll().catch((err) => {
|
|
||||||
console.error("Ошибка очистки IndexedDB при выходе:", err);
|
|
||||||
});
|
|
||||||
dispatch(clearAuth());
|
dispatch(clearAuth());
|
||||||
navigate("/");
|
navigate("/");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -225,16 +225,6 @@ class IndexedDBManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Полная очистка всех данных из IndexedDB (заметки и очередь синхронизации)
|
|
||||||
*/
|
|
||||||
async clearAll(): Promise<void> {
|
|
||||||
await Promise.all([
|
|
||||||
this.clearAllNotes(),
|
|
||||||
this.clearSyncQueue(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== Утилиты =====
|
// ===== Утилиты =====
|
||||||
|
|
||||||
async getPendingSyncCount(): Promise<number> {
|
async getPendingSyncCount(): Promise<number> {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user