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)