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

This commit is contained in:
2026-06-25 00:17:31 +02:00
parent 46111d8988
commit ac2d95fc56
140 changed files with 1168 additions and 1984 deletions

View File

@@ -0,0 +1,149 @@
<?php
declare(strict_types=1);
require_once dirname(__DIR__, 3) . '/src/App/bootstrap.php';
use ModulesCore\ModuleHttp;
session_start();
header('Content-Type: application/json; charset=utf-8');
try {
ModuleHttp::requireDesktopAccess(dirname(__DIR__, 3), true);
require_auth();
$user = auth_user() ?? [];
$ownerSub = trim((string) ($user['sub'] ?? 'local'));
$instrumentId = (int) ($_GET['instrument_id'] ?? 0);
if ($instrumentId <= 0) {
echo json_encode(['ok' => false, 'message' => 'instrument_id fehlt.'], JSON_UNESCAPED_UNICODE);
exit;
}
$pdo = module_fn('boersenchecker', 'pdo');
module_fn('boersenchecker', 'ensure_schema');
$instrumentTable = module_fn('boersenchecker', 'table', 'instruments');
$positionTable = module_fn('boersenchecker', 'table', 'positions');
$quoteTable = module_fn('boersenchecker', 'table', 'quotes');
$stmt = $pdo->prepare(
'SELECT i.id, i.name, i.symbol, i.isin, i.quote_currency
FROM ' . $instrumentTable . ' i
INNER JOIN ' . $positionTable . ' p ON p.instrument_id = i.id
WHERE i.id = :id AND p.owner_sub = :owner_sub
LIMIT 1'
);
$stmt->execute([
'id' => $instrumentId,
'owner_sub' => $ownerSub,
]);
$instrument = $stmt->fetch(PDO::FETCH_ASSOC);
if (!is_array($instrument)) {
echo json_encode(['ok' => false, 'message' => 'Aktie nicht verfuegbar.'], JSON_UNESCAPED_UNICODE);
exit;
}
$quoteStmt = $pdo->prepare(
'SELECT id, price, currency, quoted_at, source, created_at
FROM ' . $quoteTable . '
WHERE instrument_id = :instrument_id
ORDER BY quoted_at ASC, created_at ASC, id ASC'
);
$quoteStmt->execute([
'instrument_id' => $instrumentId,
]);
$quotes = $quoteStmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
if ($quotes === []) {
echo json_encode([
'ok' => false,
'message' => 'Keine lokalen Kursdaten fuer diese Aktie vorhanden.',
], JSON_UNESCAPED_UNICODE);
exit;
}
$dailyMap = [];
foreach ($quotes as $quote) {
$localDate = trim((string) module_fn(
'boersenchecker',
'format_datetime_for_display',
(string) ($quote['quoted_at'] ?? ''),
(string) ($quote['source'] ?? ''),
'Y-m-d'
));
$localDateTime = trim((string) module_fn(
'boersenchecker',
'format_datetime_for_display',
(string) ($quote['quoted_at'] ?? ''),
(string) ($quote['source'] ?? ''),
'Y-m-d H:i:s'
));
if ($localDate === '' || !is_numeric($quote['price'] ?? null)) {
continue;
}
$point = [
'date' => $localDate,
'close' => (float) $quote['price'],
'currency' => strtoupper(trim((string) ($quote['currency'] ?? ''))),
'quoted_at' => $localDateTime,
'source' => (string) ($quote['source'] ?? ''),
];
if (!isset($dailyMap[$localDate]) || strcmp($localDateTime, (string) ($dailyMap[$localDate]['quoted_at'] ?? '')) >= 0) {
$dailyMap[$localDate] = $point;
}
}
$daily = array_values($dailyMap);
usort($daily, static fn (array $left, array $right): int => strcmp((string) $left['date'], (string) $right['date']));
if ($daily === []) {
echo json_encode([
'ok' => false,
'message' => 'Keine gueltigen lokalen Schlusskurse fuer diese Aktie vorhanden.',
], JSON_UNESCAPED_UNICODE);
exit;
}
$aggregate = static function (array $points, string $format): array {
$result = [];
$timezone = new DateTimeZone(nexus_display_timezone_name());
foreach ($points as $point) {
$date = DateTimeImmutable::createFromFormat('Y-m-d', (string) ($point['date'] ?? ''), $timezone);
if (!$date instanceof DateTimeImmutable) {
continue;
}
$bucket = $date->format($format);
$result[$bucket] = $point;
}
return array_values($result);
};
echo json_encode([
'ok' => true,
'symbol' => strtoupper(trim((string) ($instrument['symbol'] ?? ''))),
'isin' => strtoupper(trim((string) ($instrument['isin'] ?? ''))),
'instrument_name' => (string) ($instrument['name'] ?? ''),
'currency' => strtoupper(trim((string) ($instrument['quote_currency'] ?? ''))),
'daily' => $daily,
'weekly' => $aggregate($daily, 'o-W'),
'monthly' => $aggregate($daily, 'Y-m'),
'source' => 'database:quotes',
'source_label' => 'Lokale Kurshistorie',
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
} catch (\Throwable $e) {
http_response_code(500);
echo json_encode([
'ok' => false,
'message' => 'Chartdaten konnten nicht geladen werden.',
'error' => $e->getMessage(),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
}

View File

@@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
require_once dirname(__DIR__, 3) . '/src/App/bootstrap.php';
require_once dirname(__DIR__) . '/bootstrap.php';
$requestUri = (string) ($_SERVER['REQUEST_URI'] ?? '');
$requestPath = (string) (parse_url($requestUri, PHP_URL_PATH) ?: '');
$prefix = '/api/boersenchecker/index.php/';
$relativePath = trim((string) ($_GET['path'] ?? ''), '/');
if ($relativePath === '') {
$relativePath = str_starts_with($requestPath, $prefix)
? substr($requestPath, strlen($prefix))
: ltrim((string) ($_SERVER['PATH_INFO'] ?? ''), '/');
}
$normalized = strtolower(trim($relativePath, '/'));
if ($normalized === 'v1/chart-data') {
require __DIR__ . '/chart_data.php';
return;
}
if ($normalized === 'v1/cron/refresh-quotes' && strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET')) === 'POST') {
header('Content-Type: application/json; charset=utf-8');
try {
$result = module_fn('boersenchecker', 'scheduled_refresh_quotes', [
'trigger_source' => 'cron',
]);
http_response_code(!empty($result['ok']) ? 200 : 500);
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
} catch (\Throwable $exception) {
http_response_code(500);
echo json_encode([
'ok' => false,
'message' => $exception->getMessage(),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
return;
}
if ($normalized === 'v1/widget/refresh-quotes' && strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET')) === 'POST') {
header('Content-Type: application/json; charset=utf-8');
try {
$result = module_fn('boersenchecker', 'scheduled_refresh_quotes', [
'trigger_source' => 'widget',
]);
http_response_code(!empty($result['ok']) ? 200 : 500);
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
} catch (\Throwable $exception) {
http_response_code(500);
echo json_encode([
'ok' => false,
'message' => $exception->getMessage(),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
return;
}
if ($normalized === 'v1/widget/status' && strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET')) === 'GET') {
header('Content-Type: application/json; charset=utf-8');
try {
$pdo = module_fn('boersenchecker', 'pdo');
$quoteTable = module_fn('boersenchecker', 'table', 'quotes');
$instrumentTable = module_fn('boersenchecker', 'table', 'instruments');
$positionTable = module_fn('boersenchecker', 'table', 'positions');
$settings = modules()->settings('boersenchecker');
$latestAt = $pdo->query('SELECT MAX(quoted_at) FROM ' . $quoteTable)->fetchColumn();
$countStmt = $pdo->query(
'SELECT COUNT(DISTINCT i.id)
FROM ' . $positionTable . ' p
INNER JOIN ' . $instrumentTable . ' i ON i.id = p.instrument_id
WHERE i.symbol IS NOT NULL
AND i.symbol <> \'\''
);
$trackedCount = (int) ($countStmt->fetchColumn() ?: 0);
$statusText = $latestAt
? 'Letzter Kursabruf: ' . date('d.m.Y H:i', strtotime((string) $latestAt)) . ' · ' . $trackedCount . ' Titel'
: 'Noch keine gespeicherten Kurse vorhanden.';
echo json_encode([
'ok' => true,
'data' => [
'status_text' => $statusText,
'latest_quoted_at' => $latestAt,
'tracked_instruments' => $trackedCount,
'report_currency' => (string) ($settings['report_currency'] ?? 'EUR'),
],
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
} catch (\Throwable $exception) {
http_response_code(500);
echo json_encode([
'ok' => false,
'message' => $exception->getMessage(),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
return;
}
http_response_code(404);
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'ok' => false,
'message' => 'Ressource nicht gefunden.',
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);