Add fxratesapi module for currency conversion functionality

Integrated a new fxratesapi module to handle currency conversions, including API configurations, rate conversion actions, and request logging. Added support for module settings such as enablement status, API key, and daily request limits. New routes, database interactions, and object handling were implemented to facilitate the module's operations.
This commit is contained in:
Jepp9350
2025-03-31 13:19:45 +02:00
parent a5cfbf64e4
commit 9b53ae4403
14 changed files with 1321 additions and 1 deletions
+518
View File
@@ -0,0 +1,518 @@
<?php
namespace classes;
require_once WD . '/modules/fxratesapi/fxratesapi_c.php';
/** Actions */
require_once WD . '/modules/fxratesapi/actions/convert_rate_a.php';
use Exception;
use fxratesapi\actions\convert_rate_a;
use fxratesapi\fxratesapi_c;
use interfaces\fxratesapi_i;
use objects\fxratesapi_conversion_rates_o;
class fxratesapi implements fxratesapi_i
{
/**
* Configuration of the fxratesapi module
* @var fxratesapi_c
*/
public fxratesapi_c $config;
/**
* API URL
* @var string
*/
private string $api_url = 'https://api.fxratesapi.com/';
/**
* ACTION: CONVERT RATE
* @see convert_rate_a
* @notation This action is when a currency conversion request is made, and logs the request in the database
* @var convert_rate_a
*/
private convert_rate_a $convert_rate;
public function __construct()
{
$this->config = new fxratesapi_c();
/** Actions */
$this->convert_rate = new convert_rate_a();
}
/**
* @inheritDoc
*/
function isCached(string $base, string $target): bool
{
// Check if the base currency is valid
self::requireValidCurrency($base);
// Check if the target currency is valid
self::requireValidCurrency($base);
// Check if the request is cached
$fxratesapi_lookups = new fxratesapi_conversion_rates_o();
return $fxratesapi_lookups->isCached($base, $target);
}
/**
* @inheritDoc
*/
function requireValidCurrency(string $currency): void
{
// Check if the currency is valid
if (!preg_match('/^[A-Z]{3}$/', $currency)) {
throw new Exception('Invalid currency');
}
}
/**
* @inheritDoc
*/
function getCachedResponse(string $base, string $target): object
{
// Check if the base and target currencies are valid
self::requireValidCurrency($base);
self::requireValidCurrency($target);
// Get the cached response from the fxratesapi request log
$fxratesapi_lookups = new fxratesapi_conversion_rates_o();
$fxratesapi_lookups->getCachedResponse($base, $target);
return $fxratesapi_lookups;
}
/**
* @inheritDoc
* @throws Exception If the base or target currency is invalid
* @throws Exception If the endpoint is invalid
* @throws Exception If the response is invalid
* @throws Exception If the response is not valid JSON
* @throws Exception If the request fails
*/
function getConversionRate(string $base, string $target, string $endpoint, array $data): object
{
// Check if the base and target currencies are valid
self::requireValidCurrency($base);
self::requireValidCurrency($target);
// Send the request
// Return the conversion rate
return self::sendRequest($base, $target, $endpoint, $data);
}
/**
* @inheritDoc
* @throws Exception If the module is not enabled, the license plate is invalid, the daily limit is exceeded, or the secret key is invalid
*/
function sendRequest(
string $base,
string $target,
string $endpoint,
array $data = [],
string $method = 'GET'
): object
{
// Validate the module is enabled
self::requireModuleEnabled();
// Validate the base and target currencies
self::requireValidCurrency($base);
self::requireValidCurrency($target);
// Validate the daily limit
self::requireDailyLimitNotExceeded();
// Validate the secret key
self::requireValidSecretKey();
// Send the request
$response = match ($method) {
'GET' => self::sendGetRequest($base, $target, $endpoint, $data),
'POST' => self::sendPostRequest($base, $target, $endpoint, $data),
'PUT' => self::sendPutRequest($base, $target, $endpoint, $data),
'DELETE' => self::sendDeleteRequest($base, $target, $endpoint, $data),
default => self::exception(
$base,
$target,
[
'method' => $method,
'endpoint' => $endpoint,
'data' => $data,
'license_plate' => $base,
'response' => null,
'status_code' => 400,
'error' => 'Invalid request method',
],
500
),
};
// Add the usage to the request log
$this->convert_rate->convert_rate($base, $target, $response, 200);
// Add the request to the log
self::addRequestToLog($base, $target, $endpoint, $response);
// Return the response
return $response;
}
/**
* @inheritDoc
*/
function requireModuleEnabled(): void
{
// Check if the module is enabled
if (!$this->config->enabled->isTrue()) {
throw new Exception('The fxratesapi module is not enabled');
}
}
/**
* @inheritDoc
*/
function requireDailyLimitNotExceeded(): void
{
// Check if the daily limit is exceeded
if ($this->getDailyRequestCounter() >= $this->config->daily_limit->getVariableValue()) {
throw new Exception('Daily limit exceeded');
}
}
/**
* @inheritDoc
*/
function getDailyRequestCounter(): int
{
// Count the rows from the fxratesapi request log that was made today
$fxratesapi_lookups = new fxratesapi_conversion_rates_o();
$fxratesapi_lookups->getTodayCount();
return $fxratesapi_lookups->getTodayCount();
}
/**
* @inheritDoc
*/
function requireValidSecretKey(): void
{
// Check if the secret key is valid
if ($this->config->secret_key->getVariableValue() === null) {
self::exception(
'',
'',
[
'status_code' => 500,
'error' => 'Invalid secret key defined in the config (fxratesapi_secret_key_c)',
],
500
);
}
}
/**
* @throws Exception
*/
function exception(string $base, string $target, array $data, int $status_code = 500): object
{
// Check if the response is valid JSON
if (!json_decode($data['response'])) {
throw new Exception('Invalid response');
}
// Add the request to the log
$this->convert_rate->convert_rate($base, $target, $data, $status_code);
return throw new Exception($data['error'] ?? 'An error occurred while processing the request, in ' . $this->config->getModuleName() . ' module');
}
/**
* @inheritDoc
* @throws Exception
*/
function sendGetRequest(string $base, string $target, string $endpoint, array $data): object
{
// Check if the base and target currencies are valid
self::requireValidCurrency($base);
self::requireValidCurrency($target);
// Add the base and target currencies to the data array
$data['base'] = $base;
// Build the query string if any data is provided
$query_string = '';
if (!empty($data)) {
$query_string = '?' . http_build_query($data);
}
// Initialize cURL session
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->api_url . $endpoint . '/' . $query_string);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// Set HTTP headers including the X-AUTH-TOKEN
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-AUTH-TOKEN: ' . $this->config->secret_key->getVariableValue(),
]);
// Execute the request and handle response
$output = curl_exec($ch);
// Check for errors
if (curl_errno($ch)) {
self::exception(
$base,
$target,
[
'method' => 'GET',
'endpoint' => $endpoint,
'data' => $data,
'response' => null,
'status_code' => 500,
'error' => curl_error($ch),
],
500
);
}
// Get the status code
$status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// Check if the response is valid JSON
if (!json_decode($output)) {
$exception_message = match ($status_code) {
401 => 'Unauthorized',
404 => 'Not found',
429 => 'Too many requests',
default => 'Invalid response',
};
self::exception(
$base,
$target,
[
'method' => 'GET',
'endpoint' => $endpoint,
'data' => $data,
'response' => null,
'status_code' => $status_code,
'error' => $exception_message,
],
$status_code
);
}
return json_decode($output);
}
/**
* @inheritDoc
* @throws Exception If the request fails
* @throws Exception If the response is invalid
* @throws Exception If the response is not valid JSON
* @throws Exception If the base or target currency is invalid
* @throws Exception If the endpoint is invalid
*/
function sendPostRequest(string $base, string $target, string $endpoint, array $data): object
{
// Check if the base and target currencies are valid
self::requireValidCurrency($base);
self::requireValidCurrency($target);
// Initialize cURL session
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->api_url . $endpoint . '/' . $base . '/' . $target);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// Set HTTP headers including the X-AUTH-TOKEN
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-AUTH-TOKEN: ' . $this->config->secret_key->getVariableValue(),
]);
// Execute the request and handle response
$output = curl_exec($ch);
// Check for errors
if (curl_errno($ch)) {
self::exception(
$base,
$target,
[
'method' => 'POST',
'endpoint' => $endpoint,
'data' => $data,
'response' => null,
'status_code' => 500,
'error' => curl_error($ch),
],
500
);
}
// Get the status code
$status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// Check if the response is valid JSON
if (!json_decode($output)) {
self::exception(
$base,
$target,
[
'method' => 'POST',
'endpoint' => $endpoint,
'data' => $data,
'response' => null,
'status_code' => $status_code,
'error' => 'Invalid response',
],
500
);
}
return json_decode($output);
}
/**
* @inheritDoc
* @throws Exception If the request fails
* @throws Exception If the response is invalid
* @throws Exception If the response is not valid JSON
* @throws Exception If the base or target currency is invalid
* @throws Exception If the endpoint is invalid
*/
function sendPutRequest(string $base, string $target, string $endpoint, array $data): object
{
// Check if the base and target currencies are valid
self::requireValidCurrency($base);
self::requireValidCurrency($target);
// Initialize cURL session
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->api_url . $endpoint . '/' . $base . '/' . $target);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// Set HTTP headers including the X-AUTH-TOKEN
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-AUTH-TOKEN: ' . $this->config->secret_key->getVariableValue(),
]);
// Execute the request and handle response
$output = curl_exec($ch);
// Check for errors
if (curl_errno($ch)) {
self::exception(
$base,
$target,
[
'method' => 'PUT',
'endpoint' => $endpoint,
'data' => $data,
'response' => null,
'status_code' => 500,
'error' => curl_error($ch),
],
500
);
}
// Get the status code
$status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// Check if the response is valid JSON
if (!json_decode($output)) {
self::exception(
$base,
$target,
[
'method' => 'PUT',
'endpoint' => $endpoint,
'data' => $data,
'response' => null,
'status_code' => $status_code,
'error' => 'Invalid response',
],
500
);
}
return json_decode($output);
}
/**
* @inheritDoc
* @throws Exception If the request fails
* @throws Exception If the response is invalid
* @throws Exception If the response is not valid JSON
* @throws Exception If the base or target currency is invalid
* @throws Exception If the endpoint is invalid
*/
function sendDeleteRequest(string $base, string $target, string $endpoint, array $data): object
{
// Check if the base and target currencies are valid
self::requireValidCurrency($base);
self::requireValidCurrency($target);
// Initialize cURL session
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->api_url . $endpoint . '/' . $base . '/' . $target);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// Set HTTP headers including the X-AUTH-TOKEN
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-AUTH-TOKEN: ' . $this->config->secret_key->getVariableValue(),
]);
// Execute the request and handle response
$output = curl_exec($ch);
// Check for errors
if (curl_errno($ch)) {
self::exception(
$base,
$target,
[
'method' => 'DELETE',
'endpoint' => $endpoint,
'data' => $data,
'response' => null,
'status_code' => 500,
'error' => curl_error($ch),
],
500
);
}
// Get the status code
$status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// Check if the response is valid JSON
if (!json_decode($output)) {
self::exception(
$base,
$target,
[
'method' => 'DELETE',
'endpoint' => $endpoint,
'data' => $data,
'response' => null,
'status_code' => $status_code,
'error' => 'Invalid response',
],
500
);
}
return json_decode($output);
}
/**
* @inheritDoc
* @throws Exception
*/
function addRequestToLog(string $base, string $target, string $endpoint, object $response): void
{
// Add the request to the fxratesapi request log
$fxratesapi_lookups = new fxratesapi_conversion_rates_o();
$fxratesapi_lookups->add($base, $target, json_encode($response), $endpoint);
// Check if the request was added successfully
if (!$fxratesapi_lookups->id) {
throw new Exception('Failed to add request to log');
}
}
}