diff --git a/Makefile b/Makefile index 43067d4..e6882e6 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,10 @@ .PHONY: build frontend-build dev clean test build: frontend-build - CGO_ENABLED=0 go build -o turnpike . + CGO_ENABLED=0 go build -ldflags "-X main.buildID=$$(git rev-parse --short HEAD)" -o turnpike . frontend-build: - cd frontend && npm ci && npm run build + cd frontend && npm ci && BUILD_ID=$$(git rev-parse --short HEAD) npm run build dev: @echo "Run in two terminals:" diff --git a/frontend/.gitignore b/frontend/.gitignore index a547bf3..7e445ac 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -8,6 +8,7 @@ pnpm-debug.log* lerna-debug.log* node_modules +.vite dist dist-ssr *.local diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index a2cdf76..04aa9bd 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -17,14 +17,27 @@ import Nav from './components/Nav.svelte' import SyncStatus from './components/SyncStatus.svelte' + const clientBuild = __BUILD_ID__ + let session = $state(null) let loading = $state(true) let route = $state(window.location.hash || '#/') + let updateAvailable = $state(false) // Check if this is a kiosk token URL before doing anything else const kioskToken = $derived(window.location.hash.match(/^#\/v\/([A-Z0-9]+)/i)?.[1] ?? '') + async function checkVersion() { + try { + const res = await fetch('/api/version') + const { build } = await res.json() + if (build && build !== clientBuild) updateAvailable = true + } catch {} + } + onMount(async () => { + checkVersion() + // Kiosk pages don't need auth if (kioskToken) { loading = false @@ -40,6 +53,9 @@ window.addEventListener('hashchange', () => { route = window.location.hash || '#/' }) + + // Periodically check for updates + setInterval(checkVersion, 60000) }) function onLogin(s) { @@ -58,6 +74,13 @@ const role = $derived(session?.user?.role ?? '') +{#if updateAvailable} +
+{/if} + {#if loading} {:else if kioskToken} @@ -100,3 +123,45 @@ {/if} + + + + diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 0f71087..69405ba 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -3,6 +3,9 @@ import { svelte } from '@sveltejs/vite-plugin-svelte' export default defineConfig({ plugins: [svelte()], + define: { + __BUILD_ID__: JSON.stringify(process.env.BUILD_ID || 'dev'), + }, server: { proxy: { '/api': 'http://localhost:8180', diff --git a/main.go b/main.go index 2fabeb6..aca8086 100644 --- a/main.go +++ b/main.go @@ -12,6 +12,8 @@ import ( "os" ) +var buildID = "dev" + //go:embed frontend/dist var frontendFS embed.FS @@ -145,6 +147,10 @@ func (app *App) registerRoutes(mux *http.ServeMux) { mux.HandleFunc("GET /api/sync/pull", auth(app.handleSyncPull)) mux.HandleFunc("GET /api/sync/stream", auth(app.handleSyncStream)) + mux.HandleFunc("GET /api/version", func(w http.ResponseWriter, r *http.Request) { + writeJSON(w, map[string]string{"build": buildID}) + }) + // Kiosk — authenticated by volunteer token, no JWT required. mux.HandleFunc("GET /api/v/{token}", app.handleKioskGet) mux.HandleFunc("POST /api/v/{token}/shifts/{id}", app.handleKioskClaim)