From cec09eda8a4f6c89e9fb9ecbe102920710ca62e4 Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 10 Feb 2025 21:42:46 +0700 Subject: [PATCH] add common view serializers --- backend/appsettings/src/config.py | 4 +- backend/common/serializers.py | 83 ++++++++++++++++++++ backend/common/views.py | 123 +++++++++++++++++++++++++----- 3 files changed, 189 insertions(+), 21 deletions(-) diff --git a/backend/appsettings/src/config.py b/backend/appsettings/src/config.py index 1b38787d..34631774 100644 --- a/backend/appsettings/src/config.py +++ b/backend/appsettings/src/config.py @@ -242,7 +242,7 @@ class ReleaseVersion: return False - def get_update(self) -> dict: + def get_update(self) -> dict | None: """return new version dict if available""" message = RedisArchivist().get_message_dict(self.NEW_KEY) - return message + return message or None diff --git a/backend/common/serializers.py b/backend/common/serializers.py index 791410ef..71dae90f 100644 --- a/backend/common/serializers.py +++ b/backend/common/serializers.py @@ -55,3 +55,86 @@ class AsyncTaskResponseSerializer(serializers.Serializer): message = serializers.CharField() task_id = serializers.CharField() filename = serializers.CharField(required=False) + + +class NotificationSerializer(serializers.Serializer): + """serialize notification messages""" + + title = serializers.CharField() + group = serializers.CharField() + api_start = serializers.BooleanField() + api_stop = serializers.BooleanField() + level = serializers.ChoiceField(choices=["info", "error"]) + messages = serializers.ListField(child=serializers.CharField()) + progress = serializers.FloatField(required=False) + + +class NotificationQueryFilterSerializer(serializers.Serializer): + """serialize notification query filter""" + + filter = serializers.ChoiceField( + choices=["download", "settings", "channel"], required=False + ) + + +class PingUpdateSerializer(serializers.Serializer): + """serialize update notification""" + + status = serializers.BooleanField() + version = serializers.CharField() + is_breaking = serializers.BooleanField() + + +class PingSerializer(serializers.Serializer): + """serialize ping response""" + + response = serializers.ChoiceField(choices=["pong"]) + user = serializers.IntegerField() + version = serializers.CharField() + ta_update = PingUpdateSerializer(required=False) + + +class WatchedDataSerializer(serializers.Serializer): + """mark as watched serializer""" + + id = serializers.CharField() + is_watched = serializers.BooleanField() + + +class RefreshQuerySerializer(serializers.Serializer): + """refresh query filtering""" + + type = serializers.ChoiceField( + choices=["video", "channel", "playlist"], required=False + ) + id = serializers.CharField(required=False) + + +class RefreshResponseSerializer(serializers.Serializer): + """serialize refresh response""" + + state = serializers.ChoiceField( + choices=["running", "queued", "empty", False] + ) + total_queued = serializers.IntegerField() + in_queue_name = serializers.CharField(required=False) + + +class RefreshAddQuerySerializer(serializers.Serializer): + """serialize add to refresh queue""" + + extract_videos = serializers.BooleanField(required=False) + + +class RefreshAddDataSerializer(serializers.Serializer): + """add to refresh queue serializer""" + + video = serializers.ListField( + child=serializers.CharField(), required=False + ) + channel = serializers.ListField( + child=serializers.CharField(), required=False + ) + playlist = serializers.ListField( + child=serializers.CharField(), required=False + ) diff --git a/backend/common/views.py b/backend/common/views.py index b7b477c9..4a30b7e0 100644 --- a/backend/common/views.py +++ b/backend/common/views.py @@ -2,10 +2,23 @@ from appsettings.src.config import ReleaseVersion from appsettings.src.reindex import ReindexProgress +from common.serializers import ( + AsyncTaskResponseSerializer, + ErrorResponseSerializer, + NotificationQueryFilterSerializer, + NotificationSerializer, + PingSerializer, + RefreshAddDataSerializer, + RefreshAddQuerySerializer, + RefreshQuerySerializer, + RefreshResponseSerializer, + WatchedDataSerializer, +) from common.src.searching import SearchForm from common.src.ta_redis import RedisArchivist from common.src.watched import WatchState from common.views_base import AdminOnly, ApiBaseView +from drf_spectacular.utils import OpenApiResponse, extend_schema from rest_framework.response import Response from task.tasks import check_reindex @@ -16,6 +29,9 @@ class PingView(ApiBaseView): """ @staticmethod + @extend_schema( + responses={200: OpenApiResponse(PingSerializer())}, + ) def get(request): """get pong""" data = { @@ -24,7 +40,8 @@ class PingView(ApiBaseView): "version": ReleaseVersion().get_local_version(), "ta_update": ReleaseVersion().get_update(), } - return Response(data) + serializer = PingSerializer(data) + return Response(serializer.data) class RefreshView(ApiBaseView): @@ -35,30 +52,69 @@ class RefreshView(ApiBaseView): permission_classes = [AdminOnly] + @extend_schema( + responses={ + 200: OpenApiResponse(RefreshResponseSerializer()), + 400: OpenApiResponse( + ErrorResponseSerializer(), description="Bad request" + ), + }, + parameters=[RefreshQuerySerializer()], + ) def get(self, request): - """handle get request""" - request_type = request.GET.get("type") - request_id = request.GET.get("id") + """get refresh status""" + query_serializer = RefreshQuerySerializer(data=request.query_params) + query_serializer.is_valid(raise_exception=True) + validated_query = query_serializer.validated_data + request_type = validated_query.get("type") + request_id = validated_query.get("id") if request_id and not request_type: - return Response({"status": "Bad Request"}, status=400) + error = ErrorResponseSerializer( + {"error": "specified id also needs type"} + ) + return Response(error.data, status=400) try: progress = ReindexProgress( request_type=request_type, request_id=request_id ).get_progress() except ValueError: - return Response({"status": "Bad Request"}, status=400) + error = ErrorResponseSerializer({"error": "bad request"}) + return Response(error.data, status=400) - return Response(progress) + response_serializer = RefreshResponseSerializer(progress) + return Response(response_serializer.data) + + @extend_schema( + request=RefreshAddDataSerializer(), + responses={ + 200: OpenApiResponse(AsyncTaskResponseSerializer()), + }, + parameters=[RefreshAddQuerySerializer()], + ) def post(self, request): - """handle post request""" - data = request.data - extract_videos = bool(request.GET.get("extract_videos", False)) - check_reindex.delay(data=data, extract_videos=extract_videos) + """add to reindex queue""" + query_serializer = RefreshAddQuerySerializer(data=request.query_params) + query_serializer.is_valid(raise_exception=True) + validated_query = query_serializer.validated_data - return Response(data) + data_serializer = RefreshAddDataSerializer(data=request.data) + data_serializer.is_valid(raise_exception=True) + validated_data = data_serializer.validated_data + + extract_videos = validated_query.get("extract_videos") + task = check_reindex.delay( + data=validated_data, extract_videos=extract_videos + ) + message = { + "message": "reindex task started", + "task_id": task.id, + } + serializer = AsyncTaskResponseSerializer(message) + + return Response(serializer.data) class WatchedView(ApiBaseView): @@ -66,17 +122,31 @@ class WatchedView(ApiBaseView): POST: change watched state of video, channel or playlist """ + @extend_schema( + request=WatchedDataSerializer(), + responses={ + 200: OpenApiResponse(WatchedDataSerializer()), + 400: OpenApiResponse( + ErrorResponseSerializer(), description="Bad request" + ), + }, + ) def post(self, request): """change watched state""" - youtube_id = request.data.get("id") - is_watched = request.data.get("is_watched") + data_serializer = WatchedDataSerializer(data=request.data) + data_serializer.is_valid(raise_exception=True) + validated_data = data_serializer.validated_data + youtube_id = validated_data.get("id") + is_watched = validated_data.get("is_watched") if not youtube_id or is_watched is None: - message = {"message": "missing id or is_watched"} - return Response(message, status=400) + error = ErrorResponseSerializer( + {"error": "missing id or is_watched"} + ) + return Response(error.data, status=400) WatchState(youtube_id, is_watched, request.user.id).change() - return Response({"message": "success"}, status=200) + return Response(data_serializer.data) class SearchView(ApiBaseView): @@ -106,11 +176,26 @@ class NotificationView(ApiBaseView): valid_filters = ["download", "settings", "channel"] + @extend_schema( + responses={ + 200: OpenApiResponse(NotificationSerializer(many=True)), + }, + parameters=[NotificationQueryFilterSerializer], + ) def get(self, request): """get all notifications""" + query_serializer = NotificationQueryFilterSerializer( + data=request.query_params + ) + query_serializer.is_valid(raise_exception=True) + validated_query = query_serializer.validated_data + filter_by = validated_query.get("filter") + query = "message" - filter_by = request.GET.get("filter", None) if filter_by in self.valid_filters: query = f"{query}:{filter_by}" - return Response(RedisArchivist().list_items(query)) + notifications = RedisArchivist().list_items(query) + response_serializer = NotificationSerializer(notifications, many=True) + + return Response(response_serializer.data)