301 lines
8.3 KiB
PHP
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');
|
|
}
|