whereNotNull('expires_at') ->where('expires_at', '>', now()) ->where('expires_at', '<=', now()->addDays(self::WARNING_DAYS)) ->whereNull('api_token_expiration_warning_sent_at') ->where('tokenable_type', User::class) ->chunkById(100, function ($tokens) { foreach ($tokens as $token) { if (! $token->team_id || ! $this->isDue($token)) { continue; } $team = Team::find($token->team_id); if (! $team) { continue; } if (! $team->members()->whereKey($token->tokenable_id)->exists()) { continue; } $warningSentAt = now(); $team->notify(new ApiTokenExpiringNotification($token)); $markedAsSent = PersonalAccessToken::query() ->whereKey($token->getKey()) ->whereNotNull('expires_at') ->where('expires_at', '>', now()) ->where('expires_at', '<=', now()->addDays(self::WARNING_DAYS)) ->whereNull('api_token_expiration_warning_sent_at') ->update(['api_token_expiration_warning_sent_at' => $warningSentAt]); if ($markedAsSent !== 1) { continue; } $token->forceFill(['api_token_expiration_warning_sent_at' => $warningSentAt]); } }); } /** * Warn 7 days before expiry. A token that lives 7 days or less gets the warning 24 hours before expiry, * so it is not warned right after it is created. */ private function isDue(PersonalAccessToken $token): bool { if ($token->created_at->diffInDays($token->expires_at) > self::WARNING_DAYS) { return true; } return $token->expires_at->lte(now()->addDay()); } }