CluPilotCloud/app/Console/Commands/PruneUnverifiedAccounts.php

83 lines
3.0 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Models\Customer;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
/**
* Registrations nobody ever confirmed, removed.
*
* An abandoned attempt used to stay in `users` for ever, and it holds the
* address hostage while it does: the unique index means the same person cannot
* register again with the address they meant. "Abmelden und neu registrieren"
* was therefore advice that could not work — signing out frees nothing.
*
* Five days, because that is long enough for somebody who registered on a
* Friday, found the mail in a spam folder on Monday, and clicked it — and short
* enough that a typo does not block the correct address for a month.
*
* What it will NOT touch, and this is the important half:
*
* - a confirmed account, whatever else is true of it;
* - an account with a customer record behind it. A paid checkout creates the
* login through the webhook, and such a user can sit unconfirmed while
* already having a contract. Deleting that is data loss, not housekeeping.
*/
class PruneUnverifiedAccounts extends Command
{
/** How long an unconfirmed registration is kept. */
public const AFTER_DAYS = 5;
protected $signature = 'clupilot:prune-unverified {--dry-run : Only say what would go}';
protected $description = 'Delete registrations that were never confirmed';
public function handle(): int
{
$stale = User::query()
->whereNull('email_verified_at')
->where('created_at', '<', now()->subDays(self::AFTER_DAYS))
// Not a join: the two tables are linked by user_id OR by address
// (see Customer::emailTaken — a row can predate or outlive the
// other), and either one means somebody is a customer.
->whereNotExists(fn ($q) => $q->selectRaw('1')->from('customers')
->whereColumn('customers.user_id', 'users.id'))
->whereNotExists(fn ($q) => $q->selectRaw('1')->from('customers')
->whereColumn('customers.email', 'users.email'))
->get();
if ($stale->isEmpty()) {
$this->info('Nothing to remove.');
return self::SUCCESS;
}
if ($this->option('dry-run')) {
foreach ($stale as $user) {
$this->line('would remove: '.$user->email.' (registered '.$user->created_at->toDateString().')');
}
return self::SUCCESS;
}
foreach ($stale as $user) {
// Logged individually rather than as a count: an address that
// disappears is the kind of thing somebody asks about later, and a
// number in a log answers nothing.
Log::info('Removing an unconfirmed registration', [
'email' => $user->email,
'registered_at' => $user->created_at?->toIso8601String(),
]);
$user->delete();
}
$this->info($stale->count().' unconfirmed registration(s) removed.');
return self::SUCCESS;
}
}