Add system status displays for Minio and Redis, and enhance backup configuration
This commit is contained in:
@@ -6,10 +6,12 @@ use classes\authentication;
|
||||
use classes\economic;
|
||||
use classes\gatewayapi;
|
||||
use classes\response;
|
||||
use classes\subusers_schema_bootstrap;
|
||||
use classes\subuser_permission_templates_service;
|
||||
use classes\virkdata;
|
||||
use Exception;
|
||||
use modules\virkdata\helpers\virkdata_response;
|
||||
use objects\customer_vehicles_o;
|
||||
use objects\logs_o;
|
||||
use objects\subuser_grants_o;
|
||||
use objects\subusers_o;
|
||||
@@ -26,6 +28,8 @@ class subusersRoute
|
||||
{
|
||||
use route_t;
|
||||
|
||||
private const DOGNVASK_PERMISSION_KEYS = ['SELFSERVE_LIST', 'SELFSERVE_ADD'];
|
||||
|
||||
private function getOwnPermissionForNode(subusers_permission_node_key $node)
|
||||
{
|
||||
return match ($node) {
|
||||
@@ -182,6 +186,100 @@ class subusersRoute
|
||||
return $name === '' || $name === 'Unknown Customer' ? null : $name;
|
||||
}
|
||||
|
||||
private function hasDognvaskAccess(array $permissions, bool $grantEnabled): bool
|
||||
{
|
||||
if (!$grantEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$permissions = subuser_grants_o::normalizePermissionsValue($permissions);
|
||||
foreach (self::DOGNVASK_PERMISSION_KEYS as $permission) {
|
||||
if (!in_array($permission, $permissions, true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function normalizeAssignedVehicleId(mixed $value): ?int
|
||||
{
|
||||
global $response;
|
||||
|
||||
if ($value === null || $value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_int($value)) {
|
||||
$id = $value;
|
||||
} else {
|
||||
$raw = trim((string)$value);
|
||||
if ($raw === '') {
|
||||
return null;
|
||||
}
|
||||
if (!preg_match('/^[1-9][0-9]*$/', $raw)) {
|
||||
$response->error('Invalid assigned vehicle id', 400);
|
||||
}
|
||||
$id = (int)$raw;
|
||||
}
|
||||
|
||||
if ($id <= 0) {
|
||||
$response->error('Invalid assigned vehicle id', 400);
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
private function assignedVehiclePayload(?int $vehicleId, ?int $customerNumber = null, bool $strict = false): ?array
|
||||
{
|
||||
global $response;
|
||||
|
||||
if ($vehicleId === null || $vehicleId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$vehicle = (new customer_vehicles_o())->select($vehicleId);
|
||||
if (!$vehicle->exists()) {
|
||||
if ($strict) {
|
||||
$response->error('Assigned vehicle not found', 404);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
$vehicle->getObjectProperties();
|
||||
if ($vehicle->deleted_at->value() !== null) {
|
||||
if ($strict) {
|
||||
$response->error('Assigned vehicle not found', 404);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
$vehicleCustomerNumber = (int)$vehicle->customer_id->value();
|
||||
if ($customerNumber !== null && $vehicleCustomerNumber !== (int)$customerNumber) {
|
||||
if ($strict) {
|
||||
$response->error('Assigned vehicle must belong to the selected customer', 400);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
$reg = strtoupper(trim((string)$vehicle->reg->value()));
|
||||
return [
|
||||
'id' => (int)$vehicle->id,
|
||||
'reg' => $reg !== '' ? $reg : null,
|
||||
];
|
||||
}
|
||||
|
||||
private function validateAssignedVehicleIdForCustomer(mixed $value, int $customerNumber): ?int
|
||||
{
|
||||
$vehicleId = $this->normalizeAssignedVehicleId($value);
|
||||
if ($vehicleId === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->assignedVehiclePayload($vehicleId, $customerNumber, true);
|
||||
return $vehicleId;
|
||||
}
|
||||
|
||||
private function assertSubuserIdentifiersAvailable(
|
||||
?int $phoneCountryCode,
|
||||
?int $phone,
|
||||
@@ -264,6 +362,170 @@ class subusersRoute
|
||||
return $frontendBaseUrl . '/complete-registration?token=' . rawurlencode($token);
|
||||
}
|
||||
|
||||
private function buildDirectSubuserLoginPath(string $sessionToken, int $customerNumber): string
|
||||
{
|
||||
return '/login/qr?token=' . rawurlencode($sessionToken)
|
||||
. '&type=subuser&customer_number=' . rawurlencode((string)$customerNumber);
|
||||
}
|
||||
|
||||
private function loadSubuserOrFail(int $subuserId): subusers_o
|
||||
{
|
||||
global $response;
|
||||
|
||||
$subuser = (new subusers_o())->select($subuserId);
|
||||
if (!$subuser->exists()) {
|
||||
$response->error('Subuser not found', 404);
|
||||
}
|
||||
$subuser->getObjectProperties();
|
||||
return $subuser;
|
||||
}
|
||||
|
||||
private function buildSubuserAccountPayload(subusers_o $subuser): array
|
||||
{
|
||||
return [
|
||||
'id' => (int)$subuser->id,
|
||||
'username' => $subuser->username->value(),
|
||||
'name' => $subuser->name->value(),
|
||||
'email' => $subuser->email->value(),
|
||||
'phone_country_code' => $subuser->phone_country_code->value() !== null
|
||||
? (int)$subuser->phone_country_code->value()
|
||||
: null,
|
||||
'phone' => $subuser->phone->value() !== null ? (int)$subuser->phone->value() : null,
|
||||
'setup_required' => $subuser->requiresSetup(),
|
||||
'updated_at' => $subuser->updated_at->value() ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
private function parseSuperuserSubuserProfileUpdates(int $subuserId): array
|
||||
{
|
||||
global $response;
|
||||
|
||||
$updates = [];
|
||||
|
||||
if (self::isParametersSet(['name'])) {
|
||||
$name = $this->normalizeOptionalString(self::getParameter('name'));
|
||||
if ($name === null || strlen($name) < 3 || strlen($name) > 255) {
|
||||
$response->error('Name must be between 3 and 255 characters long', 400);
|
||||
}
|
||||
$updates['name'] = $name;
|
||||
}
|
||||
|
||||
if (self::isParametersSet(['username'])) {
|
||||
$username = $this->normalizeOptionalString(self::getParameter('username'));
|
||||
if ($username !== null && (strlen($username) < 3 || strlen($username) > 50)) {
|
||||
$response->error('Username must be between 3 and 50 characters long', 400);
|
||||
}
|
||||
$updates['username'] = $username;
|
||||
}
|
||||
|
||||
if (self::isParametersSet(['email'])) {
|
||||
$email = $this->normalizeOptionalString(self::getParameter('email'));
|
||||
if ($email !== null && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
$response->error('Invalid email format', 400);
|
||||
}
|
||||
if ($email !== null && strlen($email) > 255) {
|
||||
$response->error('Email must be at most 255 characters long', 400);
|
||||
}
|
||||
$updates['email'] = $email;
|
||||
}
|
||||
|
||||
$hasPhoneCountryCode = self::isParametersSet(['phone_country_code']);
|
||||
$hasPhone = self::isParametersSet(['phone']);
|
||||
if ($hasPhoneCountryCode || $hasPhone) {
|
||||
if (!$hasPhoneCountryCode || !$hasPhone) {
|
||||
$response->error('Phone country code and phone must be provided together', 400);
|
||||
}
|
||||
|
||||
$phoneCountryCode = trim((string)self::getParameter('phone_country_code'));
|
||||
$phone = trim((string)self::getParameter('phone'));
|
||||
if (!preg_match('/^[0-9]{1,3}$/', $phoneCountryCode)) {
|
||||
$response->error('Phone country code must be 1-3 digits', 400);
|
||||
}
|
||||
if (!preg_match('/^[0-9]{4,15}$/', $phone)) {
|
||||
$response->error('Phone must be 4-15 digits', 400);
|
||||
}
|
||||
|
||||
$updates['phone_country_code'] = (int)$phoneCountryCode;
|
||||
$updates['phone'] = (int)$phone;
|
||||
}
|
||||
|
||||
if ($updates === []) {
|
||||
$response->error('No fields to update', 400);
|
||||
}
|
||||
|
||||
$this->assertSubuserIdentifiersAvailable(
|
||||
$updates['phone_country_code'] ?? null,
|
||||
$updates['phone'] ?? null,
|
||||
$updates['username'] ?? null,
|
||||
$updates['email'] ?? null,
|
||||
$subuserId
|
||||
);
|
||||
|
||||
return $updates;
|
||||
}
|
||||
|
||||
private function getEnabledGrantForSubuserAndCustomerOrFail(int $subuserId, int $customerNumber): subuser_grants_o
|
||||
{
|
||||
global $response;
|
||||
|
||||
if ($customerNumber <= 0) {
|
||||
$response->error('Customer number is required', 400);
|
||||
}
|
||||
|
||||
$grant = (new subuser_grants_o())->getGrantForSubuserAndCustomer($subuserId, $customerNumber, true);
|
||||
if ($grant === null || !(bool)$grant->enabled->value() || $grant->deleted_at->value() !== null) {
|
||||
$response->error('Enabled subuser grant not found for selected customer', 404);
|
||||
}
|
||||
|
||||
return $grant;
|
||||
}
|
||||
|
||||
private function resolveDirectLoginCustomerNumber(subusers_o $subuser): int
|
||||
{
|
||||
global $response;
|
||||
|
||||
if (self::isParametersSet(['grant_id'])) {
|
||||
$grant = (new subuser_grants_o())->select((int)self::getParameter('grant_id'));
|
||||
if ($grant->exists()) {
|
||||
$grant->getObjectProperties();
|
||||
}
|
||||
if (
|
||||
!$grant->exists()
|
||||
|| (int)$grant->subuser->value() !== (int)$subuser->id
|
||||
|| !(bool)$grant->enabled->value()
|
||||
|| $grant->deleted_at->value() !== null
|
||||
) {
|
||||
$response->error('Enabled subuser grant not found', 404);
|
||||
}
|
||||
|
||||
return (int)$grant->billing_customer_number->value();
|
||||
}
|
||||
|
||||
if (self::isParametersSet(['customer_number'])) {
|
||||
$customerNumber = (int)self::getParameter('customer_number');
|
||||
$this->getEnabledGrantForSubuserAndCustomerOrFail((int)$subuser->id, $customerNumber);
|
||||
return $customerNumber;
|
||||
}
|
||||
|
||||
$grants = (new subuser_grants_o())->getFieldsWhere([
|
||||
'subuser' => (int)$subuser->id,
|
||||
'enabled' => 1,
|
||||
'deleted_at' => null,
|
||||
], ['billing_customer_number']);
|
||||
|
||||
$customerNumbers = array_values(array_unique(array_map(
|
||||
static fn (array $grant): int => (int)($grant['billing_customer_number'] ?? 0),
|
||||
$grants
|
||||
)));
|
||||
$customerNumbers = array_values(array_filter($customerNumbers, static fn (int $customerNumber): bool => $customerNumber > 0));
|
||||
|
||||
if (count($customerNumbers) !== 1) {
|
||||
$response->error('Customer number or grant id is required for drivers with multiple customer grants', 400);
|
||||
}
|
||||
|
||||
return $customerNumbers[0];
|
||||
}
|
||||
|
||||
private function clientThrottleIp(): string
|
||||
{
|
||||
$remoteAddress = trim((string)($_SERVER['REMOTE_ADDR'] ?? ''));
|
||||
@@ -363,6 +625,10 @@ class subusersRoute
|
||||
$setupRequired = $subuser->requiresSetup();
|
||||
$grantEnabled = $grant ? (bool)$grant->enabled->value() : false;
|
||||
$inviteAccepted = !$setupRequired;
|
||||
$assignedVehicleId = $grant && $grant->assigned_vehicle_id->value() !== null
|
||||
? (int)$grant->assigned_vehicle_id->value()
|
||||
: null;
|
||||
$assignedVehicle = $this->assignedVehiclePayload($assignedVehicleId, $customerNumber);
|
||||
|
||||
$accessState = 'inactive';
|
||||
if ($grant !== null && $grantEnabled) {
|
||||
@@ -391,6 +657,10 @@ class subusersRoute
|
||||
'grant_id' => $grant ? (int)$grant->id : null,
|
||||
'grant_enabled' => $grantEnabled,
|
||||
'grant_note' => $grant ? $grant->note->value() : null,
|
||||
'assigned_vehicle_id' => $assignedVehicle['id'] ?? null,
|
||||
'assigned_vehicle_reg' => $assignedVehicle['reg'] ?? null,
|
||||
'assigned_vehicle' => $assignedVehicle,
|
||||
'dognvask_enabled' => $this->hasDognvaskAccess($grantPermissions, $grantEnabled),
|
||||
'grant_permissions' => $grantPermissions,
|
||||
'permissions' => $grantPermissions,
|
||||
'permission_template_key' => $templateService->classify($grantPermissions, $grantEnabled),
|
||||
@@ -454,8 +724,8 @@ class subusersRoute
|
||||
'id' => 's.`id`',
|
||||
'created_at' => 's.`created_at`',
|
||||
'updated_at' => 'row_updated_at',
|
||||
'customer_number' => 'customer_number_sort',
|
||||
'grant_id' => 'grant_id_sort',
|
||||
'customer_number' => 'g.`billing_customer_number`',
|
||||
'grant_id' => 'g.`id`',
|
||||
'name' => 's.`name`',
|
||||
];
|
||||
|
||||
@@ -492,6 +762,15 @@ class subusersRoute
|
||||
$grantPermissions = subuser_grants_o::normalizePermissionsValue($row['grant_permissions'] ?? null);
|
||||
$templateService = new subuser_permission_templates_service();
|
||||
$grantEnabled = (bool)((int)($row['grant_enabled'] ?? 0));
|
||||
$assignedVehicleId = isset($row['assigned_vehicle_id']) && $row['assigned_vehicle_id'] !== null
|
||||
? (int)$row['assigned_vehicle_id']
|
||||
: null;
|
||||
$assignedVehicleReg = isset($row['assigned_vehicle_reg']) && trim((string)$row['assigned_vehicle_reg']) !== ''
|
||||
? strtoupper(trim((string)$row['assigned_vehicle_reg']))
|
||||
: null;
|
||||
$assignedVehicle = $assignedVehicleId !== null
|
||||
? ['id' => $assignedVehicleId, 'reg' => $assignedVehicleReg]
|
||||
: null;
|
||||
|
||||
$accessState = 'inactive';
|
||||
if (!empty($row['grant_id']) && $grantEnabled) {
|
||||
@@ -506,6 +785,10 @@ class subusersRoute
|
||||
'customer_name' => $row['customer_name'] ?: null,
|
||||
'grant_enabled' => $grantEnabled,
|
||||
'grant_note' => $row['grant_note'] ?? null,
|
||||
'assigned_vehicle_id' => $assignedVehicleId,
|
||||
'assigned_vehicle_reg' => $assignedVehicleReg,
|
||||
'assigned_vehicle' => $assignedVehicle,
|
||||
'dognvask_enabled' => $this->hasDognvaskAccess($grantPermissions, $grantEnabled),
|
||||
'grant_permissions' => $grantPermissions,
|
||||
'permissions' => $grantPermissions,
|
||||
'permission_template_key' => $templateService->classify($grantPermissions, $grantEnabled),
|
||||
@@ -558,6 +841,10 @@ class subusersRoute
|
||||
'grant_id' => $primaryGrant['grant_id'] ?? null,
|
||||
'grant_enabled' => $primaryGrant['grant_enabled'] ?? false,
|
||||
'grant_note' => $primaryGrant['grant_note'] ?? null,
|
||||
'assigned_vehicle_id' => $primaryGrant['assigned_vehicle_id'] ?? null,
|
||||
'assigned_vehicle_reg' => $primaryGrant['assigned_vehicle_reg'] ?? null,
|
||||
'assigned_vehicle' => $primaryGrant['assigned_vehicle'] ?? null,
|
||||
'dognvask_enabled' => $primaryGrant['dognvask_enabled'] ?? false,
|
||||
'grant_permissions' => $primaryGrant['grant_permissions'] ?? [],
|
||||
'permissions' => $primaryGrant['permissions'] ?? [],
|
||||
'permission_template_key' => $primaryGrant['permission_template_key'] ?? subuser_permission_templates_service::TEMPLATE_DEACTIVATED,
|
||||
@@ -756,6 +1043,7 @@ class subusersRoute
|
||||
OR s.`email` LIKE ?
|
||||
OR CAST(s.`phone_country_code` AS CHAR) LIKE ?
|
||||
OR CAST(s.`phone` AS CHAR) LIKE ?
|
||||
OR cv.`reg` LIKE ?
|
||||
OR CAST(g.`billing_customer_number` AS CHAR) LIKE ?
|
||||
OR g.`note` LIKE ?
|
||||
OR EXISTS (
|
||||
@@ -766,7 +1054,7 @@ class subusersRoute
|
||||
)
|
||||
)";
|
||||
$search = '%' . $pagination['search'] . '%';
|
||||
for ($i = 0; $i < 9; $i++) {
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
$params[] = $search;
|
||||
$types .= 's';
|
||||
}
|
||||
@@ -776,9 +1064,10 @@ class subusersRoute
|
||||
$fromSql = "
|
||||
FROM `subuser_grants` g
|
||||
INNER JOIN `subusers` s ON s.`id` = g.`subuser`
|
||||
LEFT JOIN `customer_vehicles` cv ON cv.`id` = g.`assigned_vehicle_id` AND cv.`deleted_at` IS NULL
|
||||
";
|
||||
|
||||
$countSql = "SELECT COUNT(DISTINCT s.`id`) AS `count` $fromSql $whereSql";
|
||||
$countSql = "SELECT COUNT(*) AS `count` $fromSql $whereSql";
|
||||
$countStatement = $db->conn->prepare($countSql);
|
||||
if ($countStatement === false) {
|
||||
throw new Exception('Failed to prepare subuser count query: ' . $db->conn->error);
|
||||
@@ -802,23 +1091,18 @@ class subusersRoute
|
||||
s.`created_at`,
|
||||
s.`updated_at`,
|
||||
s.`suspended_at`,
|
||||
MAX(COALESCE(g.`updated_at`, s.`updated_at`)) AS `row_updated_at`,
|
||||
MIN(g.`billing_customer_number`) AS `customer_number_sort`,
|
||||
MAX(g.`id`) AS `grant_id_sort`
|
||||
COALESCE(g.`updated_at`, s.`updated_at`) AS `row_updated_at`,
|
||||
g.`id` AS `grant_id`,
|
||||
g.`billing_customer_number` AS `customer_number`,
|
||||
g.`enabled` AS `grant_enabled`,
|
||||
g.`note` AS `grant_note`,
|
||||
g.`permissions` AS `grant_permissions`,
|
||||
g.`assigned_vehicle_id`,
|
||||
cv.`reg` AS `assigned_vehicle_reg`,
|
||||
g.`created_at` AS `grant_created_at`,
|
||||
g.`updated_at` AS `grant_updated_at`
|
||||
$fromSql
|
||||
$whereSql
|
||||
GROUP BY
|
||||
s.`id`,
|
||||
s.`username`,
|
||||
s.`password`,
|
||||
s.`name`,
|
||||
s.`email`,
|
||||
s.`phone_country_code`,
|
||||
s.`phone`,
|
||||
s.`two_factor_enabled`,
|
||||
s.`created_at`,
|
||||
s.`updated_at`,
|
||||
s.`suspended_at`
|
||||
ORDER BY {$pagination['order_sql']} {$pagination['order_direction']}
|
||||
LIMIT ? OFFSET ?
|
||||
";
|
||||
@@ -834,68 +1118,15 @@ class subusersRoute
|
||||
$rows = $result->fetch_all(MYSQLI_ASSOC);
|
||||
$pageStatement->close();
|
||||
|
||||
$subuserIds = array_map(static fn (array $row): int => (int)$row['id'], $rows);
|
||||
$grantRowsBySubuserId = [];
|
||||
if ($subuserIds !== []) {
|
||||
$placeholders = implode(',', array_fill(0, count($subuserIds), '?'));
|
||||
$grantWhere = [
|
||||
'g.`deleted_at` IS NULL',
|
||||
'g.`subuser` IN (' . $placeholders . ')',
|
||||
];
|
||||
$grantParams = $subuserIds;
|
||||
$grantTypes = str_repeat('i', count($subuserIds));
|
||||
|
||||
if (!$includeNonEnabled) {
|
||||
$grantWhere[] = 'g.`enabled` = 1';
|
||||
}
|
||||
if ($customerNumber !== null) {
|
||||
$grantWhere[] = 'g.`billing_customer_number` = ?';
|
||||
$grantParams[] = $customerNumber;
|
||||
$grantTypes .= 'i';
|
||||
}
|
||||
|
||||
$grantSql = "
|
||||
SELECT
|
||||
g.`subuser` AS `subuser_id`,
|
||||
g.`id` AS `grant_id`,
|
||||
g.`billing_customer_number` AS `customer_number`,
|
||||
g.`enabled` AS `grant_enabled`,
|
||||
g.`note` AS `grant_note`,
|
||||
g.`permissions` AS `grant_permissions`,
|
||||
g.`created_at` AS `grant_created_at`,
|
||||
g.`updated_at` AS `grant_updated_at`
|
||||
FROM `subuser_grants` g
|
||||
WHERE " . implode(' AND ', $grantWhere) . "
|
||||
ORDER BY
|
||||
g.`subuser` ASC,
|
||||
g.`enabled` DESC,
|
||||
g.`billing_customer_number` ASC,
|
||||
COALESCE(g.`updated_at`, g.`created_at`) DESC,
|
||||
g.`id` DESC
|
||||
";
|
||||
$grantStatement = $db->conn->prepare($grantSql);
|
||||
if ($grantStatement === false) {
|
||||
throw new Exception('Failed to prepare subuser grant list query: ' . $db->conn->error);
|
||||
}
|
||||
$this->bindStatementParameters($grantStatement, $grantTypes, $grantParams);
|
||||
$grantStatement->execute();
|
||||
$grantResult = $grantStatement->get_result();
|
||||
$grantRows = $grantResult->fetch_all(MYSQLI_ASSOC);
|
||||
$grantStatement->close();
|
||||
|
||||
$customerNames = $this->resolveCustomerNames(array_map(
|
||||
static fn (array $grantRow): int => (int)($grantRow['customer_number'] ?? 0),
|
||||
$grantRows
|
||||
));
|
||||
|
||||
foreach ($grantRows as $grantRow) {
|
||||
$subuserId = (int)$grantRow['subuser_id'];
|
||||
$customerNumberForGrant = (int)$grantRow['customer_number'];
|
||||
$grantRow['customer_name'] = $this->resolveCustomerName($customerNumberForGrant, $customerNames);
|
||||
$grantRowsBySubuserId[$subuserId] ??= [];
|
||||
$grantRowsBySubuserId[$subuserId][] = $grantRow;
|
||||
}
|
||||
}
|
||||
$customerNames = $this->resolveCustomerNames(array_map(
|
||||
static fn (array $row): int => (int)($row['customer_number'] ?? 0),
|
||||
$rows
|
||||
));
|
||||
$rows = array_map(function (array $row) use ($customerNames): array {
|
||||
$customerNumberForGrant = (int)($row['customer_number'] ?? 0);
|
||||
$row['customer_name'] = $this->resolveCustomerName($customerNumberForGrant, $customerNames);
|
||||
return $row;
|
||||
}, $rows);
|
||||
|
||||
$response->paginate(
|
||||
(int)$pagination['page'],
|
||||
@@ -907,7 +1138,7 @@ class subusersRoute
|
||||
);
|
||||
|
||||
return array_map(
|
||||
fn(array $row): array => $this->buildSuperuserSubuserManagementPayload($row, $grantRowsBySubuserId[(int)$row['id']] ?? []),
|
||||
fn(array $row): array => $this->buildSuperuserSubuserManagementPayload($row, [$row]),
|
||||
$rows
|
||||
);
|
||||
}
|
||||
@@ -961,6 +1192,13 @@ class subusersRoute
|
||||
$updates['permissions'] = $this->parsePermissionsPayload(self::getParameter('permissions'), []);
|
||||
}
|
||||
|
||||
if (self::isParametersSet(['assigned_vehicle_id'])) {
|
||||
$updates['assigned_vehicle_id'] = $this->validateAssignedVehicleIdForCustomer(
|
||||
self::getParameter('assigned_vehicle_id'),
|
||||
$customerNumber
|
||||
);
|
||||
}
|
||||
|
||||
if ($updates === []) {
|
||||
$response->error('No fields to update', 400);
|
||||
}
|
||||
@@ -1109,6 +1347,8 @@ class subusersRoute
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
subusers_schema_bootstrap::ensureTables();
|
||||
|
||||
// =============================
|
||||
// Subuser Grant Management
|
||||
// =============================
|
||||
@@ -1182,6 +1422,7 @@ class subusersRoute
|
||||
'name' => $subuser->name->value(),
|
||||
'enabled' => $o->enabled,
|
||||
'note' => $o->note,
|
||||
'assigned_vehicle_id' => isset($o->assigned_vehicle_id) && $o->assigned_vehicle_id !== null ? (int)$o->assigned_vehicle_id : null,
|
||||
'permissions' => subuser_grants_o::normalizePermissionsValue($o->permissions ?? null),
|
||||
'created_at' => $o->created_at,
|
||||
'updated_at' => $o->updated_at,
|
||||
@@ -1228,8 +1469,18 @@ class subusersRoute
|
||||
$enabled = $templateAccess['enabled'];
|
||||
$permissions = $templateAccess['permissions'];
|
||||
}
|
||||
$assignedVehicleId = null;
|
||||
if (self::isParametersSet(['assigned_vehicle_id'])) {
|
||||
$assignedVehicleId = $this->validateAssignedVehicleIdForCustomer(
|
||||
self::getParameter('assigned_vehicle_id'),
|
||||
$customer_number
|
||||
);
|
||||
}
|
||||
try {
|
||||
$grant = (new subuser_grants_o())->add($customer_number, $subuser_id, (bool)$enabled, $note, $permissions);
|
||||
if ($assignedVehicleId !== null) {
|
||||
$grant->assigned_vehicle_id->set($assignedVehicleId);
|
||||
}
|
||||
$response->success(['grant' => $grant->asArray()]);
|
||||
} catch (Exception $e) {
|
||||
$response->error('Failed to add subuser grant', 500);
|
||||
@@ -1287,6 +1538,12 @@ class subusersRoute
|
||||
$permissions = $this->parsePermissionsPayload(self::getParameter('permissions'), []);
|
||||
$grant->permissions->set($permissions);
|
||||
}
|
||||
if (self::isParametersSet(['assigned_vehicle_id'])) {
|
||||
$grant->assigned_vehicle_id->set($this->validateAssignedVehicleIdForCustomer(
|
||||
self::getParameter('assigned_vehicle_id'),
|
||||
$targetCustomer
|
||||
));
|
||||
}
|
||||
$response->success($grant->asArray());
|
||||
},
|
||||
[
|
||||
@@ -1587,6 +1844,103 @@ class subusersRoute
|
||||
'list_subusers' => 'List all chauffeur access grants for superusers.',
|
||||
]);
|
||||
|
||||
$this->patch('/superuser/subusers/{subuser_id}', function () {
|
||||
global $response;
|
||||
|
||||
$this->requirePermission('edit_subusers');
|
||||
$subuser = $this->loadSubuserOrFail($this->routePositiveInt('subuser_id'));
|
||||
$updates = $this->parseSuperuserSubuserProfileUpdates((int)$subuser->id);
|
||||
|
||||
try {
|
||||
$subuser->update($updates);
|
||||
} catch (Exception $exception) {
|
||||
$response->error($exception->getMessage(), 400);
|
||||
}
|
||||
|
||||
$subuser = $this->loadSubuserOrFail((int)$subuser->id);
|
||||
$authUser = (new authentication())->get_user();
|
||||
(new logs_o())->add(
|
||||
'subusers',
|
||||
'global',
|
||||
1,
|
||||
$authUser !== false ? (int)$authUser->id : 0,
|
||||
'SUPERUSER_SUBUSER_PROFILE_UPDATE',
|
||||
'Updated chauffeur profile: ' . (int)$subuser->id
|
||||
);
|
||||
|
||||
$response->success(['subuser' => $this->buildSubuserAccountPayload($subuser)]);
|
||||
}, [
|
||||
'edit_subusers' => 'Edit chauffeur account profile fields as a superuser.',
|
||||
]);
|
||||
|
||||
$this->post('/superuser/subusers/{subuser_id}/password', function () {
|
||||
global $response;
|
||||
|
||||
$this->requirePermission('edit_subusers');
|
||||
self::requireParameters(['password']);
|
||||
|
||||
$password = (string)self::getParameter('password');
|
||||
$this->requireSubuserPasswordPolicy($password);
|
||||
|
||||
$subuser = $this->loadSubuserOrFail($this->routePositiveInt('subuser_id'));
|
||||
try {
|
||||
$subuser->setPassword($password);
|
||||
$subuser->invalidateCurrentSetupToken();
|
||||
} catch (Exception $exception) {
|
||||
$response->error($exception->getMessage(), 400);
|
||||
}
|
||||
|
||||
$subuser = $this->loadSubuserOrFail((int)$subuser->id);
|
||||
$authUser = (new authentication())->get_user();
|
||||
(new logs_o())->add(
|
||||
'subusers',
|
||||
'global',
|
||||
1,
|
||||
$authUser !== false ? (int)$authUser->id : 0,
|
||||
'SUPERUSER_SUBUSER_PASSWORD_SET',
|
||||
'Set chauffeur password: ' . (int)$subuser->id
|
||||
);
|
||||
|
||||
$response->success(['subuser' => $this->buildSubuserAccountPayload($subuser)]);
|
||||
}, [
|
||||
'edit_subusers' => 'Set a chauffeur account password as a superuser.',
|
||||
]);
|
||||
|
||||
$this->post('/superuser/subusers/{subuser_id}/login-link', function () {
|
||||
global $response;
|
||||
|
||||
$this->requirePermission('edit_subusers');
|
||||
$this->requirePermission('SUPERUSER_INTIMIDATE');
|
||||
|
||||
$subuser = $this->loadSubuserOrFail($this->routePositiveInt('subuser_id'));
|
||||
$customerNumber = $this->resolveDirectLoginCustomerNumber($subuser);
|
||||
|
||||
try {
|
||||
$sessionToken = $subuser->generateSession();
|
||||
} catch (Exception $exception) {
|
||||
$response->error($exception->getMessage(), 500);
|
||||
}
|
||||
|
||||
$authUser = (new authentication())->get_user();
|
||||
(new logs_o())->add(
|
||||
'auth',
|
||||
'global',
|
||||
1,
|
||||
$authUser !== false ? (int)$authUser->id : 0,
|
||||
'SUPERUSER_SUBUSER_DIRECT_LOGIN_LINK',
|
||||
'Created chauffeur direct login link for subuser ' . (int)$subuser->id . ' and customer ' . $customerNumber
|
||||
);
|
||||
|
||||
$response->success([
|
||||
'subuser_id' => (int)$subuser->id,
|
||||
'customer_number' => $customerNumber,
|
||||
'login_path' => $this->buildDirectSubuserLoginPath($sessionToken, $customerNumber),
|
||||
]);
|
||||
}, [
|
||||
'edit_subusers' => 'Create chauffeur direct login links as a superuser.',
|
||||
'SUPERUSER_INTIMIDATE' => 'Create direct login sessions for chauffeur accounts.',
|
||||
]);
|
||||
|
||||
$this->get('/superuser/users/{user_id}/subusers', function () {
|
||||
global $response;
|
||||
$this->requirePermission('list_subusers');
|
||||
|
||||
Reference in New Issue
Block a user