Benard John
feat: implement full-stack architecture with database models, authentication services, and web dashboard components
37b5223 | import axios, { AxiosError } from 'axios' | |
| import type { | |
| QueryResponse, | |
| Report, | |
| SearchFilters, | |
| FeedbackPayload, | |
| GraphData, | |
| StreamEvent, | |
| } from '@/types' | |
| import { useAuthStore } from '@/stores/auth' | |
| const API_BASE = import.meta.env.VITE_API_URL || '/api' | |
| const client = axios.create({ | |
| baseURL: API_BASE, | |
| timeout: 60000, | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| withCredentials: true, | |
| }) | |
| // Inject the JWT on every request | |
| client.interceptors.request.use((config) => { | |
| const auth = useAuthStore() | |
| if (auth.accessToken) { | |
| config.headers.Authorization = `Bearer ${auth.accessToken}` | |
| } | |
| return config | |
| }) | |
| // Track rate-limit headers + show the auth modal on 429 | |
| client.interceptors.response.use( | |
| (response) => { | |
| const auth = useAuthStore() | |
| auth.updateRateFromHeaders(response.headers as unknown as Headers) | |
| return response | |
| }, | |
| (error: AxiosError) => { | |
| const auth = useAuthStore() | |
| // Feed any rate headers we DID get back into the store | |
| if (error.response?.headers) { | |
| try { | |
| auth.updateRateFromHeaders(error.response.headers as unknown as Headers) | |
| } catch { | |
| // axios headers are not always Headers — that's fine | |
| } | |
| } | |
| if (error.response?.status === 429) { | |
| // Open the signup modal so the user can convert | |
| auth.openAuthModal() | |
| throw new Error('Daily limit reached. Sign up for 25× more queries.') | |
| } | |
| if (error.response?.status === 401) { | |
| // Token rejected — clear it and force re-auth | |
| auth.$reset?.() | |
| throw new Error('Your session has expired. Please sign in again.') | |
| } | |
| throw error | |
| } | |
| ) | |
| export const api = { | |
| // Health | |
| health: () => client.get('/health'), | |
| // RAG query (non-streaming) | |
| query: async ( | |
| query: string, | |
| filters?: SearchFilters, | |
| language: string = 'en', | |
| mode: string = 'concise' | |
| ) => { | |
| const response = await client.post<QueryResponse>('/query', { | |
| query, | |
| language, | |
| mode, | |
| filters, | |
| }) | |
| return response.data | |
| }, | |
| // Streaming query (SSE) | |
| queryStream: ( | |
| query: string, | |
| onEvent: (event: StreamEvent) => void, | |
| filters?: SearchFilters | |
| ): EventSource => { | |
| const auth = useAuthStore() | |
| const url = new URL(`${API_BASE}/agent/stream`, window.location.origin) | |
| // EventSource doesn't support custom headers, so we pass the JWT | |
| // as a query param fallback (the server may ignore if it prefers headers). | |
| if (auth.accessToken) { | |
| url.searchParams.set('access_token', auth.accessToken) | |
| } | |
| const eventSource = new EventSource(url.toString()) | |
| eventSource.onmessage = (event) => { | |
| try { | |
| const data = JSON.parse(event.data) | |
| onEvent(data) | |
| } catch { | |
| onEvent({ event: 'error', data: { message: 'Bad event payload' } }) | |
| } | |
| } | |
| eventSource.onerror = () => { | |
| onEvent({ event: 'error', data: { message: 'Stream connection failed' } }) | |
| eventSource.close() | |
| } | |
| return eventSource | |
| }, | |
| // Documents | |
| getDocuments: async (params?: { | |
| auditee?: string | |
| fy?: string | |
| limit?: number | |
| offset?: number | |
| }) => { | |
| const response = await client.get<Report[]>('/documents', { params }) | |
| return response.data | |
| }, | |
| getReport: async (id: string) => { | |
| const response = await client.get<Report>(`/documents/${id}`) | |
| return response.data | |
| }, | |
| // Graph | |
| graphQuery: async (cypher: string) => { | |
| const response = await client.post<GraphData>('/graph/query', { | |
| cypher, | |
| read_only: true, | |
| }) | |
| return response.data | |
| }, | |
| getEntityGraph: async (entityId: string) => { | |
| const response = await client.get<GraphData>(`/graph/entity/${entityId}`) | |
| return response.data | |
| }, | |
| // Feedback | |
| submitFeedback: async (payload: FeedbackPayload) => { | |
| const response = await client.post('/feedback', payload) | |
| return response.data | |
| }, | |
| // Suggestions | |
| getSuggestions: async (query: string) => { | |
| const response = await client.get<string[]>('/suggestions', { | |
| params: { q: query }, | |
| }) | |
| return response.data | |
| }, | |
| } | |
| export default api | |