From 994ba0bc01be57134706d2ad6f1310cb5e03af65 Mon Sep 17 00:00:00 2001 From: Frank Agerholm Date: Sun, 25 Jan 2026 17:00:59 +0100 Subject: [PATCH] frontend: Owner management --- frontend/app/app.vue | 2 + frontend/app/composables/useAPI.ts | 59 ++++++++++++++++ frontend/app/composables/useAuth.ts | 41 +++++++++-- frontend/app/layouts/default.vue | 27 ++++++- frontend/app/middleware/auth.global.ts | 11 +++ frontend/app/pages/login.vue | 64 +++++++++++++++++ frontend/app/pages/owners.vue | 19 ----- frontend/app/pages/owners/index.vue | 97 ++++++++++++++++++++++++++ 8 files changed, 293 insertions(+), 27 deletions(-) create mode 100644 frontend/app/composables/useAPI.ts create mode 100644 frontend/app/middleware/auth.global.ts create mode 100644 frontend/app/pages/login.vue delete mode 100644 frontend/app/pages/owners.vue create mode 100644 frontend/app/pages/owners/index.vue diff --git a/frontend/app/app.vue b/frontend/app/app.vue index ea4ef5c..a156593 100644 --- a/frontend/app/app.vue +++ b/frontend/app/app.vue @@ -1,3 +1,5 @@ diff --git a/frontend/app/composables/useAPI.ts b/frontend/app/composables/useAPI.ts new file mode 100644 index 0000000..65a6300 --- /dev/null +++ b/frontend/app/composables/useAPI.ts @@ -0,0 +1,59 @@ +export function useApi() { + const config = useRuntimeConfig() + const { token, logout } = useAuth() + + async function apiFetch(url: string, options: any = {}) { + try { + return await $fetch(url, { + baseURL: config.public.apiBase, + headers: { + ...(options.headers || {}), + ...(token.value ? { Authorization: `Bearer ${token.value}` } : {}) + }, + ...options + }) + } catch (error: any) { + // Netzwerkfehler oder Server nicht erreichbar + if (!error.response) { + console.error("API unreachable:", error) + throw new Error("Der Server ist nicht erreichbar.") + } + + const status = error.response.status + const data = error.response._data + + // 401 → Token ungültig oder abgelaufen + if (status === 401) { + console.warn("API returned 401 — logging out") + logout() + throw new Error("Deine Sitzung ist abgelaufen. Bitte erneut einloggen.") + } + + // 403 → keine Berechtigung + if (status === 403) { + throw new Error("Du hast keine Berechtigung für diese Aktion.") + } + + // 400 → Validierungsfehler + if (status === 400) { + throw new Error(data?.detail || "Ungültige Eingabe.") + } + + // 404 → nicht gefunden + if (status === 404) { + throw new Error("Die angeforderte Ressource wurde nicht gefunden.") + } + + // 500 → Serverfehler + if (status >= 500) { + console.error("Server error:", data) + throw new Error("Interner Serverfehler. Bitte später erneut versuchen.") + } + + // Fallback + throw new Error(data?.detail || "Unbekannter Fehler.") + } + } + + return { apiFetch } +} diff --git a/frontend/app/composables/useAuth.ts b/frontend/app/composables/useAuth.ts index 3acb2c5..15e3cd2 100644 --- a/frontend/app/composables/useAuth.ts +++ b/frontend/app/composables/useAuth.ts @@ -1,15 +1,46 @@ +function decodeJwt(token: string) { + try { + const payload = token.split(".")[1] + const decoded = JSON.parse(atob(payload)) + return decoded + } catch { + return null + } +} export function useAuth() { - const token = useState("token", () => null) + const token = useState("token", () => null) function setToken(t: string) { token.value = t - localStorage.setItem("token", t) + + if (typeof window !== "undefined") { + localStorage.setItem("token", t) + } } function loadToken() { - token.value = localStorage.getItem("token") + if (typeof window !== "undefined") { + const stored = localStorage.getItem("token") + if (!stored) return + + const payload = decodeJwt(stored) + if( !payload || !payload.exp || payload.exp * 1000 < Date.now()) { + console.info("Token expired - removing it from storage") + localStorage.removeItem("token") + token.value = null + } + token.value = stored + + } } - return { token, setToken, loadToken } -} + function logout() { + token.value = null + if (typeof window !== "undefined") { + localStorage.removeItem("token") + } + } + + return { token, setToken, loadToken, logout } +} diff --git a/frontend/app/layouts/default.vue b/frontend/app/layouts/default.vue index 1a115bb..6b9cc50 100644 --- a/frontend/app/layouts/default.vue +++ b/frontend/app/layouts/default.vue @@ -1,12 +1,34 @@ + + - diff --git a/frontend/app/middleware/auth.global.ts b/frontend/app/middleware/auth.global.ts new file mode 100644 index 0000000..9e713e2 --- /dev/null +++ b/frontend/app/middleware/auth.global.ts @@ -0,0 +1,11 @@ +export default defineNuxtRouteMiddleware((to) => { + if (import.meta.server) return // SSR ignorieren + // allow login-page + + const { token, loadToken } = useAuth() + loadToken() + + if (!token.value && to.path !== "/login") { + return navigateTo("/login") + } +}) diff --git a/frontend/app/pages/login.vue b/frontend/app/pages/login.vue new file mode 100644 index 0000000..dce2885 --- /dev/null +++ b/frontend/app/pages/login.vue @@ -0,0 +1,64 @@ + + + diff --git a/frontend/app/pages/owners.vue b/frontend/app/pages/owners.vue deleted file mode 100644 index 1a6bf69..0000000 --- a/frontend/app/pages/owners.vue +++ /dev/null @@ -1,19 +0,0 @@ - - - - diff --git a/frontend/app/pages/owners/index.vue b/frontend/app/pages/owners/index.vue new file mode 100644 index 0000000..7ee4eaa --- /dev/null +++ b/frontend/app/pages/owners/index.vue @@ -0,0 +1,97 @@ + + +