Files
iistwin/server/routes/roles.routes.ts

208 lines
11 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Router } from "express";
import { storage } from "../storage";
import { authenticateToken, requirePermission, type AuthenticatedRequest } from "../middleware/auth.middleware";
import { tenantIsolation } from "../middleware/tenant.middleware";
import { canManageUser } from "../utils/user-access";
const router = Router();
// GET /api/roles — список всех ролей организации
router.get('/api/roles', authenticateToken, tenantIsolation, async (req: AuthenticatedRequest, res) => {
try {
const roles = await storage.getRoles(req.organizationId!);
return res.json({ success: true, roles });
} catch (error) {
console.error('Get roles error:', error);
return res.status(500).json({ success: false, error: 'Внутренняя ошибка сервера' });
}
});
// PATCH /api/roles/:id/module-access — флаги доступа к модулям GPS/Финансы (только admin)
router.patch('/api/roles/:id/module-access', authenticateToken, tenantIsolation, async (req: AuthenticatedRequest, res) => {
try {
if (req.user!.appRole !== 'admin') {
return res.status(403).json({ success: false, error: 'Требуются права администратора' });
}
const id = parseInt(req.params.id);
if (isNaN(id)) return res.status(400).json({ success: false, error: 'Некорректный ID роли' });
const { gpsAccess, financeAccess } = req.body ?? {};
const updates: { gpsAccess?: boolean; financeAccess?: boolean } = {};
if (gpsAccess !== undefined) {
if (typeof gpsAccess !== 'boolean') {
return res.status(400).json({ success: false, error: 'gpsAccess: boolean' });
}
updates.gpsAccess = gpsAccess;
}
if (financeAccess !== undefined) {
if (typeof financeAccess !== 'boolean') {
return res.status(400).json({ success: false, error: 'financeAccess: boolean' });
}
updates.financeAccess = financeAccess;
}
if (Object.keys(updates).length === 0) {
return res.status(400).json({ success: false, error: 'Передайте gpsAccess и/или financeAccess' });
}
const role = await storage.updateRole(id, req.organizationId!, updates);
if (!role) return res.status(404).json({ success: false, error: 'Роль не найдена' });
return res.json({
success: true,
role: { id: role.id, gpsAccess: role.gpsAccess, financeAccess: role.financeAccess },
});
} catch (error) {
console.error('Update role module access error:', error);
return res.status(500).json({ success: false, error: 'Ошибка при обновлении доступа к модулям' });
}
});
// GET /api/roles/:id — одна роль с участниками
router.get('/api/roles/:id', authenticateToken, tenantIsolation, async (req: AuthenticatedRequest, res) => {
try {
const id = parseInt(req.params.id);
if (isNaN(id)) return res.status(400).json({ success: false, error: 'Некорректный ID' });
const role = await storage.getRoleMembers(id, req.organizationId!);
return res.json({ success: true, role });
} catch (error: any) {
if (error?.message === 'Роль не найдена') return res.status(404).json({ success: false, error: error.message });
console.error('Get role error:', error);
return res.status(500).json({ success: false, error: 'Внутренняя ошибка сервера' });
}
});
// POST /api/roles — создать роль (только admin)
router.post('/api/roles', authenticateToken, requirePermission('roles.manage'), tenantIsolation, async (req: AuthenticatedRequest, res) => {
try {
const { name, description, parentId, managerId } = req.body;
if (!name?.trim()) return res.status(400).json({ success: false, error: 'Название роли обязательно' });
const role = await storage.createRole({
organizationId: req.organizationId!,
name: name.trim(),
description: description ?? null,
parentId: parentId ?? null,
managerId: managerId ?? null,
});
return res.status(201).json({ success: true, role });
} catch (error) {
console.error('Create role error:', error);
return res.status(500).json({ success: false, error: 'Внутренняя ошибка сервера' });
}
});
// PUT /api/roles/:id — обновить роль (только admin)
router.put('/api/roles/:id', authenticateToken, requirePermission('roles.manage'), tenantIsolation, async (req: AuthenticatedRequest, res) => {
try {
const id = parseInt(req.params.id);
if (isNaN(id)) return res.status(400).json({ success: false, error: 'Некорректный ID' });
const { name, description, parentId, managerId } = req.body;
const updates: { name?: string; description?: string | null; parentId?: number | null; managerId?: number | null } = {};
if (name !== undefined) updates.name = name.trim();
if (description !== undefined) updates.description = description ?? null;
if (parentId !== undefined) updates.parentId = parentId ?? null;
if (managerId !== undefined) updates.managerId = managerId ?? null;
const role = await storage.updateRole(id, req.organizationId!, updates);
if (!role) return res.status(404).json({ success: false, error: 'Роль не найдена' });
return res.json({ success: true, role });
} catch (error) {
console.error('Update role error:', error);
return res.status(500).json({ success: false, error: 'Внутренняя ошибка сервера' });
}
});
// DELETE /api/roles/:id — удалить роль (только admin)
router.delete('/api/roles/:id', authenticateToken, requirePermission('roles.manage'), tenantIsolation, async (req: AuthenticatedRequest, res) => {
try {
const id = parseInt(req.params.id);
if (isNaN(id)) return res.status(400).json({ success: false, error: 'Некорректный ID' });
await storage.deleteRole(id, req.organizationId!);
return res.json({ success: true });
} catch (error) {
console.error('Delete role error:', error);
return res.status(500).json({ success: false, error: 'Внутренняя ошибка сервера' });
}
});
// POST /api/roles/:id/members — добавить участника в роль (admin или руководитель роли)
router.post('/api/roles/:id/members', authenticateToken, tenantIsolation, async (req: AuthenticatedRequest, res) => {
try {
const roleId = parseInt(req.params.id);
if (isNaN(roleId)) return res.status(400).json({ success: false, error: 'Некорректный ID роли' });
const { userId } = req.body;
if (!userId) return res.status(400).json({ success: false, error: 'userId обязателен' });
const role = await storage.getRole(roleId, req.organizationId!);
if (!role) return res.status(404).json({ success: false, error: 'Роль не найдена' });
const hasAdminPermission = req.user?.appRole === 'admin' || (req.user?.permissions?.includes('roles.manage') ?? false);
const isRoleManager = role.managerId === req.user?.id;
const canManage = await canManageUser(req.user, parseInt(userId), req.organizationId!);
if (!hasAdminPermission && !isRoleManager && !canManage) {
return res.status(403).json({ success: false, error: 'Недостаточно прав' });
}
await storage.addRoleMember(roleId, parseInt(userId), req.organizationId!);
return res.status(201).json({ success: true });
} catch (error: any) {
if (error?.message === 'Роль не найдена') return res.status(404).json({ success: false, error: error.message });
console.error('Add role member error:', error);
return res.status(500).json({ success: false, error: 'Внутренняя ошибка сервера' });
}
});
// DELETE /api/roles/:id/members/:userId — удалить участника из роли (admin или руководитель роли)
router.delete('/api/roles/:id/members/:userId', authenticateToken, tenantIsolation, async (req: AuthenticatedRequest, res) => {
try {
const roleId = parseInt(req.params.id);
const userId = parseInt(req.params.userId);
if (isNaN(roleId) || isNaN(userId)) return res.status(400).json({ success: false, error: 'Некорректный ID' });
const role = await storage.getRole(roleId, req.organizationId!);
if (!role) return res.status(404).json({ success: false, error: 'Роль не найдена' });
const hasAdminPermission = req.user?.appRole === 'admin' || (req.user?.permissions?.includes('roles.manage') ?? false);
const isRoleManager = role.managerId === req.user?.id;
const canManage = await canManageUser(req.user, userId, req.organizationId!);
if (!hasAdminPermission && !isRoleManager && !canManage) {
return res.status(403).json({ success: false, error: 'Недостаточно прав' });
}
await storage.removeRoleMember(roleId, userId, req.organizationId!);
return res.json({ success: true });
} catch (error: any) {
if (error?.message === 'Роль не найдена') return res.status(404).json({ success: false, error: error.message });
console.error('Remove role member error:', error);
return res.status(500).json({ success: false, error: 'Внутренняя ошибка сервера' });
}
});
// PUT /api/roles/:id/members/:userId/move — переместить участника в другую роль
router.put('/api/roles/:id/members/:userId/move', authenticateToken, tenantIsolation, async (req: AuthenticatedRequest, res) => {
try {
const sourceRoleId = parseInt(req.params.id);
const userId = parseInt(req.params.userId);
const { targetRoleId } = req.body;
if (isNaN(sourceRoleId) || isNaN(userId)) {
return res.status(400).json({ success: false, error: 'Некорректный ID' });
}
if (targetRoleId !== null && (typeof targetRoleId !== 'number' || isNaN(targetRoleId))) {
return res.status(400).json({ success: false, error: 'Некорректный targetRoleId' });
}
const hasAdminPermission = req.user?.appRole === 'admin' || (req.user?.permissions?.includes('roles.manage') ?? false);
const canManage = await canManageUser(req.user, userId, req.organizationId!);
if (!hasAdminPermission && !canManage) {
return res.status(403).json({ success: false, error: 'Недостаточно прав' });
}
await storage.moveRoleMember(sourceRoleId, targetRoleId, userId, req.organizationId!);
return res.json({ success: true });
} catch (error: any) {
if (error?.message?.includes('не найден')) return res.status(404).json({ success: false, error: error.message });
console.error('Move role member error:', error);
return res.status(500).json({ success: false, error: 'Внутренняя ошибка сервера' });
}
});
export default router;