whisper-money/app/Console/Commands/NotifyEncryptedDataRemovalC...

100 lines
3.1 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Console\Commands\Concerns\FindsUsersWithLegacyEncryption;
use App\Jobs\SendUpdateEmailJob;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Collection;
class NotifyEncryptedDataRemovalCommand extends Command
{
use FindsUsersWithLegacyEncryption;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'encryption:notify-removal
{--dry-run : List affected users without sending anything}
{--force : Skip the confirmation prompt}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Warn users with legacy encrypted data that their account will be deleted unless they sign in within 7 days';
private const VIEW = 'encrypted-data-removal';
private const IDENTIFIER = 'encrypted-data-removal';
private const SUBJECT = 'Action required: sign in to keep your Whisper Money account';
/**
* Execute the console command.
*/
public function handle(): int
{
$encryptedUsers = $this->usersWithLegacyEncryption()->get();
// Always report the total scope of legacy encrypted data (soft-deleted users are
// excluded by the model's default scope). This is the signal for deciding whether
// the browser-side encryption code can finally be removed, independent of who we
// actually email below.
$this->info("{$encryptedUsers->count()} non-deleted user(s) still have encrypted data.");
$users = $this->excludeBilledUsers($encryptedUsers);
if ($users->isEmpty()) {
$this->info('No users to warn.');
return self::SUCCESS;
}
if ($this->option('dry-run')) {
$this->renderTable($users);
$this->info('[dry-run] No emails sent.');
return self::SUCCESS;
}
if (! $this->option('force') && ! $this->confirm("Send the deletion warning to {$users->count()} user(s)?", true)) {
$this->info('Cancelled.');
return self::SUCCESS;
}
$progressBar = $this->output->createProgressBar($users->count());
$progressBar->start();
foreach ($users->values() as $index => $user) {
// Spread over the 'emails' queue at 50/day to match the existing bulk-email convention.
SendUpdateEmailJob::dispatch($user, self::VIEW, self::IDENTIFIER, self::SUBJECT)
->delay(now()->addDays((int) floor($index / 50)));
$progressBar->advance();
}
$progressBar->finish();
$this->newLine();
$this->info("Queued {$users->count()} warning email(s) to the 'emails' queue (50/day).");
return self::SUCCESS;
}
/**
* @param Collection<int, User> $users
*/
private function renderTable($users): void
{
$this->table(['Email', 'Last active'], $users->map(fn (User $user) => [
$user->email,
$user->last_active_at?->toDateTimeString() ?? 'never',
])->all());
}
}