TenantAtlas/apps/platform/app/Console/Commands/GraphContractCheck.php
ahmido ce0615a9c1 Spec 182: relocate Laravel platform to apps/platform (#213)
## Summary
- move the Laravel application into `apps/platform` and keep the repository root for orchestration, docs, and tooling
- update the local command model, Sail/Docker wiring, runtime paths, and ignore rules around the new platform location
- add relocation quickstart/contracts plus focused smoke coverage for bootstrap, command model, routes, and runtime behavior

## Validation
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/PlatformRelocation`
- integrated browser smoke validated `/up`, `/`, `/admin`, `/admin/choose-workspace`, and tenant route semantics for `200`, `403`, and `404`

## Remaining Rollout Checks
- validate Dokploy build context and working-directory assumptions against the new `apps/platform` layout
- confirm web, queue, and scheduler processes all start from the expected working directory in staging/production
- verify no legacy volume mounts or asset-publish paths still point at the old root-level `public/` or `storage/` locations

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #213
2026-04-08 08:40:47 +00:00

72 lines
2.3 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Services\Graph\GraphClientInterface;
use App\Services\Graph\GraphContractRegistry;
use Illuminate\Console\Command;
class GraphContractCheck extends Command
{
protected $signature = 'graph:contract:check {--tenant=}';
protected $description = 'Validate Graph contract registry against live endpoints (lightweight probes)';
public function handle(GraphClientInterface $graph, GraphContractRegistry $registry): int
{
$contracts = config('graph_contracts.types', []);
if (empty($contracts)) {
$this->warn('No graph contracts configured.');
return self::SUCCESS;
}
$tenant = $this->option('tenant');
$failures = 0;
foreach ($contracts as $type => $contract) {
$resource = $contract['resource'] ?? null;
$select = $contract['allowed_select'] ?? [];
$expand = $contract['allowed_expand'] ?? [];
if (! $resource) {
$this->error("[$type] missing resource path");
$failures++;
continue;
}
$queryInput = array_filter([
'$top' => 1,
'$select' => $select,
'$expand' => $expand,
], static fn ($value): bool => $value !== null && $value !== '' && $value !== []);
$query = $registry->sanitizeQuery($type, $queryInput)['query'];
$response = $graph->request('GET', $resource, [
'query' => $query,
'tenant' => $tenant,
]);
if ($response->failed()) {
$code = $response->meta['error_code'] ?? $response->status;
$message = $response->meta['error_message'] ?? ($response->errors[0]['message'] ?? $response->errors[0] ?? 'unknown');
$this->error("[$type] drift or capability issue ({$code}): {$message}");
$failures++;
continue;
}
if (! empty($response->warnings)) {
$this->warn("[$type] completed with warnings: ".implode('; ', $response->warnings));
} else {
$this->info("[$type] OK");
}
}
return $failures > 0 ? self::FAILURE : self::SUCCESS;
}
}