diff --git a/frontend/package-lock.json b/frontend/package-lock.json index eabb79d..683a9c3 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,7 @@ "@next/font": "^14.2.15", "next": "^15.3.3", "oidc-client-ts": "^3.3.0", + "openapi-typescript-fetch": "^2.2.1", "react": "^19.1.0", "react-dom": "^19.1.0", "react-oidc-context": "^3.3.0" @@ -1532,6 +1533,16 @@ "node": ">=18" } }, + "node_modules/openapi-typescript-fetch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/openapi-typescript-fetch/-/openapi-typescript-fetch-2.2.1.tgz", + "integrity": "sha512-aBp1cR5FTNxp4HA8bb2ST53aIqEiJgoOMyXiyzKi6YF7vogW8KkyyUQ1FeDz8D05uspxFrKvFbkVU2YiiKkULA==", + "license": "MIT", + "engines": { + "node": ">= 12.0.0", + "npm": ">= 7.0.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 1c34c1b..def9f10 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -3,6 +3,7 @@ "@next/font": "^14.2.15", "next": "^15.3.3", "oidc-client-ts": "^3.3.0", + "openapi-typescript-fetch": "^2.2.1", "react": "^19.1.0", "react-dom": "^19.1.0", "react-oidc-context": "^3.3.0" diff --git a/frontend/src/app/users/page.tsx b/frontend/src/app/users/page.tsx index a031dcc..57a5729 100644 --- a/frontend/src/app/users/page.tsx +++ b/frontend/src/app/users/page.tsx @@ -1,20 +1,15 @@ "use client"; import { getUser } from "muzak/data/fetch"; import { useEffect, useState } from "react"; +import api from "muzak/data/fetcher"; export default function Page() { - const [userData, setUserData] = useState([]); + const [userData, setUserData] = useState([]); useEffect(() => { async function getUsers() { - const user = getUser(); - const access_token = user?.access_token; - const headers = new Headers(); - headers.set("Authorization", `Bearer ${access_token}`); - headers.set("Content-Type", "application/json; charset=utf-8"); - const users = await fetch("http://127.0.0.1:8001/api/user/", { headers }); - const userData = await users.json(); - setUserData(userData); + const { data: users } = await api.users.list({}); + setUserData(users); } getUsers(); }, []); diff --git a/frontend/src/data/fetcher.ts b/frontend/src/data/fetcher.ts new file mode 100644 index 0000000..9d7f2a3 --- /dev/null +++ b/frontend/src/data/fetcher.ts @@ -0,0 +1,91 @@ +"use client"; +import { Fetcher } from "openapi-typescript-fetch"; +import { getUser } from "./fetch"; + +interface GenericPaths { + [path: string]: { + [method: string]: any; + }; +} +const fetcher = Fetcher.for(); +const BASE_URL = process.env.BACKEND_HOST || "http://127.0.0.1:8001"; + +// Configure the fetcher +fetcher.configure({ + baseUrl: BASE_URL, + init: { + headers: { + "Content-Type": "application/json", + }, + }, + use: [ + // Authentication middleware + async (url, init, next) => { + const user = getUser(); + if (user?.access_token) { + init.headers = { + ...init.headers, + Authorization: `Bearer ${user.access_token}`, + }; + } + return next(url, init); + }, + // Error handling middleware + async (url, init, next) => { + const response = await next(url, init); + + if (!response.ok) { + // Handle authentication errors + if (response.status === 401) { + console.warn("Authentication failed - token may be expired"); + // You might want to trigger a redirect to login or token refresh here + } + + // Log other errors + console.error(`API Error: ${response.status} ${response.statusText}`, { + url, + method: init.method, + }); + } + + return response; + }, + ], +}); + +// Create your API methods +export const api = { + tracks: { + list: fetcher.path("/api/track/").method("get").create(), + detail: fetcher.path("/api/track/{id}").method("get").create(), + }, + artists: { + list: fetcher.path("/api/artist").method("get").create(), + detail: fetcher.path("/api/artist/{id}").method("get").create(), + }, + albums: { + list: fetcher.path("/api/album/").method("get").create(), + detail: fetcher.path("/api/album/{id}").method("get").create(), + }, + votes: { + list: fetcher.path("/api/vote/").method("get").create(), + detail: fetcher.path("/api/vote/{id}").method("get").create(), + userVotes: fetcher.path("/api/user/{user_id}/votes").method("get").create(), + }, + users: { + list: fetcher.path("/api/user/").method("get").create(), + detail: fetcher.path("/api/user/{id}").method("get").create(), + }, + profiles: { + detail: fetcher.path("/api/profile/{id}").method("get").create(), + backgrounds: { + list: fetcher.path("/api/profile/background").method("get").create(), + detail: fetcher + .path("/api/profile/background/{id}") + .method("get") + .create(), + }, + }, +}; + +export default api;