fix: Gitea Traefik routing and connection pool optimization
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled

- Remove middleware reference from Gitea Traefik labels (caused routing issues)
- Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s)
- Add explicit service reference in Traefik labels
- Fix intermittent 504 timeouts by improving PostgreSQL connection handling

Fixes Gitea unreachability via git.michaelschiemer.de
This commit is contained in:
2025-11-09 14:46:15 +01:00
parent 85c369e846
commit 36ef2a1e2c
1366 changed files with 104925 additions and 28719 deletions

View File

@@ -0,0 +1,170 @@
<?php
declare(strict_types=1);
namespace App\Framework\Console\Commands;
use App\Framework\Console\CommandGroup;
use App\Framework\Console\ConsoleColor;
use App\Framework\Console\ConsoleCommand;
use App\Framework\Console\ConsoleInput;
use App\Framework\Console\ConsoleOutputInterface;
use App\Framework\Console\ExitCode;
use App\Framework\Filesystem\FileScanner;
use App\Framework\Filesystem\ValueObjects\FilePath;
use App\Framework\Logging\Logger;
use App\Framework\Quality\CodeQuality\CodeQualityScanner;
use App\Framework\Quality\CodeQuality\Results\CodeQualityReport;
use App\Framework\Quality\CodeQuality\Rules\DisallowInheritanceRule;
use App\Framework\Quality\CodeQuality\Rules\FinalClassRule;
use App\Framework\Quality\CodeQuality\Rules\ReadonlyGetterRule;
use App\Framework\Quality\CodeQuality\Rules\ReadonlyPropertyRule;
use App\Framework\Quality\CodeQuality\Rules\RequireStringableImplementationRule;
use App\Framework\Reflection\ReflectionService;
#[CommandGroup(
name: 'Quality',
description: 'Code quality inspections and automated rule checks',
icon: '🧹',
priority: 80
)]
final readonly class CodeQualityScanCommand
{
private CodeQualityScanner $scanner;
public function __construct(
FileScanner $fileScanner,
ReflectionService $reflectionService,
?Logger $logger = null
) {
$this->scanner = new CodeQualityScanner(
$fileScanner,
$reflectionService,
[
new RequireStringableImplementationRule(),
new FinalClassRule(),
new ReadonlyPropertyRule(),
new ReadonlyGetterRule(),
new DisallowInheritanceRule(),
],
$logger
);
}
#[ConsoleCommand('quality:scan', 'Check framework classes against predefined rules')]
public function scan(ConsoleInput $input, ConsoleOutputInterface $output): ExitCode
{
$pathArgument = $input->getArgument(0);
$directory = $pathArgument !== null
? FilePath::create($pathArgument)
: FilePath::create(getcwd() . '/src');
$skipErrors = $input->hasOption('skip-errors');
if (! $directory->exists() || ! $directory->isDirectory()) {
$output->writeLine(
sprintf('❌ Directory "%s" does not exist or is not a directory.', $directory->toString()),
ConsoleColor::RED
);
return ExitCode::INVALID_INPUT;
}
$output->writeLine(
sprintf('🔍 Scanning %s for code quality violations...', $directory->toString()),
ConsoleColor::BRIGHT_CYAN
);
$report = $this->scanner->scan($directory, $skipErrors);
if ($report->hasErrors()) {
$output->newLine();
$headerColor = $skipErrors ? ConsoleColor::YELLOW : ConsoleColor::BRIGHT_RED;
$headerIcon = $skipErrors ? '⚠️' : '❌';
$output->writeLine(
sprintf(
'%s Encountered %d file/class error(s) while scanning.',
$headerIcon,
count($report->errors())
),
$headerColor
);
foreach ($report->errors() as $error) {
$output->writeLine(' • ' . $error, $headerColor);
}
if (! $skipErrors) {
return ExitCode::FAILURE;
}
}
$this->renderReport($report, $output);
return $report->hasViolations() ? ExitCode::FAILURE : ExitCode::SUCCESS;
}
private function renderReport(CodeQualityReport $report, ConsoleOutputInterface $output): void
{
$output->newLine();
$summaryColor = $report->hasViolations() ? ConsoleColor::BRIGHT_RED : ConsoleColor::GREEN;
$summaryIcon = $report->hasViolations() ? '❌' : '✅';
$summaryMessage = $report->hasViolations()
? sprintf('Found %d violation(s).', $report->countViolations())
: 'No violations found.';
$output->writeLine(
sprintf(
'%s %s Scanned %d files, %d classes in %.2fs',
$summaryIcon,
$summaryMessage,
$report->inspectedFiles(),
$report->inspectedClasses(),
$report->durationSeconds()
),
$summaryColor
);
if (! $report->hasViolations()) {
return;
}
$output->newLine();
foreach ($report->groupedByRule() as $ruleName => $violations) {
$output->writeLine(
sprintf('%s (%d)', $ruleName, count($violations)),
ConsoleColor::BRIGHT_YELLOW
);
foreach ($violations as $violation) {
$lineInfo = $violation->line() !== null ? ':' . $violation->line() : '';
$output->writeLine(
sprintf(
' • %s',
$violation->className()->getFullyQualified()
),
ConsoleColor::WHITE
);
$output->writeLine(
sprintf(
' ↳ %s%s',
$violation->filePath()->toString(),
$lineInfo
),
ConsoleColor::GRAY
);
$output->writeLine(
sprintf(' %s', $violation->message()),
ConsoleColor::BRIGHT_RED
);
$output->newLine();
}
}
}
}

