feat: clean devices tokens and sanctum expiration
Laravel CI-CD / Tests Unitaires (push) Successful in 27s Details
Laravel CI-CD / Deploy with Kamal (push) Successful in 1m52s Details

This commit is contained in:
Leon Morival 2026-05-21 21:53:13 +02:00
parent 55215b2c36
commit 417a1bc53c
5 changed files with 97 additions and 3 deletions

View File

@ -0,0 +1,35 @@
<?php
namespace App\Console\Commands;
use App\Models\DeviceToken;
use Illuminate\Console\Command;
class PruneStaleDeviceTokens extends Command
{
protected $signature = 'device-tokens:prune-stale
{--days=180 : Number of days to retain device tokens since last refresh}';
protected $description = 'Prune stale Expo push device tokens.';
public function handle(): int
{
$days = filter_var($this->option('days'), FILTER_VALIDATE_INT, [
'options' => ['min_range' => 1],
]);
if ($days === false) {
$this->error('The --days option must be an integer greater than zero.');
return self::FAILURE;
}
$deleted = DeviceToken::query()
->where('updated_at', '<', now()->subDays($days))
->delete();
$this->info("{$deleted} stale device token(s) pruned.");
return self::SUCCESS;
}
}

View File

@ -47,7 +47,7 @@ return [
| |
*/ */
'expiration' => null, 'expiration' => 129600,
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View File

@ -17,6 +17,10 @@ Schedule::command('auth:clear-resets')
->daily() ->daily()
->withoutOverlapping() ->withoutOverlapping()
->onOneServer(); ->onOneServer();
Schedule::command('device-tokens:prune-stale --days=180')
->daily()
->withoutOverlapping()
->onOneServer();
Schedule::command('meal-posts:generate-ai') Schedule::command('meal-posts:generate-ai')
->daily() ->daily()
->withoutOverlapping(55) ->withoutOverlapping(55)

View File

@ -0,0 +1,54 @@
<?php
use App\Models\DeviceToken;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
it('prunes stale expo device tokens by last refresh date', function () {
$this->travelTo(now());
$user = User::factory()->create();
$staleToken = $user->deviceTokens()->create([
'expo_push_token' => 'ExponentPushToken[stale]',
'platform' => 'ios',
]);
$recentToken = $user->deviceTokens()->create([
'expo_push_token' => 'ExponentPushToken[recent]',
'platform' => 'android',
]);
$staleToken->forceFill([
'created_at' => now()->subDays(181),
'updated_at' => now()->subDays(181),
])->saveQuietly();
$recentToken->forceFill([
'created_at' => now()->subDays(179),
'updated_at' => now()->subDays(179),
])->saveQuietly();
$this->artisan('device-tokens:prune-stale --days=180')
->expectsOutput('1 stale device token(s) pruned.')
->assertExitCode(0);
expect(DeviceToken::query()->whereKey($staleToken->getKey())->exists())->toBeFalse()
->and(DeviceToken::query()->whereKey($recentToken->getKey())->exists())->toBeTrue();
});
it('rejects invalid retention days without deleting device tokens', function () {
$user = User::factory()->create();
$deviceToken = $user->deviceTokens()->create([
'expo_push_token' => 'ExponentPushToken[kept]',
'platform' => 'ios',
]);
$deviceToken->forceFill([
'created_at' => now()->subYear(),
'updated_at' => now()->subYear(),
])->saveQuietly();
$this->artisan('device-tokens:prune-stale --days=0')
->expectsOutput('The --days option must be an integer greater than zero.')
->assertExitCode(1);
expect(DeviceToken::query()->whereKey($deviceToken->getKey())->exists())->toBeTrue();
});

View File

@ -2,7 +2,7 @@
use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Artisan;
it('schedules expired auth token cleanup commands', function () { it('schedules expired auth and stale device token cleanup commands', function () {
Artisan::call('schedule:list', [ Artisan::call('schedule:list', [
'--json' => true, '--json' => true,
]); ]);
@ -12,5 +12,6 @@ it('schedules expired auth token cleanup commands', function () {
expect($commands) expect($commands)
->toContain('php artisan sanctum:prune-expired --hours=24') ->toContain('php artisan sanctum:prune-expired --hours=24')
->toContain('php artisan auth:clear-resets'); ->toContain('php artisan auth:clear-resets')
->toContain('php artisan device-tokens:prune-stale --days=180');
}); });