Files
app/api/admin/roles.php
2026-03-12 12:43:56 +01:00

301 lines
8.3 KiB
PHP

<?php
/**
* BOHA Automation - Roles API
*
* GET /api/admin/roles.php - List all roles with permissions
* POST /api/admin/roles.php - Create new role
* PUT /api/admin/roles.php?id=X - Update role
* DELETE /api/admin/roles.php?id=X - Delete role
*/
declare(strict_types=1);
require_once dirname(__DIR__) . '/config.php';
require_once dirname(__DIR__) . '/includes/JWTAuth.php';
require_once dirname(__DIR__) . '/includes/AuditLog.php';
// Set headers
setCorsHeaders();
setSecurityHeaders();
setNoCacheHeaders();
header('Content-Type: application/json; charset=utf-8');
// Require authentication
$authData = JWTAuth::requireAuth();
AuditLog::setUser($authData['user_id'], $authData['user']['username'] ?? 'unknown');
// Require settings.roles permission
requirePermission($authData, 'settings.roles');
$method = $_SERVER['REQUEST_METHOD'];
$roleId = isset($_GET['id']) ? (int) $_GET['id'] : null;
try {
$pdo = db();
switch ($method) {
case 'GET':
handleGetRole($pdo);
break;
case 'POST':
handleCreateRole($pdo);
break;
case 'PUT':
if (!$roleId) {
errorResponse('ID role je povinné');
}
handleUpdateRole($pdo, $roleId);
break;
case 'DELETE':
if (!$roleId) {
errorResponse('ID role je povinné');
}
handleDeleteRole($pdo, $roleId);
break;
default:
errorResponse('Metoda není povolena', 405);
}
} catch (PDOException $e) {
error_log('Roles API error: ' . $e->getMessage());
errorResponse('Chyba databáze', 500);
}
/**
* GET - List all roles with their permissions + all available permissions
*/
function handleGetRole(PDO $pdo): void
{
// Get all roles with user count (LEFT JOIN instead of correlated subquery)
$stmt = $pdo->query('
SELECT r.*, 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 * 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 * 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');
}