fix(number-fields): избегаем потери точности длинных чисел
- number-поля теперь рендерятся как text + inputMode=numeric, чтобы браузер не округлял значения через input type=number - пробелы при вставке в number-поля удаляются - бэкенд нормализует значения number-полей в строку перед сохранением - добавлен хелпер normalizeFieldValueForStorage Closes: искажение расчётного счёта и других длинных числовых полей
This commit is contained in:
186
server/routes/chat.bookmark-folders.routes.ts
Normal file
186
server/routes/chat.bookmark-folders.routes.ts
Normal file
@@ -0,0 +1,186 @@
|
||||
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: 'Ошибка при изменении порядка папок' });
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user