ddfsdfdf
This commit is contained in:
@@ -98,8 +98,8 @@ final class Router
|
||||
$maxAgeMinutes = is_numeric($input['max_age_minutes'] ?? null) ? (int) $input['max_age_minutes'] : null;
|
||||
|
||||
$result = $force
|
||||
? $this->service->refreshLatestRates(null, $base)
|
||||
: $this->service->autoRefreshLatestRates($base, null, $maxAgeMinutes);
|
||||
? $this->service->refreshLatestRates(null, $base, 'api')
|
||||
: $this->service->autoRefreshLatestRates($base, null, $maxAgeMinutes, 'api');
|
||||
|
||||
$this->respond(['data' => $result], 201);
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ final class FxRatesService
|
||||
return $amount * (float) $rate['rate'];
|
||||
}
|
||||
|
||||
public function refreshLatestRates(?array $currencies = null, ?string $baseCurrency = null): array
|
||||
public function refreshLatestRates(?array $currencies = null, ?string $baseCurrency = null, string $triggerSource = 'manual'): array
|
||||
{
|
||||
$requestedBase = $this->normalizeCurrency($baseCurrency ?: $this->defaultBaseCurrency());
|
||||
$payload = $this->fetchLatestPayload($requestedBase, null);
|
||||
@@ -209,7 +209,8 @@ final class FxRatesService
|
||||
$this->provider(),
|
||||
$rateDate,
|
||||
$rates,
|
||||
gmdate('Y-m-d H:i:s')
|
||||
gmdate('Y-m-d H:i:s'),
|
||||
$triggerSource
|
||||
);
|
||||
|
||||
return [
|
||||
@@ -223,7 +224,7 @@ final class FxRatesService
|
||||
];
|
||||
}
|
||||
|
||||
public function ensureFreshLatestRates(float $maxAgeHours = 24.0, ?string $baseCurrency = null, ?array $currencies = null): array
|
||||
public function ensureFreshLatestRates(float $maxAgeHours = 24.0, ?string $baseCurrency = null, ?array $currencies = null, string $triggerSource = 'manual'): array
|
||||
{
|
||||
$base = $this->normalizeCurrency($baseCurrency ?: $this->defaultBaseCurrency());
|
||||
$latest = $this->repository->getLatestFetch($base);
|
||||
@@ -242,16 +243,16 @@ final class FxRatesService
|
||||
];
|
||||
}
|
||||
|
||||
$result = $this->refreshLatestRates($currencies, $base);
|
||||
$result = $this->refreshLatestRates($currencies, $base, $triggerSource);
|
||||
$result['reused'] = false;
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function autoRefreshLatestRates(?string $baseCurrency = null, ?array $currencies = null, ?int $maxAgeMinutes = null): array
|
||||
public function autoRefreshLatestRates(?string $baseCurrency = null, ?array $currencies = null, ?int $maxAgeMinutes = null, string $triggerSource = 'api'): array
|
||||
{
|
||||
$minutes = $maxAgeMinutes ?? $this->refreshMaxAgeMinutes();
|
||||
$hours = max(1, $minutes) / 60;
|
||||
return $this->ensureFreshLatestRates($hours, $baseCurrency, $currencies);
|
||||
return $this->ensureFreshLatestRates($hours, $baseCurrency, $currencies, $triggerSource);
|
||||
}
|
||||
|
||||
public function history(string $fromCurrency, string $toCurrency, ?string $from = null, ?string $to = null, int $limit = 200): array
|
||||
@@ -302,7 +303,8 @@ final class FxRatesService
|
||||
|
||||
public function runScheduledRefresh(array $context = []): array
|
||||
{
|
||||
$result = $this->refreshLatestRates(null, $this->defaultBaseCurrency());
|
||||
$triggerSource = ($context['trigger'] ?? null) === 'manual_test' ? 'manual' : 'cron';
|
||||
$result = $this->refreshLatestRates(null, $this->defaultBaseCurrency(), $triggerSource);
|
||||
return [
|
||||
'ok' => true,
|
||||
'message' => 'Geplanter FX-Abruf ausgefuehrt: ' . (int) ($result['updated_count'] ?? 0) . ' Kurse gespeichert.',
|
||||
@@ -751,6 +753,7 @@ final class FxRatesService
|
||||
|
||||
$fetch['fetched_at_display'] = $this->formatDisplayTimestamp($fetch['fetched_at'] ?? null);
|
||||
$fetch['created_at_display'] = $this->formatDisplayTimestamp($fetch['created_at'] ?? null);
|
||||
$fetch['trigger_source_label'] = $this->triggerSourceLabel((string) ($fetch['trigger_source'] ?? 'manual'));
|
||||
return $fetch;
|
||||
}
|
||||
|
||||
@@ -962,4 +965,13 @@ final class FxRatesService
|
||||
{
|
||||
return $this->scheduleTimezone();
|
||||
}
|
||||
|
||||
private function triggerSourceLabel(string $source): string
|
||||
{
|
||||
return match (strtolower(trim($source))) {
|
||||
'cron' => 'Cron',
|
||||
'api' => 'API',
|
||||
default => 'Manuell',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,11 +25,13 @@ final class FxRatesRepository
|
||||
$this->pdo->exec("CREATE TABLE IF NOT EXISTS {$fetchTable} (
|
||||
id SERIAL PRIMARY KEY,
|
||||
provider VARCHAR(64) NOT NULL,
|
||||
trigger_source VARCHAR(32) NOT NULL DEFAULT 'manual',
|
||||
base_currency VARCHAR(10) NOT NULL,
|
||||
rate_date DATE NOT NULL,
|
||||
fetched_at TIMESTAMP NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
)");
|
||||
$this->pdo->exec("ALTER TABLE {$fetchTable} ADD COLUMN IF NOT EXISTS trigger_source VARCHAR(32) NOT NULL DEFAULT 'manual'");
|
||||
$this->pdo->exec("CREATE TABLE IF NOT EXISTS {$rateTable} (
|
||||
id SERIAL PRIMARY KEY,
|
||||
fetch_id INTEGER NOT NULL REFERENCES {$fetchTable}(id) ON DELETE CASCADE,
|
||||
@@ -44,6 +46,7 @@ final class FxRatesRepository
|
||||
$this->pdo->exec("CREATE TABLE IF NOT EXISTS {$fetchTable} (
|
||||
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
||||
provider VARCHAR(64) NOT NULL,
|
||||
trigger_source VARCHAR(32) NOT NULL DEFAULT 'manual',
|
||||
base_currency VARCHAR(10) NOT NULL,
|
||||
rate_date DATE NOT NULL,
|
||||
fetched_at DATETIME NOT NULL,
|
||||
@@ -51,6 +54,7 @@ final class FxRatesRepository
|
||||
KEY {$fetchTable}_base_fetch_idx (base_currency, fetched_at, id),
|
||||
KEY {$fetchTable}_rate_date_idx (rate_date)
|
||||
)");
|
||||
$this->ensureColumn($fetchTable, 'trigger_source', "ALTER TABLE {$fetchTable} ADD COLUMN trigger_source VARCHAR(32) NOT NULL DEFAULT 'manual'");
|
||||
$this->pdo->exec("CREATE TABLE IF NOT EXISTS {$rateTable} (
|
||||
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
||||
fetch_id INTEGER NOT NULL,
|
||||
@@ -63,11 +67,13 @@ final class FxRatesRepository
|
||||
$this->pdo->exec("CREATE TABLE IF NOT EXISTS {$fetchTable} (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
provider VARCHAR(64) NOT NULL,
|
||||
trigger_source VARCHAR(32) NOT NULL DEFAULT 'manual',
|
||||
base_currency VARCHAR(10) NOT NULL,
|
||||
rate_date DATE NOT NULL,
|
||||
fetched_at DATETIME NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
)");
|
||||
$this->ensureColumn($fetchTable, 'trigger_source', "ALTER TABLE {$fetchTable} ADD COLUMN trigger_source VARCHAR(32) NOT NULL DEFAULT 'manual'");
|
||||
$this->pdo->exec("CREATE TABLE IF NOT EXISTS {$rateTable} (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
fetch_id INTEGER NOT NULL,
|
||||
@@ -83,7 +89,7 @@ final class FxRatesRepository
|
||||
|
||||
public function getLatestFetch(?string $baseCurrency = null): ?array
|
||||
{
|
||||
$sql = 'SELECT id, provider, base_currency, rate_date, fetched_at, created_at FROM ' . $this->table('fetches');
|
||||
$sql = 'SELECT id, provider, trigger_source, base_currency, rate_date, fetched_at, created_at FROM ' . $this->table('fetches');
|
||||
$params = [];
|
||||
if ($baseCurrency !== null && trim($baseCurrency) !== '') {
|
||||
$sql .= ' WHERE base_currency = :base_currency';
|
||||
@@ -99,7 +105,7 @@ final class FxRatesRepository
|
||||
public function listLatestFetches(): array
|
||||
{
|
||||
$stmt = $this->pdo->query(
|
||||
'SELECT id, provider, base_currency, rate_date, fetched_at, created_at
|
||||
'SELECT id, provider, trigger_source, base_currency, rate_date, fetched_at, created_at
|
||||
FROM ' . $this->table('fetches') . '
|
||||
ORDER BY fetched_at DESC, id DESC'
|
||||
);
|
||||
@@ -120,7 +126,7 @@ final class FxRatesRepository
|
||||
public function listRecentFetches(int $limit = 20): array
|
||||
{
|
||||
$stmt = $this->pdo->prepare(
|
||||
'SELECT id, provider, base_currency, rate_date, fetched_at, created_at
|
||||
'SELECT id, provider, trigger_source, base_currency, rate_date, fetched_at, created_at
|
||||
FROM ' . $this->table('fetches') . '
|
||||
ORDER BY fetched_at DESC, id DESC
|
||||
LIMIT :limit'
|
||||
@@ -161,7 +167,7 @@ final class FxRatesRepository
|
||||
foreach (['<=', '>='] as $operator) {
|
||||
$order = $operator === '<=' ? 'DESC' : 'ASC';
|
||||
$stmt = $this->pdo->prepare(
|
||||
'SELECT id, provider, base_currency, rate_date, fetched_at, created_at
|
||||
'SELECT id, provider, trigger_source, base_currency, rate_date, fetched_at, created_at
|
||||
FROM ' . $this->table('fetches') . '
|
||||
WHERE fetched_at ' . $operator . ' :target_at
|
||||
ORDER BY fetched_at ' . $order . ', id ' . $order . '
|
||||
@@ -277,11 +283,12 @@ final class FxRatesRepository
|
||||
return array_map(fn (array $row): array => $this->normalizeRate($row), $stmt->fetchAll(PDO::FETCH_ASSOC) ?: []);
|
||||
}
|
||||
|
||||
public function saveFetch(string $baseCurrency, string $provider, string $rateDate, array $rates, ?string $fetchedAt = null): array
|
||||
public function saveFetch(string $baseCurrency, string $provider, string $rateDate, array $rates, ?string $fetchedAt = null, string $triggerSource = 'manual'): array
|
||||
{
|
||||
$baseCurrency = strtoupper(trim($baseCurrency));
|
||||
$provider = trim($provider) !== '' ? trim($provider) : 'currencyapi';
|
||||
$fetchedAt = trim((string) $fetchedAt) !== '' ? trim((string) $fetchedAt) : gmdate('Y-m-d H:i:s');
|
||||
$triggerSource = $this->normalizeTriggerSource($triggerSource);
|
||||
$normalizedRates = [];
|
||||
foreach ($rates as $currencyCode => $rate) {
|
||||
$currencyCode = strtoupper(trim((string) $currencyCode));
|
||||
@@ -301,14 +308,15 @@ final class FxRatesRepository
|
||||
if ($this->driver === 'pgsql') {
|
||||
$fetchStmt = $this->pdo->prepare(
|
||||
'INSERT INTO ' . $this->table('fetches') . ' (
|
||||
provider, base_currency, rate_date, fetched_at
|
||||
provider, trigger_source, base_currency, rate_date, fetched_at
|
||||
) VALUES (
|
||||
:provider, :base_currency, :rate_date, :fetched_at
|
||||
:provider, :trigger_source, :base_currency, :rate_date, :fetched_at
|
||||
)
|
||||
RETURNING *'
|
||||
);
|
||||
$fetchStmt->execute([
|
||||
'provider' => $provider,
|
||||
'trigger_source' => $triggerSource,
|
||||
'base_currency' => $baseCurrency,
|
||||
'rate_date' => $rateDate,
|
||||
'fetched_at' => $fetchedAt,
|
||||
@@ -317,13 +325,14 @@ final class FxRatesRepository
|
||||
} else {
|
||||
$fetchStmt = $this->pdo->prepare(
|
||||
'INSERT INTO ' . $this->table('fetches') . ' (
|
||||
provider, base_currency, rate_date, fetched_at
|
||||
provider, trigger_source, base_currency, rate_date, fetched_at
|
||||
) VALUES (
|
||||
:provider, :base_currency, :rate_date, :fetched_at
|
||||
:provider, :trigger_source, :base_currency, :rate_date, :fetched_at
|
||||
)'
|
||||
);
|
||||
$fetchStmt->execute([
|
||||
'provider' => $provider,
|
||||
'trigger_source' => $triggerSource,
|
||||
'base_currency' => $baseCurrency,
|
||||
'rate_date' => $rateDate,
|
||||
'fetched_at' => $fetchedAt,
|
||||
@@ -379,7 +388,7 @@ final class FxRatesRepository
|
||||
private function getFetchById(int $fetchId): ?array
|
||||
{
|
||||
$stmt = $this->pdo->prepare(
|
||||
'SELECT id, provider, base_currency, rate_date, fetched_at, created_at
|
||||
'SELECT id, provider, trigger_source, base_currency, rate_date, fetched_at, created_at
|
||||
FROM ' . $this->table('fetches') . '
|
||||
WHERE id = :id
|
||||
LIMIT 1'
|
||||
@@ -393,10 +402,10 @@ final class FxRatesRepository
|
||||
{
|
||||
$order = $operator === '<=' ? 'DESC' : 'ASC';
|
||||
$stmt = $this->pdo->prepare(
|
||||
'SELECT id, provider, base_currency, rate_date, fetched_at, created_at
|
||||
'SELECT id, provider, trigger_source, base_currency, rate_date, fetched_at, created_at
|
||||
FROM ' . $this->table('fetches') . '
|
||||
WHERE base_currency = :base_currency
|
||||
AND fetched_at ' . $operator . ' :target_at
|
||||
AND fetched_at ' . $operator . ' :target_at
|
||||
ORDER BY fetched_at ' . $order . ', id ' . $order . '
|
||||
LIMIT 1'
|
||||
);
|
||||
@@ -456,6 +465,7 @@ final class FxRatesRepository
|
||||
return [
|
||||
'id' => isset($row['id']) ? (int) $row['id'] : null,
|
||||
'provider' => (string) ($row['provider'] ?? ''),
|
||||
'trigger_source' => (string) ($row['trigger_source'] ?? 'manual'),
|
||||
'base_currency' => strtoupper((string) ($row['base_currency'] ?? '')),
|
||||
'rate_date' => (string) ($row['rate_date'] ?? ''),
|
||||
'fetched_at' => (string) ($row['fetched_at'] ?? ''),
|
||||
@@ -463,6 +473,34 @@ final class FxRatesRepository
|
||||
];
|
||||
}
|
||||
|
||||
private function ensureColumn(string $table, string $column, string $alterSql): void
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->query('SELECT * FROM ' . $table . ' LIMIT 1');
|
||||
if ($stmt instanceof \PDOStatement) {
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
|
||||
if (in_array(strtolower($column), array_map('strtolower', array_keys($row)), true)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
|
||||
try {
|
||||
$this->pdo->exec($alterSql);
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
}
|
||||
|
||||
private function normalizeTriggerSource(string $source): string
|
||||
{
|
||||
$source = strtolower(trim($source));
|
||||
return match ($source) {
|
||||
'cron', 'manual', 'api' => $source,
|
||||
default => 'manual',
|
||||
};
|
||||
}
|
||||
|
||||
private function normalizeRate(array $row): array
|
||||
{
|
||||
return [
|
||||
|
||||
Reference in New Issue
Block a user