chore: complete update

This commit is contained in:
2025-07-17 16:24:20 +02:00
parent 899227b0a4
commit 64a7051137
1300 changed files with 85570 additions and 2756 deletions

View File

@@ -0,0 +1,108 @@
<?php
declare(strict_types=1);
namespace App\Application\Http\Controllers;
use App\Framework\Attributes\Route;
use App\Framework\Auth\Auth;
use App\Framework\Http\Method;
use App\Framework\Http\WebSocketConnection;
use App\Framework\Router\Result\WebSocketResult;
final class ChatController
{
private array $connections = [];
#[Auth]
#[Route(path: '/chat/websocket', method: Method::GET)]
public function chatWebSocket(): WebSocketResult
{
return new WebSocketResult()
->onConnect(function(WebSocketConnection $connection) {
$this->connections[$connection->getId()] = $connection;
// Willkommensnachricht senden
$connection->sendJson([
'type' => 'system',
'message' => 'Willkommen im Chat!',
'timestamp' => time()
]);
// Andere Benutzer benachrichtigen
$this->broadcast([
'type' => 'user_joined',
'message' => 'Ein neuer Benutzer ist dem Chat beigetreten',
'timestamp' => time()
], $connection->getId());
})
->onMessage(function(WebSocketConnection $connection, string $message) {
$data = json_decode($message, true);
if (!$data || !isset($data['type'])) {
$connection->sendJson(['error' => 'Invalid message format']);
return;
}
switch ($data['type']) {
case 'chat_message':
$this->handleChatMessage($connection, $data);
break;
case 'ping':
$connection->sendJson(['type' => 'pong']);
break;
default:
$connection->sendJson(['error' => 'Unknown message type']);
}
})
->onClose(function(WebSocketConnection $connection, int $code, string $reason) {
unset($this->connections[$connection->getId()]);
// Andere Benutzer benachrichtigen
$this->broadcast([
'type' => 'user_left',
'message' => 'Ein Benutzer hat den Chat verlassen',
'timestamp' => time()
]);
})
->onError(function(WebSocketConnection $connection, \Throwable $error) {
error_log("WebSocket error: " . $error->getMessage());
$connection->close(1011, 'Internal server error');
})
->withSubprotocols(['chat'])
->withMaxMessageSize(10240) // 10KB
->withPingInterval(30); // 30 Sekunden
}
private function handleChatMessage(WebSocketConnection $sender, array $data): void
{
if (!isset($data['message'])) {
$sender->sendJson(['error' => 'Message content required']);
return;
}
$message = [
'type' => 'chat_message',
'user_id' => $sender->getId(),
'message' => $data['message'],
'timestamp' => time()
];
// Nachricht an alle Verbindungen senden
$this->broadcast($message);
}
private function broadcast(array $message, ?string $excludeId = null): void
{
foreach ($this->connections as $id => $connection) {
if ($excludeId && $id === $excludeId) {
continue;
}
if ($connection->isConnected()) {
$connection->sendJson($message);
} else {
unset($this->connections[$id]);
}
}
}
}

View File

