diff --git a/backend/config/middleware.py b/backend/config/middleware.py new file mode 100644 index 00000000..8d85e60f --- /dev/null +++ b/backend/config/middleware.py @@ -0,0 +1,13 @@ +"""middleware""" + +from django.conf import settings +from django.utils.deprecation import MiddlewareMixin + + +class StartTimeMiddleware(MiddlewareMixin): + """add a start time header""" + + def __call__(self, request): + response = self.get_response(request) + response["X-Start-Timestamp"] = settings.TA_START + return response diff --git a/backend/config/settings.py b/backend/config/settings.py index f19688e0..04b41879 100644 --- a/backend/config/settings.py +++ b/backend/config/settings.py @@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/3.2/ref/settings/ """ import hashlib +from datetime import datetime from os import environ, path from pathlib import Path @@ -76,6 +77,7 @@ MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "corsheaders.middleware.CorsMiddleware", + "config.middleware.StartTimeMiddleware", "whitenoise.middleware.WhiteNoiseMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", @@ -220,10 +222,13 @@ CORS_ALLOW_CREDENTIALS = True CORS_ALLOW_HEADERS = list(default_headers) + [ "mode", ] +CORS_EXPOSE_HEADERS = ["X-Start-Timestamp"] + # TA application settings TA_UPSTREAM = "https://github.com/tubearchivist/tubearchivist" TA_VERSION = "v0.5.8-unstable" +TA_START = str(int(datetime.now().timestamp())) # API REST_FRAMEWORK = { diff --git a/docker_assets/nginx.conf b/docker_assets/nginx.conf index 8c4a15ec..ee2841ab 100644 --- a/docker_assets/nginx.conf +++ b/docker_assets/nginx.conf @@ -50,17 +50,21 @@ server { root /app/static; index index.html; - location ~* .(?:css|js)$ { + location ~* ^/(?!static/|cache/).*\.(?:css|js|png|jpg|jpeg|gif|ico|svg|woff2?)$ { try_files $uri $uri/ /index.html =404; } location = /index.html { - add_header Cache-Control 'no-store'; + add_header Cache-Control "no-store, no-cache, must-revalidate"; + add_header Pragma "no-cache"; + add_header Expires 0; expires 0; } location / { - add_header Cache-Control 'no-store'; + add_header Cache-Control "no-store, no-cache, must-revalidate"; + add_header Pragma "no-cache"; + add_header Expires 0; try_files $uri $uri/ /index.html =404; } } diff --git a/frontend/src/functions/APIClient.ts b/frontend/src/functions/APIClient.ts index 02a8d1d2..c5c88e81 100644 --- a/frontend/src/functions/APIClient.ts +++ b/frontend/src/functions/APIClient.ts @@ -4,6 +4,7 @@ import getFetchCredentials from '../configuration/getFetchCredentials'; import logOut from '../api/actions/logOut'; import getCookie from './getCookie'; import Routes from '../configuration/routes/RouteList'; +import { useBackendStore } from '../stores/BackendStore'; export interface ApiClientOptions extends Omit { method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'; @@ -44,6 +45,12 @@ const APIClient = async ( ...options, }); + const backendTimestamp = response.headers.get('X-Start-Timestamp'); + if (backendTimestamp) { + const { setStartTimestamp } = useBackendStore.getState(); + setStartTimestamp(backendTimestamp); + } + // Handle common errors if (response.status === 400) { const data = await response.json(); diff --git a/frontend/src/stores/BackendStore.ts b/frontend/src/stores/BackendStore.ts new file mode 100644 index 00000000..a88ff3c5 --- /dev/null +++ b/frontend/src/stores/BackendStore.ts @@ -0,0 +1,18 @@ +import { create } from 'zustand'; + +interface BackendState { + startTimestamp: string | null; + setStartTimestamp: (timestamp: string) => void; +} + +export const useBackendStore = create((set, get) => ({ + startTimestamp: null, + setStartTimestamp: timestamp => { + const prev = get().startTimestamp; + if (prev && prev !== timestamp) { + console.warn('Backend restart detected — reloading frontend...'); + window.location.reload(); + } + set({ startTimestamp: timestamp }); + }, +})); diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 1065e7ce..47fb65ef 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -8,14 +8,22 @@ export default defineConfig({ base: '/', server: { host: true, - port: 3000, // This is the port which we will use in docker - // Thanks @sergiomoura for the window fix - // add the next lines if you're using windows and hot reload doesn't work + port: 3000, watch: { usePolling: true, }, }, build: { sourcemap: true, + outDir: 'dist', + assetsDir: 'assets', + manifest: true, + rollupOptions: { + output: { + entryFileNames: 'assets/[name].[hash].js', + chunkFileNames: 'assets/[name].[hash].js', + assetFileNames: 'assets/[name].[hash].[ext]', + }, + }, }, });