Optimize visit aggregation, prevent stale cache in limits, fix Redis exist check, edge mobile detection, and scheduling
This commit is contained in:
@@ -47,8 +47,26 @@ class AggregateAndPruneVisitsCommand extends Command
|
||||
$statsByUrl = [];
|
||||
$nextDate = Carbon::parse($date)->addDay()->toDateString();
|
||||
|
||||
ShortUrlVisit::where('visited_at', '>=', $date.' 00:00:00')
|
||||
DB::table('short_url_visits')
|
||||
->where('visited_at', '>=', $date.' 00:00:00')
|
||||
->where('visited_at', '<', $nextDate.' 00:00:00')
|
||||
->select([
|
||||
'short_url_id',
|
||||
'is_qr_scan',
|
||||
'ip_hash',
|
||||
'device_type',
|
||||
'browser',
|
||||
'operating_system',
|
||||
'country',
|
||||
'country_code',
|
||||
'utm_source',
|
||||
'utm_medium',
|
||||
'utm_campaign',
|
||||
'browser_language',
|
||||
'city',
|
||||
'referer_host',
|
||||
])
|
||||
->orderBy('id')
|
||||
->chunk(1000, function ($chunk) use (&$statsByUrl): void {
|
||||
foreach ($chunk as $visit) {
|
||||
$urlId = $visit->short_url_id;
|
||||
|
||||
@@ -25,9 +25,13 @@ class SyncBufferedCountersCommand extends Command
|
||||
if (Cache::getDefaultDriver() === 'redis' && class_exists(Redis::class)) {
|
||||
$tempKey = "{$dirtyKey}:temp:".time();
|
||||
try {
|
||||
Redis::rename($dirtyKey, $tempKey);
|
||||
$dirtyIds = Redis::smembers($tempKey);
|
||||
Redis::del($tempKey);
|
||||
if (Redis::exists($dirtyKey)) {
|
||||
Redis::rename($dirtyKey, $tempKey);
|
||||
$dirtyIds = Redis::smembers($tempKey);
|
||||
Redis::del($tempKey);
|
||||
} else {
|
||||
$dirtyIds = [];
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
// If key does not exist or rename fails, fallback
|
||||
$dirtyIds = [];
|
||||
|
||||
@@ -71,9 +71,7 @@ class FilamentShortUrlServiceProvider extends PackageServiceProvider
|
||||
$this->app->booted(function (): void {
|
||||
$schedule = $this->app->make(Schedule::class);
|
||||
|
||||
if (config('filament-short-url.pruning.enabled', true)) {
|
||||
$schedule->command('short-url:aggregate-and-prune')->dailyAt('02:00');
|
||||
}
|
||||
$schedule->command('short-url:aggregate-and-prune')->dailyAt('02:00');
|
||||
|
||||
if (config('filament-short-url.counter_buffering.enabled', false)) {
|
||||
$schedule->command('short-url:sync-counters')->everyMinute();
|
||||
|
||||
@@ -89,20 +89,8 @@ class ShortUrlRedirectController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Resolve Destination URL (evaluating targeting rules)
|
||||
$destination = $shortUrl->resolveDestinationUrl($request);
|
||||
|
||||
// Forward query parameters if configured
|
||||
if ($shortUrl->forward_query_params) {
|
||||
$queryParams = $request->query();
|
||||
// Remove routing/auth parameters so they don't leak to destination
|
||||
unset($queryParams['confirmed'], $queryParams['password']);
|
||||
|
||||
if (! empty($queryParams)) {
|
||||
$separator = str_contains($destination, '?') ? '&' : '?';
|
||||
$destination .= $separator.http_build_query($queryParams);
|
||||
}
|
||||
}
|
||||
// 4. Resolve Destination URL (evaluating targeting rules and forwarding query parameters)
|
||||
$destination = $this->service->resolveRedirectUrl($shortUrl, $request);
|
||||
|
||||
// 5. Warning / Intermediate Page Check
|
||||
if ($shortUrl->show_warning_page && ! $request->has('confirmed')) {
|
||||
|
||||
@@ -276,7 +276,21 @@ class ShortUrl extends Model
|
||||
|
||||
public function getRealTimeTotalVisits(): int
|
||||
{
|
||||
return $this->total_visits;
|
||||
$buffered = 0;
|
||||
if (config('filament-short-url.counter_buffering.enabled', false)) {
|
||||
$prefix = config('filament-short-url.counter_buffering.cache_key_prefix', 'filament-short-url:buffer:');
|
||||
$buffered = (int) cache()->get("{$prefix}total:{$this->id}", 0);
|
||||
}
|
||||
|
||||
if ($this->max_visits !== null) {
|
||||
$dbVal = (int) DB::table($this->table)
|
||||
->where('id', $this->id)
|
||||
->value('total_visits');
|
||||
|
||||
return $dbVal + $buffered;
|
||||
}
|
||||
|
||||
return $this->total_visits + $buffered;
|
||||
}
|
||||
|
||||
public function isActive(): bool
|
||||
@@ -285,6 +299,13 @@ class ShortUrl extends Model
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->single_use) {
|
||||
$realEnabled = DB::table($this->table)->where('id', $this->id)->value('is_enabled');
|
||||
if (! $realEnabled) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Not yet active
|
||||
if ($this->activated_at && $this->activated_at->isFuture()) {
|
||||
return false;
|
||||
|
||||
@@ -91,7 +91,7 @@ class GeoIpService
|
||||
|
||||
if (class_exists(\Locale::class)) {
|
||||
try {
|
||||
$name = \Locale::getDisplayRegion('-'.$code, 'en');
|
||||
$name = \Locale::getDisplayRegion('en-'.$code, 'en');
|
||||
if ($name && $name !== $code) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ class UserAgentParser
|
||||
// Order matters — check specific first, generic last
|
||||
$patterns = [
|
||||
'Edg/' => 'Edge',
|
||||
'EdgiOS' => 'Edge',
|
||||
'EdgA' => 'Edge',
|
||||
'OPR/' => 'Opera',
|
||||
'Opera' => 'Opera',
|
||||
'SamsungBrowser' => 'Samsung Browser',
|
||||
@@ -67,6 +69,8 @@ class UserAgentParser
|
||||
{
|
||||
$patterns = [
|
||||
'/Edg\/([0-9.]+)/',
|
||||
'/EdgiOS\/([0-9.]+)/',
|
||||
'/EdgA\/([0-9.]+)/',
|
||||
'/OPR\/([0-9.]+)/',
|
||||
'/SamsungBrowser\/([0-9.]+)/',
|
||||
'/CriOS\/([0-9.]+)/',
|
||||
|
||||
Reference in New Issue
Block a user