@@ -0,0 +1,160 @@
<?php
declare(strict_types=1);
namespace App\Application\Http\Controllers;
use App\Framework\Attributes\Route;
use App\Framework\Auth\Auth;
use App\Framework\Http\Method;
use App\Framework\Http\SseStream;
use App\Framework\Http\Status;
use App\Framework\Router\Result\SseResult;
use App\Framework\Router\Result\SseResultWithCallback;
/**
* Controller für Echtzeit-Benachrichtigungen über Server-Sent Events
*/
final class NotificationController
{
/**
* Stellt einen SSE-Stream für allgemeine Benachrichtigungen bereit
*/
#[Auth]
#[Route(path: '/api/notifications/stream', method: Method::GET)]
public function notificationStream(): SseResult
{
// SSE-Result mit 3 Sekunden Retry-Intervall
$result = new SseResult(Status::OK, 3000);
// Initiale Verbindungsbestätigung
$result->addJsonEvent(
['message' => 'Verbunden mit dem Benachrichtigungsstream'],
'connected',
'conn-' . uniqid()
);
// Einige Beispiel-Benachrichtigungen beim Start senden
$result->addJsonEvent(
[
'type' => 'info',
'title' => 'Willkommen',
'message' => 'Sie sind jetzt mit Echtzeit-Updates verbunden.',
'timestamp' => time()
],
'notification',
'notif-' . uniqid()
);
return $result;
}
/**
* Stellt einen benutzer-spezifischen SSE-Stream bereit
*/
#[Auth]
#[Route(path: '/api/notifications/user/{userId}', method: Method::GET)]
public function userNotifications(int $userId): SseResult
{
// SSE-Result mit benutzerdefinierten Headern
$result = new SseResult(Status::OK, 3000, [
'X-User-ID' => (string)$userId
]);
// Verbindungsbestätigung mit Benutzer-ID
$result->addJsonEvent(
[
'message' => 'Verbunden mit dem Benutzer-Stream',
'userId' => $userId,
'timestamp' => time()
],
'connected',
'conn-user-' . $userId
);
// Aktuelle Benachrichtigungen für den Benutzer laden
$notifications = $this->getUserNotifications($userId);
// Benachrichtigungen zum Stream hinzufügen
foreach ($notifications as $notification) {
$result->addJsonEvent($notification, 'notification', $notification['id']);
}
return $result;
}
/**
* Gibt Benachrichtigungen für einen bestimmten Benutzer zurück
* In einer realen Anwendung würde diese Methode Daten aus einer Datenbank laden
*/
private function getUserNotifications(int $userId): array
{
// Beispiel-Benachrichtigungen
return [
[
'id' => 'notif-' . uniqid(),
'type' => 'message',
'title' => 'Neue Nachricht',
'message' => 'Sie haben eine neue Nachricht erhalten',
'timestamp' => time() - 300 // Vor 5 Minuten
],
[
'id' => 'notif-' . uniqid(),
'type' => 'system',
'title' => 'System-Update',
'message' => 'Das System wurde aktualisiert',
'timestamp' => time() - 3600 // Vor 1 Stunde
]
];
}
/**
* Stellt einen Live-Stream mit dynamischen Updates bereit
*/
#[Auth]
#[Route(path: '/api/notifications/live', method: Method::GET)]
public function liveNotifications(): SseResult
{
// Erweiterte SSE-Konfiguration mit Callback
#$result = new SseResultWithCallback(Status::OK, 3000);
// Callback für dynamische Updates festlegen
$callback = function(SseStream $stream) {
// Simuliere neue Benachrichtigungen (mit 10% Wahrscheinlichkeit)
if (rand(1, 10) === 1) {
$notificationTypes = ['info', 'warning', 'update', 'message'];
$type = $notificationTypes[array_rand($notificationTypes)];
$notification = [
'id' => 'live-notif-' . uniqid(),
'type' => $type,
'title' => 'Neue ' . ucfirst($type) . '-Benachrichtigung',
'message' => 'Dies ist eine dynamisch generierte Benachrichtigung vom Typ ' . $type,
'timestamp' => time()
];
$stream->sendJson($notification, 'notification', $notification['id']);
// Kleine Pause nach dem Senden, um das Testszenario zu simulieren
sleep(1);
}
};
$result = new SseResult(
retryInterval: 500,
callback: $callback,
maxDuration: 300,
heartbeatInterval: 15,
);
// Initiale Verbindungsbestätigung
$result->addJsonEvent(
['message' => 'Verbunden mit dem Live-Stream', 'timestamp' => time()],
'connected',
'live-' . uniqid()
);
return $result;
}
}

View File

