This commit introduces a release update mechanism, including candidate detection, asset pre-downloading, and installation workflows with proper state management. Additionally, it implements API health checks both for successful and failure scenarios and adds related unit and e2e tests for enhanced reliability.
305 lines
14 KiB
PHP
305 lines
14 KiB
PHP
<?php
|
|
|
|
namespace routes;
|
|
|
|
use classes\db;
|
|
use classes\economic;
|
|
use classes\release_manager;
|
|
use classes\router;
|
|
use classes\shelly;
|
|
use classes\slack;
|
|
use classes\virkdata;
|
|
use modules\shelly\helpers\shelly_device_switch;
|
|
use modules\shelly\helpers\shelly_request_body_get_states;
|
|
use modules\virkdata\helpers\virkdata_response;
|
|
use objects\departments_o;
|
|
use objects\orders_o;
|
|
use objects\product_options_o;
|
|
use objects\products_o;
|
|
use objects\users_o;
|
|
use traits\route_t;
|
|
|
|
class workerRoute
|
|
{
|
|
use route_t;
|
|
|
|
public function run(): void
|
|
{
|
|
$this->get('/worker/version', function () {
|
|
global /** @var router $router */
|
|
$response, $router;
|
|
$response->success(['version' => redis->get('worker_target_version') ?? 'unknown'] );
|
|
});
|
|
$this->get('/worker/update-version', function () {
|
|
global /** @var router $router */
|
|
$response, $router;
|
|
self::requirePermission('worker_update_version');
|
|
self::requireParameters(['version']);
|
|
$version = (string)self::getParameter('version');
|
|
redis->set('worker_target_version', $version);
|
|
$response->success(['message' => 'Version update functionality is not yet implemented.']);
|
|
});
|
|
$this->get('/worker/test', function () {
|
|
global /** @var router $router */
|
|
$response, $router;
|
|
$response->error('This endpoint is disabled for security reasons.', 403);
|
|
$department = (new departments_o())->select((int)6);
|
|
$days = 7;
|
|
// The time should be from 00:00:00 of the start date to 23:59:59 of the end date
|
|
$date_end = date('Y-m-d 23:59:59', strtotime("-1 days")); // Yesterday (Sunday) at 23:59:59
|
|
$date_start = date('Y-m-d 00:00:00', strtotime("-$days days")); // $days ago at 00:00:00
|
|
$department->sendSlackInternalStatisticNotification($date_start, $date_end, [1,2,3,4,5,6,7]);
|
|
$response->success(['message' => 'Test message sent to Slack (not really, this is a placeholder).' ]);
|
|
exit;
|
|
// Configuration
|
|
$department_id = 6;
|
|
/**
|
|
* Weekly results for Roskilde
|
|
*
|
|
* Period: 29.01.2026 - 04.02.2026
|
|
* Washes: 67
|
|
* Fælg Flex: 55%
|
|
* Spot Free: 22%
|
|
* Special Sæbe: 11%
|
|
* 10 min ekstra: 15%
|
|
* Undervognsskyl: 44%
|
|
* Voks: 66%
|
|
*/
|
|
$max_addons = [];
|
|
$sold_addons = [];
|
|
$percentages = [];
|
|
$department = (new departments_o())->select($department_id);
|
|
echo "Testing addon sales calculation for department ID: $department_id from $date_start to $date_end\n";
|
|
$tmp = "*Weekly results for {$department->name->value()}*\n";
|
|
$tmp .= "Period: " . date('d.m.Y', strtotime($date_start)) . " - " . date('d.m.Y', strtotime($date_end)) . "\n";
|
|
// Washes
|
|
$wash_count = (new orders_o())->countWashesInDateRange($date_start, $date_end, $department_id);
|
|
$tmp .= "Washes: $wash_count\n";
|
|
// Get the percentage of addons sold out of max
|
|
foreach ($product_ids as $product_id) {
|
|
// Handle merged products
|
|
if (is_array($product_id)) {
|
|
$addon_sold_count = 0;
|
|
$addon_max_count = 0;
|
|
foreach ($product_id as $pid) {
|
|
$pid = (int)$pid;
|
|
$addon_sold_count += (new product_options_o())->countSoldAddonsInDateRange($pid, $date_start, $date_end, $department_id);
|
|
$addon_max_count += (new product_options_o())->getMaxAddonsInDateRange($pid, $date_start, $date_end, $department_id);
|
|
}
|
|
} else {
|
|
$pid = $product_id;
|
|
$addon_sold_count = (new product_options_o())->countSoldAddonsInDateRange($pid, $date_start, $date_end, $department_id);
|
|
$addon_max_count = (new product_options_o())->getMaxAddonsInDateRange($pid, $date_start, $date_end, $department_id);
|
|
}
|
|
// Prevent division by zero
|
|
if ($addon_max_count === 0) {
|
|
$addon_percentage_sold = 0;
|
|
} else {
|
|
$addon_percentage_sold = ($addon_sold_count / $addon_max_count) * 100;
|
|
}
|
|
$max_addons[is_array($product_id) ? implode('_', $product_id) : $product_id] = $addon_max_count;
|
|
$sold_addons[is_array($product_id) ? implode('_', $product_id) : $product_id] = $addon_sold_count;
|
|
$percentages[is_array($product_id) ? implode('_', $product_id) : $product_id] = number_format($addon_percentage_sold, 2);
|
|
}
|
|
foreach ($product_ids as $product_id) {
|
|
if (is_array($product_id)) {
|
|
// Merged product names
|
|
$product_names = [];
|
|
foreach ($product_id as $pid) {
|
|
$product_names[] = (new products_o())->select($pid)->name->value();
|
|
}
|
|
// Switch to joined names
|
|
$product_name = match (true) {
|
|
in_array(23, $product_id) && in_array(24, $product_id) => 'Spot Free',
|
|
default => implode(' + ', $product_names),
|
|
};
|
|
} else {
|
|
$product_name = (new products_o())->select($product_id)->name->value();
|
|
}
|
|
$tmp .= "{$product_name}: {$percentages[is_array($product_id) ? implode('_', $product_id) : $product_id]}%\n";
|
|
}
|
|
// Send test message to Slack
|
|
$slack = new slack();
|
|
$department = (new departments_o())->select($department_id);
|
|
$slack->send_message($tmp);
|
|
$response->success(['message' => 'Test message sent to Slack (not really, this is a placeholder).' ]);
|
|
});
|
|
$this->get('/worker/status', function () {
|
|
global /** @var router $router */
|
|
$response, $router, $db, $REDIS_CONFIG, $CONFIG_DB;
|
|
$response->success([
|
|
'message' => 'Worker is running',
|
|
'status' => 'OK',
|
|
'time' => date('Y-m-d H:i:s'),
|
|
'timezone' => date_default_timezone_get(),
|
|
'host' => gethostname(),
|
|
'version' => '1.0.1',
|
|
'api_commit_sha' => release_manager::backendCommitSha(),
|
|
'routes' => $router->countRoutes(),
|
|
'redis' => [
|
|
'host' => $REDIS_CONFIG['host'],
|
|
'user' => $REDIS_CONFIG['user'],
|
|
'database' => $REDIS_CONFIG['database'],
|
|
'password' => $REDIS_CONFIG['password'] ? '********' : 'NOT_SET',
|
|
'port' => $REDIS_CONFIG['port'],
|
|
'status' => (new \classes\redis())->ping() ? 'OK' : 'ERROR',
|
|
],
|
|
'database' => [
|
|
'host' => $CONFIG_DB['host'],
|
|
'database' => $CONFIG_DB['database'],
|
|
'user' => $CONFIG_DB['user'],
|
|
'status' => $db->testConnection() ? 'OK' : 'ERROR',
|
|
],
|
|
]);
|
|
});
|
|
$this->get('/worker/debug', function () {
|
|
global /** @var router $router */
|
|
$response, $router;
|
|
$response->error('This endpoint is disabled for security reasons.', 403);
|
|
$shelly = new shelly();
|
|
$shelly->requireModuleEnabled();
|
|
$shelly->requireValidSecretKey();
|
|
$parameters = new shelly_request_body_get_states();
|
|
$parameters->ids = ['e4b323243f90'];
|
|
$parameters->select = ['status'];
|
|
$result = $shelly->sendPostRequest('/v2/devices/api/get', (array)$parameters);
|
|
// Format the result
|
|
$result = array_map(function ($device) {
|
|
return (new shelly_device_switch())->populate($device);
|
|
}, $result);
|
|
//TODO: fetch statuses
|
|
$response->success($result);
|
|
});
|
|
$this->get('/worker/debug/on', function () {
|
|
global /** @var router $router */
|
|
$response, $router;
|
|
$response->error('This endpoint is disabled for security reasons.', 403);
|
|
$shelly = new shelly();
|
|
$shelly->requireModuleEnabled();
|
|
$shelly->requireValidSecretKey();
|
|
$parameters = new shelly_request_body_get_states();
|
|
$parameters->ids = ['e4b323243f90'];
|
|
$parameters->select = ['status'];
|
|
$result = $shelly->sendPostRequest('/v2/devices/api/get', (array)$parameters);
|
|
// Wait 1 second
|
|
sleep(1);
|
|
// Format the result
|
|
$result = array_map(function ($device) {
|
|
return (new shelly_device_switch())->populate($device);
|
|
}, $result);
|
|
// Open the switch
|
|
foreach ($result as $device) {
|
|
$device->switch(true);
|
|
}
|
|
//TODO: fetch statuses
|
|
$response->success($result);
|
|
});
|
|
$this->get('/worker/debug/off', function () {
|
|
global /** @var router $router */
|
|
$response, $router;
|
|
$response->error('This endpoint is disabled for security reasons.', 403);
|
|
$shelly = new shelly();
|
|
$shelly->requireModuleEnabled();
|
|
$shelly->requireValidSecretKey();
|
|
$parameters = new shelly_request_body_get_states();
|
|
$parameters->ids = ['e4b323243f90'];
|
|
$parameters->select = ['status'];
|
|
$result = $shelly->sendPostRequest('/v2/devices/api/get', (array)$parameters);
|
|
// Wait 1 second
|
|
sleep(1);
|
|
// Format the result
|
|
$result = array_map(function ($device) {
|
|
return (new shelly_device_switch())->populate($device);
|
|
}, $result);
|
|
// Open the switch
|
|
foreach ($result as $device) {
|
|
$device->switch(false);
|
|
}
|
|
//TODO: fetch statuses
|
|
$response->success($result);
|
|
});
|
|
$this->get('/worker/licenseplates', function () {
|
|
global /** @var router $router */
|
|
$response, $db;
|
|
$response->error('This endpoint is disabled for security reasons.', 403);
|
|
$counted_plates = []; // Array to hold counted license plates
|
|
// This is used to fetch all UNIQUE license plates from the database tables:
|
|
// 'customer_vehicles' -> 'reg' column
|
|
// 'orders' -> 'reg_1', 'reg_2', 'reg_3' columns
|
|
// 'bookings' -> 'regNrTraekker', 'regNrTrailer' columns
|
|
// 'plate_scans' -> 'plate' column
|
|
$tables_and_columns = [
|
|
'customer_vehicles' => ['reg'],
|
|
'orders' => ['reg_1', 'reg_2', 'reg_3'],
|
|
'bookings' => ['regNrTraekker', 'regNrTrailer'],
|
|
'plate_scans' => ['plate'],
|
|
];
|
|
foreach ($tables_and_columns as $table => $columns) {
|
|
foreach ($columns as $column) {
|
|
$results = $db->query("SELECT DISTINCT $column FROM $table WHERE $column IS NOT NULL AND $column != ''");
|
|
foreach ($results as $row) {
|
|
$formatted_plate = $this->FORMAT_LICENSE_PLATE($row[$column]);
|
|
if ($formatted_plate !== '') {
|
|
if (!isset($counted_plates[$formatted_plate])) {
|
|
$counted_plates[$formatted_plate] = 0;
|
|
}
|
|
$counted_plates[$formatted_plate]++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
$response->success(['counted_license_plates' => $counted_plates, 'total_unique_plates' => count($counted_plates)]);
|
|
});
|
|
$this->get('/economic/doesCustomerExist', function () {
|
|
global $response;
|
|
self::requirePermission( 'economic_does_customer_exist'); // TODO: Remove this
|
|
self::requireParameters(['cvr']);
|
|
$cvr = self::getParameter('cvr');
|
|
// Check if the customer exists in E-conomic.
|
|
if (!is_numeric($cvr)) {
|
|
$response->error('Invalid CVR number', 400);
|
|
}
|
|
$economic = new economic();
|
|
$results = $economic->customers->customers->search([
|
|
'corporateIdentificationNumber' => (string)$cvr,
|
|
],[
|
|
'skipPages' => 0,
|
|
'pageSize' => 1000, // Since the limit is 1000, we need to set the page size to 1000.
|
|
])->collection;
|
|
if (count($results) === 0) {
|
|
$response->error('Customer not found', 404);
|
|
}
|
|
$response->success(['cvr' => $cvr, 'result' => $results], 200);
|
|
});
|
|
$this->get('/cvr/lookup', function () {
|
|
global $response;
|
|
self::requirePermission('cvr_lookup'); // TODO: Remove this
|
|
self::requireParameters(['cvr']);
|
|
$cvr = self::getParameter('cvr');
|
|
if (!is_numeric($cvr)) {
|
|
$response->error('Invalid CVR number', 400);
|
|
}
|
|
// Check if the result is cached
|
|
if (!empty(redis->get('cvr_lookup_' . $cvr))) {
|
|
$result = (new virkdata_response())->populate((array)json_decode(redis->get('cvr_lookup_' . $cvr)));
|
|
} else {
|
|
$virkdata = new virkdata();
|
|
$result = $virkdata->getCompanyInformation($cvr, '', []);
|
|
// Cache the result
|
|
redis->set('cvr_lookup_' . $cvr, json_encode($result->asArray()));
|
|
// Set the cache expiration time (3 days)
|
|
redis->expire('cvr_lookup_' . $cvr, (60 * 60 * 24 * 4));
|
|
}
|
|
$response->success(['cvr' => $cvr, 'result' => (object)$result->asArray(), 'phone' => $result->phone, 'email' => $result->email], 200);
|
|
});
|
|
}
|
|
|
|
protected function FORMAT_LICENSE_PLATE(string $plate): string
|
|
{
|
|
// Remove all non-alphanumeric characters
|
|
$cleaned = preg_replace('/[^A-Za-z0-9]/', '', $plate);
|
|
// Convert to uppercase
|
|
return strtoupper($cleaned);
|
|
}
|
|
}
|