From 478c7546275c01b774a6d29b61bfdcfd5dd48f8a Mon Sep 17 00:00:00 2001 From: Pascal Repond Date: Thu, 15 Jan 2026 20:51:27 +0100 Subject: [PATCH] feat: add basic PWA support - Enhances the responsivity of the app (login form, media list). - Also updates dependencies to address security vulnerabilities. --- src/config/urls.py | 13 + src/static/images/icons/icon-192.png | Bin 0 -> 11824 bytes src/static/images/icons/icon-512.png | Bin 0 -> 21540 bytes src/static/js/base.js | 14 + src/static/js/service-worker.js | 90 ++++++ src/static/manifest.json | 23 ++ src/templates/base/base.html | 2 + src/templates/base/media_index.html | 2 +- .../partials/media_items/media_item.html | 2 +- src/templates/registration/login.html | 12 +- src/theme/static_src/package-lock.json | 6 +- src/theme/static_src/src/styles.css | 7 + uv.lock | 290 +++++++++--------- 13 files changed, 313 insertions(+), 148 deletions(-) create mode 100644 src/static/images/icons/icon-192.png create mode 100644 src/static/images/icons/icon-512.png create mode 100644 src/static/js/service-worker.js create mode 100644 src/static/manifest.json diff --git a/src/config/urls.py b/src/config/urls.py index 8787d0b..4aac360 100644 --- a/src/config/urls.py +++ b/src/config/urls.py @@ -1,10 +1,23 @@ from django.conf import settings from django.conf.urls.static import static from django.contrib import admin +from django.http import HttpResponse, HttpResponseNotFound from django.urls import include, path, re_path from django.views.static import serve + +def service_worker(_request): + """Serve the service worker at the root scope.""" + sw_path = settings.BASE_DIR / "static" / "js" / "service-worker.js" + try: + with sw_path.open() as f: + return HttpResponse(f.read(), content_type="application/javascript") + except (FileNotFoundError, PermissionError): + return HttpResponseNotFound("Service worker not found") + + urlpatterns = [ + path("service-worker.js", service_worker, name="service_worker"), path("", include("core.urls")), path("accounts/", include("accounts.urls")), path("accounts/", include("django.contrib.auth.urls")), diff --git a/src/static/images/icons/icon-192.png b/src/static/images/icons/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..8ec27e9e3662eaba1cf2e1e7eb03fb920df3dd4a GIT binary patch literal 11824 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 { + console.log('Service Worker registered with scope:', registration.scope); + }) + .catch((error) => { + console.error('Service Worker registration failed:', error); + }); + } +} + // INITIALIZE ALL FEATURES ON DOM READY document.addEventListener('DOMContentLoaded', function() { initThemeSwitcher(); cleanUrlParameters(); initToastMessages(); + registerServiceWorker(); }); diff --git a/src/static/js/service-worker.js b/src/static/js/service-worker.js new file mode 100644 index 0000000..f82188e --- /dev/null +++ b/src/static/js/service-worker.js @@ -0,0 +1,90 @@ +const CACHE_NAME = 'datakult-v1'; +const STATIC_ASSETS = [ + '/', + '/static/manifest.json', + '/static/images/bookshelf.png', + '/static/images/icons/icon-192.png', + '/static/images/icons/icon-512.png', +]; + +// Install event: cache static assets +self.addEventListener('install', (event) => { + event.waitUntil( + caches.open(CACHE_NAME).then((cache) => { + return cache.addAll(STATIC_ASSETS); + }) + ); + self.skipWaiting(); +}); + +// Activate event: clean up old caches +self.addEventListener('activate', (event) => { + event.waitUntil( + caches.keys().then((cacheNames) => { + return Promise.all( + cacheNames + .filter((name) => name !== CACHE_NAME) + .map((name) => caches.delete(name)) + ); + }) + ); + self.clients.claim(); +}); + +// Fetch event: network-first strategy for HTML, cache-first for static assets +self.addEventListener('fetch', (event) => { + const { request } = event; + const url = new URL(request.url); + + // Skip non-GET requests + if (request.method !== 'GET') { + return; + } + + // Skip cross-origin requests + if (url.origin !== location.origin) { + return; + } + + // For HTML pages: network-first (try network, fallback to cache) + if (request.headers.get('Accept')?.includes('text/html')) { + event.respondWith( + fetch(request) + .then((response) => { + if (response.ok) { + const responseClone = response.clone(); + caches.open(CACHE_NAME).then((cache) => { + cache.put(request, responseClone); + }); + } + return response; + }) + .catch(() => { + // Fallback to cache if network fails + return caches.match(request); + }) + ); + return; + } + + // For static assets: cache-first (try cache, fallback to network) + if (url.pathname.startsWith('/static/')) { + event.respondWith( + caches.match(request).then((cachedResponse) => { + if (cachedResponse) { + return cachedResponse; + } + return fetch(request).then((response) => { + if (response.ok) { + const responseClone = response.clone(); + caches.open(CACHE_NAME).then((cache) => { + cache.put(request, responseClone); + }); + } + return response; + }); + }) + ); + return; + } +}); diff --git a/src/static/manifest.json b/src/static/manifest.json new file mode 100644 index 0000000..978ac89 --- /dev/null +++ b/src/static/manifest.json @@ -0,0 +1,23 @@ +{ + "name": "Datakult", + "short_name": "Datakult", + "description": "Application de gestion de médias", + "start_url": "/", + "display": "standalone", + "background_color": "#1d232a", + "theme_color": "#1d232a", + "icons": [ + { + "src": "/static/images/icons/icon-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/static/images/icons/icon-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any" + } + ] +} diff --git a/src/templates/base/base.html b/src/templates/base/base.html index ea125c3..5c06d44 100644 --- a/src/templates/base/base.html +++ b/src/templates/base/base.html @@ -18,6 +18,8 @@ + + {% tailwind_css %} {% htmx_script %} {% block extra_css %} diff --git a/src/templates/base/media_index.html b/src/templates/base/media_index.html index a50daf2..ea7491b 100644 --- a/src/templates/base/media_index.html +++ b/src/templates/base/media_index.html @@ -35,7 +35,7 @@

{% translate "My media" %}

{% if filters.review_to %}{% endif %} {% if filters.has_review %}{% endif %} {% if filters.has_cover %}{% endif %} -