@@ -0,0 +1,101 @@
<?php
declare(strict_types=1);
namespace App\Application\Http\Controllers;
use App\Application\Service\QrCodeService;
use App\Domain\QrCode\ValueObject\ErrorCorrectionLevel;
use App\Framework\Attributes\Route;
use App\Framework\Auth\Auth;
use App\Framework\Http\Method;
use App\Framework\Http\Status;
use App\Framework\Http\Response;
final class QrCodeController
{
public function __construct(
private readonly QrCodeService $qrCodeService
) {
}
/**
* Generiert einen QR-Code als SVG
*/
#[Auth]
#[Route(path: '/api/qrcode/svg', method: Method::GET)]
public function generateSvg(): Response
{
$data = $_GET['data'] ?? 'https://example.com';
$errorLevel = $this->getErrorLevel($_GET['error_level'] ?? 'M');
$moduleSize = (int) ($_GET['module_size'] ?? 4);
$margin = (int) ($_GET['margin'] ?? 4);
$foreground = $_GET['foreground'] ?? '#000000';
$background = $_GET['background'] ?? '#FFFFFF';
$config = new QrCodeConfig($moduleSize, $margin, $foreground, $background);
$svg = $this->qrCodeService->generateSvg($data, $errorLevel, $config);
return new Response(
body: $svg,
status: Status::OK,
headers: ['Content-Type' => 'image/svg+xml']
);
}
/**
* Generiert einen QR-Code als PNG
*/
#[Auth]
#[Route(path: '/api/qrcode/png', method: Method::GET)]
public function generatePng(): Response
{
$data = $_GET['data'] ?? 'https://example.com';
$errorLevel = $this->getErrorLevel($_GET['error_level'] ?? 'M');
$moduleSize = (int) ($_GET['module_size'] ?? 4);
$margin = (int) ($_GET['margin'] ?? 4);
$config = new QrCodeConfig($moduleSize, $margin);
$png = $this->qrCodeService->generatePng($data, $errorLevel, $config);
return new Response(
body: $png,
status: Status::OK,
headers: ['Content-Type' => 'image/png']
);
}
/**
* Generiert einen QR-Code als ASCII-Art
*/
#[Auth]
#[Route(path: '/api/qrcode/ascii', method: Method::GET)]
public function generateAscii(): Response
{
$data = $_GET['data'] ?? 'https://example.com';
$errorLevel = $this->getErrorLevel($_GET['error_level'] ?? 'M');
$ascii = $this->qrCodeService->generateAscii($data, $errorLevel);
return new Response(
body: "<pre>$ascii</pre>",
status: Status::OK,
headers: ['Content-Type' => 'text/html; charset=utf-8']
);
}
/**
* Konvertiert einen String in ein ErrorCorrectionLevel-Enum
*/
private function getErrorLevel(string $level): ErrorCorrectionLevel
{
return match (strtoupper($level)) {
'L' => ErrorCorrectionLevel::L,
'Q' => ErrorCorrectionLevel::Q,
'H' => ErrorCorrectionLevel::H,
default => ErrorCorrectionLevel::M,
};
}
}

View File

