weitere Bearibeitung
This commit is contained in:
+2
-2
@@ -68,11 +68,11 @@ final class Auth
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->rehashPasswordIfNeeded((int) $membership['id'], (string) $membership['password_hash'], $password);
|
||||
$this->rehashPasswordIfNeeded((int) $membership['user_id'], (string) $membership['password_hash'], $password);
|
||||
|
||||
$this->session->regenerate();
|
||||
$this->session->put('auth', [
|
||||
'user_id' => (int) $membership['id'],
|
||||
'user_id' => (int) $membership['user_id'],
|
||||
'tenant_id' => (int) $membership['tenant_id'],
|
||||
'tenant_slug' => $membership['tenant_slug'],
|
||||
'tenant_role' => $membership['role'],
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace App\Core;
|
||||
use PDO;
|
||||
use PDOException;
|
||||
|
||||
final class Database
|
||||
final class Database implements DatabaseInterface
|
||||
{
|
||||
private ?PDO $pdo = null;
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
use PDO;
|
||||
|
||||
/**
|
||||
* Gemeinsames Interface für Database (MySQL) und DatabaseSqlite.
|
||||
* Ermöglicht den Betrieb beider Backends ohne Type-Errors.
|
||||
*/
|
||||
interface DatabaseInterface
|
||||
{
|
||||
public function pdo(): PDO;
|
||||
public function isConfigured(): bool;
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
use PDO;
|
||||
use PDOException;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* SQLite-basierte Datenbank für Test-/Entwicklungsumgebungen.
|
||||
* Ersetzt die MySQL-Database-Klasse und emuliert MySQL-spezifische Features.
|
||||
*/
|
||||
final class DatabaseSqlite implements DatabaseInterface
|
||||
{
|
||||
private ?PDO $pdo = null;
|
||||
private readonly string $dbPath;
|
||||
|
||||
public function __construct(private readonly array $config = [])
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,15 @@ final class Response
|
||||
{
|
||||
public static function redirect(string $url): never
|
||||
{
|
||||
if (str_starts_with($url, '/') && function_exists('url')) {
|
||||
$url = \url($url);
|
||||
} else {
|
||||
$prefix = $_SERVER['KAFFEEKASSE_PROXY_PREFIX'] ?? '';
|
||||
if ($prefix !== '' && str_starts_with($url, '/') && !str_starts_with($url, $prefix)) {
|
||||
$url = $prefix . $url;
|
||||
}
|
||||
}
|
||||
|
||||
header('Location: ' . $url);
|
||||
exit;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user