You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
117 lines
3.0 KiB
117 lines
3.0 KiB
/** |
|
* API client utilities for repository operations |
|
* Provides centralized API call functions with error handling and logging |
|
*/ |
|
|
|
import { get } from 'svelte/store'; |
|
import { userStore } from '$lib/stores/user-store.js'; |
|
import logger from '$lib/services/logger.js'; |
|
|
|
/** |
|
* Builds API headers with user pubkey for authenticated requests |
|
*/ |
|
export function buildApiHeaders(): Record<string, string> { |
|
const headers: Record<string, string> = {}; |
|
const currentUser = get(userStore); |
|
const currentUserPubkeyHex = currentUser?.userPubkeyHex; |
|
if (currentUserPubkeyHex) { |
|
headers['X-User-Pubkey'] = currentUserPubkeyHex; |
|
logger.debug({ pubkey: currentUserPubkeyHex.substring(0, 16) + '...' }, '[API] Sending X-User-Pubkey header'); |
|
} |
|
return headers; |
|
} |
|
|
|
/** |
|
* Makes an API request with error handling and logging |
|
*/ |
|
export async function apiRequest<T>( |
|
url: string, |
|
options: RequestInit = {} |
|
): Promise<T> { |
|
const headers = { |
|
...buildApiHeaders(), |
|
...options.headers, |
|
'Content-Type': 'application/json', |
|
}; |
|
|
|
logger.debug({ url, method: options.method || 'GET' }, '[API] Making request'); |
|
|
|
try { |
|
const response = await fetch(url, { |
|
...options, |
|
headers, |
|
credentials: 'same-origin', |
|
}); |
|
|
|
if (!response.ok) { |
|
let errorMessage = `API request failed: ${response.status} ${response.statusText}`; |
|
try { |
|
const errorData = await response.json(); |
|
if (errorData.message) { |
|
errorMessage = errorData.message; |
|
} else if (errorData.error) { |
|
errorMessage = errorData.error; |
|
} |
|
} catch { |
|
try { |
|
const text = await response.text(); |
|
if (text) { |
|
errorMessage = text.substring(0, 200); |
|
} |
|
} catch { |
|
// Ignore parsing errors |
|
} |
|
} |
|
|
|
// 404s are expected when repo isn't cloned - log as debug, not error |
|
if (response.status === 404) { |
|
logger.debug({ url, status: response.status, error: errorMessage }, '[API] Request failed (404 - expected for uncloned repos)'); |
|
} else { |
|
logger.error({ url, status: response.status, error: errorMessage }, '[API] Request failed'); |
|
} |
|
throw new Error(errorMessage); |
|
} |
|
|
|
const data = await response.json(); |
|
logger.debug({ url }, '[API] Request successful'); |
|
return data as T; |
|
} catch (err) { |
|
logger.error({ url, error: err }, '[API] Request error'); |
|
throw err; |
|
} |
|
} |
|
|
|
/** |
|
* Makes a POST request |
|
*/ |
|
export async function apiPost<T>( |
|
url: string, |
|
body: unknown |
|
): Promise<T> { |
|
return apiRequest<T>(url, { |
|
method: 'POST', |
|
body: JSON.stringify(body), |
|
}); |
|
} |
|
|
|
/** |
|
* Makes a PUT request |
|
*/ |
|
export async function apiPut<T>( |
|
url: string, |
|
body: unknown |
|
): Promise<T> { |
|
return apiRequest<T>(url, { |
|
method: 'PUT', |
|
body: JSON.stringify(body), |
|
}); |
|
} |
|
|
|
/** |
|
* Makes a DELETE request |
|
*/ |
|
export async function apiDelete<T>(url: string): Promise<T> { |
|
return apiRequest<T>(url, { |
|
method: 'DELETE', |
|
}); |
|
}
|
|
|