feat: add comprehensive framework features and deployment improvements

Major additions:
- Storage abstraction layer with filesystem and in-memory implementations
- Gitea API integration with MCP tools for repository management
- Console dialog mode with interactive command execution
- WireGuard VPN DNS fix implementation and documentation
- HTTP client streaming response support
- Router generic result type
- Parameter type validator for framework core

Framework enhancements:
- Console command registry improvements
- Console dialog components
- Method signature analyzer updates
- Route mapper refinements
- MCP server and tool mapper updates
- Queue job chain and dependency commands
- Discovery tokenizer improvements

Infrastructure:
- Deployment architecture documentation
- Ansible playbook updates for WireGuard client regeneration
- Production environment configuration updates
- Docker Compose local configuration updates
- Remove obsolete docker-compose.yml (replaced by environment-specific configs)

Documentation:
- PERMISSIONS.md for access control guidelines
- WireGuard DNS fix implementation details
- Console dialog mode usage guide
- Deployment architecture overview

Testing:
- Multi-purpose attribute tests
- Gitea Actions integration tests (typed and untyped)
This commit is contained in:
2025-11-04 20:39:48 +01:00
parent 700fe8118b
commit 3ed2685e74
80 changed files with 9891 additions and 850 deletions

View File

@@ -0,0 +1,199 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use App\Infrastructure\Api\Gitea\GiteaClient;
use App\Infrastructure\Api\Gitea\GiteaConfig;
use App\Framework\HttpClient\CurlHttpClient;
// Load environment variables manually
$envFile = __DIR__ . '/../../.env';
if (file_exists($envFile)) {
$lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
if (strpos(trim($line), '#') === 0) {
continue;
}
putenv($line);
}
}
// Initialize GiteaClient
$config = new GiteaConfig(
baseUrl: getenv('GITEA_URL') ?: '',
token: getenv('GITEA_TOKEN') ?: null,
username: getenv('GITEA_USERNAME') ?: null,
password: getenv('GITEA_PASSWORD') ?: null,
timeout: (float) (getenv('GITEA_TIMEOUT') ?: 30.0)
);
$httpClient = new CurlHttpClient();
$giteaClient = new GiteaClient($config, $httpClient);
echo "\n╔═══════════════════════════════════════════════════════════════════╗\n";
echo "║ Gitea Actions API - Value Objects Demo ║\n";
echo "╚═══════════════════════════════════════════════════════════════════╝\n\n";
$owner = 'michael';
$repo = 'michaelschiemer';
try {
// =====================================================================
// Test 1: List Workflows (Typed)
// =====================================================================
echo "📋 Test 1: Listing Workflows (Type-Safe)\n";
echo str_repeat('─', 60) . "\n";
$workflows = $giteaClient->actions->listWorkflowsTyped($owner, $repo);
echo " Total workflows: {$workflows->count()}\n";
echo " Active workflows: " . count($workflows->active()) . "\n";
echo " Disabled workflows: " . count($workflows->disabled()) . "\n\n";
foreach ($workflows as $workflow) {
$statusIcon = $workflow->isActive() ? '✅' : '❌';
echo " {$statusIcon} Workflow: {$workflow->name}\n";
echo " ID: {$workflow->id}\n";
echo " Path: {$workflow->path}\n";
echo " File: {$workflow->getFileName()}\n";
echo " State: {$workflow->state}\n";
echo "\n";
}
// =====================================================================
// Test 2: List Workflow Runs (Typed)
// =====================================================================
echo "🏃 Test 2: Listing Recent Workflow Runs (Type-Safe)\n";
echo str_repeat('─', 60) . "\n";
$runs = $giteaClient->actions->listRunsTyped($owner, $repo, ['limit' => 10]);
echo " Total runs: {$runs->count()}\n";
echo " Running: " . count($runs->running()) . "\n";
$successRate = $runs->successRate() * 100;
echo " Successful: {$runs->successCount()} ({$successRate}%)\n";
echo " Failed: {$runs->failureCount()}\n\n";
foreach ($runs as $run) {
echo " {$run->getStatusSummary()} Run #{$run->id}\n";
echo " Title: {$run->displayTitle}\n";
echo " Status: {$run->status->value}\n";
echo " Branch: {$run->headBranch}\n";
echo " Started: {$run->startedAt->format('Y-m-d H:i:s')}\n";
if ($run->isCompleted() && $run->getDuration() !== null) {
$duration = $run->getDuration();
echo " Duration: {$duration->toSeconds()}s\n";
} elseif ($run->isRunning()) {
$elapsed = $run->getElapsedTime();
echo " Elapsed: {$elapsed->toSeconds()}s (still running)\n";
}
echo "\n";
}
// =====================================================================
// Test 3: Get Specific Run Details (Typed)
// =====================================================================
if (!$runs->isEmpty()) {
echo "🔍 Test 3: Detailed Run Information (Type-Safe)\n";
echo str_repeat('─', 60) . "\n";
$latestRun = $runs->latest();
echo " Run ID: {$latestRun->id}\n";
echo " Title: {$latestRun->displayTitle}\n";
echo " Status: {$latestRun->getStatusSummary()}\n";
echo " Branch: {$latestRun->headBranch}\n";
echo " Commit: " . substr($latestRun->headSha, 0, 8) . "\n";
echo " Triggered by: {$latestRun->event}\n";
echo " Run Number: {$latestRun->runNumber}\n";
echo "\n";
echo " Business Logic Methods:\n";
echo " - isSuccessful(): " . ($latestRun->isSuccessful() ? 'true' : 'false') . "\n";
echo " - isFailed(): " . ($latestRun->isFailed() ? 'true' : 'false') . "\n";
echo " - isRunning(): " . ($latestRun->isRunning() ? 'true' : 'false') . "\n";
echo " - wasCancelled(): " . ($latestRun->wasCancelled() ? 'true' : 'false') . "\n";
if ($latestRun->getDuration() !== null) {
$duration = $latestRun->getDuration();
echo " - getDuration(): {$duration->toSeconds()}s\n";
}
echo "\n";
}
// =====================================================================
// Test 4: Collection Methods Demo
// =====================================================================
echo "📊 Test 4: Collection Methods Demo\n";
echo str_repeat('─', 60) . "\n";
// Filter by branch
$mainBranchRuns = $runs->forBranch('main');
echo " Runs on 'main' branch: " . count($mainBranchRuns) . "\n";
// Find by ID
if (!$runs->isEmpty()) {
$firstRunId = $runs->latest()->id;
$foundRun = $runs->findById($firstRunId);
echo " Found run by ID: " . ($foundRun !== null ? 'Yes' : 'No') . "\n";
}
echo "\n";
// =====================================================================
// Test 5: Backward Compatibility Check
// =====================================================================
echo "🔄 Test 5: Backward Compatibility (Raw Arrays Still Work)\n";
echo str_repeat('─', 60) . "\n";
// Old API still works
$rawWorkflows = $giteaClient->actions->listWorkflows($owner, $repo);
echo " Raw array method works: Yes\n";
echo " Raw workflows count: " . count($rawWorkflows['workflows'] ?? []) . "\n";
$rawRuns = $giteaClient->actions->listRuns($owner, $repo, ['limit' => 5]);
echo " Raw runs method works: Yes\n";
echo " Raw runs count: " . count($rawRuns['workflow_runs'] ?? []) . "\n";
echo "\n";
echo "✅ All Tests SUCCESSFUL!\n\n";
// =====================================================================
// Insights
// =====================================================================
echo "╔═══════════════════════════════════════════════════════════════════╗\n";
echo "║ ★ Insights ─────────────────────────────────────────────────────║\n";
echo "╠═══════════════════════════════════════════════════════════════════╣\n";
echo "║ ║\n";
echo "║ Value Objects bieten mehrere Vorteile: ║\n";
echo "║ ║\n";
echo "║ 1. Type Safety - IDE erkennt Fehler zur Compile-Zeit ║\n";
echo "\$run->id (RunId) statt \$run['id'] (mixed) ║\n";
echo "║ ║\n";
echo "║ 2. Business Logic - Methoden wie isSuccessful(), getDuration() ║\n";
echo "║ direkt am Objekt statt externe Helper-Funktionen ║\n";
echo "║ ║\n";
echo "║ 3. Collection Methods - Filtern, Suchen, Aggregieren ║\n";
echo "\$runs->successful(), \$runs->successRate() etc. ║\n";
echo "║ ║\n";
echo "║ 4. API Evolution - Änderungen in Gitea API isoliert ║\n";
echo "║ fromApiResponse() mappt API-Änderungen transparent ║\n";
echo "║ ║\n";
echo "║ 5. Backward Compatible - Raw arrays funktionieren weiterhin ║\n";
echo "║ listWorkflows() (array) + listWorkflowsTyped() (VOs) ║\n";
echo "║ ║\n";
echo "╚═══════════════════════════════════════════════════════════════════╝\n\n";
} catch (\Exception $e) {
echo "\n❌ Error: " . $e->getMessage() . "\n";
echo "File: " . $e->getFile() . "\n";
echo "Line: " . $e->getLine() . "\n";
exit(1);
}

