feat(encryption): always report count of non-deleted users with encrypted data

encryption:notify-removal now prints the total number of non-deleted users
who still hold legacy browser-encrypted data on every run, before the billing
exclusion. This is the signal for deciding when the browser-side encryption
code can be removed. Email-sending logic is unchanged: subscribed users are
still never warned.
This commit is contained in:
Víctor Falcón 2026-07-17 11:23:32 +02:00
parent 808d41eee2
commit afd09eab87
2 changed files with 43 additions and 4 deletions

View File

@ -39,16 +39,22 @@ class NotifyEncryptedDataRemovalCommand extends Command
*/
public function handle(): int
{
$users = $this->excludeBilledUsers($this->usersWithLegacyEncryption()->get());
$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 with encrypted data found.');
$this->info('No users to warn.');
return self::SUCCESS;
}
$this->info("Found {$users->count()} user(s) with encrypted data.");
if ($this->option('dry-run')) {
$this->renderTable($users);
$this->info('[dry-run] No emails sent.');

View File

@ -45,6 +45,39 @@ test('it never warns a subscribed user', function () {
Queue::assertNotPushed(SendUpdateEmailJob::class);
});
test('it always reports the total count of non-deleted encrypted users, including subscribed ones', function () {
config(['subscriptions.enabled' => true]);
$subscribed = User::factory()->create();
Transaction::factory()->for($subscribed)->create();
$subscribed->subscriptions()->create([
'type' => 'default',
'stripe_id' => 'sub_test123',
'stripe_status' => 'active',
'stripe_price' => 'price_test123',
]);
$unsubscribed = User::factory()->create();
Transaction::factory()->for($unsubscribed)->create();
$deleted = User::factory()->create();
Transaction::factory()->for($deleted)->create();
$deleted->delete();
$clean = User::factory()->create();
Transaction::factory()->for($clean)->plaintext()->create();
// The count spans everyone still holding encrypted data (subscribed included); the
// soft-deleted and plaintext users are excluded.
artisan('encryption:notify-removal', ['--force' => true])
->expectsOutputToContain('2 non-deleted user(s) still have encrypted data.')
->assertSuccessful();
// Sending is unchanged: the subscribed user is counted but never emailed.
Queue::assertPushed(SendUpdateEmailJob::class, 1);
Queue::assertPushed(SendUpdateEmailJob::class, fn (SendUpdateEmailJob $job) => $job->user->is($unsubscribed));
});
test('it queues on the emails queue', function () {
$user = User::factory()->create();
Transaction::factory()->for($user)->create();