Introduce a new database table `department_selfserve_path_confirmations` to store path confirmations related to department configurations. Update `PingApiTest` to verify additional keys, ensuring `backend_version` and `api_commit_sha` are checked in the response.
339 lines
17 KiB
PHP
339 lines
17 KiB
PHP
<?php
|
|
|
|
namespace routes;
|
|
|
|
use classes\authentication;
|
|
use modules\selfserve\classes\selfserve_config_versioning;
|
|
use modules\selfserve\classes\selfserve_studio_graph;
|
|
use modules\selfserve\classes\selfserve_wash_flow;
|
|
use objects\logs_o;
|
|
use traits\route_t;
|
|
|
|
class departmentSelfserveStudioRoute
|
|
{
|
|
use route_t;
|
|
|
|
public function run(): void
|
|
{
|
|
$this->get('/department/selfserve/studio/graph', function (): void {
|
|
global $response;
|
|
$user = $this->requireStudioUser('list_department_selfserve_config_versions');
|
|
self::requireParameters(['department']);
|
|
$departmentId = (int)self::getParameter('department');
|
|
$this->assertDepartmentAccess($user, $departmentId, ['view_all_department_selfserve_config_versions']);
|
|
|
|
$service = new selfserve_studio_graph();
|
|
(new logs_o())->add('selfserve_studio', $departmentId, 1, $user->id, 'GET_STUDIO_GRAPH', 'Fetched self-serve studio graph');
|
|
$response->success($service->buildGraph($departmentId, (int)$user->id, $this->studioPermissions()));
|
|
}, [
|
|
'list_department_selfserve_config_versions' => 'View the all-in-one self-serve studio graph',
|
|
'view_all_department_selfserve_config_versions' => 'View the all-in-one self-serve studio graph across departments',
|
|
]);
|
|
|
|
$this->put('/department/selfserve/studio/graph', function (): void {
|
|
global $response;
|
|
$user = $this->requireStudioUser('edit_department_selfserve_config_versions');
|
|
self::requireParameters(['department']);
|
|
$departmentId = (int)self::getParameter('department');
|
|
$this->assertDepartmentAccess($user, $departmentId);
|
|
|
|
try {
|
|
$payload = self::getParametersAsArray();
|
|
$service = new selfserve_studio_graph();
|
|
$graph = $service->applyGraphSave($departmentId, $payload, (int)$user->id, $this->studioPermissions());
|
|
(new logs_o())->add('selfserve_studio', $departmentId, 1, $user->id, 'SAVE_STUDIO_GRAPH', 'Saved self-serve studio graph');
|
|
$response->success($graph);
|
|
} catch (\RuntimeException $exception) {
|
|
$response->error($exception->getMessage(), 422);
|
|
}
|
|
}, [
|
|
'edit_department_selfserve_config_versions' => 'Create, update, delete, connect, and reorder self-serve studio graph objects',
|
|
]);
|
|
|
|
$this->put('/department/selfserve/studio/layout', function (): void {
|
|
global $response;
|
|
$user = $this->requireStudioUser('edit_department_selfserve_config_versions');
|
|
self::requireParameters(['department', 'layout']);
|
|
self::requireType(self::getParameter('layout'), self::TYPE_ARRAY());
|
|
$departmentId = (int)self::getParameter('department');
|
|
$this->assertDepartmentAccess($user, $departmentId);
|
|
|
|
try {
|
|
$layout = (new selfserve_studio_graph())->saveLayout($departmentId, (int)$user->id, (array)self::getParameter('layout'));
|
|
(new logs_o())->add('selfserve_studio', $departmentId, 1, $user->id, 'SAVE_STUDIO_LAYOUT', 'Saved self-serve studio layout');
|
|
$response->success($layout);
|
|
} catch (\RuntimeException $exception) {
|
|
$response->error($exception->getMessage(), 422);
|
|
}
|
|
}, [
|
|
'edit_department_selfserve_config_versions' => 'Save canvas-only self-serve studio layout',
|
|
]);
|
|
|
|
$this->put('/department/selfserve/studio/virtual-hardware', function (): void {
|
|
global $response;
|
|
$user = $this->requireStudioUser('edit_department_selfserve_config_versions');
|
|
self::requireParameters(['department', 'operation']);
|
|
$departmentId = (int)self::getParameter('department');
|
|
$this->assertDepartmentAccess($user, $departmentId);
|
|
|
|
try {
|
|
$payload = self::getParametersAsArray();
|
|
$graph = (new selfserve_studio_graph())->applyVirtualHardwareOperation(
|
|
$departmentId,
|
|
$payload,
|
|
(int)$user->id,
|
|
$this->studioPermissions()
|
|
);
|
|
(new logs_o())->add('selfserve_studio', $departmentId, 1, $user->id, 'SAVE_STUDIO_VIRTUAL_HARDWARE', 'Saved self-serve studio virtual hardware');
|
|
$response->success($graph);
|
|
} catch (\RuntimeException $exception) {
|
|
$response->error($exception->getMessage(), 422);
|
|
}
|
|
}, [
|
|
'edit_department_selfserve_config_versions' => 'Generate and edit studio-only virtual hardware',
|
|
]);
|
|
|
|
$this->post('/department/selfserve/studio/validate', function (): void {
|
|
global $response;
|
|
$user = $this->requireStudioUser('edit_department_selfserve_config_versions');
|
|
self::requireParameters(['department']);
|
|
$departmentId = (int)self::getParameter('department');
|
|
$this->assertDepartmentAccess($user, $departmentId);
|
|
|
|
$validation = (new selfserve_studio_graph())->validatePayload($departmentId, self::getParametersAsArray());
|
|
(new logs_o())->add('selfserve_studio', $departmentId, 1, $user->id, 'VALIDATE_STUDIO_GRAPH', 'Validated self-serve studio graph');
|
|
$response->success($validation);
|
|
}, [
|
|
'edit_department_selfserve_config_versions' => 'Validate the self-serve studio graph',
|
|
]);
|
|
|
|
$this->post('/department/selfserve/studio/simulate', function (): void {
|
|
global $response;
|
|
$user = $this->requireStudioUser('list_department_selfserve_vehicle_conditions');
|
|
self::requireParameters(['department', 'lane_id', 'reg']);
|
|
$departmentId = (int)self::getParameter('department');
|
|
$laneId = (int)self::getParameter('lane_id');
|
|
self::requireParameterIntPositive($laneId, 'lane_id');
|
|
$this->assertDepartmentAccess($user, $departmentId);
|
|
|
|
try {
|
|
$result = (new selfserve_studio_graph())->simulateGraph(
|
|
$departmentId,
|
|
self::getParametersAsArray(),
|
|
(int)$user->id,
|
|
$this->studioPermissions()
|
|
);
|
|
(new logs_o())->add('selfserve_studio', $departmentId, 1, $user->id, 'SIMULATE_STUDIO_GRAPH', 'Simulated self-serve studio graph');
|
|
$response->success($result);
|
|
} catch (\Throwable $exception) {
|
|
$response->error($exception->getMessage(), 422);
|
|
}
|
|
}, [
|
|
'list_department_selfserve_vehicle_conditions' => 'Run the self-serve studio simulator',
|
|
]);
|
|
|
|
$this->post('/department/selfserve/studio/path-outcomes/stream', function (): void {
|
|
$user = $this->requireStudioUser('list_department_selfserve_vehicle_conditions');
|
|
self::requireParameters(['department']);
|
|
$departmentId = (int)self::getParameter('department');
|
|
$this->assertDepartmentAccess($user, $departmentId);
|
|
|
|
ini_set('display_errors', '0');
|
|
ini_set('html_errors', '0');
|
|
header('Content-Type: application/x-ndjson; charset=utf-8');
|
|
header('Cache-Control: no-cache, no-transform');
|
|
header('X-Accel-Buffering: no');
|
|
|
|
$emit = static function (array $event): void {
|
|
echo json_encode($event, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n";
|
|
if (function_exists('ob_flush')) {
|
|
@ob_flush();
|
|
}
|
|
@flush();
|
|
};
|
|
|
|
try {
|
|
$result = (new selfserve_studio_graph())->projectPathOutcomes(
|
|
$departmentId,
|
|
self::getParametersAsArray(),
|
|
(int)$user->id,
|
|
$this->studioPermissions(),
|
|
static function (array $partial) use ($emit): void {
|
|
$emit(['type' => 'progress', 'data' => $partial]);
|
|
}
|
|
);
|
|
(new logs_o())->add('selfserve_studio', $departmentId, 1, $user->id, 'PROJECT_STUDIO_PATH_OUTCOMES', 'Projected self-serve studio path outcomes');
|
|
$emit(['type' => 'complete', 'data' => $result]);
|
|
} catch (\Throwable $exception) {
|
|
$emit(['type' => 'error', 'message' => $exception->getMessage()]);
|
|
}
|
|
exit;
|
|
}, [
|
|
'list_department_selfserve_vehicle_conditions' => 'Stream grouped self-serve studio question path outcome progress',
|
|
]);
|
|
|
|
$this->post('/department/selfserve/studio/path-outcomes', function (): void {
|
|
global $response;
|
|
$user = $this->requireStudioUser('list_department_selfserve_vehicle_conditions');
|
|
self::requireParameters(['department']);
|
|
$departmentId = (int)self::getParameter('department');
|
|
$this->assertDepartmentAccess($user, $departmentId);
|
|
|
|
try {
|
|
$result = (new selfserve_studio_graph())->projectPathOutcomes(
|
|
$departmentId,
|
|
self::getParametersAsArray(),
|
|
(int)$user->id,
|
|
$this->studioPermissions()
|
|
);
|
|
(new logs_o())->add('selfserve_studio', $departmentId, 1, $user->id, 'PROJECT_STUDIO_PATH_OUTCOMES', 'Projected self-serve studio path outcomes');
|
|
$response->success($result);
|
|
} catch (\Throwable $exception) {
|
|
$response->error($exception->getMessage(), 422);
|
|
}
|
|
}, [
|
|
'list_department_selfserve_vehicle_conditions' => 'Project grouped self-serve studio question path outcomes',
|
|
]);
|
|
|
|
$this->post('/department/selfserve/studio/path-confirmations', function (): void {
|
|
global $response;
|
|
$user = $this->requireStudioUser('edit_department_selfserve_config_versions');
|
|
self::requireParameters(['department', 'path_signature']);
|
|
$departmentId = (int)self::getParameter('department');
|
|
$this->assertDepartmentAccess($user, $departmentId);
|
|
|
|
try {
|
|
$payload = self::getParametersAsArray();
|
|
$action = strtolower(trim((string)($payload['action'] ?? 'confirm')));
|
|
$service = new selfserve_studio_graph();
|
|
$result = in_array($action, ['delete', 'reset', 'clear'], true)
|
|
? $service->resetPathConfirmation($departmentId, $payload)
|
|
: $service->confirmPathOutcome($departmentId, $payload, (int)$user->id);
|
|
(new logs_o())->add('selfserve_studio', $departmentId, 1, $user->id, 'CONFIRM_STUDIO_PATH', 'Updated self-serve studio path confirmation');
|
|
$response->success($result);
|
|
} catch (\RuntimeException $exception) {
|
|
$response->error($exception->getMessage(), 422);
|
|
}
|
|
}, [
|
|
'edit_department_selfserve_config_versions' => 'Confirm or reset projected self-serve studio answer paths',
|
|
]);
|
|
|
|
$this->post('/department/selfserve/studio/publish', function (): void {
|
|
global $response;
|
|
$user = $this->requireStudioUser('publish_department_selfserve_config_versions');
|
|
self::requireParameters(['department']);
|
|
$departmentId = (int)self::getParameter('department');
|
|
$this->assertDepartmentAccess($user, $departmentId);
|
|
|
|
try {
|
|
$published = (new selfserve_config_versioning())->publishDraft($departmentId, (int)$user->id);
|
|
$validation = (new selfserve_studio_graph())->validatePayload($departmentId);
|
|
$published['warnings'] = (array)($validation['warnings'] ?? []);
|
|
$published['validation'] = $validation;
|
|
(new logs_o())->add('selfserve_studio', $departmentId, 1, $user->id, 'PUBLISH_STUDIO_GRAPH', 'Published self-serve studio graph');
|
|
$response->success($published);
|
|
} catch (\RuntimeException $exception) {
|
|
$response->error($exception->getMessage(), 422);
|
|
}
|
|
}, [
|
|
'publish_department_selfserve_config_versions' => 'Publish the self-serve studio draft',
|
|
]);
|
|
|
|
$this->post('/department/selfserve/studio/rollback', function (): void {
|
|
global $response;
|
|
$user = $this->requireStudioUser('rollback_department_selfserve_config_versions');
|
|
self::requireParameters(['department', 'target_version_id']);
|
|
$departmentId = (int)self::getParameter('department');
|
|
$targetVersionId = (int)self::getParameter('target_version_id');
|
|
self::requireParameterIntPositive($targetVersionId, 'target_version_id');
|
|
$this->assertDepartmentAccess($user, $departmentId);
|
|
|
|
try {
|
|
$rolledBack = (new selfserve_config_versioning())->rollbackToVersion($departmentId, $targetVersionId, (int)$user->id);
|
|
(new logs_o())->add('selfserve_studio', $departmentId, 1, $user->id, 'ROLLBACK_STUDIO_GRAPH', 'Rolled back self-serve studio graph');
|
|
$response->success($rolledBack);
|
|
} catch (\RuntimeException $exception) {
|
|
$response->error($exception->getMessage(), 422);
|
|
}
|
|
}, [
|
|
'rollback_department_selfserve_config_versions' => 'Rollback the self-serve studio draft to an earlier version',
|
|
]);
|
|
|
|
$this->post('/department/selfserve/studio/gateway-action', function (): void {
|
|
global $response;
|
|
$user = $this->requireStudioUser('modules_shelly_config');
|
|
self::requireParameters(['department', 'gateway_id', 'action']);
|
|
$departmentId = (int)self::getParameter('department');
|
|
$gatewayId = (int)self::getParameter('gateway_id');
|
|
self::requireParameterIntPositive($gatewayId, 'gateway_id');
|
|
$this->assertDepartmentAccess($user, $departmentId);
|
|
|
|
$action = strtolower((string)self::getParameter('action'));
|
|
$confirmed = filter_var(self::getParameter('confirm'), FILTER_VALIDATE_BOOLEAN);
|
|
if (in_array($action, ['uninstall', 'rotate_credentials'], true) && $confirmed !== true) {
|
|
$response->error('This gateway action requires explicit confirmation.', 428);
|
|
}
|
|
|
|
try {
|
|
$payload = self::getParametersAsArray();
|
|
$result = (new selfserve_studio_graph())->runGatewayAction($departmentId, $gatewayId, $action, $payload, (int)$user->id);
|
|
(new logs_o())->add('selfserve_studio', $departmentId, 1, $user->id, 'GATEWAY_STUDIO_ACTION', 'Ran self-serve studio gateway action: ' . $action);
|
|
$response->success($result);
|
|
} catch (\Throwable $exception) {
|
|
$response->error($exception->getMessage(), 422);
|
|
}
|
|
}, [
|
|
'modules_shelly_config' => 'Run permission-gated self-serve studio edge gateway actions',
|
|
]);
|
|
}
|
|
|
|
private function requireStudioUser(string $permission): object
|
|
{
|
|
global $response;
|
|
$this->requirePermission($permission);
|
|
$user = (new authentication())->get_user();
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
|
|
return $user;
|
|
}
|
|
|
|
/**
|
|
* @param array<int,string> $bypassPermissions
|
|
*/
|
|
private function assertDepartmentAccess(object $user, int $departmentId, array $bypassPermissions = []): void
|
|
{
|
|
$authorizedDepartmentIds = $user->getGroup()->getDepartments();
|
|
if (in_array($departmentId, $authorizedDepartmentIds, true)) {
|
|
return;
|
|
}
|
|
|
|
foreach ($bypassPermissions as $permission) {
|
|
if ($this->hasPermission($permission)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
$this->forbidDepartmentAccess($departmentId, $bypassPermissions);
|
|
}
|
|
|
|
/**
|
|
* @return array<string,bool>
|
|
*/
|
|
private function studioPermissions(): array
|
|
{
|
|
return [
|
|
'can_view' => $this->hasPermission('list_department_selfserve_config_versions'),
|
|
'can_edit' => $this->hasPermission('edit_department_selfserve_config_versions'),
|
|
'can_publish' => $this->hasPermission('publish_department_selfserve_config_versions'),
|
|
'can_rollback' => $this->hasPermission('rollback_department_selfserve_config_versions'),
|
|
'can_simulate' => $this->hasPermission('list_department_selfserve_vehicle_conditions'),
|
|
'modules_shelly_config' => $this->hasPermission('modules_shelly_config'),
|
|
'can_manage_gateways' => $this->hasPermission('modules_shelly_config'),
|
|
'can_run_gateway_destructive_actions' => $this->hasPermission('modules_shelly_config'),
|
|
'can_run_live_lane_actions' => $this->hasPermission('modules_selfserve_sessions_force_stop'),
|
|
];
|
|
}
|
|
}
|