frontend: apply bearer token to centralized API requests

This commit is contained in:
2026-02-21 15:03:13 -03:00
parent b25e508a00
commit c1a7011d71
4 changed files with 59 additions and 19 deletions

View File

@@ -18,6 +18,39 @@ import type {
*/
const API_BASE = import.meta.env.VITE_API_BASE ?? 'http://localhost:8000/api/v1';
/**
* Optional bearer token used for authenticated backend routes.
*/
const API_TOKEN = import.meta.env.VITE_API_TOKEN?.trim();
type ApiRequestInit = Omit<RequestInit, 'headers'> & { headers?: HeadersInit };
/**
* Merges request headers and appends bearer authorization when configured.
*/
function buildRequestHeaders(headers?: HeadersInit): Headers | undefined {
if (!API_TOKEN && !headers) {
return undefined;
}
const requestHeaders = new Headers(headers);
if (API_TOKEN) {
requestHeaders.set('Authorization', `Bearer ${API_TOKEN}`);
}
return requestHeaders;
}
/**
* Executes an API request with centralized auth-header handling.
*/
function apiRequest(input: string, init: ApiRequestInit = {}): Promise<Response> {
const headers = buildRequestHeaders(init.headers);
return fetch(input, {
...init,
...(headers ? { headers } : {}),
});
}
/**
* Encodes query parameters while skipping undefined and null values.
*/
@@ -72,7 +105,7 @@ export async function listDocuments(options?: {
processed_from: options?.processedFrom,
processed_to: options?.processedTo,
});
const response = await fetch(`${API_BASE}/documents${query}`);
const response = await apiRequest(`${API_BASE}/documents${query}`);
if (!response.ok) {
throw new Error('Failed to load documents');
}
@@ -108,7 +141,7 @@ export async function searchDocuments(
processed_from: options?.processedFrom,
processed_to: options?.processedTo,
});
const response = await fetch(`${API_BASE}/search${query}`);
const response = await apiRequest(`${API_BASE}/search${query}`);
if (!response.ok) {
throw new Error('Search failed');
}
@@ -128,7 +161,7 @@ export async function listProcessingLogs(options?: {
offset: options?.offset ?? 0,
document_id: options?.documentId,
});
const response = await fetch(`${API_BASE}/processing/logs${query}`);
const response = await apiRequest(`${API_BASE}/processing/logs${query}`);
if (!response.ok) {
throw new Error('Failed to load processing logs');
}
@@ -146,7 +179,7 @@ export async function trimProcessingLogs(options?: {
keep_document_sessions: options?.keepDocumentSessions ?? 2,
keep_unbound_entries: options?.keepUnboundEntries ?? 80,
});
const response = await fetch(`${API_BASE}/processing/logs/trim${query}`, {
const response = await apiRequest(`${API_BASE}/processing/logs/trim${query}`, {
method: 'POST',
});
if (!response.ok) {
@@ -159,7 +192,7 @@ export async function trimProcessingLogs(options?: {
* Clears all persisted processing logs.
*/
export async function clearProcessingLogs(): Promise<{ deleted_entries: number }> {
const response = await fetch(`${API_BASE}/processing/logs/clear`, {
const response = await apiRequest(`${API_BASE}/processing/logs/clear`, {
method: 'POST',
});
if (!response.ok) {
@@ -173,7 +206,7 @@ export async function clearProcessingLogs(): Promise<{ deleted_entries: number }
*/
export async function listTags(includeTrashed = false): Promise<string[]> {
const query = buildQuery({ include_trashed: includeTrashed });
const response = await fetch(`${API_BASE}/documents/tags${query}`);
const response = await apiRequest(`${API_BASE}/documents/tags${query}`);
if (!response.ok) {
throw new Error('Failed to load tags');
}
@@ -186,7 +219,7 @@ export async function listTags(includeTrashed = false): Promise<string[]> {
*/
export async function listPaths(includeTrashed = false): Promise<string[]> {
const query = buildQuery({ include_trashed: includeTrashed });
const response = await fetch(`${API_BASE}/documents/paths${query}`);
const response = await apiRequest(`${API_BASE}/documents/paths${query}`);
if (!response.ok) {
throw new Error('Failed to load paths');
}
@@ -199,7 +232,7 @@ export async function listPaths(includeTrashed = false): Promise<string[]> {
*/
export async function listTypes(includeTrashed = false): Promise<string[]> {
const query = buildQuery({ include_trashed: includeTrashed });
const response = await fetch(`${API_BASE}/documents/types${query}`);
const response = await apiRequest(`${API_BASE}/documents/types${query}`);
if (!response.ok) {
throw new Error('Failed to load document types');
}
@@ -228,7 +261,7 @@ export async function uploadDocuments(
formData.append('tags', options.tags);
formData.append('conflict_mode', options.conflictMode);
const response = await fetch(`${API_BASE}/documents/upload`, {
const response = await apiRequest(`${API_BASE}/documents/upload`, {
method: 'POST',
body: formData,
});
@@ -245,7 +278,7 @@ export async function updateDocumentMetadata(
documentId: string,
payload: { original_filename?: string; logical_path?: string; tags?: string[] },
): Promise<DmsDocument> {
const response = await fetch(`${API_BASE}/documents/${documentId}`, {
const response = await apiRequest(`${API_BASE}/documents/${documentId}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
@@ -262,7 +295,7 @@ export async function updateDocumentMetadata(
* Moves a document to trash state without removing stored files.
*/
export async function trashDocument(documentId: string): Promise<DmsDocument> {
const response = await fetch(`${API_BASE}/documents/${documentId}/trash`, { method: 'POST' });
const response = await apiRequest(`${API_BASE}/documents/${documentId}/trash`, { method: 'POST' });
if (!response.ok) {
throw new Error('Failed to trash document');
}
@@ -273,7 +306,7 @@ export async function trashDocument(documentId: string): Promise<DmsDocument> {
* Restores a document from trash to active state.
*/
export async function restoreDocument(documentId: string): Promise<DmsDocument> {
const response = await fetch(`${API_BASE}/documents/${documentId}/restore`, { method: 'POST' });
const response = await apiRequest(`${API_BASE}/documents/${documentId}/restore`, { method: 'POST' });
if (!response.ok) {
throw new Error('Failed to restore document');
}
@@ -284,7 +317,7 @@ export async function restoreDocument(documentId: string): Promise<DmsDocument>
* Permanently deletes a document record and associated stored files.
*/
export async function deleteDocument(documentId: string): Promise<{ deleted_documents: number; deleted_files: number }> {
const response = await fetch(`${API_BASE}/documents/${documentId}`, { method: 'DELETE' });
const response = await apiRequest(`${API_BASE}/documents/${documentId}`, { method: 'DELETE' });
if (!response.ok) {
throw new Error('Failed to delete document');
}
@@ -295,7 +328,7 @@ export async function deleteDocument(documentId: string): Promise<{ deleted_docu
* Loads full details for one document, including extracted text content.
*/
export async function getDocumentDetails(documentId: string): Promise<DmsDocumentDetail> {
const response = await fetch(`${API_BASE}/documents/${documentId}`);
const response = await apiRequest(`${API_BASE}/documents/${documentId}`);
if (!response.ok) {
throw new Error('Failed to load document details');
}
@@ -306,7 +339,7 @@ export async function getDocumentDetails(documentId: string): Promise<DmsDocumen
* Re-enqueues one document for extraction and classification processing.
*/
export async function reprocessDocument(documentId: string): Promise<DmsDocument> {
const response = await fetch(`${API_BASE}/documents/${documentId}/reprocess`, {
const response = await apiRequest(`${API_BASE}/documents/${documentId}/reprocess`, {
method: 'POST',
});
if (!response.ok) {
@@ -352,7 +385,7 @@ export async function exportContentsMarkdown(payload: {
include_trashed?: boolean;
only_trashed?: boolean;
}): Promise<{ blob: Blob; filename: string }> {
const response = await fetch(`${API_BASE}/documents/content-md/export`, {
const response = await apiRequest(`${API_BASE}/documents/content-md/export`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -373,7 +406,7 @@ export async function exportContentsMarkdown(payload: {
* Retrieves persisted application settings from backend.
*/
export async function getAppSettings(): Promise<AppSettings> {
const response = await fetch(`${API_BASE}/settings`);
const response = await apiRequest(`${API_BASE}/settings`);
if (!response.ok) {
throw new Error('Failed to load application settings');
}
@@ -384,7 +417,7 @@ export async function getAppSettings(): Promise<AppSettings> {
* Updates provider and task settings for OpenAI-compatible model execution.
*/
export async function updateAppSettings(payload: AppSettingsUpdate): Promise<AppSettings> {
const response = await fetch(`${API_BASE}/settings`, {
const response = await apiRequest(`${API_BASE}/settings`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
@@ -401,7 +434,7 @@ export async function updateAppSettings(payload: AppSettingsUpdate): Promise<App
* Resets persisted provider and task settings to backend defaults.
*/
export async function resetAppSettings(): Promise<AppSettings> {
const response = await fetch(`${API_BASE}/settings/reset`, {
const response = await apiRequest(`${API_BASE}/settings/reset`, {
method: 'POST',
});
if (!response.ok) {

View File

@@ -0,0 +1 @@
{"root":["./src/app.tsx","./src/main.tsx","./src/types.ts","./src/components/actionmodal.tsx","./src/components/documentcard.tsx","./src/components/documentgrid.tsx","./src/components/documentviewer.tsx","./src/components/pathinput.tsx","./src/components/processinglogpanel.tsx","./src/components/searchfiltersbar.tsx","./src/components/settingsscreen.tsx","./src/components/taginput.tsx","./src/components/uploadsurface.tsx","./src/lib/api.ts"],"version":"5.9.2"}