cyxc
All checks were successful
Deploy / deploy-staging (push) Successful in 8s
Deploy / deploy-production (push) Has been skipped

This commit is contained in:
2026-05-05 20:36:41 +02:00
parent fd092ef615
commit eb8b04a621
14 changed files with 39 additions and 969 deletions

View File

@@ -151,14 +151,6 @@ final class Router
$this->respond(['data' => $this->probeFxRates(Http::input())], 200);
}
if ($resource === 'currencies-refresh' && $method === 'POST') {
$this->respond(['data' => $this->refreshCurrencies()], 201);
}
if ($resource === 'currencies-probe' && $method === 'POST') {
$this->respond(['data' => $this->probeCurrencies()], 200);
}
if ($resource === 'fx-history' && $method === 'GET') {
$this->respond(['data' => $this->fxHistory()]);
}
@@ -297,18 +289,6 @@ final class Router
Http::json(['data' => $this->purchaseMiner($projectKey, (int) $matches[1], Http::input())], 201);
}
if ($resource === 'currencies' && $method === 'GET') {
Http::json(['data' => $this->currencies()]);
}
if ($resource === 'currency-aliases' && $method === 'GET') {
Http::json(['data' => $this->currencyAliases()]);
}
if ($resource === 'currency-aliases' && $method === 'POST') {
Http::json(['data' => $this->saveCurrencyAlias(Http::input())], 201);
}
throw new ApiException('Ressource nicht gefunden.', 404, ['resource' => $resource, 'method' => $method]);
} catch (ApiException $exception) {
$this->debug->add('router.handle.api_exception', [
@@ -513,8 +493,6 @@ final class Router
$backup = [
'project' => $this->repository()->getProject($projectKey),
'settings' => $this->safeRead(fn () => $this->repository()->getSettings($projectKey)),
'currencies' => $this->safeRead(fn () => $this->repository()->listCurrencies(), []),
'currency_aliases' => $this->safeRead(fn () => $this->repository()->listCurrencyAliases(), []),
'cost_plans' => $this->safeRead(fn () => $this->repository()->listCostPlans($projectKey), []),
'measurements' => $this->safeRead(fn () => $this->repository()->listAllMeasurements($projectKey), []),
'measurement_rates' => $this->safeRead(fn () => $this->repository()->listMeasurementRates($projectKey), []),
@@ -538,23 +516,6 @@ final class Router
$projectName = is_array($backup['project']) ? ($backup['project']['project_name'] ?? null) : null;
$this->repository()->ensureProject($projectKey, is_string($projectName) ? $projectName : null);
foreach ($backup['currencies'] as $currency) {
$this->repository()->saveCurrency([
'code' => $currency['code'],
'name' => $currency['name'],
'symbol' => $currency['symbol'] ?? null,
'is_active' => !empty($currency['is_active']) ? 1 : 0,
'is_crypto' => !empty($currency['is_crypto']) ? 1 : 0,
'sort_order' => (int) ($currency['sort_order'] ?? 0),
]);
}
foreach ($backup['currency_aliases'] as $alias) {
if (!empty($alias['alias_code']) && !empty($alias['currency_code'])) {
$this->repository()->saveCurrencyAlias((string) $alias['alias_code'], (string) $alias['currency_code']);
}
}
if (is_array($backup['settings'])) {
$this->repository()->saveSettings($projectKey, [
'baseline_measured_at' => $backup['settings']['baseline_measured_at'],
@@ -750,7 +711,6 @@ final class Router
'targets' => count($backup['targets']),
'dashboards' => count($backup['dashboards']),
'miner_offers' => count($backup['miner_offers']),
'currency_aliases' => count($backup['currency_aliases']),
'fx_rates' => $restoredFxRates,
],
]);
@@ -795,20 +755,6 @@ final class Router
return $this->fx()->probeLatestRates($base);
}
private function refreshCurrencies(): array
{
$result = $this->fx()->refreshCurrencyCatalog();
$synced = $this->syncLocalCurrencyCatalogFromFxRates(true);
return $result + [
'local_catalog_synced' => count($synced),
];
}
private function probeCurrencies(): array
{
return $this->fx()->probeCurrencyCatalog();
}
private function fxHistory(): array
{
if (!modules()->isEnabled('fx-rates') || !modules()->hasFunction('fx-rates', 'recent_fetches')) {
@@ -1293,7 +1239,7 @@ final class Router
private function bootstrapMeasurements(string $projectKey, array $settings, string $view): array
{
if (in_array($view, ['settings', 'currencies', 'dashboards'], true)) {
if (in_array($view, ['settings', 'dashboards'], true)) {
return [];
}
@@ -1373,7 +1319,7 @@ final class Router
private function normalizeBootstrapView(string $view): string
{
$normalized = trim(strtolower($view));
return in_array($normalized, ['overview', 'measurements', 'dashboards', 'currencies', 'mining', 'settings'], true)
return in_array($normalized, ['overview', 'measurements', 'dashboards', 'mining', 'settings'], true)
? $normalized
: 'overview';
}
@@ -1475,22 +1421,12 @@ final class Router
return;
}
$knownCodes = array_map(
static fn (array $currency): string => strtoupper((string) ($currency['code'] ?? '')),
$this->currencies()
);
if (in_array($priceCurrency, $knownCodes, true)) {
return;
}
$matched = $this->currencyCatalogEntry($priceCurrency);
$this->repository()->ensureCurrencyCode($priceCurrency, $matched['name'] ?? $priceCurrency);
try {
$this->refreshCurrencies();
} catch (\Throwable) {
// Measurement save must not fail because the external currency sync is unavailable.
if ($this->currencyCatalogEntry($priceCurrency) === null) {
throw new ApiException(
'Waehrung ist im fx-rates Katalog nicht vorhanden.',
422,
['currency' => $priceCurrency]
);
}
}
@@ -1580,25 +1516,7 @@ final class Router
private function currencies(): array
{
$catalog = $this->syncLocalCurrencyCatalogFromFxRates();
return $catalog !== [] ? $catalog : $this->repository()->listCurrencies();
}
private function currencyAliases(): array
{
return $this->repository()->listCurrencyAliases();
}
private function saveCurrencyAlias(array $input): array
{
$aliasCode = strtoupper(trim((string) ($input['alias_code'] ?? '')));
$currencyCode = $this->requiredCurrency($input['currency_code'] ?? null, 'currency_code');
if (!preg_match('/^[A-Z0-9]{3,10}$/', $aliasCode)) {
throw new ApiException('Feld alias_code muss ein gueltiger Waehrungscode sein.', 422);
}
return $this->repository()->saveCurrencyAlias($aliasCode, $currencyCode);
return $this->currencyCatalog();
}
private function minerOffers(string $projectKey): array
@@ -1945,24 +1863,18 @@ final class Router
throw new ApiException("Feld {$field} muss ein gueltiger Waehrungscode sein.", 422);
}
$resolved = $this->repository()->resolveCurrencyCode($currency);
if ($resolved !== null && !empty($resolved['code'])) {
return (string) $resolved['code'];
}
$catalogEntry = $this->currencyCatalogEntry($currency);
if (is_array($catalogEntry)) {
$this->repository()->ensureCurrencyCode($currency, (string) ($catalogEntry['name'] ?? $currency));
return $currency;
}
throw new ApiException(
"Feld {$field} verweist auf keinen vorhandenen Waehrungsrecord.",
"Feld {$field} verweist auf keinen vorhandenen fx-rates Waehrungscode.",
422,
[
'field' => $field,
'missing_currency' => $currency,
'hint' => 'Synchronisiere zuerst den Waehrungskatalog aus fx-rates oder hinterlege einen Alias auf einen bestehenden Waehrungsrecord.',
'hint' => 'Synchronisiere zuerst den Waehrungskatalog im Modul fx-rates.',
'available_currencies' => array_slice(array_map(
static fn (array $item): string => (string) ($item['code'] ?? ''),
$this->currencies()
@@ -1981,10 +1893,15 @@ final class Router
private function assertCurrencyType(string $code, bool $expectedCrypto, string $field): void
{
$this->ensureLocalCurrencyRecord($code);
$resolved = $this->repository()->resolveCurrencyCode($code);
$currency = is_array($resolved) ? ($resolved['currency'] ?? null) : null;
$isCrypto = !empty($currency['is_crypto']);
if ($this->currencyCatalogEntry($code) === null) {
throw new ApiException(
"Feld {$field} verweist auf keinen vorhandenen fx-rates Waehrungscode.",
422,
['field' => $field, 'currency' => $code]
);
}
$isCrypto = $this->isCryptoCurrencyCode($code);
if ($isCrypto !== $expectedCrypto) {
throw new ApiException(
$expectedCrypto
@@ -2248,22 +2165,6 @@ final class Router
return null;
}
private function syncLocalCurrencyCatalogFromFxRates(bool $forceRefresh = false): array
{
if ($forceRefresh) {
$this->fxRatesSettingsCache = null;
$this->currencyCatalogCache = null;
}
$catalog = $this->currencyCatalog();
if ($catalog === []) {
return [];
}
$this->repository()->saveCurrencies($catalog);
return $this->repository()->listCurrencies();
}
private function syncFxRatesPreferredCurrencies(array $preferredCurrencies): void
{
if (!modules()->isEnabled('fx-rates') || !modules()->hasFunction('fx-rates', 'save_runtime_settings')) {
@@ -2274,22 +2175,7 @@ final class Router
'preferred_currencies' => $preferredCurrencies,
]);
$this->fxRatesSettingsCache = null;
}
private function ensureLocalCurrencyRecord(string $code): void
{
$resolved = $this->repository()->resolveCurrencyCode($code);
if ($resolved !== null) {
return;
}
$catalogEntry = $this->currencyCatalogEntry($code);
if ($catalogEntry !== null) {
$this->repository()->saveCurrency($catalogEntry);
return;
}
$this->repository()->ensureCurrencyCode($code, $code);
$this->currencyCatalogCache = null;
}
private function isCryptoCurrencyCode(string $code): bool