dbPath = $this->config['path'] ?? dirname(__DIR__, 2) . '/storage/database.sqlite'; } public function pdo(): PDO { if ($this->pdo instanceof PDO) { return $this->pdo; } $dbDir = dirname($this->dbPath); if (!is_dir($dbDir) && !mkdir($dbDir, 0775, true) && !is_dir($dbDir)) { throw new RuntimeException('Das Datenbank-Verzeichnis konnte nicht erstellt werden: ' . $dbDir); } try { $this->pdo = new PDO( 'sqlite:' . $this->dbPath, null, null, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ] ); $this->pdo->exec('PRAGMA journal_mode=WAL'); $this->pdo->exec('PRAGMA foreign_keys=ON'); $this->pdo->exec('PRAGMA busy_timeout=5000'); } catch (PDOException $exception) { throw new PDOException( 'SQLite connection failed: ' . $exception->getMessage(), (int) $exception->getCode(), $exception ); } return $this->pdo; } public function isConfigured(): bool { return $this->dbPath !== ''; } /** * Führt schema.sqlite.sql aus, um die Datenbank zu initialisieren. */ public function initializeSchema(): void { $schemaFile = dirname(__DIR__, 2) . '/database/schema.sqlite.sql'; if (!is_file($schemaFile)) { throw new RuntimeException('SQLite-Schema nicht gefunden: ' . $schemaFile); } $schema = file_get_contents($schemaFile); if (!is_string($schema) || $schema === '') { throw new RuntimeException('SQLite-Schema konnte nicht geladen werden.'); } $this->pdo()->exec($schema); } /** * SQLite-kompatibler UPSERT: INSERT OR REPLACE + SELECT als Alternative zu ON DUPLICATE KEY. */ public function upsertRateLimit(string $actionKey, string $bucketKey, string $now): void { $pdo = $this->pdo(); $existing = $pdo->prepare( 'SELECT id FROM rate_limits WHERE action_key = :action_key AND bucket_key = :bucket_key LIMIT 1' ); $existing->execute([ 'action_key' => $actionKey, 'bucket_key' => $bucketKey, ]); if (!$existing->fetchColumn()) { $insert = $pdo->prepare( 'INSERT INTO rate_limits (action_key, bucket_key, attempts, window_start, last_attempt_at, created_at) VALUES (:action_key, :bucket_key, 0, :window_start, :last_attempt_at, :created_at)' ); $insert->execute([ 'action_key' => $actionKey, 'bucket_key' => $bucketKey, 'window_start' => $now, 'last_attempt_at' => $now, 'created_at' => $now, ]); } } public function getDbPath(): string { return $this->dbPath; } }