From 2a4c0d8bbe29178ae90e776bb9453f86e6d23bd9 Mon Sep 17 00:00:00 2001
From: Wicklow <123956049+wickloww@users.noreply.github.com>
Date: Wed, 12 Apr 2023 07:32:20 +0000
Subject: Feature/filter already watched videos (#5739)

* filter already watched videos

* Updated code based on review comments
---
 .../sql/video/videos-id-list-query-builder.ts      | 22 ++++++++++++++++++++++
 server/models/video/video.ts                       | 10 ++++++++--
 2 files changed, 30 insertions(+), 2 deletions(-)

(limited to 'server/models')

diff --git a/server/models/video/sql/video/videos-id-list-query-builder.ts b/server/models/video/sql/video/videos-id-list-query-builder.ts
index 62f1855c7..cba77c1d1 100644
--- a/server/models/video/sql/video/videos-id-list-query-builder.ts
+++ b/server/models/video/sql/video/videos-id-list-query-builder.ts
@@ -78,6 +78,8 @@ export type BuildVideosListQueryOptions = {
 
   transaction?: Transaction
   logging?: boolean
+
+  excludeAlreadyWatched?: boolean
 }
 
 export class VideosIdListQueryBuilder extends AbstractRunQuery {
@@ -260,6 +262,14 @@ export class VideosIdListQueryBuilder extends AbstractRunQuery {
       this.whereDurationMax(options.durationMax)
     }
 
+    if (options.excludeAlreadyWatched) {
+      if (exists(options.user.id)) {
+        this.whereExcludeAlreadyWatched(options.user.id)
+      } else {
+        throw new Error('Cannot use excludeAlreadyWatched parameter when auth token is not provided')
+      }
+    }
+
     this.whereSearch(options.search)
 
     if (options.isCount === true) {
@@ -598,6 +608,18 @@ export class VideosIdListQueryBuilder extends AbstractRunQuery {
     this.replacements.durationMax = durationMax
   }
 
+  private whereExcludeAlreadyWatched (userId: number) {
+    this.and.push(
+      'NOT EXISTS (' +
+      '  SELECT 1' +
+      '  FROM "userVideoHistory"' +
+      '  WHERE "video"."id" = "userVideoHistory"."videoId"' +
+      '  AND "userVideoHistory"."userId" = :excludeAlreadyWatchedUserId' +
+      ')'
+    )
+    this.replacements.excludeAlreadyWatchedUserId = userId
+  }
+
   private groupForTrending (trendingDays: number) {
     const viewsGteDate = new Date(new Date().getTime() - (24 * 3600 * 1000) * trendingDays)
 
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 0c5ed64ec..f817c4a33 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -1086,6 +1086,8 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
     countVideos?: boolean
 
     search?: string
+
+    excludeAlreadyWatched?: boolean
   }) {
     VideoModel.throwIfPrivateIncludeWithoutUser(options.include, options.user)
     VideoModel.throwIfPrivacyOneOfWithoutUser(options.privacyOneOf, options.user)
@@ -1124,7 +1126,8 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
         'historyOfUser',
         'hasHLSFiles',
         'hasWebtorrentFiles',
-        'search'
+        'search',
+        'excludeAlreadyWatched'
       ]),
 
       serverAccountIdForBlock: serverActor.Account.id,
@@ -1170,6 +1173,8 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
     durationMin?: number // seconds
     durationMax?: number // seconds
     uuids?: string[]
+
+    excludeAlreadyWatched?: boolean
   }) {
     VideoModel.throwIfPrivateIncludeWithoutUser(options.include, options.user)
     VideoModel.throwIfPrivacyOneOfWithoutUser(options.privacyOneOf, options.user)
@@ -1203,7 +1208,8 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
         'hasWebtorrentFiles',
         'uuids',
         'search',
-        'displayOnlyForFollower'
+        'displayOnlyForFollower',
+        'excludeAlreadyWatched'
       ]),
       serverAccountIdForBlock: serverActor.Account.id
     }
-- 
cgit v1.2.3