chore: complete update
This commit is contained in:
108
src/Application/Http/Controllers/ChatController.php
Normal file
108
src/Application/Http/Controllers/ChatController.php
Normal 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
160
src/Application/Http/Controllers/NotificationController.php
Normal file
160
src/Application/Http/Controllers/NotificationController.php
Normal 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;
|
||||
}
|
||||
}
|
||||
101
src/Application/Http/Controllers/QrCodeController.php
Normal file
101
src/Application/Http/Controllers/QrCodeController.php
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
146
src/Application/Http/Smartlink.php
Normal file
146
src/Application/Http/Smartlink.php
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
9
src/Application/Http/templates/smartlinks-error.view.php
Normal file
9
src/Application/Http/templates/smartlinks-error.view.php
Normal 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>
|
||||
Reference in New Issue
Block a user