Add CRUD operations for subuser grants and list permission nodes

- Implement routes for managing subuser grants: listing, creating, updating, and deleting.
- Add endpoint to fetch available permission nodes grouped by type.
- Extend `subuser_grants_o` with new utility methods, including `asArray`.
- Update OpenAPI documentation to include new endpoints and schemas.
This commit is contained in:
Jeppe Bundgaard
2026-02-11 17:25:53 +01:00
parent a4f71261d9
commit 354ad86de3
3 changed files with 477 additions and 0 deletions
+189
View File
@@ -13,6 +13,12 @@ use objects\logs_o;
use objects\subuser_grants_o;
use objects\subusers_o;
use objects\users_o;
use modules\subusers\permissions\subusers_permission_nodes_bookings;
use modules\subusers\permissions\subusers_permission_nodes_orders;
use modules\subusers\permissions\subusers_permission_nodes_selfserve;
use modules\subusers\permissions\subusers_permission_nodes_subusers;
use modules\subusers\permissions\subusers_permission_nodes_vehicles;
use modules\subusers\helpers\subusers_permission_node_key;
use traits\route_t;
class subusersRoute
@@ -29,6 +35,189 @@ class subusersRoute
public function run(): void
{
// =============================
// Subuser Grant Management
// =============================
$this->get('/subusers/grants', function () {
global $response;
// Optional filters: customer_number, subuser_id
$filters = ['deleted_at' => null];
$hasFilter = false;
if (self::isParametersSet(['customer_number'])) {
$customer_number = (int)self::getParameter('customer_number');
self::requireType($customer_number, self::type_int());
$filters['billing_customer_number'] = $customer_number;
$hasFilter = true;
}
if (self::isParametersSet(['subuser_id'])) {
$subuser_id = (int)self::getParameter('subuser_id');
self::requireType($subuser_id, self::type_int());
$filters['subuser'] = $subuser_id;
$hasFilter = true;
}
if (!$hasFilter) {
$response->error('You must provide at least one of: customer_number or subuser_id', 400);
}
$rows = (new subuser_grants_o())->getFieldsWhere($filters, [
'id', 'billing_customer_number', 'subuser', 'enabled', 'note', 'permissions', 'created_at', 'updated_at', 'deleted_at'
]);
// Decode permissions JSON
$data = array_map(function ($row) {
$row['id'] = (int)$row['id'];
$row['billing_customer_number'] = (int)$row['billing_customer_number'];
$row['subuser'] = (int)$row['subuser'];
$row['enabled'] = (bool)$row['enabled'];
$row['permissions'] = (!empty($row['permissions'])) ? (json_decode($row['permissions'], true) ?: []) : [];
return $row;
}, $rows);
$response->success(['grants' => $data]);
}, []);
$this->post('/subusers/grants', function () {
global $response;
self::requireParameters(['customer_number', 'subuser_id']);
$customer_number = (int)self::getParameter('customer_number');
$subuser_id = (int)self::getParameter('subuser_id');
self::requireType($customer_number, self::type_int());
self::requireType($subuser_id, self::type_int());
$enabled = true;
if (self::isParametersSet(['enabled'])) {
$enabled = (bool)filter_var(self::getParameter('enabled'), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
if ($enabled === null) { $enabled = true; }
}
$note = null;
if (self::isParametersSet(['note'])) {
$note = (string)self::getParameter('note');
self::requireType($note, self::type_string());
self::requireMaxLength('note', 65535);
}
$permissions = null;
if (self::isParametersSet(['permissions'])) {
$raw = self::getParameter('permissions');
// Expect array (already parsed) or JSON string
if (is_string($raw)) {
$decoded = json_decode($raw, true);
if (!is_array($decoded)) {
$response->error('Invalid permissions payload', 400);
}
$permissions = $decoded;
} elseif (is_array($raw)) {
$permissions = $raw;
} else {
$response->error('Invalid permissions type', 400);
}
// Validate each permission is a known key
foreach ($permissions as $perm) {
if (!is_string($perm) || subusers_permission_node_key::tryFrom($perm) === null) {
$response->error('Unknown permission key: ' . (string)$perm, 400);
}
}
}
try {
$grant = (new subuser_grants_o())->add($customer_number, $subuser_id, (bool)$enabled, $note, $permissions);
$response->success(['grant' => $grant->asArray()]);
} catch (Exception $e) {
$response->error('Failed to add subuser grant', 500);
}
}, []);
$this->patch('/subusers/grants/{id}', function () {
global $response;
$id = (int)self::fromRoute('id');
self::requireType($id, self::type_int());
$grant = (new subuser_grants_o())->select($id);
$grant->requireSelected();
$data = [];
if (self::isParametersSet(['enabled'])) {
$enabled = (bool)filter_var(self::getParameter('enabled'), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
if ($enabled === null) {
$response->error('Invalid enabled flag', 400);
}
$data['enabled'] = (bool)$enabled;
}
if (self::isParametersSet(['note'])) {
$note = (string)self::getParameter('note');
self::requireType($note, self::type_string());
self::requireMaxLength('note', 65535);
$data['note'] = $note;
}
if (self::isParametersSet(['permissions'])) {
$raw = self::getParameter('permissions');
$permissions = null;
if (is_string($raw)) {
$decoded = json_decode($raw, true);
if (!is_array($decoded)) {
$response->error('Invalid permissions payload', 400);
}
$permissions = $decoded;
} elseif (is_array($raw)) {
$permissions = $raw;
} else {
$response->error('Invalid permissions type', 400);
}
foreach ($permissions as $perm) {
if (!is_string($perm) || subusers_permission_node_key::tryFrom($perm) === null) {
$response->error('Unknown permission key: ' . (string)$perm, 400);
}
}
$data['permissions'] = $permissions;
}
if (empty($data)) {
$response->error('No updatable fields provided', 400);
}
try {
$grant->update($data);
$response->success(['grant' => $grant->asArray()]);
} catch (Exception $e) {
$response->error('Failed to update grant', 500);
}
}, []);
$this->delete('/subusers/grants/{id}', function () {
global $response;
$id = (int)self::fromRoute('id');
self::requireType($id, self::type_int());
$grant = (new subuser_grants_o())->select($id);
$grant->requireSelected();
try {
$grant->delete();
$response->success(['message' => 'Grant deleted']);
} catch (Exception $e) {
$response->error('Failed to delete grant', 500);
}
}, []);
$this->get('/subusers/permission-nodes', function () {
global $response;
// Build groups
$groups = [
new subusers_permission_nodes_bookings(),
new subusers_permission_nodes_orders(),
new subusers_permission_nodes_selfserve(),
new subusers_permission_nodes_subusers(),
new subusers_permission_nodes_vehicles(),
];
$out = [];
foreach ($groups as $group) {
$nodes = [];
foreach ($group->nodes as $key => $node) {
$nodes[] = [
'key' => $key,
'name' => $node->name,
'description' => $node->description,
'type' => $node->type->name,
'default' => (bool)($node->value ?? false),
];
}
$out[] = [
'group' => $group->group_name,
'description' => $group->description,
'nodes' => $nodes,
];
}
$response->success(['permission_nodes' => $out]);
}, []);
$this->post('/subusers', function () {
global /** @var response $response */
$response;