From c7d2f25ef64049b1cc827ca1930e09ac56944882 Mon Sep 17 00:00:00 2001 From: ghassan Date: Wed, 21 Oct 2026 00:38:59 +0300 Subject: [PATCH] feat(views): skip owner views, 30-minute device dedupe - Insert is skipped entirely when the authenticated viewer owns the video. Owners re-watching, previewing an edit, or impersonating no longer inflate their own view counts. - Dedupe window drops 1h -> 30min for both auth and guest paths. - Auth-path dedupe now also matches on device fingerprint / device_id cookie, so signing out and back in during the same session doesn't double-count a single viewer. Co-Authored-By: Claude Opus 4.7 (1M context) --- app/Http/Controllers/VideoController.php | 30 +++++++++++++++++++----- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/VideoController.php b/app/Http/Controllers/VideoController.php index dc54846..070b450 100644 --- a/app/Http/Controllers/VideoController.php +++ b/app/Http/Controllers/VideoController.php @@ -691,11 +691,28 @@ class VideoController extends Controller $viewFp = $request->cookie('_fp'); $viewFp = ($viewFp && preg_match('/^[a-f0-9]{64}$/', $viewFp)) ? $viewFp : null; - if (Auth::check()) { + // Owners don't count toward their own view totals. Previews, re-watches + // from the owner's own account, and even owner-impersonation sessions + // would otherwise inflate the number. The share/analytics screens + // already filter owner_id separately, so skipping the insert is safe. + $isOwnerViewing = Auth::check() && Auth::id() === (int) $video->user_id; + + if (Auth::check() && ! $isOwnerViewing) { + // 30-minute user-level dedupe. Also dedupe against the strongest + // device signal available (fingerprint > device_id cookie) so a + // user signing out and back in during the same session doesn't + // double-count. $exists = \DB::table('video_views') - ->where('user_id', Auth::id()) ->where('video_id', $video->id) - ->where('watched_at', '>', now()->subHour()) + ->where('watched_at', '>', now()->subMinutes(30)) + ->where(function ($q) use ($viewFp, $viewDid) { + $q->where('user_id', Auth::id()); + if ($viewFp) { + $q->orWhere('device_hash', $viewFp); + } elseif ($viewDid) { + $q->orWhere('device_id', $viewDid); + } + }) ->exists(); if (! $exists) { @@ -711,12 +728,13 @@ class VideoController extends Controller 'watched_at' => now(), ]); } - } else { - // Guest: prefer the fingerprint hash for dedup (strongest signal); fall back to device_id cookie + } elseif (! $isOwnerViewing) { + // Guest: 30-minute dedupe keyed on the strongest device signal + // (fingerprint hash > device_id cookie). $exists = \DB::table('video_views') ->whereNull('user_id') ->where('video_id', $video->id) - ->where('watched_at', '>', now()->subHour()) + ->where('watched_at', '>', now()->subMinutes(30)) ->where(function ($q) use ($viewFp, $viewDid) { if ($viewFp) $q->where('device_hash', $viewFp); else $q->where('device_id', $viewDid);