TenantAtlas/app/Console/Commands/TenantpilotDispatchDirectoryGroupsSync.php
2026-01-11 22:02:06 +01:00

117 lines
3.5 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Models\Tenant;
use Carbon\CarbonImmutable;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class TenantpilotDispatchDirectoryGroupsSync extends Command
{
protected $signature = 'tenantpilot:directory-groups:dispatch {--tenant=* : Limit to tenant_id/external_id}';
protected $description = 'Dispatch scheduled directory group sync runs (idempotent per tenant minute-slot).';
public function handle(): int
{
if (! (bool) config('directory_groups.schedule.enabled', false)) {
return self::SUCCESS;
}
$now = CarbonImmutable::now('UTC');
$timeUtc = (string) config('directory_groups.schedule.time_utc', '02:00');
if (! $this->isDueAt($now, $timeUtc)) {
return self::SUCCESS;
}
if (! class_exists(\App\Jobs\EntraGroupSyncJob::class)) {
$this->warn('EntraGroupSyncJob is not available; skipping scheduled directory group sync dispatch.');
return self::SUCCESS;
}
$tenantIdentifiers = array_values(array_filter(array_map('strval', array_merge(
(array) $this->option('tenant'),
(array) config('directory_groups.schedule.tenants', []),
))));
$tenants = $this->resolveTenants($tenantIdentifiers);
$selectionKey = 'groups-v1:all';
$slotKey = $now->format('YmdHi').'Z';
$created = 0;
$skipped = 0;
foreach ($tenants as $tenant) {
$inserted = DB::table('entra_group_sync_runs')->insertOrIgnore([
'tenant_id' => $tenant->getKey(),
'selection_key' => $selectionKey,
'slot_key' => $slotKey,
'status' => 'pending',
'initiator_user_id' => null,
'created_at' => $now,
'updated_at' => $now,
]);
if ($inserted === 1) {
$created++;
dispatch(new \App\Jobs\EntraGroupSyncJob(
tenantId: $tenant->getKey(),
selectionKey: $selectionKey,
slotKey: $slotKey,
));
} else {
$skipped++;
}
}
$this->info(sprintf(
'Scanned %d tenant(s), created %d run(s), skipped %d duplicate run(s).',
$tenants->count(),
$created,
$skipped,
));
return self::SUCCESS;
}
/**
* @param array<int, string> $tenantIdentifiers
*/
private function resolveTenants(array $tenantIdentifiers): \Illuminate\Support\Collection
{
$query = Tenant::activeQuery();
if ($tenantIdentifiers !== []) {
$query->where(function ($subQuery) use ($tenantIdentifiers) {
foreach ($tenantIdentifiers as $identifier) {
if (ctype_digit($identifier)) {
$subQuery->orWhereKey((int) $identifier);
continue;
}
$subQuery->orWhere('tenant_id', $identifier)
->orWhere('external_id', $identifier);
}
});
}
return $query->get();
}
private function isDueAt(CarbonImmutable $now, string $timeUtc): bool
{
if (! preg_match('/^(?<hour>[01]\\d|2[0-3]):(?<minute>[0-5]\\d)$/', $timeUtc, $matches)) {
return false;
}
return (int) $matches['hour'] === (int) $now->format('H')
&& (int) $matches['minute'] === (int) $now->format('i');
}
}