query(' SELECT r.id, r.name, r.display_name, r.description, r.created_at, COUNT(u.id) as user_count FROM roles r LEFT JOIN users u ON u.role_id = r.id GROUP BY r.id ORDER BY r.id '); $roles = $stmt->fetchAll(); // Batch fetch all role-permission mappings in one query (was N+1) $stmt = $pdo->query(' SELECT rp.role_id, p.name FROM role_permissions rp JOIN permissions p ON p.id = rp.permission_id '); $allRolePerms = $stmt->fetchAll(); // Group permissions by role_id $permsByRole = []; foreach ($allRolePerms as $rp) { $permsByRole[$rp['role_id']][] = $rp['name']; } foreach ($roles as &$role) { $role['permissions'] = $permsByRole[$role['id']] ?? []; $role['permission_count'] = count($role['permissions']); } unset($role); // Get all available permissions grouped by module $stmt = $pdo->query('SELECT id, name, display_name, description FROM permissions ORDER BY id'); $allPermissions = $stmt->fetchAll(); $grouped = []; foreach ($allPermissions as $perm) { $parts = explode('.', $perm['name'], 2); $module = $parts[0]; if (!isset($grouped[$module])) { $grouped[$module] = []; } $grouped[$module][] = $perm; } successResponse([ 'roles' => $roles, 'permissions' => $allPermissions, 'permission_groups' => $grouped, ]); } /** * POST - Create new role */ function handleCreateRole(PDO $pdo): void { $input = getJsonInput(); $name = trim($input['name'] ?? ''); $displayName = trim($input['display_name'] ?? ''); $description = trim($input['description'] ?? ''); $permissions = $input['permissions'] ?? []; if (!$name) { errorResponse('Název role je povinný'); } if (!$displayName) { errorResponse('Zobrazovaný název je povinný'); } // Validate name format (slug) if (!preg_match('/^[a-z0-9_-]+$/', $name)) { errorResponse('Název role může obsahovat pouze malá písmena, čísla, pomlčky a podtržítka'); } // Check uniqueness $stmt = $pdo->prepare('SELECT id FROM roles WHERE name = ?'); $stmt->execute([$name]); if ($stmt->fetch()) { errorResponse('Role s tímto názvem již existuje'); } $pdo->beginTransaction(); try { // Create role $stmt = $pdo->prepare(' INSERT INTO roles (name, display_name, description) VALUES (?, ?, ?) '); $stmt->execute([$name, $displayName, $description ?: null]); $newRoleId = (int)$pdo->lastInsertId(); // Assign permissions if (!empty($permissions)) { $stmt = $pdo->prepare(' INSERT INTO role_permissions (role_id, permission_id) SELECT ?, id FROM permissions WHERE name = ? '); foreach ($permissions as $permName) { $stmt->execute([$newRoleId, $permName]); } } $pdo->commit(); AuditLog::logCreate('role', $newRoleId, [ 'name' => $name, 'display_name' => $displayName, 'permissions' => $permissions, ], "Vytvořena role '$displayName'"); successResponse(['id' => $newRoleId], 'Role byla vytvořena'); } catch (PDOException $e) { $pdo->rollBack(); throw $e; } } /** * PUT - Update role */ function handleUpdateRole(PDO $pdo, int $roleId): void { // Get existing role $stmt = $pdo->prepare( 'SELECT id, name, display_name, description FROM roles WHERE id = ?' ); $stmt->execute([$roleId]); $role = $stmt->fetch(); if (!$role) { errorResponse('Role nebyla nalezena', 404); } // Block editing admin role name if ($role['name'] === 'admin') { errorResponse('Roli administrátora nelze upravovat'); } $input = getJsonInput(); $displayName = trim($input['display_name'] ?? $role['display_name']); $description = trim($input['description'] ?? $role['description'] ?? ''); $permissions = $input['permissions'] ?? null; if (!$displayName) { errorResponse('Zobrazovaný název je povinný'); } $pdo->beginTransaction(); try { // Update role $stmt = $pdo->prepare(' UPDATE roles SET display_name = ?, description = ? WHERE id = ? '); $stmt->execute([$displayName, $description ?: null, $roleId]); // Update permissions if provided if ($permissions !== null) { // Remove existing permissions $stmt = $pdo->prepare('DELETE FROM role_permissions WHERE role_id = ?'); $stmt->execute([$roleId]); // Add new permissions if (!empty($permissions)) { $stmt = $pdo->prepare(' INSERT INTO role_permissions (role_id, permission_id) SELECT ?, id FROM permissions WHERE name = ? '); foreach ($permissions as $permName) { $stmt->execute([$roleId, $permName]); } } } $pdo->commit(); AuditLog::logUpdate('role', $roleId, [ 'display_name' => $role['display_name'], ], [ 'display_name' => $displayName, 'permissions' => $permissions, ], "Upravena role '$displayName'"); successResponse(null, 'Role byla aktualizována'); } catch (PDOException $e) { $pdo->rollBack(); throw $e; } } /** * DELETE - Delete role */ function handleDeleteRole(PDO $pdo, int $roleId): void { $stmt = $pdo->prepare( 'SELECT id, name, display_name, description FROM roles WHERE id = ?' ); $stmt->execute([$roleId]); $role = $stmt->fetch(); if (!$role) { errorResponse('Role nebyla nalezena', 404); } // Block deleting admin role if ($role['name'] === 'admin') { errorResponse('Roli administrátora nelze smazat'); } // Check if role has users $stmt = $pdo->prepare('SELECT COUNT(*) FROM users WHERE role_id = ?'); $stmt->execute([$roleId]); $userCount = $stmt->fetchColumn(); if ($userCount > 0) { errorResponse("Nelze smazat roli s {$userCount} přiřazenými uživateli. Nejprve změňte roli těmto uživatelům."); } // Delete role (cascade deletes role_permissions) $stmt = $pdo->prepare('DELETE FROM roles WHERE id = ?'); $stmt->execute([$roleId]); AuditLog::logDelete('role', $roleId, $role, "Smazána role '{$role['display_name']}'"); successResponse(null, 'Role byla smazána'); }