@@ -0,0 +1,146 @@
<?php
namespace App\Application\Http;
use App\Framework\Attributes\Route;
use App\Framework\CommandBus\CommandBus;
use App\Framework\Http\HeaderKey;
use App\Framework\Http\Request;
use App\Framework\Http\Method;
use App\Framework\Http\Status;
use App\Framework\Router\ActionResult;
use App\Framework\Router\Result\ViewResult;
use App\Framework\Router\Result\Redirect;
use App\Framework\Meta\MetaData;
use App\Framework\Smartlinks\Actions\ActionRegistry;
use App\Framework\Smartlinks\Commands\ExecuteSmartlinkCommand;
use App\Framework\Smartlinks\Commands\GenerateSmartlinkCommand;
use App\Framework\Smartlinks\Commands\GenerateSmartlinkHandler;
use App\Framework\Smartlinks\Services\SmartlinkService;
use App\Framework\Smartlinks\SmartLinkToken;
use App\Framework\Smartlinks\TokenAction;
final readonly class Smartlink
{
public function __construct(
private CommandBus $commandBus,
private ActionRegistry $actionRegistry,
private SmartlinkService $smartlinkService,
private GenerateSmartlinkHandler $handler,
) {}
#[Route('/smartlink/{token}', method: Method::GET)]
#[Route('/smartlink/{token}', method: Method::POST)]
public function execute(string $token, Request $request): ActionResult
{
$command = new GenerateSmartlinkCommand(
action: new TokenAction('email_verification'),
payload: ['user_id' => 123, 'email' => 'user@example.com'],
baseUrl: 'https://localhost'
);
#debug($this->handler->handle($command));
try {
$smartlinkToken = new SmartlinkToken($token);
// Token validieren
$smartlinkData = $this->smartlinkService->validate($smartlinkToken);
if (!$smartlinkData) {
return new ViewResult(
template: 'smartlinks-error',
metaData: new MetaData(
title: 'Ungültiger Link',
description: 'Der Link ist ungültig oder abgelaufen'
),
data: [
'error' => 'Ungültiger oder abgelaufener Link',
'error_code' => 'INVALID_TOKEN'
],
status: Status::NOT_FOUND
);
}
// Action holen
$action = $this->actionRegistry->get($smartlinkData->action);
if (!$action) {
return new ViewResult(
template: 'smartlinks-error',
metaData: new MetaData(
title: 'Unbekannte Aktion',
description: 'Die angeforderte Aktion ist nicht verfügbar'
),
data: [
'error' => 'Unbekannte Aktion',
'error_code' => 'UNKNOWN_ACTION'
],
status: Status::BAD_REQUEST
);
}
// Context für Action vorbereiten
$context = [
'request_method' => $request->method->value,
'request_data' => $request->parsedBody ?? [],
'query_params' => $request->queryParameters ?? [],
#'ip_address' => $request->serverEnvironment->ipAddress?->value,
'user_agent' => $request->headers->getFirst(HeaderKey::USER_AGENT),
'headers' => $request->headers
];
// Command ausführen
$command = new ExecuteSmartlinkCommand($smartlinkToken, $context);
$result = $this->commandBus->dispatch($command);
// Ergebnis verarbeiten
if (!$result->isSuccess()) {
return new ViewResult(
template: $action->getErrorTemplate(),
metaData: new MetaData(
title: 'Fehler bei Ausführung',
description: $result->message
),
data: [
'error' => $result->message,
'errors' => $result->errors,
'action' => $action->getName()
],
status: Status::BAD_REQUEST
);
}
// Redirect oder View
if ($result->hasRedirect()) {
return new Redirect($result->redirectUrl);
}
return new ViewResult(
template: $action->getViewTemplate(),
metaData: new MetaData(
title: $action->getName(),
description: $result->message
),
data: [
'result' => $result,
'action' => $action->getName(),
'token' => $token
]
);
} catch (\Exception $e) {
return new ViewResult(
template: 'smartlinks-error',
metaData: new MetaData(
title: 'Systemfehler',
description: 'Ein unerwarteter Fehler ist aufgetreten'
),
data: [
'error' => 'Ein Fehler ist aufgetreten',
'error_code' => 'SYSTEM_ERROR',
'debug_message' => $e->getMessage() // Nur für Development
],
status: Status::INTERNAL_SERVER_ERROR
);
}
}
}

View File

@@ -0,0 +1,9 @@
<layout src="main"/>
<div class="error-container">
<h1>Fehler</h1>
<p>{{ error }}</p>
<?php if (isset($error_code)): ?>
<p><small>Fehlercode: <?= htmlspecialchars($error_code) ?></small></p>
<?php endif; ?>
</div>