diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index a73333b..73edc0d 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -325,29 +325,41 @@ class UserController extends Controller } // Watch history - public function history() + public function history(Request $request) { $user = Auth::user(); - // Get videos the user has watched, ordered by most recently watched - // Include private videos since they are the user's own - $videoIds = \DB::table('video_views') - ->where('user_id', $user->id) - ->orderBy('watched_at', 'desc') - ->pluck('video_id') - ->unique(); - - $videos = Video::whereIn('id', $videoIds) + // Latest-first by watched_at. Paginate at the join so a user who + // has watched tens of thousands of videos doesn't pull every row + // into memory on each page load. Fetch the videos keyed by latest + // view so sort order is deterministic even when the same video has + // been watched more than once. + // + // Each history row is one (video_views + videos) pair, with the + // uniqueness constraint deferred to presentation: duplicates per + // video are expected (one row per view event) and the UI collapses + // them with view-count badges. + $per = 48; + $rows = \DB::table('video_views') + ->join('videos', 'videos.id', '=', 'video_views.video_id') + ->where('video_views.user_id', $user->id) ->where(function ($q) use ($user) { - $q->where('visibility', '!=', 'private') - ->orWhere('user_id', $user->id); + $q->where('videos.visibility', '!=', 'private') + ->orWhere('videos.user_id', $user->id); }) - ->get() - ->sortBy(function ($video) use ($videoIds) { - return $videoIds->search($video->id); - }); + ->orderBy('video_views.watched_at', 'desc') + ->select('videos.id as id', 'video_views.watched_at') + ->simplePaginate($per); - return view('user.history', compact('videos')); + $videoIds = collect($rows->items())->pluck('id')->unique(); + $videoMap = Video::whereIn('id', $videoIds)->get()->keyBy('id'); + $videos = collect($rows->items()) + ->map(fn ($r) => $videoMap->get($r->id)) + ->filter() + ->unique('id') // collapse re-watches of the same video on this page + ->values(); + + return view('user.history', ['videos' => $videos, 'paginator' => $rows]); } // Clear watch history @@ -392,14 +404,18 @@ class UserController extends Controller public function liked() { $user = Auth::user(); - // Include private videos in liked (user's own private videos) + // Include private videos in liked (user's own private videos). + // Cursor pagination matches the Phase 2 feed endpoints — stable + // across inserts (unlike ?page=N which can duplicate or skip rows + // when a new like lands between page fetches) and loads a bounded + // slice regardless of how many thousands of likes the user has. $videos = $user->likes() ->where(function ($q) use ($user) { $q->where('visibility', '!=', 'private') ->orWhere('videos.user_id', $user->id); }) - ->latest() - ->paginate(12); + ->latest('videos.id') + ->cursorPaginate(24); return view('user.liked', compact('videos')); }