diff --git a/app/today/views.py b/app/today/views.py index 40d10d4..2467128 100644 --- a/app/today/views.py +++ b/app/today/views.py @@ -1,8 +1,12 @@ -from django.shortcuts import render +from typing import Optional + +from rest_framework.request import Request +from rest_framework.response import Response from rest_framework import viewsets from rest_framework.decorators import action from rest_framework.response import Response -from rest_framework import status +from django.utils import dateparse +from django.core import paginator from today.models import Video from today.serializers import VideoDetailsSerializer @@ -18,9 +22,72 @@ class VideoDetailsViewSet(viewsets.ModelViewSet): serializer_class = VideoDetailsSerializer @action(detail=False, methods=["get"], url_path="latest") - def latest_video(self, request): + def latest_video(self, request: Request) -> Response: + """ + Fetches the most recently uploaded video + :param request: the HTTP request - unused param + :return: A JSON response with the latest video metadata and a 200 status code if successful, else 404 if no + videos are available + """ recent_video = self.get_queryset().order_by("-upload_time").first() if recent_video is not None: serializer = self.get_serializer(recent_video) return Response(serializer.data) return Response({"detail": "No videos available"}, status=404) + + @action(detail=False, methods=["get"], url_path="paginated-videos") + def paginated_videos(self, request: Request) -> Response: + """ + Fetches videos within a specified date range and paginates the results. + + Query Parameters: + - start_date (str, optional): Filter videos uploaded on or after this date (YYYY-MM-DD). + - end_date (str, optional): Filter videos uploaded on or before this date (YYYY-MM-DD). + - page_num (int, optional): The page number to fetch (default is 1). + - limit (int, optional): The number of videos per page (default is 7). + + :param request: The HTTP request containing query parameters. + :return: A JSON response containing paginated video data and metadata + """ + start_date = request.query_params.get("start_date") + end_date = request.query_params.get("end_date") + page_num = request.query_params.get("page_num", 1) + limit = request.query_params.get("limit", 7) + + queryset = self.get_queryset() + if start_date is not None: + start_date_parsed = dateparse.parse_date(start_date) + if start_date_parsed is not None: + queryset = queryset.filter(upload_time__gte=start_date_parsed) + if end_date is not None: + end_date_parsed = dateparse.parse_date(end_date) + if end_date_parsed is not None: + queryset = queryset.filter(upload_time__lte=end_date_parsed) + + queryset = queryset.order_by("-upload_time") + + pages = paginator.Paginator(queryset, limit) + try: + videos_page = pages.page(page_num) + except paginator.PageNotAnInteger: + return Response({"detail": "Page number must be an integer"}, status=400) + except paginator.EmptyPage: + return Response({ + "videos": [], + "detail": "Page number out of range - it's likely that no older videos are available", + "total_videos": pages.count, + "total_pages": pages.num_pages, + "current_page": page_num, + "has_next": False, + "has_previous": False, + }, status=200) + + serializer = self.get_serializer(videos_page, many=True) + return Response({ + "videos": serializer.data, + "total_videos": pages.count, + "total_pages": pages.num_pages, + "current_page": videos_page.number, + "has_next": videos_page.has_next(), + "has_previous": videos_page.has_previous(), + }) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index b7359b3..6bc12af 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,9 @@ "name": "frontend", "version": "0.1.0", "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.7.2", + "@fortawesome/free-solid-svg-icons": "^6.7.2", + "@fortawesome/vue-fontawesome": "^3.0.8", "axios": "^1.7.7", "core-js": "^3.8.3", "vue": "^3.2.13", @@ -1846,6 +1849,49 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz", + "integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", + "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", + "license": "MIT", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.7.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz", + "integrity": "sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA==", + "license": "(CC-BY-4.0 AND MIT)", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.7.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/vue-fontawesome": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-3.0.8.tgz", + "integrity": "sha512-yyHHAj4G8pQIDfaIsMvQpwKMboIZtcHTUvPqXjOHyldh1O1vZfH4W03VDPv5RvI9P6DLTzJQlmVgj9wCf7c2Fw==", + "license": "MIT", + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "vue": ">= 3.0.0 < 4" + } + }, "node_modules/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 535fdff..0dc0288 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,6 +8,9 @@ "lint": "vue-cli-service lint" }, "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.7.2", + "@fortawesome/free-solid-svg-icons": "^6.7.2", + "@fortawesome/vue-fontawesome": "^3.0.8", "axios": "^1.7.7", "core-js": "^3.8.3", "vue": "^3.2.13", diff --git a/frontend/src/components/LatestVideo.vue b/frontend/src/components/LatestVideo.vue index a8919f4..370e916 100644 --- a/frontend/src/components/LatestVideo.vue +++ b/frontend/src/components/LatestVideo.vue @@ -2,95 +2,159 @@

Spanish Word of the Day

-

{{ video.upload_time }}

+

{{ currentVideo.upload_time }}

-
- - -
-
Word of the Day!
-
{{ video.word }}
-
- -
-
- Example Usage -
+ -

Loading videos...

+

Loading videos...

- - - + \ No newline at end of file