- Add /api/config endpoint for runtime env var fetching - Add config.ts service with sync getters (getApiUrl, getBackofficeUrl, etc.) - Add ConfigContext for React components - Update api.ts, auth.ts, storage.ts to use runtime config - Update layout.tsx to wrap app with ConfigProvider - Fix Dockerfile default port from 8080 to 8521 This allows the frontend to read environment variables at runtime instead of baking them in during build time.
128 lines
3.6 KiB
TypeScript
128 lines
3.6 KiB
TypeScript
/**
|
|
* Runtime Configuration Service
|
|
*
|
|
* This service fetches and caches configuration from the /api/config endpoint,
|
|
* allowing the application to use runtime environment variables instead of
|
|
* build-time values.
|
|
*
|
|
* Usage:
|
|
* import { getApiUrl, getBackofficeUrl, initConfig } from '@/lib/config';
|
|
*
|
|
* // Initialize once at app startup (optional, auto-initializes on first use)
|
|
* await initConfig();
|
|
*
|
|
* // Get URLs (returns cached values or defaults)
|
|
* const apiUrl = getApiUrl();
|
|
*/
|
|
|
|
export interface RuntimeConfig {
|
|
apiUrl: string;
|
|
backofficeUrl: string;
|
|
seederApiUrl: string;
|
|
scraperApiUrl: string;
|
|
}
|
|
|
|
// Default values (fallback to build-time env or hardcoded defaults)
|
|
const defaultConfig: RuntimeConfig = {
|
|
apiUrl: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8521',
|
|
backofficeUrl: process.env.NEXT_PUBLIC_BACKOFFICE_URL || 'http://localhost:3001',
|
|
seederApiUrl: process.env.NEXT_PUBLIC_SEEDER_API_URL || 'http://localhost:3002',
|
|
scraperApiUrl: process.env.NEXT_PUBLIC_SCRAPER_API_URL || 'http://localhost:3003',
|
|
};
|
|
|
|
let cachedConfig: RuntimeConfig | null = null;
|
|
let isInitialized = false;
|
|
let initPromise: Promise<RuntimeConfig> | null = null;
|
|
|
|
/**
|
|
* Initialize the config from the server.
|
|
* Safe to call multiple times - only fetches once.
|
|
*/
|
|
export async function initConfig(): Promise<RuntimeConfig> {
|
|
// If already initialized, return cached config
|
|
if (isInitialized && cachedConfig) {
|
|
return cachedConfig;
|
|
}
|
|
|
|
// If already initializing, wait for that promise
|
|
if (initPromise) {
|
|
return initPromise;
|
|
}
|
|
|
|
// Start initialization
|
|
initPromise = (async () => {
|
|
try {
|
|
// Only fetch if we're in the browser
|
|
if (typeof window !== 'undefined') {
|
|
const response = await fetch('/api/config');
|
|
if (response.ok) {
|
|
cachedConfig = await response.json();
|
|
console.log('[Config] Loaded runtime config:', cachedConfig);
|
|
} else {
|
|
console.warn('[Config] Failed to fetch config, using defaults');
|
|
cachedConfig = defaultConfig;
|
|
}
|
|
} else {
|
|
// Server-side: use defaults
|
|
cachedConfig = defaultConfig;
|
|
}
|
|
} catch (error) {
|
|
console.warn('[Config] Error fetching config, using defaults:', error);
|
|
cachedConfig = defaultConfig;
|
|
}
|
|
|
|
isInitialized = true;
|
|
return cachedConfig!;
|
|
})();
|
|
|
|
return initPromise;
|
|
}
|
|
|
|
/**
|
|
* Get the full config object.
|
|
* Returns cached config if initialized, otherwise defaults.
|
|
*/
|
|
export function getConfig(): RuntimeConfig {
|
|
// Trigger async init in background if not initialized
|
|
if (!isInitialized && typeof window !== 'undefined') {
|
|
initConfig();
|
|
}
|
|
return cachedConfig || defaultConfig;
|
|
}
|
|
|
|
/**
|
|
* Get the API base URL.
|
|
* @returns The API URL (e.g., "https://api.gohorsejobs.com")
|
|
*/
|
|
export function getApiUrl(): string {
|
|
return getConfig().apiUrl;
|
|
}
|
|
|
|
/**
|
|
* Get the full API URL with /api/v1 suffix.
|
|
* @returns The full API URL (e.g., "https://api.gohorsejobs.com/api/v1")
|
|
*/
|
|
export function getApiV1Url(): string {
|
|
return `${getConfig().apiUrl}/api/v1`;
|
|
}
|
|
|
|
/**
|
|
* Get the Backoffice URL.
|
|
*/
|
|
export function getBackofficeUrl(): string {
|
|
return getConfig().backofficeUrl;
|
|
}
|
|
|
|
/**
|
|
* Get the Seeder API URL.
|
|
*/
|
|
export function getSeederApiUrl(): string {
|
|
return getConfig().seederApiUrl;
|
|
}
|
|
|
|
/**
|
|
* Get the Scraper API URL.
|
|
*/
|
|
export function getScraperApiUrl(): string {
|
|
return getConfig().scraperApiUrl;
|
|
}
|