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 %}