Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ jobs:
- name: Static analysis (PHPStan)
run: ./vendor/bin/phpstan analyse --no-progress

- name: Rector compliance (dry-run)
run: ./vendor/bin/rector --dry-run --no-progress-bar

- name: Execute tests (Unit and Feature tests) via PHPUnit, with coverage gate
# Baseline measured at 52.6% lines (2026-08-02); floor set 2 points
# under it. Ratchet upward deliberately as coverage-focused PRs land;
Expand Down
2 changes: 2 additions & 0 deletions app/Auth/FakeOktaProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
*/
class FakeOktaProvider extends OktaProvider
{
#[\Override]
protected function getAuthUrl($state): string
{
return route('fake-okta.form', ['state' => $state]);
}

#[\Override]
public function user()
{
if ($this->hasInvalidState()) {
Expand Down
2 changes: 1 addition & 1 deletion app/Console/Commands/AttachWebhook.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public function handle(): int
$storeId = $this->option('store-id');
if (! $storeId) {
$storeOptions = $stores
->mapWithKeys(fn ($store) => [$store->id => "{$store->name} (ID: {$store->id})"])
->mapWithKeys(fn ($store): array => [$store->id => "{$store->name} (ID: {$store->id})"])
->toArray();

$selectedStoreValue = $this->choice('Select a store to attach webhook to:', $storeOptions);
Expand Down
32 changes: 18 additions & 14 deletions app/Console/Commands/BanEmailsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public function handle(): int
$bannedUserIds = $users->pluck('id')->toArray();

// 3. Identify User Groups associated with these users
$groupsToCheck = UserGroup::whereHas('users', function ($query) use ($bannedUserIds) {
$groupsToCheck = UserGroup::whereHas('users', function ($query) use ($bannedUserIds): void {
$query->whereIn('user_id', $bannedUserIds);
})->get();

Expand Down Expand Up @@ -121,7 +121,7 @@ public function handle(): int
$deletedGroups = [];

// 7. Perform DB modifications inside a transaction for atomic safety
DB::transaction(function () use ($patterns, $reason, $users, $groupsToCheck, $registrations, $instances, &$deletedGroups) {
DB::transaction(function () use ($patterns, $reason, $users, $groupsToCheck, $registrations, $instances, &$deletedGroups): void {
// Save patterns in polydock_banned_patterns table
foreach ($patterns as $pattern) {
PolydockBannedPattern::firstOrCreate(
Expand All @@ -141,11 +141,12 @@ public function handle(): int
// Initiate graceful force-purge for matched app instances
foreach ($instances as $instance) {
// Skip if already fully removed or in removal/purge stages
if (in_array($instance->status, PolydockAppInstance::$stageRemoveStatuses, true) ||
in_array($instance->status, PolydockAppInstance::$stagePurgeStatuses, true)) {
if (in_array($instance->status, PolydockAppInstance::$stageRemoveStatuses, true)) {
continue;
}
if (in_array($instance->status, PolydockAppInstance::$stagePurgeStatuses, true)) {
continue;
}

$instance->force_purge_requested_at = now();
$instance->setStatus(
PolydockAppInstanceStatus::PENDING_PRE_REMOVE,
Expand Down Expand Up @@ -210,7 +211,10 @@ protected function normalizePatterns(array $inputs): array
$normalized = [];
foreach ($inputs as $input) {
$input = trim(strtolower($input));
if (empty($input)) {
if ($input === '') {
continue;
}
if ($input === '0') {
continue;
}

Expand Down Expand Up @@ -269,11 +273,11 @@ protected function escapeLikePattern(string $pattern): string
*/
protected function findMatchingUsers(array $patterns): Collection
{
if (empty($patterns)) {
if ($patterns === []) {
return new Collection;
}

return User::where(function ($query) use ($patterns) {
return User::where(function ($query) use ($patterns): void {
foreach ($patterns as $pattern) {
$escapedPattern = $this->escapeLikePattern($pattern);
$query->orWhereRaw("email LIKE ? ESCAPE '='", [$escapedPattern]);
Expand All @@ -290,12 +294,12 @@ protected function findMatchingUsers(array $patterns): Collection
*/
protected function findMatchingRegistrations(array $patterns, array $userIds): Collection
{
if (empty($patterns) && empty($userIds)) {
if ($patterns === [] && $userIds === []) {
return new Collection;
}

return UserRemoteRegistration::where(function ($query) use ($patterns, $userIds) {
if (! empty($userIds)) {
return UserRemoteRegistration::where(function ($query) use ($patterns, $userIds): void {
if ($userIds !== []) {
$query->whereIn('user_id', $userIds);
}
foreach ($patterns as $pattern) {
Expand All @@ -314,14 +318,14 @@ protected function findMatchingRegistrations(array $patterns, array $userIds): C
*/
protected function findMatchingAppInstances(array $patterns, array $groupIds): Collection
{
if (empty($patterns) && empty($groupIds)) {
if ($patterns === [] && $groupIds === []) {
return new Collection;
}

$connectionType = DB::connection()->getDriverName();

return PolydockAppInstance::where(function ($query) use ($patterns, $groupIds, $connectionType) {
if (! empty($groupIds)) {
return PolydockAppInstance::where(function ($query) use ($patterns, $groupIds, $connectionType): void {
if ($groupIds !== []) {
$query->whereIn('user_group_id', $groupIds);
}

Expand Down
2 changes: 1 addition & 1 deletion app/Console/Commands/CreateStoreApp.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public function handle(): int
$storeId = $this->option('store-id');
if (! $storeId) {
$storeOptions = $stores
->mapWithKeys(fn ($store) => [$store->id => "{$store->name} (ID: {$store->id})"])
->mapWithKeys(fn ($store): array => [$store->id => "{$store->name} (ID: {$store->id})"])
->toArray();

$selectedValue = $this->choice('Select a store to create app in:', $storeOptions);
Expand Down
2 changes: 1 addition & 1 deletion app/Console/Commands/DispatchMidtrialEmailJobsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public function handle(): void
$eligibleInstances = PolydockAppInstance::query()
->with(['storeApp', 'userGroup.owners']) // Eager load relationships
->where('is_trial', true)
->whereHas('storeApp', function ($query) {
->whereHas('storeApp', function ($query): void {
$query->where('send_midtrial_email', true);
})
->whereNotNull('send_midtrial_email_at')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public function handle(): void
$eligibleInstances = PolydockAppInstance::query()
->with(['storeApp', 'userGroup.owners']) // Eager load relationships
->where('is_trial', true)
->whereHas('storeApp', function ($query) {
->whereHas('storeApp', function ($query): void {
$query->where('send_one_day_left_email', true);
})
->whereNotNull('send_one_day_left_email_at')
Expand Down
6 changes: 3 additions & 3 deletions app/Console/Commands/DispatchProjectPurgeJobsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,16 @@ public function handle(): int
$candidates = PolydockAppInstance::query()
->where('status', PolydockAppInstanceStatus::REMOVED)
->where('purge_attempts', '<', $maxAttempts)
->where(function ($query) use ($now) {
->where(function ($query) use ($now): void {
// Either grace period elapsed naturally...
$query->where(function ($q) use ($now) {
$query->where(function ($q) use ($now): void {
$q->whereNotNull('purge_eligible_at')
->where('purge_eligible_at', '<=', $now);
})
// ...or admin-forced.
->orWhereNotNull('force_purge_requested_at');
})
->where(function ($query) use ($backoffCutoff) {
->where(function ($query) use ($backoffCutoff): void {
$query->whereNull('purge_last_attempted_at')
->orWhere('purge_last_attempted_at', '<=', $backoffCutoff);
})
Expand Down
13 changes: 8 additions & 5 deletions app/Console/Commands/DispatchScheduledRedeploysCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,11 @@ public function handle(PolydockDeploymentService $service): int

foreach ($due->groupBy('polydock_store_app_id') as $group) {
$run = $service->redeploy($group->all(), PolydockDeploymentRunTriggerSourceEnum::SCHEDULED);

if (! $run || $run->status === PolydockDeploymentRunStatusEnum::FAILED) {
if (! $run) {
// Leave next_redeploy_at untouched so these retry on a later tick.
continue;
}
if ($run->status === PolydockDeploymentRunStatusEnum::FAILED) {
// Leave next_redeploy_at untouched so these retry on a later tick.
continue;
}
Expand Down Expand Up @@ -76,17 +79,17 @@ private function dueInstances(int $limit): Collection
->with(['storeApp', 'userGroup'])
->whereIn('status', PolydockAppInstance::$redeployEligibleStatuses)
->where('is_trial', false)
->whereHas('storeApp', function ($query) {
->whereHas('storeApp', function ($query): void {
$query->where('redeploy_enabled', true)
->whereNotNull('redeploy_interval_days');
})
->whereDoesntHave('deploymentRun', function ($query) {
->whereDoesntHave('deploymentRun', function ($query): void {
$query->whereIn('status', [
PolydockDeploymentRunStatusEnum::PENDING->value,
PolydockDeploymentRunStatusEnum::RUNNING->value,
]);
})
->where(function ($query) {
->where(function ($query): void {
$query->whereNull('next_redeploy_at')
->orWhere('next_redeploy_at', '<=', now());
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public function handle(): void
$eligibleInstances = PolydockAppInstance::query()
->with(['storeApp', 'userGroup.owners']) // Eager load relationships
->where('is_trial', true)
->whereHas('storeApp', function ($query) {
->whereHas('storeApp', function ($query): void {
$query->where('send_trial_complete_email', true);
})
->whereNotNull('trial_ends_at')
Expand Down
38 changes: 18 additions & 20 deletions app/Console/Commands/ExportRegistrationData.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public function handle(): int
}

if ($store = $this->option('store')) {
$query->whereHas('storeApp.store', function ($q) use ($store) {
$query->whereHas('storeApp.store', function ($q) use ($store): void {
$q->where('name', 'like', "%{$store}%");
});
}
Expand All @@ -70,23 +70,21 @@ public function handle(): int
return self::FAILURE;
}

$exportData = $registrations->map(function (UserRemoteRegistration $registration) {
return [
'id' => $registration->id,
'type' => $registration->type->value ?? '',
'email' => $registration->email,
'user_name' => $registration->user->name ?? '',
'user_group_name' => $registration->userGroup->name ?? '',
'store_name' => $registration->storeApp->store->name ?? '',
'store_app_name' => $registration->storeApp->name ?? '',
'status' => $registration->status->value,
'created_at' => $registration->created_at?->format('Y-m-d H:i:s'),
'updated_at' => $registration->updated_at?->format('Y-m-d H:i:s'),
'app_instance_name' => $registration->appInstance->name ?? '',
'app_instance_url' => $registration->appInstance->app_url ?? '',
'request_data' => json_encode(SensitiveDataRedactor::redact($registration->request_data ?? [])),
];
});
$exportData = $registrations->map(fn (UserRemoteRegistration $registration): array => [
'id' => $registration->id,
'type' => $registration->type->value ?? '',
'email' => $registration->email,
'user_name' => $registration->user->name ?? '',
'user_group_name' => $registration->userGroup->name ?? '',
'store_name' => $registration->storeApp->store->name ?? '',
'store_app_name' => $registration->storeApp->name ?? '',
'status' => $registration->status->value,
'created_at' => $registration->created_at?->format('Y-m-d H:i:s'),
'updated_at' => $registration->updated_at?->format('Y-m-d H:i:s'),
'app_instance_name' => $registration->appInstance->name ?? '',
'app_instance_url' => $registration->appInstance->app_url ?? '',
'request_data' => json_encode(SensitiveDataRedactor::redact($registration->request_data ?? [])),
]);

$content = $format === 'json'
? $exportData->toJson(JSON_PRETTY_PRINT)
Expand Down Expand Up @@ -124,10 +122,10 @@ private function generateCsv(array $rows): string
{
$handle = fopen('php://temp', 'r+');

fputcsv($handle, array_keys($rows[0]));
fputcsv($handle, array_keys($rows[0]), escape: '\\');

foreach ($rows as $row) {
fputcsv($handle, $row);
fputcsv($handle, $row, escape: '\\');
}

rewind($handle);
Expand Down
2 changes: 1 addition & 1 deletion app/Console/Commands/ExtendAppInstanceTrial.php
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ public function handle(): int
hint: $header,
);

if (empty($selectedIds)) {
if ($selectedIds === []) {
$this->info('No instances selected.');

return 0;
Expand Down
10 changes: 5 additions & 5 deletions app/Console/Commands/MarkStuckInstancesFailedCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ class MarkStuckInstancesFailedCommand extends BaseCommand
*
* @return array<int, PolydockAppInstanceStatus>
*/
private static function intermediateStatuses(): array
private function intermediateStatuses(): array
{
return PolydockAppInstance::unallocatedInProgressStatuses();
}

/**
* Resolve the corresponding failed status for a given intermediate status.
*/
private static function resolveFailedStatus(PolydockAppInstanceStatus $status): PolydockAppInstanceStatus
private function resolveFailedStatus(PolydockAppInstanceStatus $status): PolydockAppInstanceStatus
{
return match ($status) {
PolydockAppInstanceStatus::NEW,
Expand Down Expand Up @@ -80,11 +80,11 @@ public function handle(): int
$rows = [];

PolydockAppInstance::query()
->whereIn('status', self::intermediateStatuses())
->whereIn('status', $this->intermediateStatuses())
->where('updated_at', '<=', $cutoff)
->chunkById($chunkSize, function ($instances) use ($dryRun, $threshold, &$totalMarked, &$rows) {
->chunkById($chunkSize, function ($instances) use ($dryRun, $threshold, &$totalMarked, &$rows): void {
foreach ($instances as $instance) {
$failedStatus = self::resolveFailedStatus($instance->status);
$failedStatus = $this->resolveFailedStatus($instance->status);

$rows[] = [
$instance->id,
Expand Down
2 changes: 1 addition & 1 deletion app/Console/Commands/PollDeploymentRunsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public function handle(): int
PolydockDeploymentRunStatusEnum::RUNNING->value,
])
->where('poll_attempts', '<', $maxAttempts)
->where(function ($query) use ($threshold) {
->where(function ($query) use ($threshold): void {
$query->whereNull('last_polled_at')
->orWhere('last_polled_at', '<=', $threshold);
})
Expand Down
2 changes: 1 addition & 1 deletion app/Console/Commands/PollDeploymentStatusCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public function handle(): int
while (now()->lt($endTime)) {
$instances = PolydockAppInstance::query()
->where('status', PolydockAppInstanceStatus::DEPLOY_RUNNING)
->where(function ($query) {
->where(function ($query): void {
$query->whereNull('next_poll_after')
->orWhere('next_poll_after', '<=', now());
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ public function checkOnce(): int
$apps = PolydockStoreApp::query()
->where('status', PolydockStoreAppStatusEnum::AVAILABLE)
->withCount([
'instances as unallocated_instances_count' => function ($query) {
'instances as unallocated_instances_count' => function ($query): void {
$query->whereNull('user_group_id')
->where(function ($q) {
->where(function ($q): void {
$q->where('status', PolydockAppInstanceStatus::RUNNING_HEALTHY_UNCLAIMED)
->orWhereIn('status', PolydockAppInstance::unallocatedInProgressStatuses());
});
Expand Down
2 changes: 1 addition & 1 deletion app/Console/Commands/PolydockCloneStoreAppCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public function handle(): int

// Create store selection array
$storeChoices = $stores
->mapWithKeys(fn ($store) => [$store->id => "{$store->name} (ID: {$store->id})"])
->mapWithKeys(fn ($store): array => [$store->id => "{$store->name} (ID: {$store->id})"])
->toArray();

// Ask user which store to clone into
Expand Down
Loading