TenantAtlas/apps/platform/app/Console/Commands/WarmSettingsCatalogDefinitionsCache.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

184 lines
6.3 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Models\Policy;
use App\Services\Intune\SettingsCatalogDefinitionResolver;
use Illuminate\Console\Command;
class WarmSettingsCatalogDefinitionsCache extends Command
{
protected $signature = 'intune:warm-definitions-cache
{--policy= : Specific policy ID to warm cache for}
{--all : Warm cache for all Settings Catalog policies}
{--force : Force re-fetch even if cached}';
protected $description = 'Warm the Settings Catalog definitions cache by fetching display names from Graph API';
public function handle(SettingsCatalogDefinitionResolver $resolver): int
{
if ($this->option('policy')) {
return $this->warmForPolicy($this->option('policy'), $resolver);
}
if ($this->option('all')) {
return $this->warmForAllPolicies($resolver);
}
$this->error('Please specify either --policy=ID or --all');
return self::FAILURE;
}
private function warmForPolicy(string $policyId, SettingsCatalogDefinitionResolver $resolver): int
{
$policy = Policy::find($policyId);
if (! $policy) {
$this->error("Policy {$policyId} not found");
return self::FAILURE;
}
if ($policy->policy_type !== 'settingsCatalog' && $policy->policy_type !== 'settingsCatalogPolicy') {
$this->error("Policy {$policyId} is not a Settings Catalog policy");
return self::FAILURE;
}
$this->info("Warming cache for policy: {$policy->display_name} ({$policy->id})");
$snapshot = $policy->versions()->latest('version_number')->first()?->snapshot ?? [];
$definitionIds = $this->extractDefinitionIds($snapshot);
if (empty($definitionIds)) {
$this->warn('No definition IDs found in policy snapshot');
return self::SUCCESS;
}
$this->info('Found '.count($definitionIds).' definition IDs');
$this->newLine();
$progressBar = $this->output->createProgressBar(count($definitionIds));
$progressBar->start();
$success = 0;
$failed = 0;
$cached = 0;
foreach ($definitionIds as $definitionId) {
try {
$definition = $resolver->resolveOne($definitionId);
if ($definition) {
if (isset($definition['displayName']) && ! str_contains($definition['displayName'], 'Device Vendor Msft')) {
$success++;
$this->line("\n{$definitionId}{$definition['displayName']}", 'info');
} else {
$cached++;
$this->line("\n{$definitionId} → (fallback: {$definition['displayName']})", 'comment');
}
} else {
$failed++;
$this->line("\n{$definitionId} → Failed to resolve", 'error');
}
} catch (\Exception $e) {
$failed++;
$this->line("\n{$definitionId} → Error: {$e->getMessage()}", 'error');
}
$progressBar->advance();
}
$progressBar->finish();
$this->newLine(2);
$this->table(
['Status', 'Count'],
[
['✓ Successfully fetched from Graph', $success],
['⚠ Using fallback (not in Graph)', $cached],
['✗ Failed', $failed],
['Total', count($definitionIds)],
]
);
return self::SUCCESS;
}
private function warmForAllPolicies(SettingsCatalogDefinitionResolver $resolver): int
{
$policies = Policy::where(function ($query) {
$query->where('policy_type', 'settingsCatalog')
->orWhere('policy_type', 'settingsCatalogPolicy');
})->get();
if ($policies->isEmpty()) {
$this->warn('No Settings Catalog policies found');
return self::SUCCESS;
}
$this->info("Found {$policies->count()} Settings Catalog policies");
$this->newLine();
foreach ($policies as $policy) {
$this->warmForPolicy((string) $policy->id, $resolver);
$this->newLine();
}
return self::SUCCESS;
}
private function extractDefinitionIds(array $snapshot): array
{
$ids = [];
$settings = $snapshot['settings'] ?? [];
$walk = function (array $nodes) use (&$walk, &$ids): void {
foreach ($nodes as $node) {
if (! is_array($node)) {
continue;
}
// Top-level settings have settingInstance wrapper
if (isset($node['settingInstance']['settingDefinitionId'])) {
$ids[] = $node['settingInstance']['settingDefinitionId'];
$instance = $node['settingInstance'];
}
// Nested children have settingDefinitionId directly
elseif (isset($node['settingDefinitionId'])) {
$ids[] = $node['settingDefinitionId'];
$instance = $node;
} else {
continue;
}
// Handle nested children in choice setting value
if (isset($instance['choiceSettingValue']['children']) && is_array($instance['choiceSettingValue']['children'])) {
$walk($instance['choiceSettingValue']['children']);
}
// Handle nested children in group collections
if (isset($instance['groupSettingCollectionValue'])) {
foreach ($instance['groupSettingCollectionValue'] as $group) {
if (isset($group['children']) && is_array($group['children'])) {
$walk($group['children']);
}
}
}
// Handle nested children in group setting value
if (isset($instance['groupSettingValue']['children']) && is_array($instance['groupSettingValue']['children'])) {
$walk($instance['groupSettingValue']['children']);
}
}
};
$walk($settings);
return array_unique($ids);
}
}