diff --git a/feeds/urls.py b/feeds/urls.py
index 9c73db4..1bb9f8b 100644
--- a/feeds/urls.py
+++ b/feeds/urls.py
@@ -4,6 +4,8 @@ from django.urls import URLPattern, path
from feeds import views
+from .views import APIView, CustomLoginView, CustomLogoutView, ProfileView, RegisterView
+
app_name: str = "feeds"
urlpatterns: list[URLPattern] = [
@@ -11,4 +13,15 @@ urlpatterns: list[URLPattern] = [
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"),
+]
+
+# Account urls
+urlpatterns += [
+ path(route="accounts/login/", view=CustomLoginView.as_view(), name="login"),
+ path(route="accounts/register/", view=RegisterView.as_view(), name="register"),
+ path(route="accounts/logout/", view=CustomLogoutView.as_view(), name="logout"),
+ # path(route="accounts/change-password/", view=CustomPasswordChangeView.as_view(), name="change_password"),
+ path(route="accounts/profile/", view=ProfileView.as_view(), name="profile"),
]
diff --git a/feeds/views.py b/feeds/views.py
index a018c9a..176a063 100644
--- a/feeds/views.py
+++ b/feeds/views.py
@@ -1,16 +1,27 @@
from __future__ import annotations
+from typing import TYPE_CHECKING
+
from django.contrib import messages
+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.shortcuts import get_object_or_404, render
from django.template import loader
+from django.urls import reverse_lazy
from django.views import View
+from django.views.generic.edit import CreateView
from django.views.generic.list import ListView
from feeds.add_feeds import add_feed
from feeds.models import Entry, Feed
from feeds.stats import get_db_size
+if TYPE_CHECKING:
+ from django.contrib.auth.models import User
+
class IndexView(View):
"""Index path."""
@@ -93,10 +104,6 @@ class AddView(View):
def post(self, request: HttpRequest) -> HttpResponse:
"""Add a feed."""
-
- # Temporary turn off the /add page.
- return HttpResponse(content="Not available", status=404)
-
urls: str | None = request.POST.get("urls", None)
if not urls:
return HttpResponse(content="No urls", status=400)
@@ -116,3 +123,110 @@ class AddView(View):
# Render the index page.
template = loader.get_template(template_name="index.html")
return HttpResponse(content=template.render(context={}, request=request))
+
+
+class UploadView(View):
+ """Upload a file."""
+
+ def get(self, request: HttpRequest) -> HttpResponse:
+ """Load the index page."""
+ template = loader.get_template(template_name="index.html")
+ context = {
+ "db_size": get_db_size(),
+ "amount_of_feeds": Feed.objects.count(),
+ "description": "FeedVault allows users to archive and search their favorite web feeds.",
+ "keywords": "feed, rss, atom, archive, rss list",
+ "author": "TheLovinator",
+ "canonical": "https://feedvault.se/",
+ }
+ return HttpResponse(content=template.render(context=context, request=request))
+
+ def post(self, request: HttpRequest) -> HttpResponse:
+ """Upload a file."""
+ file = request.FILES.get("file", None)
+ if not file:
+ return HttpResponse(content="No file", status=400)
+
+ # Split the urls by newline.
+ for url in file.read().decode("utf-8").split("\n"):
+ feed: None | Feed = add_feed(url)
+ if not feed:
+ messages.error(request, f"{url} - Failed to add")
+ continue
+ # Check if bozo is true.
+ if feed.bozo:
+ messages.warning(request, f"{feed.feed_url} - Bozo: {feed.bozo_exception}")
+
+ messages.success(request, f"{feed.feed_url} added")
+
+ # Render the index page.
+ template = loader.get_template(template_name="index.html")
+ return HttpResponse(content=template.render(context={}, request=request))
+
+
+class CustomLoginView(LoginView):
+ """Custom login view."""
+
+ template_name = "accounts/login.html"
+
+ def form_valid(self, form: AuthenticationForm) -> HttpResponse:
+ """Check if the form is valid."""
+ user: User = form.get_user()
+ login(self.request, user)
+ return super().form_valid(form)
+
+
+class RegisterView(CreateView):
+ """Register view."""
+
+ template_name = "accounts/register.html"
+ form_class = UserCreationForm
+ success_url = reverse_lazy("feeds:login")
+
+
+class CustomLogoutView(LogoutView):
+ """Logout view."""
+
+ next_page = "feeds:index" # Redirect to index after logout
+
+
+class CustomPasswordChangeView(SuccessMessageMixin, PasswordChangeView):
+ """Custom password change view."""
+
+ template_name = "accounts/change_password.html"
+ success_url = reverse_lazy("feeds:index")
+ success_message = "Your password was successfully updated!"
+
+
+class ProfileView(View):
+ """Profile page."""
+
+ def get(self, request: HttpRequest) -> HttpResponse:
+ """Load the profile page."""
+ template = loader.get_template(template_name="accounts/profile.html")
+ context = {
+ "db_size": get_db_size(),
+ "amount_of_feeds": Feed.objects.count(),
+ "description": "FeedVault allows users to archive and search their favorite web feeds.",
+ "keywords": "feed, rss, atom, archive, rss list",
+ "author": "TheLovinator",
+ "canonical": "https://feedvault.se/",
+ }
+ return HttpResponse(content=template.render(context=context, request=request))
+
+
+class APIView(View):
+ """API documentation page."""
+
+ def get(self, request: HttpRequest) -> HttpResponse:
+ """Load the API page."""
+ template = loader.get_template(template_name="api.html")
+ context = {
+ "db_size": get_db_size(),
+ "amount_of_feeds": Feed.objects.count(),
+ "description": "FeedVault allows users to archive and search their favorite web feeds.",
+ "keywords": "feed, rss, atom, archive, rss list",
+ "author": "TheLovinator",
+ "canonical": "https://feedvault.se/api/",
+ }
+ return HttpResponse(content=template.render(context=context, request=request))
diff --git a/feedvault/asgi.py b/feedvault/asgi.py
deleted file mode 100644
index a1ac775..0000000
--- a/feedvault/asgi.py
+++ /dev/null
@@ -1,15 +0,0 @@
-"""ASGI config for feedvault project.
-
-It exposes the ASGI callable as a module-level variable named ``application``.
-
-For more information on this file, see
-https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
-"""
-
-import os
-
-from django.core.asgi import get_asgi_application
-
-os.environ.setdefault("DJANGO_SETTINGS_MODULE", "feedvault.settings")
-
-application = get_asgi_application()
diff --git a/feedvault/settings.py b/feedvault/settings.py
index 137cbca..e5f002f 100644
--- a/feedvault/settings.py
+++ b/feedvault/settings.py
@@ -171,3 +171,5 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
# Our site ID
SITE_ID = 1
+
+PASSWORD_HASHERS: list[str] = ["django.contrib.auth.hashers.Argon2PasswordHasher"]
diff --git a/poetry.lock b/poetry.lock
index ae094ac..e805428 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,5 +1,62 @@
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
+[[package]]
+name = "argon2-cffi"
+version = "23.1.0"
+description = "Argon2 for Python"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea"},
+ {file = "argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08"},
+]
+
+[package.dependencies]
+argon2-cffi-bindings = "*"
+
+[package.extras]
+dev = ["argon2-cffi[tests,typing]", "tox (>4)"]
+docs = ["furo", "myst-parser", "sphinx", "sphinx-copybutton", "sphinx-notfound-page"]
+tests = ["hypothesis", "pytest"]
+typing = ["mypy"]
+
+[[package]]
+name = "argon2-cffi-bindings"
+version = "21.2.0"
+description = "Low-level CFFI bindings for Argon2"
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3"},
+ {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367"},
+ {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d"},
+ {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae"},
+ {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c"},
+ {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86"},
+ {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f"},
+ {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e"},
+ {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082"},
+ {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f"},
+ {file = "argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93"},
+ {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3e385d1c39c520c08b53d63300c3ecc28622f076f4c2b0e6d7e796e9f6502194"},
+ {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3e3cc67fdb7d82c4718f19b4e7a87123caf8a93fde7e23cf66ac0337d3cb3f"},
+ {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a22ad9800121b71099d0fb0a65323810a15f2e292f2ba450810a7316e128ee5"},
+ {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9f8b450ed0547e3d473fdc8612083fd08dd2120d6ac8f73828df9b7d45bb351"},
+ {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:93f9bf70084f97245ba10ee36575f0c3f1e7d7724d67d8e5b08e61787c320ed7"},
+ {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583"},
+ {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d"},
+ {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670"},
+ {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb"},
+ {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a"},
+]
+
+[package.dependencies]
+cffi = ">=1.0.1"
+
+[package.extras]
+dev = ["cogapp", "pre-commit", "pytest", "wheel"]
+tests = ["pytest"]
+
[[package]]
name = "asgiref"
version = "3.7.2"
@@ -14,6 +71,70 @@ files = [
[package.extras]
tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
+[[package]]
+name = "cffi"
+version = "1.16.0"
+description = "Foreign Function Interface for Python calling C code."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"},
+ {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"},
+ {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"},
+ {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"},
+ {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"},
+ {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"},
+ {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"},
+ {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"},
+ {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"},
+ {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"},
+ {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"},
+ {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"},
+ {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"},
+ {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"},
+ {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"},
+ {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"},
+ {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"},
+ {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"},
+ {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"},
+ {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"},
+ {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"},
+ {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"},
+ {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"},
+ {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"},
+ {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"},
+ {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"},
+ {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"},
+]
+
+[package.dependencies]
+pycparser = "*"
+
[[package]]
name = "click"
version = "8.1.7"
@@ -66,6 +187,7 @@ files = [
]
[package.dependencies]
+argon2-cffi = {version = ">=19.1.0", optional = true, markers = "extra == \"argon2\""}
asgiref = ">=3.7.0,<4"
sqlparse = ">=0.3.1"
tzdata = {version = "*", markers = "sys_platform == \"win32\""}
@@ -180,13 +302,13 @@ six = ">=1.13.0"
[[package]]
name = "json5"
-version = "0.9.14"
+version = "0.9.17"
description = "A Python implementation of the JSON5 data format."
optional = false
-python-versions = "*"
+python-versions = ">=3.8"
files = [
- {file = "json5-0.9.14-py2.py3-none-any.whl", hash = "sha256:740c7f1b9e584a468dbb2939d8d458db3427f2c93ae2139d05f47e453eae964f"},
- {file = "json5-0.9.14.tar.gz", hash = "sha256:9ed66c3a6ca3510a976a9ef9b8c0787de24802724ab1860bc0153c7fdd589b02"},
+ {file = "json5-0.9.17-py2.py3-none-any.whl", hash = "sha256:f8ec1ecf985951d70f780f6f877c4baca6a47b6e61e02c4cd190138d10a7805a"},
+ {file = "json5-0.9.17.tar.gz", hash = "sha256:717d99d657fa71b7094877b1d921b1cce40ab444389f6d770302563bb7dfd9ae"},
]
[package.extras]
@@ -312,6 +434,17 @@ files = [
{file = "psycopg_binary-3.1.18-cp39-cp39-win_amd64.whl", hash = "sha256:d4422af5232699f14b7266a754da49dc9bcd45eba244cf3812307934cd5d6679"},
]
+[[package]]
+name = "pycparser"
+version = "2.21"
+description = "C parser in Python"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+files = [
+ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
+ {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
+]
+
[[package]]
name = "python-dotenv"
version = "1.0.1"
@@ -595,4 +728,4 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = "^3.12"
-content-hash = "a54ee43c5cdb06ea0c2bef22d740942fc29a208de5e9aab46d54bacfdead3b5b"
+content-hash = "77057a901adc104b3834fccf3755cd80c62d9e01f2ced853c75cfa9a25d4796d"
diff --git a/pyproject.toml b/pyproject.toml
index e9f1f3f..74dff7d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,7 +7,7 @@ readme = "README.md"
[tool.poetry.dependencies]
python = "^3.12"
-django = "^5.0.2"
+django = {extras = ["argon2"], version = "^5.0.2"}
python-dotenv = "^1.0.1"
feedparser = "^6.0.11"
psycopg = {extras = ["binary"], version = "^3.1.18"}
diff --git a/templates/accounts/change_password.html b/templates/accounts/change_password.html
new file mode 100644
index 0000000..3b56031
--- /dev/null
+++ b/templates/accounts/change_password.html
@@ -0,0 +1,9 @@
+{% extends "base.html" %}
+{% block content %}
+ Change Password
+
+{% endblock %}
diff --git a/templates/accounts/login.html b/templates/accounts/login.html
new file mode 100644
index 0000000..b48f0d3
--- /dev/null
+++ b/templates/accounts/login.html
@@ -0,0 +1,12 @@
+{% extends "base.html" %}
+{% block content %}
+
+ You can register here .
+
+ Login
+
+{% endblock %}
diff --git a/templates/accounts/profile.html b/templates/accounts/profile.html
new file mode 100644
index 0000000..47fa4b1
--- /dev/null
+++ b/templates/accounts/profile.html
@@ -0,0 +1,27 @@
+{% extends "base.html" %}
+{% block content %}
+ {{ user.username }}
+ Feeds
+
+ Subscriptions
+
+ Subscribers
+
+
+
+{% endblock %}
diff --git a/templates/accounts/register.html b/templates/accounts/register.html
new file mode 100644
index 0000000..8e5afd6
--- /dev/null
+++ b/templates/accounts/register.html
@@ -0,0 +1,9 @@
+{% extends "base.html" %}
+{% block content %}
+ Register
+
+{% endblock %}
diff --git a/templates/api.html b/templates/api.html
new file mode 100644
index 0000000..4f2fd1a
--- /dev/null
+++ b/templates/api.html
@@ -0,0 +1,5 @@
+{% extends "base.html" %}
+{% block content %}
+ API Documentation
+ This is the API documentation.
+{% endblock %}
diff --git a/templates/base.html b/templates/base.html
index c77376e..6b21ef8 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -82,19 +82,15 @@
{% endif %}
Archive of
web feeds .
- {% if amount_of_feeds %}
- {{ amount_of_feeds }} feeds.
- {% else %}
- 0 feeds.
- {% endif %}
- ~{{ db_size }}.
+ {% if amount_of_feeds %}{{ amount_of_feeds }} feeds.{% endif %}
+ {% if db_size %}~{{ db_size }}.{% endif %}
@@ -108,12 +104,20 @@
@@ -127,7 +131,7 @@
diff --git a/templates/index.html b/templates/index.html
index 9d7be4f..527a346 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -1,20 +1,39 @@
{% extends "base.html" %}
{% block content %}
- Feeds to archive
-
- Input the URLs of the feeds you wish to archive below. You can add as many as needed, and access them through the website or API. Alternatively, include links to .opml files, and the feeds within will be archived.
-
-
-
-
You can also upload .opml files containing the feeds you wish to archive:
-
-
- Upload OPML
-
+ {% if user.is_authenticated %}
+
Welcome, {{ user.username }}!
+
+ Input the URLs of the feeds you wish to archive below. You can add as many as needed, and access them through the website or API. Alternatively, include links to .opml files, and the feeds within will be archived.
+
+
+ {% csrf_token %}
+
+ Add feeds
+
+
+
Upload
+
+ You can also upload files containing the feeds you wish to archive.
+ Currently supported file formats: .opml, .xml, .json. Your file will be parsed in the future if not currently supported. Feel free to upload databases, backups, or any other files containing feeds. The service will parse the files and add the feeds to the archive.
+
+
+ {% csrf_token %}
+
+
+
+ Upload feeds
+
+ {% else %}
+
Welcome to FeedVault!
+
+ FeedVault is a service that archives web feeds. It allows users to access and search for historical content from various websites. The service is designed to preserve the history of the web and provide a reliable source for accessing content that may no longer be available on the original websites.
+
+
+ You need to login or register to add new feeds or upload files.
+
+ {% endif %}
FAQ
What are web feeds?
@@ -39,13 +58,6 @@
-
- How does it work?
-
- FeedVault is written in Go and uses the gofeed library to parse The service periodically checks for new content in the feeds and stores it in a database. Users can access the archived feeds through the website or API.
-
-
-
How can I access the archived feeds?