1 Commits

7 changed files with 56 additions and 23 deletions

View File

@@ -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;

View File

@@ -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 = [];

View File

@@ -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();

View File

@@ -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')) {

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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.]+)/',