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
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:
170
src/Framework/Console/Commands/CodeQualityScanCommand.php
Normal file
170
src/Framework/Console/Commands/CodeQualityScanCommand.php
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
242
src/Framework/Console/Commands/DatabaseBrowserCommand.php
Normal file
242
src/Framework/Console/Commands/DatabaseBrowserCommand.php
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user