Files
iistwin/server/routes/form-access.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

158 lines
5.7 KiB
TypeScript

import { Router } from "express";
import { storage } from "../storage";
import { authenticateToken, requireFormAdminOrPermission } from "../middleware/auth.middleware";
import { z } from "zod";
const router = Router();
// GET /api/forms/:id/access-rules
router.get('/api/forms/:id/access-rules',
authenticateToken,
async (req, res) => {
try {
const formId = parseInt(req.params.id);
if (isNaN(formId)) return res.status(400).json({ error: 'Неверный ID формы' });
const rules = await storage.getFormAccessRules(formId, (req as any).organizationId!);
res.json({ success: true, rules });
} catch (err) {
console.error('Get form access rules error:', err);
res.status(500).json({ error: 'Ошибка при получении правил доступа' });
}
}
);
// POST /api/forms/:id/access-rules
router.post('/api/forms/:id/access-rules',
authenticateToken,
async (req, res) => {
try {
const formId = parseInt(req.params.id);
if (isNaN(formId)) return res.status(400).json({ error: 'Неверный ID формы' });
const schema = z.object({
targetType: z.enum(['user', 'role']),
targetId: z.number().positive(),
accessLevel: z.enum(['participate', 'view_all', 'admin']).default('participate'),
});
const body = schema.parse(req.body);
const rule = await storage.createFormAccessRule({
formId,
organizationId: (req as any).organizationId!,
targetType: body.targetType,
targetId: body.targetId,
accessLevel: body.accessLevel,
});
res.json({ success: true, rule });
} catch (err) {
if (err instanceof z.ZodError) {
return res.status(400).json({ error: 'Неверные данные', details: err.errors });
}
console.error('Create form access rule error:', err);
res.status(500).json({ error: 'Ошибка при создании правила доступа' });
}
}
);
// PUT /api/forms/:id/access-rules/:ruleId
router.put('/api/forms/:id/access-rules/:ruleId',
authenticateToken,
async (req, res) => {
try {
const ruleId = parseInt(req.params.ruleId);
if (isNaN(ruleId)) return res.status(400).json({ error: 'Неверный ID правила' });
const schema = z.object({
accessLevel: z.enum(['participate', 'view_all', 'admin']),
});
const body = schema.parse(req.body);
const rule = await storage.updateFormAccessRule(ruleId, (req as any).organizationId!, {
accessLevel: body.accessLevel,
});
res.json({ success: true, rule });
} catch (err) {
if (err instanceof z.ZodError) {
return res.status(400).json({ error: 'Неверные данные', details: err.errors });
}
console.error('Update form access rule error:', err);
res.status(500).json({ error: 'Ошибка при обновлении правила доступа' });
}
}
);
// DELETE /api/forms/:id/access-rules/:ruleId
router.delete('/api/forms/:id/access-rules/:ruleId',
authenticateToken,
requireFormAdminOrPermission('forms.manage'),
async (req, res) => {
try {
const ruleId = parseInt(req.params.ruleId);
if (isNaN(ruleId)) return res.status(400).json({ error: 'Неверный ID правила' });
await storage.deleteFormAccessRule(ruleId, (req as any).organizationId!);
res.json({ success: true });
} catch (err) {
console.error('Delete form access rule error:', err);
res.status(500).json({ error: 'Ошибка при удалении правила доступа' });
}
}
);
// PUT /api/forms/:id/visibility
router.put('/api/forms/:id/visibility',
authenticateToken,
async (req, res) => {
try {
const formId = parseInt(req.params.id);
if (isNaN(formId)) return res.status(400).json({ error: 'Неверный ID формы' });
const schema = z.object({
visibility: z.enum(['organization', 'restricted']),
});
const body = schema.parse(req.body);
const { db } = await import("../db");
const { forms } = await import("@shared/schema");
const { eq, and } = await import("drizzle-orm");
const [form] = await db.update(forms)
.set({ visibility: body.visibility })
.where(and(eq(forms.id, formId), eq(forms.organizationId, (req as any).organizationId!)))
.returning();
res.json({ success: true, form });
} catch (err) {
if (err instanceof z.ZodError) {
return res.status(400).json({ error: 'Неверные данные', details: err.errors });
}
console.error('Update form visibility error:', err);
res.status(500).json({ error: 'Ошибка при обновлении видимости формы' });
}
}
);
// POST /api/forms/:id/transfer-ownership
router.post('/api/forms/:id/transfer-ownership',
authenticateToken,
async (req, res) => {
try {
const formId = parseInt(req.params.id);
if (isNaN(formId)) return res.status(400).json({ error: 'Неверный ID формы' });
const schema = z.object({
newAuthorId: z.number().positive(),
});
const body = schema.parse(req.body);
await storage.transferFormOwnership(formId, (req as any).organizationId!, body.newAuthorId);
res.json({ success: true });
} catch (err) {
if (err instanceof z.ZodError) {
return res.status(400).json({ error: 'Неверные данные', details: err.errors });
}
console.error('Transfer form ownership error:', err);
res.status(500).json({ error: 'Ошибка при передаче прав на форму' });
}
}
);
export default router;