Phase 1: Setup & Database (13 tasks completed) - Add assignments JSONB column to backup_items table - Add group_mapping JSONB column to restore_runs table - Extend BackupItem model with 7 assignment accessor methods - Extend RestoreRun model with 8 group mapping helper methods - Add scopeWithAssignments() query scope to BackupItem - Update graph_contracts.php with assignments endpoints - Create 5 factories: BackupItem, RestoreRun, Tenant, BackupSet, Policy - Add 30 unit tests (15 BackupItem, 15 RestoreRun) - all passing Phase 2: Graph API Integration (16 tasks completed) - Create AssignmentFetcher service with fallback strategy - Create GroupResolver service with orphaned ID handling - Create ScopeTagResolver service with 1-hour caching - Implement fail-soft error handling for all services - Add 17 unit tests (5 AssignmentFetcher, 6 GroupResolver, 6 ScopeTagResolver) - all passing - Total: 71 assertions across all Phase 2 tests Test Results: - Phase 1: 30/30 tests passing (45 assertions) - Phase 2: 17/17 tests passing (71 assertions) - Total: 47/47 tests passing (116 assertions) - Code formatted with Pint (PSR-12 compliant) Next: Phase 3 - US1 Backup with Assignments (12 tasks)
107 lines
3.2 KiB
PHP
107 lines
3.2 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Graph;
|
|
|
|
class AssignmentFetcher
|
|
{
|
|
public function __construct(
|
|
private readonly MicrosoftGraphClient $graphClient,
|
|
private readonly GraphLogger $logger,
|
|
) {}
|
|
|
|
/**
|
|
* Fetch policy assignments with fallback strategy.
|
|
*
|
|
* Primary: GET /deviceManagement/configurationPolicies/{id}/assignments
|
|
* Fallback: GET /deviceManagement/configurationPolicies?$expand=assignments&$filter=id eq '{id}'
|
|
*
|
|
* @return array Returns assignment array or empty array on failure
|
|
*/
|
|
public function fetch(string $tenantId, string $policyId): array
|
|
{
|
|
try {
|
|
// Try primary endpoint
|
|
$assignments = $this->fetchPrimary($tenantId, $policyId);
|
|
|
|
if (! empty($assignments)) {
|
|
$this->logger->logDebug('Fetched assignments via primary endpoint', [
|
|
'tenant_id' => $tenantId,
|
|
'policy_id' => $policyId,
|
|
'count' => count($assignments),
|
|
]);
|
|
|
|
return $assignments;
|
|
}
|
|
|
|
// Try fallback with $expand
|
|
$this->logger->logDebug('Primary endpoint returned empty, trying fallback', [
|
|
'tenant_id' => $tenantId,
|
|
'policy_id' => $policyId,
|
|
]);
|
|
|
|
$assignments = $this->fetchWithExpand($tenantId, $policyId);
|
|
|
|
if (! empty($assignments)) {
|
|
$this->logger->logDebug('Fetched assignments via fallback endpoint', [
|
|
'tenant_id' => $tenantId,
|
|
'policy_id' => $policyId,
|
|
'count' => count($assignments),
|
|
]);
|
|
|
|
return $assignments;
|
|
}
|
|
|
|
// Both methods returned empty
|
|
$this->logger->logDebug('No assignments found for policy', [
|
|
'tenant_id' => $tenantId,
|
|
'policy_id' => $policyId,
|
|
]);
|
|
|
|
return [];
|
|
} catch (GraphException $e) {
|
|
$this->logger->logWarning('Failed to fetch assignments', [
|
|
'tenant_id' => $tenantId,
|
|
'policy_id' => $policyId,
|
|
'error' => $e->getMessage(),
|
|
'context' => $e->context,
|
|
]);
|
|
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch assignments using primary endpoint.
|
|
*/
|
|
private function fetchPrimary(string $tenantId, string $policyId): array
|
|
{
|
|
$path = "/deviceManagement/configurationPolicies/{$policyId}/assignments";
|
|
|
|
$response = $this->graphClient->get($path, $tenantId);
|
|
|
|
return $response['value'] ?? [];
|
|
}
|
|
|
|
/**
|
|
* Fetch assignments using $expand fallback.
|
|
*/
|
|
private function fetchWithExpand(string $tenantId, string $policyId): array
|
|
{
|
|
$path = '/deviceManagement/configurationPolicies';
|
|
$params = [
|
|
'$expand' => 'assignments',
|
|
'$filter' => "id eq '{$policyId}'",
|
|
];
|
|
|
|
$response = $this->graphClient->get($path, $tenantId, $params);
|
|
|
|
$policies = $response['value'] ?? [];
|
|
|
|
if (empty($policies)) {
|
|
return [];
|
|
}
|
|
|
|
return $policies[0]['assignments'] ?? [];
|
|
}
|
|
}
|