83 lines
3.0 KiB
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;
|
|
}
|
|
}
|