208 lines
11 KiB
TypeScript
208 lines
11 KiB
TypeScript
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;
|