158 lines
5.6 KiB
TypeScript
158 lines
5.6 KiB
TypeScript
/**
|
|
* API Configuration
|
|
* Centralized configuration for API endpoints
|
|
*
|
|
* In Development: Calls backend directly at http://localhost:8000
|
|
* In Production: Uses Next.js rewrites/nginx proxy at /api (internal network only)
|
|
*/
|
|
|
|
// Production: Use relative URLs (nginx proxy) for client-side
|
|
// For server-side (SSR), use internal backend URL or public domain
|
|
const isProduction = process.env.NODE_ENV === 'production';
|
|
|
|
// Detect if we're on the server (Node.js) or client (browser)
|
|
const isServer = typeof window === 'undefined';
|
|
|
|
// For server-side rendering, we need an absolute URL
|
|
// During build time, use internal backend URL directly (faster, no SSL issues)
|
|
// At runtime, use public domain (goes through nginx which adds API key header)
|
|
const getServerApiUrl = () => {
|
|
if (isProduction) {
|
|
// Check if we're in build context (no access to window, and NEXT_PHASE might be set)
|
|
// During build, use internal backend URL directly
|
|
// At runtime (SSR), use public domain through nginx
|
|
const isBuildTime = process.env.NEXT_PHASE === 'phase-production-build' ||
|
|
!process.env.NEXT_RUNTIME;
|
|
|
|
if (isBuildTime) {
|
|
// Build time: use internal backend URL directly
|
|
return process.env.INTERNAL_API_URL || 'http://127.0.0.1:1086';
|
|
} else {
|
|
// Runtime SSR: use public domain - nginx will proxy and add API key header
|
|
return process.env.NEXT_PUBLIC_SITE_URL || 'https://gnxsoft.com';
|
|
}
|
|
}
|
|
return process.env.NEXT_PUBLIC_API_URL || 'http://127.0.0.1:1086';
|
|
};
|
|
|
|
// For client-side, use relative URLs in production (proxied by nginx)
|
|
// For server-side, use absolute URLs
|
|
export const API_BASE_URL = isServer
|
|
? getServerApiUrl() // Server-side: absolute URL
|
|
: (isProduction
|
|
? '' // Client-side production: relative URLs (proxied by nginx)
|
|
: (process.env.NEXT_PUBLIC_API_URL || 'http://127.0.0.1:1086')); // Development: direct backend
|
|
|
|
// Internal API key for server-side requests (must match backend INTERNAL_API_KEY)
|
|
// This is only used when calling backend directly (build time or internal requests)
|
|
// SECURITY: Never hardcode API keys in production - always use environment variables
|
|
const getInternalApiKey = (): string => {
|
|
const apiKey = process.env.INTERNAL_API_KEY;
|
|
|
|
// Check if we're in build phase (Next.js build context)
|
|
// During build, NEXT_RUNTIME is typically not set
|
|
// Also check for specific build phases
|
|
const isBuildTime =
|
|
!process.env.NEXT_RUNTIME || // Most reliable indicator - not set during build
|
|
process.env.NEXT_PHASE === 'phase-production-build' ||
|
|
process.env.NEXT_PHASE === 'phase-production-compile' ||
|
|
process.env.NEXT_PHASE === 'phase-development-build';
|
|
|
|
if (!apiKey) {
|
|
// During build time, be lenient - allow build to proceed
|
|
// The key will be validated when actually used (in getApiHeaders)
|
|
if (isBuildTime) {
|
|
// Build time: allow fallback (will be validated when actually used)
|
|
return 'build-time-fallback-key';
|
|
}
|
|
|
|
// Runtime production: require the key (but only validate when actually used)
|
|
if (isProduction) {
|
|
// Don't throw here - validate lazily in getApiHeaders when actually needed
|
|
// This allows the build to complete even if key is missing
|
|
return 'runtime-requires-key';
|
|
}
|
|
|
|
// Development fallback (only for local development)
|
|
return 'dev-key-not-for-production';
|
|
}
|
|
|
|
return apiKey;
|
|
};
|
|
|
|
// Lazy getter - only evaluates when accessed
|
|
let _internalApiKey: string | null = null;
|
|
export const getInternalApiKeyLazy = (): string => {
|
|
if (_internalApiKey === null) {
|
|
_internalApiKey = getInternalApiKey();
|
|
}
|
|
return _internalApiKey;
|
|
};
|
|
|
|
// For backward compatibility - evaluates at module load but uses lenient validation
|
|
export const INTERNAL_API_KEY = getInternalApiKeyLazy();
|
|
|
|
// Helper to get headers for API requests
|
|
// Adds API key header when calling internal backend directly
|
|
export const getApiHeaders = (): Record<string, string> => {
|
|
const headers: Record<string, string> = {
|
|
'Content-Type': 'application/json',
|
|
};
|
|
|
|
// If we're calling the internal backend directly (not through nginx),
|
|
// add the API key header (lazy evaluation - only when actually needed)
|
|
if (isServer && API_BASE_URL.includes('127.0.0.1:1086')) {
|
|
const apiKey = getInternalApiKeyLazy();
|
|
|
|
// Validate API key when actually used (not at module load time)
|
|
if (apiKey === 'runtime-requires-key' && isProduction) {
|
|
const actualKey = process.env.INTERNAL_API_KEY;
|
|
if (!actualKey) {
|
|
throw new Error(
|
|
'INTERNAL_API_KEY environment variable is required in production runtime. ' +
|
|
'Set it in your .env.production file or environment variables.'
|
|
);
|
|
}
|
|
// Update cached value
|
|
_internalApiKey = actualKey;
|
|
headers['X-Internal-API-Key'] = actualKey;
|
|
} else {
|
|
headers['X-Internal-API-Key'] = apiKey;
|
|
}
|
|
}
|
|
|
|
return headers;
|
|
};
|
|
|
|
export const API_CONFIG = {
|
|
// Django API Base URL
|
|
BASE_URL: API_BASE_URL,
|
|
|
|
// Media files URL (for uploaded images, etc.)
|
|
MEDIA_URL: `${API_BASE_URL}/media`,
|
|
|
|
// API Endpoints
|
|
ENDPOINTS: {
|
|
CONTACT: '/api/contact',
|
|
CONTACT_SUBMISSIONS: '/api/contact/submissions',
|
|
CONTACT_STATS: '/api/contact/submissions/stats',
|
|
CONTACT_RECENT: '/api/contact/submissions/recent',
|
|
SERVICES: '/api/services',
|
|
SERVICES_FEATURED: '/api/services/featured',
|
|
SERVICES_SEARCH: '/api/services/search',
|
|
SERVICES_STATS: '/api/services/stats',
|
|
SERVICES_CATEGORIES: '/api/services/categories',
|
|
},
|
|
|
|
// Request timeout (in milliseconds)
|
|
TIMEOUT: 10000,
|
|
|
|
// Retry configuration
|
|
RETRY: {
|
|
ATTEMPTS: 3,
|
|
DELAY: 1000,
|
|
}
|
|
} as const;
|
|
|
|
export default API_CONFIG;
|