diff --git a/feeds/urls.py b/feeds/urls.py index bc321ba..0f2dfcb 100644 --- a/feeds/urls.py +++ b/feeds/urls.py @@ -9,16 +9,26 @@ from .views import APIView, CustomLoginView, CustomLogoutView, ProfileView, Regi app_name: str = "feeds" +# Normal pages urlpatterns: list[URLPattern] = [ path(route="", view=views.IndexView.as_view(), name="index"), path(route="feed//", view=views.FeedView.as_view(), name="feed"), path(route="feeds/", view=views.FeedsView.as_view(), name="feeds"), path(route="add", view=views.AddView.as_view(), name="add"), path(route="upload", view=views.UploadView.as_view(), name="upload"), - path(route="api/", view=APIView.as_view(), name="api"), path(route="robots.txt", view=cache_page(timeout=60 * 60 * 365)(views.RobotsView.as_view()), name="robots"), ] +# API urls +urlpatterns += [ + path(route="api/", view=APIView.as_view(), name="api"), + path(route="api/feeds/", view=views.APIFeedsView.as_view(), name="api_feeds"), + path(route="api/feeds//", view=views.APIFeedView.as_view(), name="api_feeds_id"), + path(route="api/feeds//entries/", view=views.APIFeedEntriesView.as_view(), name="api_feed_entries"), + path(route="api/entries/", view=views.APIEntriesView.as_view(), name="api_entries"), + path(route="api/entries//", view=views.APIEntryView.as_view(), name="api_entries_id"), +] + # Account urls urlpatterns += [ path(route="accounts/login/", view=CustomLoginView.as_view(), name="login"), diff --git a/feeds/views.py b/feeds/views.py index 414c027..3a5908b 100644 --- a/feeds/views.py +++ b/feeds/views.py @@ -7,7 +7,9 @@ from django.contrib.auth import login from django.contrib.auth.forms import AuthenticationForm, UserCreationForm from django.contrib.auth.views import LoginView, LogoutView, PasswordChangeView from django.contrib.messages.views import SuccessMessageMixin -from django.http import HttpRequest, HttpResponse +from django.core.paginator import EmptyPage, Page, PageNotAnInteger, Paginator +from django.forms.models import model_to_dict +from django.http import HttpRequest, HttpResponse, JsonResponse from django.shortcuts import get_object_or_404, render from django.template import loader from django.urls import reverse_lazy @@ -270,3 +272,178 @@ class RobotsView(View): content="""User-agent: *\nDisallow: /add\nDisallow: /upload\nDisallow: /accounts/""", content_type="text/plain", ) + + +class APIFeedsView(View): + """API Feeds view.""" + + def get(self, request: HttpRequest) -> HttpResponse: + """Get all feeds with pagination.""" + # Retrieve all feeds + feeds_list = Feed.objects.all() + + # Pagination settings + page: int = int(request.GET.get("page", 1)) # Get the page number from the query parameters, default to 1 + per_page: int = int(request.GET.get("per_page", 1000)) # Number of feeds per page, default to 1000 (max 1000) + + # Add a ceiling to the per_page value + max_per_page = 1000 + if per_page > max_per_page: + per_page = max_per_page + + # Create Paginator instance + paginator = Paginator(feeds_list, per_page) + + try: + feeds: Page = paginator.page(page) + except PageNotAnInteger: + # If page is not an integer, deliver first page. + feeds = paginator.page(1) + except EmptyPage: + # If page is out of range (e.g., 9999), deliver last page of results. + feeds = paginator.page(paginator.num_pages) + + # Convert feeds to dictionary + feeds_dict = [model_to_dict(feed) for feed in feeds] + + # Return the paginated entries as JsonResponse + response = JsonResponse(feeds_dict, safe=False) + + # Add pagination headers + response["X-Page"] = feeds.number + response["X-Page-Count"] = paginator.num_pages + response["X-Per-Page"] = per_page + response["X-Total-Count"] = paginator.count + response["X-First-Page"] = 1 + response["X-Last-Page"] = paginator.num_pages + + # Next and previous page links + if feeds.has_next(): + response["X-Next-Page"] = feeds.next_page_number() + if feeds.has_previous(): + response["X-Prev-Page"] = feeds.previous_page_number() + + return response + + +class APIFeedView(View): + """API Feed view.""" + + def get(self, request: HttpRequest, feed_id: int) -> HttpResponse: # noqa: ARG002 + """Get a single feed.""" + feed = get_object_or_404(Feed, id=feed_id) + return JsonResponse(model_to_dict(feed), safe=False) + + +class APIEntriesView(View): + """API Entries view.""" + + def get(self, request: HttpRequest) -> HttpResponse: + """Get all entries with pagination.""" + # Retrieve all entries + entries_list = Entry.objects.all() + + # Pagination settings + page: int = int(request.GET.get("page", 1)) # Get the page number from the query parameters, default to 1 + per_page: int = int(request.GET.get("per_page", 1000)) + + # Add a ceiling to the per_page value + max_per_page = 1000 + if per_page > max_per_page: + per_page = max_per_page + + # Create Paginator instance + paginator = Paginator(entries_list, per_page) + + try: + entries: Page = paginator.page(page) + except PageNotAnInteger: + # If page is not an integer, deliver first page. + entries = paginator.page(1) + except EmptyPage: + # If page is out of range (e.g. 9999), deliver last page of results. + entries = paginator.page(paginator.num_pages) + + # Convert entries to dictionary + entries_dict = [model_to_dict(entry) for entry in entries] + + # Return the paginated entries as JsonResponse + response = JsonResponse(entries_dict, safe=False) + + # Add pagination headers + response["X-Page"] = entries.number + response["X-Page-Count"] = paginator.num_pages + response["X-Per-Page"] = per_page + response["X-Total-Count"] = paginator.count + response["X-First-Page"] = 1 + response["X-Last-Page"] = paginator.num_pages + + # Next and previous page links + if entries.has_next(): + response["X-Next-Page"] = entries.next_page_number() + if entries.has_previous(): + response["X-Prev-Page"] = entries.previous_page_number() + + return response + + +class APIEntryView(View): + """API Entry view.""" + + def get(self, request: HttpRequest, entry_id: int) -> HttpResponse: # noqa: ARG002 + """Get a single entry.""" + entry = get_object_or_404(Entry, id=entry_id) + return JsonResponse(model_to_dict(entry), safe=False) + + +class APIFeedEntriesView(View): + """API Feed Entries view.""" + + def get(self, request: HttpRequest, feed_id: int) -> HttpResponse: + """Get all entries for a single feed with pagination.""" + # Retrieve all entries for a single feed + entries_list = Entry.objects.filter(feed_id=feed_id) + + # Pagination settings + page: int = int(request.GET.get("page", 1)) # Get the page number from the query parameters, default to 1 + per_page: int = int(request.GET.get("per_page", 1000)) + + # Add a ceiling to the per_page value + max_per_page = 1000 + if per_page > max_per_page: + per_page = max_per_page + + # Create Paginator instance + paginator = Paginator(entries_list, per_page) + + try: + entries: Page = paginator.page(page) + except PageNotAnInteger: + # If page is not an integer, deliver first page. + entries = paginator.page(1) + except EmptyPage: + # If page is out of range (e.g. 9999), deliver last page of results. + entries = paginator.page(paginator.num_pages) + + # Convert entries to dictionary + entries_dict = [model_to_dict(entry) for entry in entries] + + # Return the paginated entries as JsonResponse + response = JsonResponse(entries_dict, safe=False) + + # Add pagination headers + response["X-Page"] = entries.number + response["X-Page-Count"] = paginator.num_pages + response["X-Per-Page"] = per_page + response["X-Total-Count"] = paginator.count + response["X-First-Page"] = 1 + response["X-Last-Page"] = paginator.num_pages + + # Next and previous page links + if entries.has_next(): + response["X-Next-Page"] = entries.next_page_number() + if entries.has_previous(): + response["X-Prev-Page"] = entries.previous_page_number() + + return response + diff --git a/templates/api.html b/templates/api.html index 4f2fd1a..3cfb989 100644 --- a/templates/api.html +++ b/templates/api.html @@ -1,5 +1,160 @@ {% extends "base.html" %} {% block content %}

