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) <[email protected]>
This commit is contained in:
ghassanandClaude Opus 4.7 committed 2026-10-21 00:38:59 +03:00
1 parent 48c947d171
commit c7d2f25ef6
1 file changed
+24 -6
+24 -6
View File
@@ -691,11 +691,28 @@ class VideoController extends Controller
$viewFp = $request->cookie('_fp'); $viewFp = $request->cookie('_fp');
$viewFp = ($viewFp && preg_match('/^[a-f0-9]{64}$/', $viewFp)) ? $viewFp : null; $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') $exists = \DB::table('video_views')
->where('user_id', Auth::id())
->where('video_id', $video->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(); ->exists();
if (! $exists) { if (! $exists) {
@@ -711,12 +728,13 @@ class VideoController extends Controller
'watched_at' => now(), 'watched_at' => now(),
]); ]);
} }
} else { } elseif (! $isOwnerViewing) {
// Guest: prefer the fingerprint hash for dedup (strongest signal); fall back to device_id cookie // Guest: 30-minute dedupe keyed on the strongest device signal
// (fingerprint hash > device_id cookie).
$exists = \DB::table('video_views') $exists = \DB::table('video_views')
->whereNull('user_id') ->whereNull('user_id')
->where('video_id', $video->id) ->where('video_id', $video->id)
->where('watched_at', '>', now()->subHour()) ->where('watched_at', '>', now()->subMinutes(30))
->where(function ($q) use ($viewFp, $viewDid) { ->where(function ($q) use ($viewFp, $viewDid) {
if ($viewFp) $q->where('device_hash', $viewFp); if ($viewFp) $q->where('device_hash', $viewFp);
else $q->where('device_id', $viewDid); else $q->where('device_id', $viewDid);