Add new cron tasks and enhance user synchronization logic

Introduced cron tasks for syncing user economic customer details and discounts. Enhanced caching mechanisms for users and departments, added Slack messaging capabilities, and updated CLI/script routing for better flexibility. New routes and utility methods improve cron execution and logging.
This commit is contained in:
Jepp9350
2025-01-24 13:04:10 +01:00
parent f450650db0
commit d3e9391160
13 changed files with 253 additions and 42 deletions
+14 -4
View File
@@ -87,10 +87,15 @@ class redis implements redis_i
public function cache_department_name(int $department_id, string $department_name): self
{
// Cache the department name (If it is not empty or null)
if (!empty($department_name)) {
if (empty($department_name)) {
$department_name = 'IS_EMPTY_OR_NULL';
return $this;
}
$this->set('department_name_' . $department_id, $department_name);
// Set the department's name in the cache
if ($this->get_department_name($department_id) === null) {
$this->set('department_' . $department_id, json_encode(['name' => $department_name]
));
};
return $this;
}
@@ -100,7 +105,12 @@ class redis implements redis_i
public function get_department_name(int $department_id): string|null
{
// Get the department name
return $this->get('department_name_' . $department_id);
$tmp_department = $this->get('department_' . $department_id);
if ($tmp_department === 'IS_EMPTY_OR_NULL' || $tmp_department === null) {
return null;
}
// Return the department name
return json_decode($tmp_department, true)->name;
}
/**
@@ -109,7 +119,7 @@ class redis implements redis_i
public function clear_department_name(int $department_id): self
{
// Clear the department name
$this->delete('department_name_' . $department_id);
$this->delete('department_' . $department_id);
return $this;
}
+7
View File
@@ -129,4 +129,11 @@ class slack implements notification_i
. "Notes: $notes\n"
. "Status: $status";
}
public function send_message(string $string): void
{
global $SLACK_DEFAULT_WEBHOOK;
// Send the message to the slack webhook
self::add_log(self::send_webhook_message($string, $SLACK_DEFAULT_WEBHOOK));
}
}
+22 -2
View File
@@ -1,9 +1,27 @@
<?php
global $EMAIL_WASH_CERTIFICATE_TOKEN;
// This script only runs when the program is called from the command line. It is used to run the CLI script.
if (php_sapi_name() !== 'cli') {
if (php_sapi_name() !== 'cli' && !isset($_GET['internalCronCall'])) {
exit;
}
$args = $argv;
// If the program was called from the command line, get the arguments
if (php_sapi_name() === 'cli') {
$args = $argv;
} else {
// If the program was called from the browser, get the arguments from the URL
$args = [];
$args[1] = $_GET['script'];
$args[2] = $_GET['action'];
$auth = $_GET['auth_key'] ?? null;
if ($auth !== $EMAIL_WASH_CERTIFICATE_TOKEN) {
echo json_encode([
'status' => 'error',
'message' => 'Invalid token'
]);
exit;
}
}
// If the first argument is 'run', switch to the second argument
if ($args[1] === 'run') {
@@ -45,4 +63,6 @@ if ($args[1] === 'run') {
echo "Invalid script name";
break;
}
} else {
echo "Invalid action";
}
+2 -1
View File
@@ -1,4 +1,4 @@
<?php global $CONFIG_DB, $DEBUG, $ENCRYPTION_KEY, $CORS, $ECONOMIC_API, $WORDPRESS_STATIC_TOKEN, $USE_PROD_ECONOMIC_IN_DEBUG, $REDIS_CONFIG, $EMAIL_WASH_CERTIFICATE_TOKEN, $MINIO, $WORDPRESS_API_URL;
<?php global $CONFIG_DB, $DEBUG, $ENCRYPTION_KEY, $CORS, $ECONOMIC_API, $WORDPRESS_STATIC_TOKEN, $USE_PROD_ECONOMIC_IN_DEBUG, $REDIS_CONFIG, $EMAIL_WASH_CERTIFICATE_TOKEN, $MINIO, $WORDPRESS_API_URL, $SLACK_DEFAULT_WEBHOOK;
$CONFIG_DB = [
'host' => '', // IP address of the database server e.g. 127.0.0.1
'user' => '', // Username of the database server e.g. root
@@ -29,6 +29,7 @@ $MINIO = [
'access_key' => '', // Minio access
'secret_key' => '' // Minio secret key
];
$SLACK_DEFAULT_WEBHOOK = ''; // Default Slack webhook URL e.g. https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX
$REDIS_CONFIG = [
'host' => '', // Redis host (IP address)
'database' => 0, // Redis database number (0-15)
@@ -0,0 +1,22 @@
<?php
/**
* This script is used to sync bookings from the remote API to the local database.
* It is run as a cron job.
*
* index.php run bookingSync
*/
// prevent direct access
use objects\users_o;
if (!defined('WD')) {
exit;
}
$start = microtime(true);
// Sync the discounts
$users_o = new users_o();
$users_o->clearAllUsersEconomicCustomerDetailsFromCache();
$end = microtime(true);
//$slack = new \classes\slack();
//$slack->send_message("The booking sync script has finished. It took " . round($end - $start, 2) . " seconds to run.");
@@ -0,0 +1,22 @@
<?php
/**
* This script is used to sync bookings from the remote API to the local database.
* It is run as a cron job.
*
* index.php run bookingSync
*/
// prevent direct access
use objects\users_o;
if (!defined('WD')) {
exit;
}
$start = microtime(true);
// Sync the discounts
$users_o = new users_o();
$users_o->clearAllUsersEconomicCustomerDiscountsFromCache();
$end = microtime(true);
//$slack = new \classes\slack();
//$slack->send_message("The booking sync script has finished. It took " . round($end - $start, 2) . " seconds to run.");
+30 -2
View File
@@ -3,6 +3,7 @@
use objects\bookings_o;
use objects\logs_o;
use objects\users_o;
if (!defined('WD')) {
exit;
@@ -14,6 +15,8 @@ function warn($message): void
echo "\n\033[33m$message\033[0m\n";
}
$response_cron = [];
// Define the cron tasks
$cron_tasks = [
'CheckUnfulfilledBookings' => [
@@ -35,6 +38,18 @@ $cron_tasks = [
'next_run' => 0,
'function' => 'syncLogsToDatabase',
],
'SyncUserEconomicCustomerDiscounts' => [
'interval' => 600, // 10 minutes
'last_run' => 0,
'next_run' => 0,
'function' => 'SyncUserEconomicCustomerDiscounts',
],
'SyncUserEconomicCustomerDetails' => [
'interval' => 43200, // 12 hours
'last_run' => 0,
'next_run' => 0,
'function' => 'SyncUserEconomicCustomerDetails',
],
];
function checkUnfulfilledBookings(): void
@@ -46,7 +61,7 @@ function checkUnfulfilledBookings(): void
function syncBookings(): void
{
$bookings_o = new bookings_o();
$bookings_o->syncBookings();
$response_cron[] = $bookings_o->syncBookings();
}
function syncLogsToDatabase(): void
@@ -55,6 +70,19 @@ function syncLogsToDatabase(): void
$logs_o->syncLogsToDatabase();
}
function SyncUserEconomicCustomerDiscounts(): void
{
$users_o = new users_o();
$users_o->clearAllUsersEconomicCustomerDiscountsFromCache();
}
function SyncUserEconomicCustomerDetails(): void
{
$users_o = new users_o();
$users_o->clearAllUsersEconomicCustomerDetailsFromCache();
$users_o->syncAllUsersEconomicCustomerDetails();
}
foreach ( $cron_tasks as $task => $data ) {
$lastRun = redis->get_last_crond_run($task) === null ? 0 : redis->get_last_crond_run($task);
$nextRun = $lastRun + $data['interval'];
@@ -64,6 +92,6 @@ foreach ( $cron_tasks as $task => $data ) {
$data['function']();
redis->set_last_crond_run($task, time());
} else {
warn('Task ' . $task . ' is not due to run yet, next run is at ' . date('Y-m-d H:i:s', $nextRun) . ' (' . ($nextRun - time()) . ' seconds)');
$response_cron[] = $task . ' is not due to run yet, next run is at ' . date('Y-m-d H:i:s', $nextRun) . ' (' . ($nextRun - time()) . ' seconds)';
}
}
+4 -1
View File
@@ -13,7 +13,10 @@ use objects\bookings_o;
if (!defined('WD')) {
exit;
}
$start = microtime(true);
// Sync the bookings
$bookings_o = new bookings_o();
$bookings_o->syncBookings();
$end = microtime(true);
//$slack = new \classes\slack();
//$slack->send_message("The booking sync script has finished. It took " . round($end - $start, 2) . " seconds to run.");
+1 -1
View File
@@ -96,7 +96,7 @@ foreach ( glob(WD . '/objects/*.php') as $object ) {
}
// If the program was called from the command line, run the cli script
if (php_sapi_name() === 'cli') {
if (php_sapi_name() === 'cli' || isset($_GET['internalCronCall'])) {
require_once 'cli.php';
exit;
}
+2
View File
@@ -58,12 +58,14 @@ class logs_o extends db
echo "\r\033[33m" . str_repeat('=', $percentage) . str_repeat(' ', 100 - $percentage) . "\033[0m " . number_format($percentage, 2) . '%';
}
// Show the percentage, completed and total
if ($display_progress) {
echo "\r\033[33m" . number_format($percentage, 2) . '% (' . $current . '/' . $total . ")\033[0m";
// If the current is equal to the total, add a new line
if ($current === $total) {
echo "\n";
}
}
}
global /** @var db $db */
$db;
+61 -26
View File
@@ -259,11 +259,9 @@ class users_o extends db
if ($customer_number === 0) {
return null;
}
$tmp_user = redis->get_economic_customer_name((int)$customer_number);
if ($tmp_user !== null) {
return $tmp_user;
}
// Create a temporary user object
$tmp_user = new users_o();
$tmp_user->getUserByCustomerNumber($customer_number);
// Get the customer name (Check cache first)
$cached = $tmp_user->getCached('economic_customer');
if (!$cached) {
@@ -578,23 +576,6 @@ class users_o extends db
$this->keys->setUser($this->id)->deleteValue('open_invoice_draft');
}
/**
* Set a custom price (DISCOUNT) for the user
* @param int $user_id
* @param int $object_id The ID of the object
* @param int $discount_percentage The discount percentage
* @param bool $is_category If the object is a category
* @return void
*/
public function setCustomPrice(int $user_id, int|string $object_id, int $discount_percentage, bool $is_category = false): void
{
$this->id = $user_id;
// Get the user object properties
$this->getObjectProperties();
// Set the custom price (key = 'custom_price')
$this->price_overrides->setUser($this->id)->setPrice($is_category, $object_id, $discount_percentage);
}
/**
* Get the custom price (DISCOUNT) for the user
* @param int $object_id The ID of the object
@@ -726,7 +707,7 @@ class users_o extends db
// Check if the name is cached
$cached_name = $this->getCached('economic_customer', $id);
if (!$cached_name) {
echo 'No cached name found for user with ID: ' . $id;
// No cached name, get the name from the external source
// Get the name from the external source
$tmp_user = new users_o();
$tmp_user->getUserById($id);
@@ -741,6 +722,38 @@ class users_o extends db
return $cached_name->name ?? null;
}
public function hasPassword(): bool
{
return $this->password->value() !== null;
}
public function getPassword(): string|null
{
return $this->password->value();
}
public function clearAllUsersEconomicCustomerDiscountsFromCache(): void
{
// Get all the cached results matching the pattern 'users_*_economic_customer_discount_percentage'
$cached_results = redis->get_keys('users_*_economic_customer_discount_percentage');
// Loop through the cached results
foreach ( $cached_results as $key ) {
// Clear the cached discount percentage
redis->delete($key);
}
}
public function clearAllUsersEconomicCustomerDetailsFromCache(): void
{
// Get all the cached results matching the pattern 'users_*_economic_customer'
$cached_results = redis->get_keys('users_*_economic_customer');
// Loop through the cached results
foreach ( $cached_results as $key ) {
// Clear the cached economic customer details
redis->delete($key);
}
}
public function getEconomicCustomerDiscountPercentage(): int
{
// Check if the discount percentage is cached
@@ -756,13 +769,35 @@ class users_o extends db
return $discount_percentage;
}
public function hasPassword(): bool
/**
* Set a custom price (DISCOUNT) for the user
* @param int $user_id
* @param int $object_id The ID of the object
* @param int $discount_percentage The discount percentage
* @param bool $is_category If the object is a category
* @return void
*/
public function setCustomPrice(int $user_id, int|string $object_id, int $discount_percentage, bool $is_category = false): void
{
return $this->password->value() !== null;
$this->id = $user_id;
// Get the user object properties
$this->getObjectProperties();
// Set the custom price (key = 'custom_price')
$this->price_overrides->setUser($this->id)->setPrice($is_category, $object_id, $discount_percentage);
}
public function getPassword(): string|null
public function syncAllUsersEconomicCustomerDetails(): void
{
return $this->password->value();
// Get all users
$users = $this->getFields(['id', 'customer_number']);
// Loop through the users
foreach ( $users as $user ) {
// Check if the customer number is set (Or if it is 0)
if ($user['customer_number'] === 0) {
continue;
}
// Get the customer data from the external source
$this->getCustomerEcocomicData($user['customer_number']);
}
}
}
+48
View File
@@ -0,0 +1,48 @@
<?php
namespace routes;
use classes\authentication;
use objects\logs_o;
use traits\route_t;
class cronRoute
{
use route_t;
public function run(): void
{
$this->post('/superuser/cron', function () {
// Get the post data
global $response;
// Make sure the user has the SUPERUSER_RUN_CRON permission
if (!$this->requirePermission('SUPERUSER_RUN_CRON')) {
$response->error('Permission denied', 403);
}
// Get the user object
$user = (new authentication())->get_user();
// Get the post data
$data = json_decode(file_get_contents('php://input'), true);
// Check if a specific cron job is requested
if (isset($data['job'])) {
// Check if the cron job exists
if (!file_exists(WD . '/cron/' . $data['job'] . '.php')) {
$response->error('Cron job not found', 404);
}
// Include the cron job
require_once WD . '/cron/' . $data['job'] . '.php';
// Log the incident
(new logs_o())->add('cron', 'global', 1, $user->id, 'CRON_JOB_RUN', 'Ran cron job: ' . $data['job']);
$response->success('Cron job ran successfully');
}
// If no specific cron job is requested, run all cron jobs (through the cron.php file)
require_once WD . '/cron/Cron.php';
// Log the incident
(new logs_o())->add('cron', 'global', 1, $user->id, 'CRON_RUN', 'Ran all cron jobs');
$response->success([
'message' => 'All cron jobs ran successfully',
'data' => $response_cron ?? []
]);
});
}
}
+13
View File
@@ -375,4 +375,17 @@ trait db_object_t
// Delete the cached data
redis->delete($this->table . '_' . $objectId . '_' . $key);
}
/**
* Get all objects fields
* @param array $fields The fields to get from the objects (Eg. ['name', 'description'])
* @return array The list of fields from the objects (Eg. [['name' => 'John', 'description' => 'Doe'], ['name' => 'Jane', 'description' => 'Doe']])
*/
public function getFields(array $fields): array
{
global $db;
$sql = "SELECT " . implode(', ', $fields) . " FROM $this->table";
$result = $db->query($sql);
return $db->fetch_all($result);
}
}