API Documentation

-

This is the API documentation.

+

+ Missing something? Let me know. +

+

Get All Feeds

+

GET /api/feeds/

+

Query Parameters

+
    +
  • + page (optional): The page number of feeds to retrieve. Defaults to 1 if not specified. +
  • +
  • + per_page (optional): The number of feeds per page. Defaults to 1000 if not specified. The maximum value is 1000. +
  • +
+

Headers

+

+ The following headers are sent with the response: +

    +
  • + X-Page: The current page number. +
  • +
  • + X-Page-Count: The total number of pages. +
  • +
  • + X-Per-Page: The number of feeds per page. +
  • +
  • + X-Total-Count: The total number of feeds. +
  • +
  • + X-First-Page: The page number of the first page. +
  • +
  • + X-Last-Page: The page number of the last page. +
  • +
+

+

Python example

+
+import requests
+
+page_num = 1
+per_page = 1000
+
+url = f'https://feedvault.se/api/feeds/?page={page_num}&per_page={per_page}'
+response = requests.get(url)
+print(response.json())
+
+

Example URL

+

+ https://feedvault.se/api/feeds/?page=1&per_page=1000 +

+
+

Get All Entries

+

GET /api/entries/

+

Query Parameters

+
    +
  • + page (optional): The page number of entries to retrieve. Defaults to 1 if not specified. +
  • +
  • + per_page (optional): The number of entries per page. Defaults to 1000 if not specified. The maximum value is 1000. +
  • +