View File

@@ -0,0 +1,242 @@
<?php
declare(strict_types=1);
namespace App\Framework\Console\Commands;
use App\Framework\Console\ConsoleColor;
use App\Framework\Console\ConsoleCommand;
use App\Framework\Console\ConsoleInput;
use App\Framework\Console\ConsoleOutput;
use App\Framework\Console\ConsoleStyle;
use App\Framework\Database\Browser\Registry\DatabaseRegistry;
use App\Framework\Database\Browser\Registry\TableRegistry;
use App\Framework\Display\Components\Console\Table;
final readonly class DatabaseBrowserCommand
{
public function __construct(
private DatabaseRegistry $databaseRegistry,
private TableRegistry $tableRegistry,
) {
}
#[ConsoleCommand('db:browse', 'Browse database tables and schema')]
public function browse(
ConsoleInput $input,
ConsoleOutput $output,
?string $table = null,
?string $format = null,
bool $compact = false,
bool $verbose = false
): int {
$format = $format ?? 'table';
try {
if ($table !== null) {
return $this->showTableDetails($output, $table, $format, $compact, $verbose);
}
return $this->showTablesList($output, $format, $compact);
} catch (\Exception $e) {
$output->writeError("Error: {$e->getMessage()}");
return 1;
}
}
private function showTablesList(ConsoleOutput $output, string $format, bool $compact): int
{
$database = $this->databaseRegistry->getCurrentDatabase();
$tables = $this->tableRegistry->getAllTables();
if ($format === 'json') {
$output->writeLine(json_encode([
'database' => $database->toArray(),
'tables' => array_map(fn ($t) => $t->toArray(), $tables),
], JSON_PRETTY_PRINT));
return 0;
}
// Show database info
$output->writeLine("Database: {$database->name}", ConsoleStyle::create(
color: ConsoleColor::BRIGHT_CYAN,
format: \App\Framework\Console\ConsoleFormat::BOLD
));
$output->newLine();
// Create table
$table = new Table(
headerStyle: ConsoleStyle::create(
color: ConsoleColor::BRIGHT_WHITE,
format: \App\Framework\Console\ConsoleFormat::BOLD
),
borderStyle: ConsoleStyle::create(color: ConsoleColor::GRAY)
);
if ($compact) {
$table->setHeaders(['Name', 'Rows', 'Size (MB)']);
foreach ($tables as $t) {
$table->addRow([
$t->name,
$t->rowCount !== null ? number_format($t->rowCount) : 'N/A',
$t->sizeMb !== null ? number_format($t->sizeMb, 2) : 'N/A',
]);
}
} else {
$table->setHeaders(['Name', 'Rows', 'Size (MB)', 'Engine', 'Collation']);
foreach ($tables as $t) {
$table->addRow([
$t->name,
$t->rowCount !== null ? number_format($t->rowCount) : 'N/A',
$t->sizeMb !== null ? number_format($t->sizeMb, 2) : 'N/A',
$t->engine ?? 'N/A',
$t->collation ?? 'N/A',
]);
}
}
$output->write($table->render());
$output->newLine();
$output->writeLine("Total tables: " . count($tables), ConsoleStyle::create(color: ConsoleColor::GRAY));
return 0;
}
private function showTableDetails(
ConsoleOutput $output,
string $tableName,
string $format,
bool $compact,
bool $verbose
): int {
$tableSchema = $this->tableRegistry->getTableSchema($tableName);
if ($tableSchema === null) {
$output->writeError("Table '{$tableName}' not found");
return 1;
}
if ($format === 'json') {
$output->writeLine(json_encode($tableSchema->toArray(), JSON_PRETTY_PRINT));
return 0;
}
// Show table info
$output->writeLine("Table: {$tableSchema->name}", ConsoleStyle::create(
color: ConsoleColor::BRIGHT_CYAN,
format: \App\Framework\Console\ConsoleFormat::BOLD
));
$output->writeLine("Rows: " . ($tableSchema->rowCount !== null ? number_format($tableSchema->rowCount) : 'N/A'));
$output->writeLine("Size: " . ($tableSchema->sizeMb !== null ? number_format($tableSchema->sizeMb, 2) . ' MB' : 'N/A'));
$output->writeLine("Engine: " . ($tableSchema->engine ?? 'N/A'));
$output->newLine();
// Show columns
$output->writeLine("Columns:", ConsoleStyle::create(
color: ConsoleColor::BRIGHT_YELLOW,
format: \App\Framework\Console\ConsoleFormat::BOLD
));
$columnsTable = new Table(
headerStyle: ConsoleStyle::create(
color: ConsoleColor::BRIGHT_WHITE,
format: \App\Framework\Console\ConsoleFormat::BOLD
),
borderStyle: ConsoleStyle::create(color: ConsoleColor::GRAY)
);
if ($compact) {
$columnsTable->setHeaders(['Name', 'Type', 'Nullable', 'Key']);
foreach ($tableSchema->columns as $column) {
$key = $column->key ?? '';
$keyColor = match ($key) {
'PRI' => ConsoleColor::BRIGHT_GREEN,
'UNI' => ConsoleColor::BRIGHT_YELLOW,
'MUL' => ConsoleColor::YELLOW,
default => ConsoleColor::GRAY,
};
$columnsTable->addRow([
$column->name,
$column->type,
$column->nullable ? 'Yes' : 'No',
$key !== '' ? $key : '-',
]);
}
} else {
$columnsTable->setHeaders(['Name', 'Type', 'Nullable', 'Default', 'Key', 'Extra']);
foreach ($tableSchema->columns as $column) {
$columnsTable->addRow([
$column->name,
$column->type,
$column->nullable ? 'Yes' : 'No',
$column->default ?? '-',
$column->key ?? '-',
$column->extra ?? '-',
]);
}
}
$output->write($columnsTable->render());
$output->newLine();
// Show indexes if verbose
if ($verbose && !empty($tableSchema->indexes)) {
$output->writeLine("Indexes:", ConsoleStyle::create(
color: ConsoleColor::BRIGHT_YELLOW,
format: \App\Framework\Console\ConsoleFormat::BOLD
));
$indexesTable = new Table(
headerStyle: ConsoleStyle::create(
color: ConsoleColor::BRIGHT_WHITE,
format: \App\Framework\Console\ConsoleFormat::BOLD
),
borderStyle: ConsoleStyle::create(color: ConsoleColor::GRAY)
);
$indexesTable->setHeaders(['Name', 'Columns', 'Unique', 'Type']);
foreach ($tableSchema->indexes as $index) {
$indexesTable->addRow([
$index->name,
implode(', ', $index->columns),
$index->unique ? 'Yes' : 'No',
$index->type ?? '-',
]);
}
$output->write($indexesTable->render());
$output->newLine();
}
// Show foreign keys if verbose
if ($verbose && !empty($tableSchema->foreignKeys)) {
$output->writeLine("Foreign Keys:", ConsoleStyle::create(
color: ConsoleColor::BRIGHT_YELLOW,
format: \App\Framework\Console\ConsoleFormat::BOLD
));
$fkTable = new Table(
headerStyle: ConsoleStyle::create(
color: ConsoleColor::BRIGHT_WHITE,
format: \App\Framework\Console\ConsoleFormat::BOLD
),
borderStyle: ConsoleStyle::create(color: ConsoleColor::GRAY)
);
$fkTable->setHeaders(['Name', 'Column', 'Referenced Table', 'Referenced Column']);
foreach ($tableSchema->foreignKeys as $fk) {
$fkTable->addRow([
$fk->name,
$fk->column,
$fk->referencedTable,
$fk->referencedColumn,
]);
}
$output->write($fkTable->render());
}
return 0;
}
}