View File

@@ -0,0 +1,116 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use App\Infrastructure\Api\Gitea\GiteaClient;
use App\Infrastructure\Api\Gitea\GiteaConfig;
use App\Framework\HttpClient\CurlHttpClient;
// Load environment variables manually
$envFile = __DIR__ . '/../../.env';
if (file_exists($envFile)) {
$lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
if (strpos(trim($line), '#') === 0) {
continue;
}
putenv($line);
}
}
// Initialize GiteaClient
$config = new GiteaConfig(
baseUrl: getenv('GITEA_URL') ?: '',
token: getenv('GITEA_TOKEN') ?: null,
username: getenv('GITEA_USERNAME') ?: null,
password: getenv('GITEA_PASSWORD') ?: null,
timeout: (float) (getenv('GITEA_TIMEOUT') ?: 30.0)
);
$httpClient = new CurlHttpClient();
$giteaClient = new GiteaClient($config, $httpClient);
echo "\n=== Testing Gitea Actions/Workflows API ===\n\n";
$owner = 'michael';
$repo = 'michaelschiemer';
try {
// Test 1: List Workflows
echo "1. Listing Workflows for {$owner}/{$repo}:\n";
echo str_repeat('-', 60) . "\n";
$workflows = $giteaClient->actions->listWorkflows($owner, $repo);
if (isset($workflows['workflows']) && !empty($workflows['workflows'])) {
foreach ($workflows['workflows'] as $workflow) {
echo " - Workflow: {$workflow['name']}\n";
echo " ID: {$workflow['id']}\n";
echo " Path: {$workflow['path']}\n";
echo " State: {$workflow['state']}\n";
echo "\n";
}
} else {
echo " No workflows found.\n\n";
}
// Test 2: List Workflow Runs (last 10)
echo "2. Listing Recent Workflow Runs (last 10):\n";
echo str_repeat('-', 60) . "\n";
$runs = $giteaClient->actions->listRuns($owner, $repo, [
'limit' => 10
]);
if (isset($runs['workflow_runs']) && !empty($runs['workflow_runs'])) {
foreach ($runs['workflow_runs'] as $run) {
echo " - Run #{$run['id']}\n";
echo " Title: {$run['display_title']}\n";
echo " Status: {$run['status']}\n";
echo " Conclusion: " . ($run['conclusion'] ?? 'N/A') . "\n";
echo " Started: {$run['started_at']}\n";
echo " Branch: {$run['head_branch']}\n";
echo "\n";
}
// Test 3: Get detailed info about latest run
$latestRun = $runs['workflow_runs'][0];
echo "3. Detailed Info for Latest Run (#{$latestRun['id']}):\n";
echo str_repeat('-', 60) . "\n";
$runDetails = $giteaClient->actions->getRun($owner, $repo, $latestRun['id']);
echo " Workflow: {$runDetails['name']}\n";
echo " Status: {$runDetails['status']}\n";
echo " Conclusion: " . ($runDetails['conclusion'] ?? 'N/A') . "\n";
echo " Triggered by: {$runDetails['event']}\n";
echo " Branch: {$runDetails['head_branch']}\n";
echo " Commit: {$runDetails['head_sha']}\n";
echo " Run Number: {$runDetails['run_number']}\n";
echo " Started: {$runDetails['run_started_at']}\n";
if (isset($runDetails['jobs'])) {
echo "\n Jobs:\n";
foreach ($runDetails['jobs'] as $job) {
echo " - {$job['name']}: {$job['status']}";
if (isset($job['conclusion'])) {
echo " ({$job['conclusion']})";
}
echo "\n";
}
}
} else {
echo " No workflow runs found.\n";
}
echo "\n✅ Actions/Workflows API Test SUCCESSFUL!\n";
} catch (\Exception $e) {
echo "\n❌ Error: " . $e->getMessage() . "\n";
echo "File: " . $e->getFile() . "\n";
echo "Line: " . $e->getLine() . "\n";
exit(1);
}