Add module action handling and license plate lookup logging

Implemented `module_action_t` trait to standardize module actions with validation, logging, and execution. Added `module_action_logs_o` for logging module actions and integrated `license_plate_lookup_a` in motorapi for license plate lookup requests. Enhanced error handling to log failed actions and provide detailed feedback.
This commit is contained in:
Jepp9350
2025-03-05 11:11:09 +01:00
parent cb62c66e5b
commit a02729d5c6
4 changed files with 547 additions and 4 deletions
+75 -4
View File
@@ -4,9 +4,12 @@ namespace classes;
require_once WD . '/modules/motorapi/motorapi_c.php';
require_once WD . '/modules/motorapi/helpers/motorapi_vehicle_types.php';
/** Actions */
require_once WD . '/modules/motorapi/actions/license_plate_lookup_a.php';
use Exception;
use interfaces\motorapi_i;
use motorapi\actions\license_plate_lookup_a;
use motorapi\helpers\motorapi_vehicle_types;
use motorapi\motorapi_c;
use objects\motorapi_lookups_o;
@@ -30,10 +33,20 @@ class motorapi implements motorapi_i
*/
private string $api_url = 'https://v1.motorapi.dk/';
/**
* ACTION: LICENSE_PLATE_LOOKUP
* @see license_plate_lookup_a
* @notation This action is when a license plate lookup request is made, and logs the request in the database
* @var license_plate_lookup_a
*/
private license_plate_lookup_a $license_plate_lookup;
public function __construct()
{
$this->config = new motorapi_c();
$this->vehicle_types = new motorapi_vehicle_types();
/** Actions */
$this->license_plate_lookup = new license_plate_lookup_a();
}
/**
@@ -117,8 +130,12 @@ class motorapi implements motorapi_i
*/
function getCachedResult(string $licensePlate): object
{
global /** @var response $response */
$response;
// Get the cached result from the log/local database/cache
$motorapi_lookups = new motorapi_lookups_o();
// Add the cached value to the meta
$response->add_meta('cached', true);
return json_decode($motorapi_lookups->getCachedResult($licensePlate)->result->value());
}
@@ -142,7 +159,18 @@ class motorapi implements motorapi_i
'POST' => self::sendPostRequest($licensePlate, $endpoint, $data),
'PUT' => self::sendPutRequest($licensePlate, $endpoint, $data),
'DELETE' => self::sendDeleteRequest($licensePlate, $endpoint, $data),
default => throw new Exception('Invalid method'),
default => self::exception(
$licensePlate, [
'method' => $method,
'endpoint' => $endpoint,
'data' => $data,
'license_plate' => $licensePlate,
'response' => null,
'status_code' => 400,
'error' => 'Invalid request method',
],
500
),
};
// Add the request to the log
self::addRequestToLog($licensePlate, $endpoint, $response);
@@ -179,10 +207,27 @@ class motorapi implements motorapi_i
{
// Check if the secret key is valid
if ($this->config->secret_key->getVariableValue() === null) {
throw new Exception('Invalid secret key');
self::exception(
'',
[
'status_code' => 500,
'error' => 'Invalid secret key defined in the config (motorapi_secret_key_c)',
],
500
);
}
}
/**
* @throws Exception
*/
function exception(string $licensePlate, array $data = [], int $status_code = 500): exception
{
// Add the request to the log
$this->license_plate_lookup->license_plate_lookup($licensePlate, $data, $status_code);
return throw new Exception($data['error'] ?? 'An error occurred while processing the request, in ' . $this->config->getModuleName() . ' module');
}
/**
* @inheritDoc
* @throws Exception
@@ -210,7 +255,18 @@ class motorapi implements motorapi_i
// Check for errors
if (curl_errno($ch)) {
throw new Exception('cURL error: ' . curl_error($ch));
self::exception(
$licensePlate, [
'method' => 'GET',
'endpoint' => $endpoint,
'data' => $data,
'license_plate' => $licensePlate,
'response' => null,
'status_code' => 500,
'error' => curl_error($ch),
],
500
);
}
// Get the status code
@@ -225,8 +281,20 @@ class motorapi implements motorapi_i
429 => 'Too many requests',
default => 'Invalid response',
};
throw new Exception($exception_message);
self::exception(
$licensePlate, [
'method' => 'GET',
'endpoint' => $endpoint,
'data' => $data,
'license_plate' => $licensePlate,
'response' => null,
'status_code' => $status_code,
'error' => $exception_message,
],
$status_code
);
}
$this->license_plate_lookup->license_plate_lookup($licensePlate, json_decode($output), $status_code);
return json_decode($output);
}
@@ -243,6 +311,7 @@ class motorapi implements motorapi_i
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
curl_close($ch);
$this->license_plate_lookup->license_plate_lookup($licensePlate, json_decode($output), 200);
return json_decode($output);
}
@@ -259,6 +328,7 @@ class motorapi implements motorapi_i
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
curl_close($ch);
$this->license_plate_lookup->license_plate_lookup($licensePlate, json_decode($output), 200);
return json_decode($output);
}
@@ -275,6 +345,7 @@ class motorapi implements motorapi_i
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
curl_close($ch);
$this->license_plate_lookup->license_plate_lookup($licensePlate, json_decode($output), 200);
return json_decode($output);
}
@@ -0,0 +1,68 @@
<?php
namespace motorapi\actions;
use Exception;
use traits\module_action_t;
class license_plate_lookup_a
{
use module_action_t;
/**
* @inheritDoc
* @throws Exception If the module name is invalid
*/
public function run(): void
{
self::set_module_name('MOTORAPI');
/** Add the action to the list of valid actions */
// LICENSE_PLATE_LOOKUP
self::add_action(
'LICENSE_PLATE_LOOKUP',
'This action is when a license plate lookup request is made',
// Method to call
self::class . '::license_plate_lookup',
[
'license_plate' => 'The license plate that was looked up',
'data' => 'The data returned from the lookup (If any)',
'status_code' => 'The status code of the action',
]
);
}
/**
* License plate lookup action
* @param string $license_plate The license plate that was looked up
* @param array|object $data The data returned from the lookup (If any)
* @param int $status_code The status code of the action
* @throws Exception If the action name is invalid
* @throws Exception If the action data is invalid
* @throws Exception If the action status code is invalid
* @throws Exception If the action is not valid
*/
public function license_plate_lookup(string $license_plate, array|object $data = [], int $status_code = 0): void
{
self::set_action('LICENSE_PLATE_LOOKUP');
// If the data is an object, convert it to an array
if (is_object($data)) {
$data = (array)$data;
}
// Validate the action data
if (!is_array($data)) {
throw new Exception('Action data is invalid');
}
// Validate the action status code
self::set_data([
'license_plate' => $license_plate,
'data' => $data,
]);
self::set_status($status_code);
// Add the action to the log
self::add_action_log(
self::get_action_name(),
(int)self::get_status(),
);
}
}
@@ -0,0 +1,119 @@
<?php
namespace objects;
use classes\db;
use classes\object_property;
use Exception;
use traits\db_object_t;
class module_action_logs_o extends db
{
use db_object_t;
/**
* The module name
* @var object_property $module The module name
*/
public object_property $module;
/**
* The action name
* @var object_property $action The action name
*/
public object_property $action;
/**
* The action status code (HTTP status code, e.g. 200, 404, 500)
* @var object_property $status_code The action status code (HTTP status code, e.g. 200, 404, 500)
*/
public object_property $status_code;
/**
* The action data
* @var object_property $data The data JSON encoded
*/
public object_property $data;
/**
* The action created at
* @var object_property $created_at The action created at
*/
public object_property $created_at;
public function structure(): void
{
$this->setTable('module_usage_logs');
}
/**
* Add a module action log
* @param string $module The module name
* @param string $action The action name
* @param int $status_code The action status code (HTTP status code, e.g. 200, 404, 500)
* @param array $data The action data
* @return void
* @throws Exception If the object was not created successfully
*/
public function add(string $module, string $action, int $status_code, array $data): void
{
global /** @var db $db */
$db;
// Sanitize the input
$module = $db->escape_string($module);
$action = $db->escape_string($action);
$status_code = (int)$status_code;
// JSON encode the data
$encoded_data = $db->escape_string(json_encode($data));
// Add the object
$tmp_id = self::add_object([
'module' => $module,
'action' => $action,
'status_code' => $status_code,
'data' => $encoded_data,
]);
if (!$tmp_id) {
throw new Exception('The object was not created successfully.');
}
$this->id = $tmp_id;
self::getObjectProperties();
self::objectChanged();
}
public function getObjectProperties(): void
{
$this->module = new object_property($this->table, $this->id, 'module', 'string', false);
$this->action = new object_property($this->table, $this->id, 'action', 'string', false);
$this->status_code = new object_property($this->table, $this->id, 'status_code', 'int', false);
$this->data = new object_property($this->table, $this->id, 'data', 'string', false);
$this->created_at = new object_property($this->table, $this->id, 'created_at', 'datetime', false);
}
public function objectChanged(): void
{
//TODO: Add cache invalidation
}
/**
* Get the object as an array
* @return array The object as an array
*/
public function asArray(): array
{
return [
'id' => (int)$this->id,
'module' => (string)$this->module->value(),
'action' => (string)$this->action->value(),
'status_code' => (int)$this->status_code->value(),
'data' => self::decodeData((string)$this->data->value()),
'created_at' => (string)$this->created_at->value(),
];
}
/**
* Decode the data
* @param string $data The data JSON encoded
* @return array The data
*/
public static function decodeData(string $data): array
{
return json_decode($data, true);
}
}
@@ -0,0 +1,285 @@
<?php
namespace traits;
use Exception;
use motorapi\actions\license_plate_lookup_a;
use objects\module_action_logs_o;
trait module_action_t
{
/**
* The module name
* @var string The module name
*/
private string $module = '';
/**
* The modules valid actions
* @var array The modules valid actions
*/
private array $actions = [];
/**
* The relevant data for the action (Only set when the action is called)
* @var array Data for the action
*/
private array $data = [];
/**
* The HTTP status code, if not set, defaults to 0
* @var int HTTP status code
*/
private int $status = 0;
/**
* The action name (Only set when the action is called)
* @var string The action name
* @see set_action() - Sets the action name
* @see get_action() - Gets the action name
*/
private string $action = '';
/**
* Constructor
* @throws Exception If the module name is invalid
* @throws Exception If the action name is invalid
* @throws Exception If the action is not valid
*/
public function __construct()
{
// Run the run method
self::run();
}
/**
* The run method, to be implemented by the module
* This should be used to define the following:
* - The module name
* - The actions
* @return void
*/
abstract public function run(): void;
/**
* Set the action name
* @param string $action The action name
* @return module_action_t|license_plate_lookup_a
* @throws Exception If the action name is invalid
*/
public function set_action(string $action): self
{
self::validate_action_name($action);
$this->action = strtoupper($action);
return $this;
}
/**
* Validate the action name
* @param string $action The action name
* @return void
* @throws Exception If the action name is empty
* @throws Exception If the action name is invalid (only letters, numbers and underscores are allowed)
* @throws Exception If the action name is longer than 50 characters
* @throws Exception If the action name is shorter than 3 characters
*/
private static function validate_action_name(string $action): void
{
// Action name cannot be empty
if (empty($action)) {
throw new Exception('Action name cannot be empty');
}
// Action name can only contain letters, numbers and underscores
if (!preg_match('/^[a-zA-Z0-9_]+$/', $action)) {
throw new Exception('Action name can only contain letters, numbers and underscores');
}
// Action name cannot be longer than 50 characters
if (strlen($action) > 50) {
throw new Exception('Action name cannot be longer than 50 characters');
}
// Action name cannot be shorter than 3 characters
if (strlen($action) < 3) {
throw new Exception('Action name cannot be shorter than 3 characters');
}
}
/**
* Set the module name
* @param string $module The module name
* @return module_action_t|license_plate_lookup_a
* @throws Exception If the module name is invalid
*/
public function set_module_name(string $module): self
{
self::validate_module_name($module);
$this->module = strtoupper($module);
return $this;
}
/**
* Validate the module name
* @param string $module The module name
* @return void
* @throws Exception If the module name is empty
* @throws Exception If the module name is invalid (only letters, numbers and underscores are allowed)
* @throws Exception If the module name is longer than 50 characters
* @throws Exception If the module name is shorter than 3 characters
*/
private static function validate_module_name(string $module): void
{
// Module name cannot be empty
if (empty($module)) {
throw new Exception('Module name cannot be empty');
}
// Module name can only contain letters, numbers and underscores
if (!preg_match('/^[a-zA-Z0-9_]+$/', $module)) {
throw new Exception('Module name can only contain letters, numbers and underscores');
}
// Module name cannot be longer than 50 characters
if (strlen($module) > 50) {
throw new Exception('Module name cannot be longer than 50 characters');
}
// Module name cannot be shorter than 3 characters
if (strlen($module) < 3) {
throw new Exception('Module name cannot be shorter than 3 characters');
}
}
/**
* Add an action to the module
* @param string $action The action name
* @param string $description The action description
* @param callable $callback The action callback function
* @param array $parameters The action parameters
* @return module_action_t|license_plate_lookup_a
* @throws Exception If the action name is invalid
*/
public function add_action(string $action, string $description, callable $callback, array $parameters = []): self
{
// Validate the action name
self::validate_action_name($action);
// Check if the action name is already set
if (isset($this->actions[$action])) {
throw new Exception('Action name is already set');
}
// Check if the action callback function is callable
if (!is_callable($callback)) {
throw new Exception('Action callback function is not callable');
}
// Check if the action parameters are an array
if (!is_array($parameters)) {
throw new Exception('Action parameters are not an array');
}
// Add the action to the module
$this->actions[$action] = [
'description' => $description,
'callback' => $callback,
'parameters' => $parameters
];
return $this;
}
/**
* Get the action name
* @return string The action name
*/
public function get_action_name(): string
{
return $this->action;
}
/**
* Get the actions
* @return array The actions
*/
public function get_actions(): array
{
return $this->actions;
}
/**
* Add data to the action
* @param array $data The data to be added
* @return self
*/
public function add_data(array $data): self
{
$this->data = array_merge($this->data, $data);
return $this;
}
/**
* Get the HTTP status code
* @return int The HTTP status code
*/
public function get_status(): int
{
return $this->status;
}
/**
* Set the HTTP status code
* @param int $status The HTTP status code
* @return self
*/
public function set_status(int $status): self
{
$this->status = $status;
return $this;
}
/**
* Add action log to the database
* @param string $action The action name
* @param int $status The action status code (HTTP status code, e.g. 200, 404, 500)
* @return self
* @throws Exception If the action name is empty
* @throws Exception If the action status code is empty
* @throws Exception If the object was not created successfully
*/
public function add_action_log(string $action, int $status = 0): self
{
// Validate the action name
if (empty($action)) {
throw new Exception('Action name cannot be empty');
}
// Validate the action status code
if (empty($status)) {
throw new Exception('Action status code cannot be empty');
}
// Add action log to the database
$module_action_log = new module_action_logs_o();
$module_action_log->add(self::get_module_name(), (string)$action, (int)$status, self::get_data());
return $this;
}
/**
* Get the module name
* @return string The module name
* @throws Exception If the module name isn't set
*/
public function get_module_name(): string
{
if (empty($this->module)) {
throw new Exception('Module name is not set');
}
return $this->module;
}
/**
* Get the data
* @return array The data
*/
public function get_data(): array
{
return $this->data;
}
/**
* Set the data
* @param array $data The data to be returned
* @return self
*/
public function set_data(array $data): self
{
$this->data = $data;
return $this;
}
}