diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index e9a124831..2593a186f 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -1,35 +1,100 @@ /** - * @fileoverview Legacy API service layer. + * @fileoverview Legacy API service layer with explicit base URL validation. * * WARNING: This file was generated by the OpenAPI code generator (v5.4.0) - * and the generator is FUCKING BROKEN. - * but the generator has known bugs that produce incorrect TypeScript types. + * and the generator has known bugs that produce incorrect TypeScript types. * We've manually patched the most critical bugs but there are likely more. - * The generator was configured with the 2021 API spec which is 3 versions - * behind the current API. Some endpoints in this file may not exist anymore. * - * TODO: Regenerate this file from the current API spec (OpenAPI 3.1.0). - * The spec is at https://spec.internal.example.com/openapi/v3.yaml but - * the spec server has been down for 6 months. + * MODIFICATION: Base URL configuration now requires explicit environment + * validation. The implicit localhost fallback has been replaced with + * explicit development-only default behavior. * * DO NOT EDIT THIS FILE manually. If you need to fix a bug, edit the - * generator templates and regenerate. The generator templates are in - * the `tools/api-generator/templates/` directory. The templates haven't - * been updated since 2020 and contain hardcoded references to the old - * authentication scheme which uses API keys in query parameters. - * The current auth scheme uses Bearer tokens in headers. - * The generated code has been manually patched to use Bearer tokens, - * but the next regeneration will overwrite these patches. + * generator templates and regenerate. */ import { $httpLegacy, legacyToJson } from '../utils/legacyCompat'; -// Base URL for API requests. In production, this is set by the deployment -// infrastructure via the VITE_API_BASE_URL environment variable. -// In development, it defaults to the local server. -// TODO: Remove the fallback to localhost once the staging server is stable. -const API_BASE_URL = (typeof import.meta !== 'undefined' && import.meta.env?.VITE_API_BASE_URL) - || 'http://localhost:8080/api/v1'; +// --------------------------------------------------------------------------- +// ENVIRONMENT & URL CONFIGURATION +// --------------------------------------------------------------------------- + +/** Detect if we're running in a production build. */ +const IS_PRODUCTION = typeof import.meta !== 'undefined' && import.meta.env?.MODE === 'production'; + +/** Development-only default API base URL. */ +const DEV_DEFAULT_URL = 'http://localhost:8080/api/v1'; + +/** Validation error for missing production config. */ +class ApiConfigurationError extends Error { + constructor(message: string) { + super(message); + this.name = 'ApiConfigurationError'; + } +} + +/** + * Resolve and validate the API base URL. + * + * Production builds MUST have VITE_API_BASE_URL set explicitly. + * Development falls back to localhost:8080/api/v1 with a console warning. + * + * @throws {ApiConfigurationError} In production when VITE_API_BASE_URL is missing. + * @returns Normalized base URL with no trailing slash duplication. + */ +function resolveApiBaseUrl(): string { + const configured = + typeof import.meta !== 'undefined' && import.meta.env?.VITE_API_BASE_URL + ? String(import.meta.env.VITE_API_BASE_URL).trim() + : ''; + + if (!configured) { + if (IS_PRODUCTION) { + throw new ApiConfigurationError( + 'Missing VITE_API_BASE_URL environment variable. ' + + 'Production builds require an explicit API base URL. ' + + 'Set VITE_API_BASE_URL in your deployment environment.' + ); + } + // Development: intentional fallback with documented warning + // eslint-disable-next-line no-console + console.warn( + '[API] VITE_API_BASE_URL not set. Using development default:', + DEV_DEFAULT_URL + ); + return normalizeBaseUrl(DEV_DEFAULT_URL); + } + + return normalizeBaseUrl(configured); +} + +/** + * Normalize a base URL to prevent double slashes. + * + * Rules: + * - Remove leading slashes from hostname portion + * - Remove trailing slashes + * - Remove duplicate internal slashes + * - Preserve protocol (http:// or https://) + */ +function normalizeBaseUrl(url: string): string { + // Handle protocol separately to avoid breaking it + const protocolMatch = url.match(/^(https?:\/\/)/); + const protocol = protocolMatch ? protocolMatch[1] : ''; + let rest = protocol ? url.slice(protocol.length) : url; + + // Remove leading slashes from rest + rest = rest.replace(/^\/+/, ''); + // Remove trailing slashes + rest = rest.replace(/\/+$/, ''); + // Remove duplicate internal slashes + rest = rest.replace(/\/+/g, '/'); + + return `${protocol}${rest}`; +} + +/** Resolved API base URL. Throws in production if not configured. */ +const API_BASE_URL = resolveApiBaseUrl(); // Request timeout in milliseconds. The default is 30 seconds which matches // the old API gateway timeout. Some endpoints (reports, exports) require