From f99e5c00812daf06de785d8286c4e080b7a5a579 Mon Sep 17 00:00:00 2001
From: Simon <simobilleter@gmail.com>
Date: Fri, 2 Aug 2024 18:31:49 +0200
Subject: [PATCH] implement video query building from url params

---
 tubearchivist/channel/urls.py             |   5 --
 tubearchivist/channel/views.py            |  22 -----
 tubearchivist/playlist/urls.py            |   5 --
 tubearchivist/playlist/views.py           |  18 ----
 tubearchivist/video/src/constants.py      |  18 ++++
 tubearchivist/video/src/query_building.py | 100 ++++++++++++++++++++++
 tubearchivist/video/views.py              |  15 +++-
 7 files changed, 132 insertions(+), 51 deletions(-)
 create mode 100644 tubearchivist/video/src/query_building.py

diff --git a/tubearchivist/channel/urls.py b/tubearchivist/channel/urls.py
index c00c2080..8f992988 100644
--- a/tubearchivist/channel/urls.py
+++ b/tubearchivist/channel/urls.py
@@ -19,9 +19,4 @@ urlpatterns = [
         views.ChannelApiView.as_view(),
         name="api-channel",
     ),
-    path(
-        "<slug:channel_id>/video/",
-        views.ChannelApiVideoView.as_view(),
-        name="api-channel-video",
-    ),
 ]
diff --git a/tubearchivist/channel/views.py b/tubearchivist/channel/views.py
index 601f78a0..448dc535 100644
--- a/tubearchivist/channel/views.py
+++ b/tubearchivist/channel/views.py
@@ -129,25 +129,3 @@ class ChannelApiSearchView(ApiBaseView):
         self.get_document(parsed["url"])
 
         return Response(self.response, status=self.status_code)
-
-
-class ChannelApiVideoView(ApiBaseView):
-    """resolves to /api/channel/<channel-id>/video
-    GET: returns a list of videos of channel
-    """
-
-    search_base = "ta_video/_search/"
-
-    def get(self, request, channel_id):
-        """handle get request"""
-        self.data.update(
-            {
-                "query": {
-                    "term": {"channel.channel_id": {"value": channel_id}}
-                },
-                "sort": [{"published": {"order": "desc"}}],
-            }
-        )
-        self.get_document_list(request)
-
-        return Response(self.response, status=self.status_code)
diff --git a/tubearchivist/playlist/urls.py b/tubearchivist/playlist/urls.py
index d1dc6fe4..c144e2e0 100644
--- a/tubearchivist/playlist/urls.py
+++ b/tubearchivist/playlist/urls.py
@@ -14,9 +14,4 @@ urlpatterns = [
         views.PlaylistApiView.as_view(),
         name="api-playlist",
     ),
-    path(
-        "<slug:playlist_id>/video/",
-        views.PlaylistApiVideoView.as_view(),
-        name="api-playlist-video",
-    ),
 ]
diff --git a/tubearchivist/playlist/views.py b/tubearchivist/playlist/views.py
index a712f9ab..696ad688 100644
--- a/tubearchivist/playlist/views.py
+++ b/tubearchivist/playlist/views.py
@@ -118,21 +118,3 @@ class PlaylistApiView(ApiBaseView):
             YoutubePlaylist(playlist_id).delete_metadata()
 
         return Response({"success": True})
-
-
-class PlaylistApiVideoView(ApiBaseView):
-    """resolves to /api/playlist/<playlist_id>/video
-    GET: returns list of videos in playlist
-    """
-
-    search_base = "ta_video/_search/"
-
-    def get(self, request, playlist_id):
-        """handle get request"""
-        self.data["query"] = {
-            "term": {"playlist.keyword": {"value": playlist_id}}
-        }
-        self.data.update({"sort": [{"published": {"order": "desc"}}]})
-
-        self.get_document_list(request)
-        return Response(self.response, status=self.status_code)
diff --git a/tubearchivist/video/src/constants.py b/tubearchivist/video/src/constants.py
index 9ccd0464..39dcfff3 100644
--- a/tubearchivist/video/src/constants.py
+++ b/tubearchivist/video/src/constants.py
@@ -10,3 +10,21 @@ class VideoTypeEnum(enum.Enum):
     STREAMS = "streams"
     SHORTS = "shorts"
     UNKNOWN = "unknown"