+

Headers

+

+ The following headers are sent with the response: +

    +
  • + X-Page: The current page number. +
  • +
  • + X-Page-Count: The total number of pages. +
  • +
  • + X-Per-Page: The number of feeds per page. +
  • +
  • + X-Total-Count: The total number of feeds. +
  • +
  • + X-First-Page: The page number of the first page. +
  • +
  • + X-Last-Page: The page number of the last page. +
  • +
+

+

Python example

+
+import requests
+
+page_num = 1
+per_page = 1000
+
+url = f'https://feedvault.se/api/entries/?page={page_num}&per_page={per_page}'
+response = requests.get(url)
+print(response.json())
+
+

Example URL

+

+ https://feedvault.se/api/entries/?page=1&per_page=1000 +

+
+

Get All Entries for a Feed

+

GET /api/feeds/{feed_id}/entries/

+

Query Parameters

+
    +
  • + page (optional): The page number of entries to retrieve. Defaults to 1 if not specified. +
  • +
  • + per_page (optional): The number of entries per page. Defaults to 1000 if not specified. The maximum value is 1000. +
  • +
+

Headers

+

+ The following headers are sent with the response: +

    +
  • + X-Page: The current page number. +
  • +
  • + X-Page-Count: The total number of pages. +
  • +
  • + X-Per-Page: The number of feeds per page. +
  • +
  • + X-Total-Count: The total number of feeds. +
  • +
  • + X-First-Page: The page number of the first page. +
  • +
  • + X-Last-Page: The page number of the last page. +
  • +
+

+

Python example

+
+import requests
+
+page_num = 1
+per_page = 1000
+feed_id = 1
+
+url = f'https://feedvault.se/api/feeds/{feed_id}/entries/?page={page_num}&per_page={per_page}'
+response = requests.get(url)
+print(response.json())
+    
+

Example URL

+

+ https://feedvault.se/api/feeds/1/entries/?page=1&per_page=1000 +

{% endblock %} diff --git a/templates/base.html b/templates/base.html index 6b21ef8..bf5402d 100644 --- a/templates/base.html +++ b/templates/base.html @@ -14,7 +14,7 @@ {% endif %}