View File

@@ -68,20 +68,38 @@ final readonly class HotReloadCommand
});
// Handle signals for graceful shutdown
if (extension_loaded('pcntl')) {
pcntl_signal(SIGINT, function () use ($hotReloadServer, $output) {
try {
$pcntlService = $container->get(\App\Framework\Pcntl\PcntlService::class);
$pcntlService->registerSignal(\App\Framework\Pcntl\ValueObjects\Signal::SIGINT, function () use ($hotReloadServer, $output) {
$output->writeln('');
$output->info('Received interrupt signal, shutting down...');
$hotReloadServer->stop();
exit(0);
});
pcntl_signal(SIGTERM, function () use ($hotReloadServer, $output) {
$pcntlService->registerSignal(\App\Framework\Pcntl\ValueObjects\Signal::SIGTERM, function () use ($hotReloadServer, $output) {
$output->writeln('');
$output->info('Received termination signal, shutting down...');
$hotReloadServer->stop();
exit(0);
});
} catch (\Throwable $e) {
// Fallback to direct pcntl_signal if PCNTL service not available
if (extension_loaded('pcntl')) {
pcntl_signal(SIGINT, function () use ($hotReloadServer, $output) {
$output->writeln('');
$output->info('Received interrupt signal, shutting down...');
$hotReloadServer->stop();
exit(0);
});
pcntl_signal(SIGTERM, function () use ($hotReloadServer, $output) {
$output->writeln('');
$output->info('Received termination signal, shutting down...');
$hotReloadServer->stop();
exit(0);
});
}
}
$output->success('Hot Reload server is running!');
@@ -104,8 +122,14 @@ final readonly class HotReloadCommand
sleep(1);
// Process signals if available
if (extension_loaded('pcntl')) {
pcntl_signal_dispatch();
try {
$pcntlService = $container->get(\App\Framework\Pcntl\PcntlService::class);
$pcntlService->dispatchSignals();
} catch (\Throwable $e) {
// Fallback to direct dispatch if PCNTL service not available
if (extension_loaded('pcntl')) {
pcntl_signal_dispatch();
}
}
}