+
+
+class SortEnum(enum.Enum):
+    """all sort by options"""
+
+    PUBLISHED = "published"
+    DOWNLOADED = "date_downloaded"
+    VIEWS = "stats.view_count"
+    LIKES = "stats.like_count"
+    DURATION = "player.duration"
+    MEDIASIZE = "media_size"
+
+
+class OrderEnum(enum.Enum):
+    """all order by options"""
+
+    ASC = "asc"
+    DESC = "desc"
diff --git a/tubearchivist/video/src/query_building.py b/tubearchivist/video/src/query_building.py
new file mode 100644
index 00000000..fb525051
--- /dev/null
+++ b/tubearchivist/video/src/query_building.py
@@ -0,0 +1,100 @@
+"""build query for video fetching"""
+
+from common.src.ta_redis import RedisArchivist
+from video.src.constants import OrderEnum, SortEnum, VideoTypeEnum
+
+
+class QueryBuilder:
+    """contain functionality"""
+
+    WATCH_OPTIONS = ["watched", "unwatched", "continue"]
+
+    def __init__(self, user_id: int, **kwargs):
+        self.user_id = user_id
+        self.request_params = kwargs
+
+    def build_data(self) -> dict:
+        """build data dict"""
+        data = {}
+        data["query"] = self.build_query()
+        if sort := self.parse_sort():
+            data["sort"] = sort
+
+        return data
+
+    def build_query(self) -> dict:
+        """build query key"""
+        must_list = []
+        channel = self.request_params.get("channel")
+        if channel:
+            must_list.append({"match": {"channel.channel_id": channel[0]}})
+
+        playlist = self.request_params.get("playlist")
+        if playlist:
+            must_list.append({"match": {"playlist.keyword": playlist[0]}})
+
+        watch = self.request_params.get("watch")
+        if watch:
+            watch_must_list = self._parse_watch(watch[0])
+            must_list.append(watch_must_list)
+
+        video_type = self.request_params.get("type")
+        if video_type:
+            type_list_list = self._parse_type(video_type[0])
+            must_list.append(type_list_list)
+
+        query = {"bool": {"must": must_list}}
+
+        return query
+
+    def _parse_watch(self, watch: str) -> dict:
+        """build query"""
+        if watch not in self.WATCH_OPTIONS:
+            raise ValueError(f"'{watch}' not in {self.WATCH_OPTIONS}")
+
+        if watch == "continue":
+            continue_must = self._build_continue_must()
+            return continue_must
+
+        return {"match": {"player.watched": watch == "watched"}}
+
+    def _build_continue_must(self):
+        results = RedisArchivist().list_items(f"{self.user_id}:progress:")
+        if not results:
+            return None
+
+        ids = [{"match": {"youtube_id": i.get("youtube_id")}} for i in results]
+        continue_ids = {"bool": {"should": ids}}
+
+        return continue_ids
+
+    def _parse_type(self, video_type: str):
+        """parse video type"""
+        if not hasattr(VideoTypeEnum, video_type.upper()):
+            raise ValueError(f"'{video_type}' not in VideoTypeEnum")
+
+        vid_type = getattr(VideoTypeEnum, video_type.upper()).value
+
+        return {"match": {"vid_type": vid_type}}
+
+    def parse_sort(self) -> list | None:
+        """build sort key"""
+        sort = self.request_params.get("sort")
+        if not sort:
+            return None
+
+        sort = sort[0]
+        if not hasattr(SortEnum, sort.upper()):
+            raise ValueError(f"'{sort}' not in SortEnum")
+
+        sort_field = getattr(SortEnum, sort.upper()).value
+
+        order = self.request_params.get("order", ["desc"])
+        order = order[0]
+        if not hasattr(OrderEnum, order.upper()):
+            raise ValueError(f"'{order}' not in OrderEnum")
+
+        order_by = getattr(OrderEnum, order.upper()).value
+        sort_key = [{sort_field: {"order": order_by}}]
+
+        return sort_key
diff --git a/tubearchivist/video/views.py b/tubearchivist/video/views.py
index 21f8406d..aa42b7ae 100644
--- a/tubearchivist/video/views.py
+++ b/tubearchivist/video/views.py
@@ -5,18 +5,31 @@ from common.views_base import AdminWriteOnly, ApiBaseView
 from playlist.src.index import YoutubePlaylist
 from rest_framework.response import Response
 from video.src.index import YoutubeVideo
+from video.src.query_building import QueryBuilder
 
 
 class VideoApiListView(ApiBaseView):
     """resolves to /api/video/
     GET: returns list of videos
+    params:
+    - playlist:str=<playlist-id>
+    - channel:str=<channel-id>
+    - watch:enum=watched|unwatched|continue
+    - sort:enum=published|downloaded|views|likes|duration|filesize
+    - order:enum=asc|desc
+    - type:enum=videos|streams|shorts
     """
 
     search_base = "ta_video/_search/"
 
     def get(self, request):
         """get request"""
-        self.data.update({"sort": [{"published": {"order": "desc"}}]})
+        try:
+            data = QueryBuilder(request.user.id, **request.GET).build_data()
+        except ValueError as err:
+            return Response({"error": str(err)}, status=400)
+
+        self.data = data
         self.get_document_list(request)
 
         return Response(self.response)