weitere Bearibeitung
This commit is contained in:
@@ -6,6 +6,7 @@ use DateTimeImmutable;
|
||||
use DateTimeZone;
|
||||
use PDO;
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
|
||||
final class RateLimiterService
|
||||
{
|
||||
@@ -43,45 +44,69 @@ final class RateLimiterService
|
||||
$nowTs = time();
|
||||
$now = $this->formatTimestamp($nowTs);
|
||||
|
||||
$isSqlite = $this->isSqlite();
|
||||
$this->pdo->beginTransaction();
|
||||
|
||||
try {
|
||||
$bootstrapStatement = $this->pdo->prepare(
|
||||
'INSERT INTO rate_limits (
|
||||
action_key,
|
||||
bucket_key,
|
||||
attempts,
|
||||
window_start,
|
||||
last_attempt_at,
|
||||
blocked_until,
|
||||
created_at
|
||||
) VALUES (
|
||||
:action_key,
|
||||
:bucket_key,
|
||||
0,
|
||||
:window_start,
|
||||
:last_attempt_at,
|
||||
NULL,
|
||||
:created_at
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
action_key = action_key'
|
||||
);
|
||||
$bootstrapStatement->execute([
|
||||
'action_key' => $policy['action'],
|
||||
'bucket_key' => $bucketKey,
|
||||
'window_start' => $now,
|
||||
'last_attempt_at' => $now,
|
||||
'created_at' => $now,
|
||||
]);
|
||||
if ($isSqlite) {
|
||||
$existingStmt = $this->pdo->prepare(
|
||||
'SELECT id FROM rate_limits WHERE action_key = :action_key AND bucket_key = :bucket_key LIMIT 1'
|
||||
);
|
||||
$existingStmt->execute([
|
||||
'action_key' => $policy['action'],
|
||||
'bucket_key' => $bucketKey,
|
||||
]);
|
||||
|
||||
if (!$existingStmt->fetchColumn()) {
|
||||
$this->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)'
|
||||
)->execute([
|
||||
'action_key' => $policy['action'],
|
||||
'bucket_key' => $bucketKey,
|
||||
'window_start' => $now,
|
||||
'last_attempt_at' => $now,
|
||||
'created_at' => $now,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$bootstrapStatement = $this->pdo->prepare(
|
||||
'INSERT INTO rate_limits (
|
||||
action_key,
|
||||
bucket_key,
|
||||
attempts,
|
||||
window_start,
|
||||
last_attempt_at,
|
||||
blocked_until,
|
||||
created_at
|
||||
) VALUES (
|
||||
:action_key,
|
||||
:bucket_key,
|
||||
0,
|
||||
:window_start,
|
||||
:last_attempt_at,
|
||||
NULL,
|
||||
:created_at
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
action_key = action_key'
|
||||
);
|
||||
$bootstrapStatement->execute([
|
||||
'action_key' => $policy['action'],
|
||||
'bucket_key' => $bucketKey,
|
||||
'window_start' => $now,
|
||||
'last_attempt_at' => $now,
|
||||
'created_at' => $now,
|
||||
]);
|
||||
}
|
||||
|
||||
$forUpdate = $isSqlite ? '' : ' FOR UPDATE';
|
||||
$selectStatement = $this->pdo->prepare(
|
||||
'SELECT attempts, window_start, last_attempt_at, blocked_until
|
||||
FROM rate_limits
|
||||
WHERE action_key = :action_key
|
||||
AND bucket_key = :bucket_key
|
||||
LIMIT 1
|
||||
FOR UPDATE'
|
||||
LIMIT 1' . $forUpdate
|
||||
);
|
||||
$selectStatement->execute([
|
||||
'action_key' => $policy['action'],
|
||||
@@ -136,7 +161,7 @@ final class RateLimiterService
|
||||
}
|
||||
|
||||
$this->pdo->commit();
|
||||
} catch (\Throwable $throwable) {
|
||||
} catch (Throwable $throwable) {
|
||||
if ($this->pdo->inTransaction()) {
|
||||
$this->pdo->rollBack();
|
||||
}
|
||||
@@ -239,6 +264,15 @@ final class RateLimiterService
|
||||
];
|
||||
}
|
||||
|
||||
private function isSqlite(): bool
|
||||
{
|
||||
try {
|
||||
return $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME) === 'sqlite';
|
||||
} catch (\Throwable) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function bucketKey(string $scope): string
|
||||
{
|
||||
$scope = trim($scope);
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use DateTimeZone;
|
||||
use PDO;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* SQLite-kompatible Version des RateLimiterService.
|
||||
* Ersetzt MySQL-spezifisches ON DUPLICATE KEY UPDATE durch SELECT+INSERT.
|
||||
*/
|
||||
final class RateLimiterSqliteService
|
||||
{
|
||||
private readonly DateTimeZone $utc;
|
||||
|
||||
public function __construct(
|
||||
private readonly PDO $pdo,
|
||||
private readonly array $config = []
|
||||
) {
|
||||
$this->utc = new DateTimeZone('UTC');
|
||||
}
|
||||
|
||||
public function inspect(string $action, string $scope): array
|
||||
{
|
||||
$policy = $this->resolvePolicy($action);
|
||||
$statement = $this->pdo->prepare(
|
||||
'SELECT attempts, window_start, last_attempt_at, blocked_until
|
||||
FROM rate_limits
|
||||
WHERE action_key = :action_key
|
||||
AND bucket_key = :bucket_key
|
||||
LIMIT 1'
|
||||
);
|
||||
$statement->execute([
|
||||
'action_key' => $policy['action'],
|
||||
'bucket_key' => $this->bucketKey($scope),
|
||||
]);
|
||||
|
||||
return $this->buildState($policy, $statement->fetch() ?: null, time());
|
||||
}
|
||||
|
||||
public function hit(string $action, string $scope): array
|
||||
{
|
||||
$policy = $this->resolvePolicy($action);
|
||||
$bucketKey = $this->bucketKey($scope);
|
||||
$nowTs = time();
|
||||
$now = $this->formatTimestamp($nowTs);
|
||||
|
||||
$this->pdo->beginTransaction();
|
||||
|
||||
try {
|
||||
// SQLite-kompatibler Upsert: SELECT + INSERT falls nicht vorhanden
|
||||
$existingStmt = $this->pdo->prepare(
|
||||
'SELECT id FROM rate_limits WHERE action_key = :action_key AND bucket_key = :bucket_key LIMIT 1'
|
||||
);
|
||||
$existingStmt->execute([
|
||||
'action_key' => $policy['action'],
|
||||
'bucket_key' => $bucketKey,
|
||||
]);
|
||||
|
||||
if (!$existingStmt->fetchColumn()) {
|
||||
$insertStmt = $this->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)'
|
||||
);
|
||||
$insertStmt->execute([
|
||||
'action_key' => $policy['action'],
|
||||
'bucket_key' => $bucketKey,
|
||||
'window_start' => $now,
|
||||
'last_attempt_at' => $now,
|
||||
'created_at' => $now,
|
||||
]);
|
||||
}
|
||||
|
||||
$selectStatement = $this->pdo->prepare(
|
||||
'SELECT attempts, window_start, last_attempt_at, blocked_until
|
||||
FROM rate_limits
|
||||
WHERE action_key = :action_key
|
||||
AND bucket_key = :bucket_key
|
||||
LIMIT 1'
|
||||
);
|
||||
$selectStatement->execute([
|
||||
'action_key' => $policy['action'],
|
||||
'bucket_key' => $bucketKey,
|
||||
]);
|
||||
$row = $selectStatement->fetch();
|
||||
|
||||
if (!$row) {
|
||||
throw new RuntimeException('Der Rate-Limit-Zustand konnte nicht geladen werden.');
|
||||
}
|
||||
|
||||
$windowStartTs = $this->timestampFromDatabase($row['window_start']);
|
||||
$blockedUntilTs = $this->timestampFromDatabase($row['blocked_until']);
|
||||
$isBlocked = $blockedUntilTs !== null && $blockedUntilTs > $nowTs;
|
||||
$resetWindow = $windowStartTs === null
|
||||
|| ($windowStartTs + $policy['window_seconds']) <= $nowTs
|
||||
|| ($blockedUntilTs !== null && $blockedUntilTs <= $nowTs);
|
||||
|
||||
if ($isBlocked) {
|
||||
$updatedRow = $row;
|
||||
} else {
|
||||
$attempts = $resetWindow ? 1 : ((int) $row['attempts'] + 1);
|
||||
$windowStart = $resetWindow ? $now : (string) $row['window_start'];
|
||||
$blockedUntil = $attempts >= $policy['max_attempts']
|
||||
? $this->formatTimestamp($nowTs + $policy['block_seconds'])
|
||||
: null;
|
||||
|
||||
$updateStatement = $this->pdo->prepare(
|
||||
'UPDATE rate_limits
|
||||
SET attempts = :attempts,
|
||||
window_start = :window_start,
|
||||
last_attempt_at = :last_attempt_at,
|
||||
blocked_until = :blocked_until
|
||||
WHERE action_key = :action_key
|
||||
AND bucket_key = :bucket_key'
|
||||
);
|
||||
$updateStatement->execute([
|
||||
'attempts' => $attempts,
|
||||
'window_start' => $windowStart,
|
||||
'last_attempt_at' => $now,
|
||||
'blocked_until' => $blockedUntil,
|
||||
'action_key' => $policy['action'],
|
||||
'bucket_key' => $bucketKey,
|
||||
]);
|
||||
|
||||
$updatedRow = [
|
||||
'attempts' => $attempts,
|
||||
'window_start' => $windowStart,
|
||||
'last_attempt_at' => $now,
|
||||
'blocked_until' => $blockedUntil,
|
||||
];
|
||||
}
|
||||
|
||||
$this->pdo->commit();
|
||||
} catch (\Throwable $throwable) {
|
||||
if ($this->pdo->inTransaction()) {
|
||||
$this->pdo->rollBack();
|
||||
}
|
||||
|
||||
throw $throwable;
|
||||
}
|
||||
|
||||
return $this->buildState($policy, $updatedRow, $nowTs);
|
||||
}
|
||||
|
||||
public function clear(string $action, string $scope): void
|
||||
{
|
||||
$policy = $this->resolvePolicy($action);
|
||||
$statement = $this->pdo->prepare(
|
||||
'DELETE FROM rate_limits
|
||||
WHERE action_key = :action_key
|
||||
AND bucket_key = :bucket_key'
|
||||
);
|
||||
$statement->execute([
|
||||
'action_key' => $policy['action'],
|
||||
'bucket_key' => $this->bucketKey($scope),
|
||||
]);
|
||||
}
|
||||
|
||||
public function tooManyAttempts(string $action, string $scope): bool
|
||||
{
|
||||
return !$this->inspect($action, $scope)['allowed'];
|
||||
}
|
||||
|
||||
public function prune(int $staleAfterSeconds = 604800): int
|
||||
{
|
||||
$cutoff = $this->formatTimestamp(time() - max(3600, $staleAfterSeconds));
|
||||
$statement = $this->pdo->prepare(
|
||||
'DELETE FROM rate_limits
|
||||
WHERE (blocked_until IS NULL AND last_attempt_at < :cutoff)
|
||||
OR (blocked_until IS NOT NULL AND blocked_until < :cutoff)'
|
||||
);
|
||||
$statement->execute(['cutoff' => $cutoff]);
|
||||
|
||||
return $statement->rowCount();
|
||||
}
|
||||
|
||||
private function buildState(array $policy, ?array $row, int $nowTs): array
|
||||
{
|
||||
$attempts = 0;
|
||||
$blockedUntil = null;
|
||||
$retryAfter = 0;
|
||||
$allowed = true;
|
||||
$resetAtTs = $nowTs + $policy['window_seconds'];
|
||||
|
||||
if ($row) {
|
||||
$windowStartTs = $this->timestampFromDatabase($row['window_start']);
|
||||
$blockedUntilTs = $this->timestampFromDatabase($row['blocked_until']);
|
||||
|
||||
if ($blockedUntilTs !== null && $blockedUntilTs > $nowTs) {
|
||||
$attempts = (int) $row['attempts'];
|
||||
$blockedUntil = (string) $row['blocked_until'];
|
||||
$retryAfter = max(1, $blockedUntilTs - $nowTs);
|
||||
$allowed = false;
|
||||
$resetAtTs = $blockedUntilTs;
|
||||
} elseif ($windowStartTs !== null && ($windowStartTs + $policy['window_seconds']) > $nowTs) {
|
||||
$attempts = (int) $row['attempts'];
|
||||
$resetAtTs = $windowStartTs + $policy['window_seconds'];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'action' => $policy['action'],
|
||||
'allowed' => $allowed,
|
||||
'attempts' => $attempts,
|
||||
'max_attempts' => $policy['max_attempts'],
|
||||
'remaining_attempts' => max(0, $policy['max_attempts'] - $attempts),
|
||||
'window_seconds' => $policy['window_seconds'],
|
||||
'block_seconds' => $policy['block_seconds'],
|
||||
'retry_after_seconds' => $retryAfter,
|
||||
'reset_at' => $this->formatTimestamp($resetAtTs),
|
||||
'blocked_until' => $blockedUntil,
|
||||
];
|
||||
}
|
||||
|
||||
private function resolvePolicy(string $action): array
|
||||
{
|
||||
$normalizedAction = mb_strtolower(trim($action));
|
||||
|
||||
if ($normalizedAction === '') {
|
||||
throw new RuntimeException('Die Rate-Limit-Aktion ist erforderlich.');
|
||||
}
|
||||
|
||||
$policy = $this->config[$normalizedAction] ?? null;
|
||||
|
||||
if (!is_array($policy)) {
|
||||
throw new RuntimeException(sprintf('Fuer die Aktion "%s" ist kein Rate-Limit konfiguriert.', $normalizedAction));
|
||||
}
|
||||
|
||||
return [
|
||||
'action' => $normalizedAction,
|
||||
'max_attempts' => max(1, (int) ($policy['max_attempts'] ?? 5)),
|
||||
'window_seconds' => max(60, (int) ($policy['window_seconds'] ?? 900)),
|
||||
'block_seconds' => max(60, (int) ($policy['block_seconds'] ?? 900)),
|
||||
];
|
||||
}
|
||||
|
||||
private function bucketKey(string $scope): string
|
||||
{
|
||||
$scope = trim($scope);
|
||||
|
||||
if ($scope === '') {
|
||||
throw new RuntimeException('Der Rate-Limit-Scope ist erforderlich.');
|
||||
}
|
||||
|
||||
return hash('sha256', $scope);
|
||||
}
|
||||
|
||||
private function formatTimestamp(int $timestamp): string
|
||||
{
|
||||
return gmdate('Y-m-d H:i:s', $timestamp);
|
||||
}
|
||||
|
||||
private function timestampFromDatabase(?string $value): ?int
|
||||
{
|
||||
if ($value === null || trim($value) === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$date = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $value, $this->utc);
|
||||
|
||||
if (!$date instanceof DateTimeImmutable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $date->getTimestamp();
|
||||
}
|
||||
}
|
||||
+112
-37
@@ -45,37 +45,16 @@ final class SetupService
|
||||
|
||||
$this->resolveMailFrom($input, $this->normalizeAppUrl((string) $input['app_url']));
|
||||
|
||||
$config = [
|
||||
'host' => trim((string) $input['db_host']),
|
||||
'port' => (int) $input['db_port'],
|
||||
'name' => trim((string) $input['db_name']),
|
||||
'user' => trim((string) $input['db_user']),
|
||||
'pass' => (string) ($input['db_pass'] ?? ''),
|
||||
'charset' => 'utf8mb4',
|
||||
];
|
||||
|
||||
$dsn = sprintf(
|
||||
'mysql:host=%s;port=%d;dbname=%s;charset=%s',
|
||||
$config['host'],
|
||||
$config['port'],
|
||||
$config['name'],
|
||||
$config['charset']
|
||||
);
|
||||
|
||||
$pdo = new PDO($dsn, $config['user'], $config['pass'], [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
]);
|
||||
|
||||
$schema = file_get_contents($this->rootPath . '/database/schema.sql');
|
||||
|
||||
if (!is_string($schema) || $schema === '') {
|
||||
throw new RuntimeException('Das Datenbankschema konnte nicht geladen werden.');
|
||||
// Prüfe verfügbare PDO-Treiber und verwende entsprechende Datenbank
|
||||
$availableDrivers = PDO::getAvailableDrivers();
|
||||
$useMySQL = in_array('mysql', $availableDrivers) && trim((string) $input['db_host']) !== 'sqlite';
|
||||
|
||||
if ($useMySQL) {
|
||||
$pdo = $this->setupMySQLDatabase($input);
|
||||
} else {
|
||||
$pdo = $this->setupSQLiteDatabase();
|
||||
}
|
||||
|
||||
$pdo->exec($schema);
|
||||
|
||||
$statement = $pdo->prepare('SELECT id FROM users WHERE email = :email LIMIT 1');
|
||||
$statement->execute(['email' => mb_strtolower((string) $input['admin_email'])]);
|
||||
$existing = $statement->fetchColumn();
|
||||
@@ -92,7 +71,7 @@ final class SetupService
|
||||
]);
|
||||
}
|
||||
|
||||
$envContent = $this->buildEnvFile($input);
|
||||
$envContent = $this->buildEnvFile($input, $useMySQL);
|
||||
|
||||
if (file_put_contents($this->rootPath . '/.env', $envContent) === false) {
|
||||
throw new RuntimeException('Die .env-Datei konnte nicht geschrieben werden.');
|
||||
@@ -101,12 +80,90 @@ final class SetupService
|
||||
$lockContent = json_encode([
|
||||
'installed_at' => gmdate(DATE_ATOM),
|
||||
'app_url' => trim((string) $input['app_url']),
|
||||
'database_type' => $useMySQL ? 'mysql' : 'sqlite',
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
file_put_contents($this->rootPath . '/storage/installed.lock', (string) $lockContent);
|
||||
}
|
||||
|
||||
private function buildEnvFile(array $input): string
|
||||
private function setupMySQLDatabase(array $input): PDO
|
||||
{
|
||||
$config = [
|
||||
'host' => trim((string) $input['db_host']),
|
||||
'port' => (int) $input['db_port'],
|
||||
'name' => trim((string) $input['db_name']),
|
||||
'user' => trim((string) $input['db_user']),
|
||||
'pass' => (string) ($input['db_pass'] ?? ''),
|
||||
'charset' => 'utf8mb4',
|
||||
];
|
||||
|
||||
$dsn = sprintf(
|
||||
'mysql:host=%s;port=%d;dbname=%s;charset=%s',
|
||||
$config['host'],
|
||||
$config['port'],
|
||||
$config['name'],
|
||||
$config['charset']
|
||||
);
|
||||
|
||||
try {
|
||||
$pdo = new PDO($dsn, $config['user'], $config['pass'], [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
]);
|
||||
|
||||
$schema = file_get_contents($this->rootPath . '/database/schema.sql');
|
||||
|
||||
if (!is_string($schema) || $schema === '') {
|
||||
throw new RuntimeException('Das MySQL-Datenbankschema konnte nicht geladen werden.');
|
||||
}
|
||||
|
||||
$pdo->exec($schema);
|
||||
return $pdo;
|
||||
} catch (\PDOException $e) {
|
||||
throw new RuntimeException('MySQL-Datenbankverbindung fehlgeschlagen: ' . $e->getMessage() . '. Bitte prüfen Sie Ihre Datenbankeinstellungen oder verwenden Sie SQLite als Alternative.');
|
||||
}
|
||||
}
|
||||
|
||||
private function setupSQLiteDatabase(): PDO
|
||||
{
|
||||
$dbPath = $this->rootPath . '/storage/database.sqlite';
|
||||
$dbDir = dirname($dbPath);
|
||||
|
||||
if (!is_dir($dbDir) && !mkdir($dbDir, 0775, true) && !is_dir($dbDir)) {
|
||||
throw new RuntimeException('Das Datenbank-Verzeichnis konnte nicht erstellt werden: ' . $dbDir);
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = new PDO(
|
||||
'sqlite:' . $dbPath,
|
||||
null,
|
||||
null,
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
]
|
||||
);
|
||||
|
||||
$pdo->exec('PRAGMA journal_mode=WAL');
|
||||
$pdo->exec('PRAGMA foreign_keys=ON');
|
||||
$pdo->exec('PRAGMA busy_timeout=5000');
|
||||
|
||||
$schema = file_get_contents($this->rootPath . '/database/schema.sqlite.sql');
|
||||
|
||||
if (!is_string($schema) || $schema === '') {
|
||||
throw new RuntimeException('Das SQLite-Datenbankschema konnte nicht geladen werden.');
|
||||
}
|
||||
|
||||
$pdo->exec($schema);
|
||||
return $pdo;
|
||||
} catch (\PDOException $e) {
|
||||
throw new RuntimeException('SQLite-Datenbankverbindung fehlgeschlagen: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function buildEnvFile(array $input, bool $useMySQL = true): string
|
||||
{
|
||||
$appUrl = $this->normalizeAppUrl((string) $input['app_url']);
|
||||
$mailFrom = $this->resolveMailFrom($input, $appUrl);
|
||||
@@ -123,11 +180,29 @@ final class SetupService
|
||||
'ALLOW_PUBLIC_REGISTRATION=1',
|
||||
'RFID_INGEST_ENABLED=0',
|
||||
'',
|
||||
'DB_HOST=' . trim((string) $input['db_host']),
|
||||
'DB_PORT=' . (int) $input['db_port'],
|
||||
'DB_NAME=' . trim((string) $input['db_name']),
|
||||
'DB_USER=' . trim((string) $input['db_user']),
|
||||
'DB_PASS=' . (string) ($input['db_pass'] ?? ''),
|
||||
];
|
||||
|
||||
// Datenbankeinstellungen je nach verwendetem System
|
||||
if ($useMySQL) {
|
||||
$lines = array_merge($lines, [
|
||||
'DB_HOST=' . trim((string) $input['db_host']),
|
||||
'DB_PORT=' . (int) $input['db_port'],
|
||||
'DB_NAME=' . trim((string) $input['db_name']),
|
||||
'DB_USER=' . trim((string) $input['db_user']),
|
||||
'DB_PASS=' . (string) ($input['db_pass'] ?? ''),
|
||||
]);
|
||||
} else {
|
||||
// SQLite-Konfiguration
|
||||
$lines = array_merge($lines, [
|
||||
'DB_HOST=sqlite',
|
||||
'DB_PORT=0',
|
||||
'DB_NAME=' . $this->rootPath . '/storage/database.sqlite',
|
||||
'DB_USER=',
|
||||
'DB_PASS=',
|
||||
]);
|
||||
}
|
||||
|
||||
$lines = array_merge($lines, [
|
||||
'',
|
||||
'MAIL_FROM=' . $mailFrom,
|
||||
'MAIL_REPLY_TO=' . $mailFrom,
|
||||
@@ -148,7 +223,7 @@ final class SetupService
|
||||
'RATE_LIMIT_REGISTRATION_BLOCK_SECONDS=3600',
|
||||
'',
|
||||
'RFID_SHARED_SECRET=' . $rfidSecret,
|
||||
];
|
||||
]);
|
||||
|
||||
return implode(PHP_EOL, $lines) . PHP_EOL;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user