mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2025-12-20 20:29:06 +00:00
refactor: migrate from @hey-api/openapi-ts to openapi-react-query
- Replace @hey-api/openapi-ts with openapi-typescript and openapi-react-query - Generate TypeScript types from OpenAPI spec - Set up React Query infrastructure with QueryClientProvider - Migrate all API hooks to use React Query patterns - Maintain backward compatibility for existing components - Remove old API infrastructure and dependencies
This commit is contained in:
33
www/app/lib/ApiAuthProvider.tsx
Normal file
33
www/app/lib/ApiAuthProvider.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useContext } from "react";
|
||||
import { client, configureApiAuth } from "./apiClient";
|
||||
import useSessionAccessToken from "./useSessionAccessToken";
|
||||
import { DomainContext } from "../domainContext";
|
||||
|
||||
export function ApiAuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const { accessToken } = useSessionAccessToken();
|
||||
const { api_url } = useContext(DomainContext);
|
||||
|
||||
useEffect(() => {
|
||||
// Configure base URL
|
||||
if (api_url) {
|
||||
client.use({
|
||||
onRequest({ request }) {
|
||||
// Update the base URL for all requests
|
||||
const url = new URL(request.url);
|
||||
const apiUrl = new URL(api_url);
|
||||
url.protocol = apiUrl.protocol;
|
||||
url.host = apiUrl.host;
|
||||
url.port = apiUrl.port;
|
||||
return new Request(url.toString(), request);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Configure authentication
|
||||
configureApiAuth(accessToken);
|
||||
}, [accessToken, api_url]);
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
110
www/app/lib/api-hooks.ts
Normal file
110
www/app/lib/api-hooks.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
"use client";
|
||||
|
||||
import { $api } from "./apiClient";
|
||||
import { useError } from "../(errors)/errorContext";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import type { paths } from "../reflector-api";
|
||||
|
||||
// Rooms hooks
|
||||
export function useRoomsList(page: number = 1) {
|
||||
const { setError } = useError();
|
||||
|
||||
return $api.useQuery(
|
||||
"get",
|
||||
"/v1/rooms",
|
||||
{
|
||||
params: {
|
||||
query: { page },
|
||||
},
|
||||
},
|
||||
{
|
||||
onError: (error) => {
|
||||
setError(error as Error, "There was an error fetching the rooms");
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Transcripts hooks
|
||||
export function useTranscriptsSearch(
|
||||
q: string = "",
|
||||
options: {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
room_id?: string;
|
||||
source_kind?: string;
|
||||
} = {},
|
||||
) {
|
||||
const { setError } = useError();
|
||||
|
||||
return $api.useQuery(
|
||||
"get",
|
||||
"/v1/transcripts/search",
|
||||
{
|
||||
params: {
|
||||
query: {
|
||||
q,
|
||||
limit: options.limit,
|
||||
offset: options.offset,
|
||||
room_id: options.room_id,
|
||||
source_kind: options.source_kind as any,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
onError: (error) => {
|
||||
setError(error as Error, "There was an error searching transcripts");
|
||||
},
|
||||
keepPreviousData: true, // For smooth pagination
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function useTranscriptDelete() {
|
||||
const { setError } = useError();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return $api.useMutation("delete", "/v1/transcripts/{transcript_id}", {
|
||||
onSuccess: () => {
|
||||
// Invalidate transcripts queries to refetch
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: $api.queryOptions("get", "/v1/transcripts/search").queryKey,
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
setError(error as Error, "There was an error deleting the transcript");
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useTranscriptProcess() {
|
||||
const { setError } = useError();
|
||||
|
||||
return $api.useMutation("post", "/v1/transcripts/{transcript_id}/process", {
|
||||
onError: (error) => {
|
||||
setError(error as Error, "There was an error processing the transcript");
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useTranscriptGet(transcriptId: string | null) {
|
||||
const { setError } = useError();
|
||||
|
||||
return $api.useQuery(
|
||||
"get",
|
||||
"/v1/transcripts/{transcript_id}",
|
||||
{
|
||||
params: {
|
||||
path: {
|
||||
transcript_id: transcriptId || "",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
enabled: !!transcriptId,
|
||||
onError: (error) => {
|
||||
setError(error as Error, "There was an error loading the transcript");
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
40
www/app/lib/apiClient.tsx
Normal file
40
www/app/lib/apiClient.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
"use client";
|
||||
|
||||
import createClient from "openapi-fetch";
|
||||
import type { paths } from "../reflector-api";
|
||||
import {
|
||||
queryOptions,
|
||||
useMutation,
|
||||
useQuery,
|
||||
useSuspenseQuery,
|
||||
} from "@tanstack/react-query";
|
||||
import createFetchClient from "openapi-react-query";
|
||||
|
||||
// Create the base openapi-fetch client
|
||||
export const client = createClient<paths>({
|
||||
// Base URL will be set dynamically via middleware
|
||||
baseUrl: "",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
// Create the React Query client wrapper
|
||||
export const $api = createFetchClient<paths>(client);
|
||||
|
||||
// Configure authentication
|
||||
export const configureApiAuth = (token: string | null | undefined) => {
|
||||
if (token) {
|
||||
client.use({
|
||||
onRequest({ request }) {
|
||||
request.headers.set("Authorization", `Bearer ${token}`);
|
||||
return request;
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Export typed hooks for convenience
|
||||
export const useApiQuery = $api.useQuery;
|
||||
export const useApiMutation = $api.useMutation;
|
||||
export const useApiSuspenseQuery = $api.useSuspenseQuery;
|
||||
17
www/app/lib/queryClient.tsx
Normal file
17
www/app/lib/queryClient.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import { QueryClient } from "@tanstack/react-query";
|
||||
|
||||
export const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 60 * 1000, // 1 minute
|
||||
gcTime: 5 * 60 * 1000, // 5 minutes (formerly cacheTime)
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
mutations: {
|
||||
retry: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,37 +0,0 @@
|
||||
import { useSession, signOut } from "next-auth/react";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { DomainContext, featureEnabled } from "../domainContext";
|
||||
import { OpenApi, DefaultService } from "../api";
|
||||
import { CustomSession } from "./types";
|
||||
import useSessionStatus from "./useSessionStatus";
|
||||
import useSessionAccessToken from "./useSessionAccessToken";
|
||||
|
||||
export default function useApi(): DefaultService | null {
|
||||
const api_url = useContext(DomainContext).api_url;
|
||||
const [api, setApi] = useState<OpenApi | null>(null);
|
||||
const { isLoading, isAuthenticated } = useSessionStatus();
|
||||
const { accessToken, error } = useSessionAccessToken();
|
||||
|
||||
if (!api_url) throw new Error("no API URL");
|
||||
|
||||
useEffect(() => {
|
||||
if (error === "RefreshAccessTokenError") {
|
||||
signOut();
|
||||
}
|
||||
}, [error]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoading || (isAuthenticated && !accessToken)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const openApi = new OpenApi({
|
||||
BASE: api_url,
|
||||
TOKEN: accessToken || undefined,
|
||||
});
|
||||
|
||||
setApi(openApi);
|
||||
}, [isLoading, isAuthenticated, accessToken]);
|
||||
|
||||
return api?.default ?? null;
|
||||
}
|
||||
Reference in New Issue
Block a user