VPS and dedicated server ordering, managed panels, SSO, billing, and launcher integration — forked from hosting infrastructure with server-focused routes and dashboard. Co-authored-by: Cursor <cursoragent@cursor.com>
125 lines
4.8 KiB
PHP
125 lines
4.8 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Models\HostingAccount;
|
|
use App\Notifications\HostingSuspendedNotification;
|
|
use App\Services\Hosting\HostingProvisioningService;
|
|
use App\Services\Hosting\Providers\SharedNodeProvider;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class ProcessExpiredHostingAccountsCommand extends Command
|
|
{
|
|
private const EXPIRY_SUSPENSION_GRACE_DAYS = 7;
|
|
|
|
protected $signature = 'hosting:process-expired-accounts';
|
|
|
|
protected $description = 'Suspend expired hosting accounts (serve expired page) and terminate those past the grace period';
|
|
|
|
public function handle(SharedNodeProvider $nodeProvider, HostingProvisioningService $provisioning): int
|
|
{
|
|
$this->suspendNewlyExpired($nodeProvider);
|
|
$this->terminatePastGracePeriod($provisioning);
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
private function suspendNewlyExpired(SharedNodeProvider $nodeProvider): void
|
|
{
|
|
$accounts = HostingAccount::query()
|
|
->where('status', HostingAccount::STATUS_ACTIVE)
|
|
->whereNotNull('expires_at')
|
|
->where('expires_at', '<', now()->subDays(self::EXPIRY_SUSPENSION_GRACE_DAYS))
|
|
->with(['node', 'sites', 'user'])
|
|
->get();
|
|
|
|
if ($accounts->isEmpty()) {
|
|
$this->info('No newly expired accounts to suspend.');
|
|
return;
|
|
}
|
|
|
|
$this->info("Found {$accounts->count()} expired account(s) to suspend.");
|
|
|
|
foreach ($accounts as $account) {
|
|
try {
|
|
$nodeProvider->serveExpiredPage($account);
|
|
|
|
$reason = 'Hosting account expired on '.$account->expires_at->format('M d, Y').'. Renew to restore service.';
|
|
|
|
$account->update([
|
|
'status' => HostingAccount::STATUS_SUSPENDED,
|
|
'suspension_reason' => $reason,
|
|
'suspended_at' => now(),
|
|
'metadata' => array_merge((array) ($account->metadata ?? []), [
|
|
'suspension_origin' => 'expiry',
|
|
'expired_at' => $account->expires_at->toIso8601String(),
|
|
'expiry_suspension_grace_days' => self::EXPIRY_SUSPENSION_GRACE_DAYS,
|
|
'grace_period_ends_at' => $account->gracePeriodEndsAt()->toIso8601String(),
|
|
]),
|
|
]);
|
|
|
|
if ($account->user) {
|
|
$account->user->notify(new HostingSuspendedNotification($account->fresh(), $reason));
|
|
}
|
|
|
|
Log::info('ProcessExpiredHostingAccounts: Suspended expired account', [
|
|
'account_id' => $account->id,
|
|
'username' => $account->username,
|
|
'expired_at' => $account->expires_at,
|
|
]);
|
|
|
|
$this->line(" Suspended: {$account->username} (expired {$account->expires_at->format('M d, Y')})");
|
|
} catch (\Throwable $e) {
|
|
Log::error('ProcessExpiredHostingAccounts: Failed to suspend expired account', [
|
|
'account_id' => $account->id,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
|
|
$this->error(" Failed to suspend {$account->username}: {$e->getMessage()}");
|
|
}
|
|
}
|
|
}
|
|
|
|
private function terminatePastGracePeriod(HostingProvisioningService $provisioning): void
|
|
{
|
|
$graceDeadline = now()->subMonths(HostingAccount::GRACE_PERIOD_MONTHS);
|
|
|
|
$accounts = HostingAccount::query()
|
|
->where('status', HostingAccount::STATUS_SUSPENDED)
|
|
->whereNotNull('expires_at')
|
|
->where('expires_at', '<', $graceDeadline)
|
|
->whereJsonContains('metadata->suspension_origin', 'expiry')
|
|
->with(['node', 'sites'])
|
|
->get();
|
|
|
|
if ($accounts->isEmpty()) {
|
|
$this->info('No accounts past grace period to terminate.');
|
|
return;
|
|
}
|
|
|
|
$this->info("Found {$accounts->count()} account(s) past grace period to terminate.");
|
|
|
|
foreach ($accounts as $account) {
|
|
try {
|
|
$provisioning->terminateAccount($account);
|
|
|
|
Log::info('ProcessExpiredHostingAccounts: Terminated account past grace period', [
|
|
'account_id' => $account->id,
|
|
'username' => $account->username,
|
|
'expired_at' => $account->expires_at,
|
|
]);
|
|
|
|
$this->line(" Terminated: {$account->username} (expired {$account->expires_at->format('M d, Y')})");
|
|
} catch (\Throwable $e) {
|
|
Log::error('ProcessExpiredHostingAccounts: Failed to terminate account', [
|
|
'account_id' => $account->id,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
|
|
$this->error(" Failed to terminate {$account->username}: {$e->getMessage()}");
|
|
}
|
|
}
|
|
}
|
|
}
|