// src/routes/api/[...path]/+server.ts import { BACKEND_API_URL } from '$lib/server/config'; import { json, error } from '@sveltejs/kit'; // import type { RequestHandler } from './$types'; // Helper function to handle both JSON and file responses async function handleResponse(response: Response) { if (!response.ok) { throw error(response.status, await response.text()); } const contentType = response.headers.get('Content-Type') || ''; if (contentType.includes('application/json')) { // Handle JSON response const data = await response.json(); return json(data); } else { // Handle file or other non-JSON response const responseBody = await response.arrayBuffer(); return new Response(responseBody, { status: response.status, headers: { 'Content-Type': contentType, 'Content-Disposition': response.headers.get('Content-Disposition') || '', 'Content-Length': response.headers.get('Content-Length') || '' } }); } } /** * The body to forward, as fetch init options. * * Passed through as a stream rather than read into memory first: this process should * never hold a whole upload, which for a zipped Calibre library could be many * gigabytes. A request with no body contributes nothing, since `duplex` without a body * is rejected. */ function bodyOf(request: Request) { if (!request.body) return {}; return { body: request.body, duplex: 'half' } as RequestInit; } // Shared function to prepare the request with authentication function prepareRequest(locals: App.Locals, request: Request) { const token = locals.authToken || 'server-default-token'; const headers = new Headers({ Authorization: `Bearer ${token}` }); // Forward important headers from the original request const contentType = request.headers.get('Content-Type'); if (contentType) headers.set('Content-Type', contentType); const accept = request.headers.get('Accept'); if (accept) headers.set('Accept', accept); return headers; } // GET handler export const GET: RequestHandler = async ({ params, locals, fetch, request, url }) => { try { const path = params.path; const queryString = url.search; const backendUrl = `${BACKEND_API_URL}/${path}${queryString}`; const headers = prepareRequest(locals, request); const response = await fetch(backendUrl, { method: 'GET', headers }); return handleResponse(response); } catch (e) { console.error('API proxy error (GET):', e); throw error(500, 'Failed to fetch from API'); } }; // POST handler export const POST: RequestHandler = async ({ params, locals, fetch, request, url }) => { try { const path = params.path; const queryString = url.search; const backendUrl = `${BACKEND_API_URL}/${path}${queryString}`; const headers = prepareRequest(locals, request); const response = await fetch(backendUrl, { method: 'POST', headers, // Streamed, not buffered. `arrayBuffer()` held the whole upload in this // process before forwarding a byte of it, which is survivable for one book // and not for a zipped Calibre library. `duplex: 'half'` is required by the // fetch spec whenever the body is a stream. ...bodyOf(request) }); return handleResponse(response); } catch (e) { console.error('API proxy error (POST):', e); throw error(500, 'Failed to post to API'); } }; // PATCH handler export const PATCH: RequestHandler = async ({ params, locals, fetch, request, url }) => { try { const path = params.path; const queryString = url.search; const backendUrl = `${BACKEND_API_URL}/${path}${queryString}`; const headers = prepareRequest(locals, request); const response = await fetch(backendUrl, { method: 'PATCH', headers, ...bodyOf(request) }); return handleResponse(response); } catch (e) { console.error('API proxy error (PATCH):', e); throw error(500, 'Failed to update API resource'); } }; // DELETE handler export const DELETE: RequestHandler = async ({ params, locals, fetch, request, url }) => { try { const path = params.path; const queryString = url.search; const backendUrl = `${BACKEND_API_URL}/${path}${queryString}`; const headers = prepareRequest(locals, request); // DELETE may or may not have a body let options: RequestInit = { method: 'DELETE', headers }; // Add body if the request has one if ( request.headers.get('Content-Length') && parseInt(request.headers.get('Content-Length') || '0') > 0 ) { options.body = await request.arrayBuffer(); } const response = await fetch(backendUrl, options); return response; } catch (e) { console.error('API proxy error (DELETE):', e); throw error(500, 'Failed to delete API resource'); } };