Files
iistwin/server/routes/chat.bookmark-folders.routes.ts
Ильяс Султанов 1f5ecb6da4 fix(number-fields): избегаем потери точности длинных чисел
- number-поля теперь рендерятся как text + inputMode=numeric,
  чтобы браузер не округлял значения через input type=number
- пробелы при вставке в number-поля удаляются
- бэкенд нормализует значения number-полей в строку перед сохранением
- добавлен хелпер normalizeFieldValueForStorage

Closes: искажение расчётного счёта и других длинных числовых полей
2026-07-07 21:03:40 +03:00

187 lines
7.6 KiB
TypeScript

import { Router } from "express";
import { z } from 'zod';
import { storage } from "../storage";
import { authenticateToken, type AuthenticatedRequest } from "../middleware/auth.middleware";
import { tenantIsolation } from "../middleware/tenant.middleware";
import { validateRequest } from "../middleware/validation.middleware";
import { insertBookmarkFolderSchema, createBookmarkFolderSchema } from "@shared/schema";
export function registerBookmarkFolderRoutes(router: Router): void {
// Get all bookmark folders for organization
router.get('/api/bookmark-folders',
authenticateToken,
tenantIsolation,
async (req: AuthenticatedRequest, res) => {
try {
const folders = await storage.getBookmarkFoldersByOrganization(req.organizationId!, req.user!.id);
res.json({ success: true, folders });
} catch (error) {
console.error('Get bookmark folders error:', error);
res.status(500).json({ success: false, error: 'Ошибка при загрузке папок' });
}
}
);
// Get bookmark folders by parent
router.get('/api/bookmark-folders/by-parent/:parentId',
authenticateToken,
tenantIsolation,
async (req: AuthenticatedRequest, res) => {
try {
const parentId = req.params.parentId === 'null' ? null : parseInt(req.params.parentId);
if (parentId !== null && isNaN(parentId)) {
return res.status(400).json({ success: false, error: 'Некорректный ID родительской папки' });
}
const folders = await storage.getBookmarkFoldersByParent(parentId, req.organizationId!, req.user!.id);
res.json({ success: true, folders });
} catch (error) {
console.error('Get bookmark folders by parent error:', error);
res.status(500).json({ success: false, error: 'Ошибка при загрузке дочерних папок' });
}
}
);
// Create bookmark folder
router.post('/api/bookmark-folders',
authenticateToken,
tenantIsolation,
validateRequest(createBookmarkFolderSchema),
async (req: AuthenticatedRequest, res) => {
try {
if (req.body.parentId) {
const parentFolder = await storage.getBookmarkFolder(req.body.parentId, req.organizationId!, req.user!.id);
if (!parentFolder) {
return res.status(400).json({ success: false, error: 'Родительская папка не найдена или не принадлежит вам' });
}
}
const rawFolderData = {
...req.body,
organizationId: req.organizationId!,
userId: req.user!.id,
createdBy: req.user!.id,
isExpanded: true
};
const parsedFolder = insertBookmarkFolderSchema.safeParse(rawFolderData);
if (!parsedFolder.success) {
return res.status(400).json({
success: false,
error: 'Некорректные данные папки',
details: parsedFolder.error.flatten()
});
}
const createdFolder = await storage.createBookmarkFolder(parsedFolder.data);
res.status(201).json({ success: true, message: 'Папка создана', folder: createdFolder });
} catch (error) {
console.error('Create bookmark folder error:', error);
res.status(500).json({ success: false, error: 'Ошибка при создании папки' });
}
}
);
// Update bookmark folder
router.put('/api/bookmark-folders/:id',
authenticateToken,
tenantIsolation,
async (req: AuthenticatedRequest, res) => {
try {
const folderId = parseInt(req.params.id);
if (isNaN(folderId)) {
return res.status(400).json({ success: false, error: 'Некорректный ID папки' });
}
const existingFolder = await storage.getBookmarkFolder(folderId, req.organizationId!, req.user!.id);
if (!existingFolder) {
return res.status(404).json({ success: false, error: 'Папка не найдена' });
}
const { userId: _uF, createdBy: _cbF, organizationId: _oF, id: _idF, ...folderUpdates } = req.body;
if (folderUpdates.parentId) {
const parentFolder = await storage.getBookmarkFolder(folderUpdates.parentId, req.organizationId!, req.user!.id);
if (!parentFolder) {
return res.status(400).json({ success: false, error: 'Родительская папка не найдена или не принадлежит вам' });
}
}
const updatedFolder = await storage.updateBookmarkFolder(
folderId, req.organizationId!, folderUpdates, req.user!.id
);
res.json({ success: true, message: 'Папка обновлена', folder: updatedFolder });
} catch (error) {
console.error('Update bookmark folder error:', error);
res.status(500).json({ success: false, error: 'Ошибка при обновлении папки' });
}
}
);
// Delete bookmark folder
router.delete('/api/bookmark-folders/:id',
authenticateToken,
tenantIsolation,
async (req: AuthenticatedRequest, res) => {
try {
const folderId = parseInt(req.params.id);
if (isNaN(folderId)) {
return res.status(400).json({ success: false, error: 'Некорректный ID папки' });
}
const existingFolder = await storage.getBookmarkFolder(folderId, req.organizationId!, req.user!.id);
if (!existingFolder) {
return res.status(404).json({ success: false, error: 'Папка не найдена' });
}
await storage.deleteBookmarkFolder(folderId, req.organizationId!, req.user!.id);
res.json({ success: true, message: 'Папка удалена' });
} catch (error) {
console.error('Delete bookmark folder error:', error);
res.status(500).json({ success: false, error: 'Ошибка при удалении папки' });
}
}
);
// Normalize bookmark folder positions
router.post('/api/bookmark-folders/normalize-positions',
authenticateToken,
tenantIsolation,
async (req: AuthenticatedRequest, res) => {
try {
await storage.normalizeBookmarkFolderPositions(req.organizationId!, req.user!.id);
res.json({ success: true, message: 'Позиции папок закладок нормализованы' });
} catch (error) {
console.error('Normalize bookmark folder positions error:', error);
res.status(500).json({ success: false, error: 'Ошибка при нормализации позиций папок закладок' });
}
}
);
// Reorder bookmark folders
router.post('/api/bookmark-folders/reorder',
authenticateToken,
tenantIsolation,
async (req: AuthenticatedRequest, res) => {
try {
const reorderItemSchema = z.object({
items: z.array(z.object({
id: z.number().int().positive(),
position: z.number().int().min(0),
})).min(1),
});
const parsed = reorderItemSchema.safeParse(req.body);
if (!parsed.success) {
return res.status(400).json({ success: false, error: 'Некорректный формат данных' });
}
await storage.batchUpdateBookmarkFolderPositions(parsed.data.items, req.organizationId!, req.user!.id);
res.json({ success: true });
} catch (error) {
console.error('Reorder bookmark folders error:', error);
res.status(500).json({ success: false, error: 'Ошибка при изменении порядка папок' });
}
}
);
}