docker setup

This commit is contained in:
2025-12-26 18:59:50 +01:00
parent 64377de548
commit 904feb41b8
11 changed files with 198 additions and 30 deletions

View File

@@ -14,6 +14,18 @@ import { FeatureAvailabilityGuard } from './domain/policy/FeatureAvailabilityGua
async function bootstrap() {
const app = await NestFactory.create(AppModule, process.env.GENERATE_OPENAPI ? { logger: false } : undefined);
// Website runs on a different origin in dev/docker (e.g. http://localhost:3000 -> http://localhost:3001),
// and our website HTTP client uses `credentials: 'include'`, so we must support CORS with credentials.
app.enableCors({
credentials: true,
origin: (origin, callback) => {
if (!origin) {
return callback(null, false);
}
return callback(null, origin);
},
});
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,

View File

@@ -378,13 +378,10 @@ export default function LeaguesPage() {
const [activeCategory, setActiveCategory] = useState<CategoryId>('all');
const [showFilters, setShowFilters] = useState(false);
useEffect(() => {
void loadLeagues();
}, []);
const { leagueService } = useServices();
const loadLeagues = async () => {
const loadLeagues = useCallback(async () => {
try {
const { leagueService } = useServices();
const leagues = await leagueService.getAllLeagues();
setRealLeagues(leagues);
} catch (error) {
@@ -392,7 +389,11 @@ export default function LeaguesPage() {
} finally {
setLoading(false);
}
};
}, [leagueService]);
useEffect(() => {
void loadLeagues();
}, [loadLeagues]);
const leagues = realLeagues;

View File

@@ -14,13 +14,11 @@ import SimPlatformMockup from '@/components/mockups/SimPlatformMockup';
import MockupStack from '@/components/ui/MockupStack';
import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
import { ServiceFactory } from '@/lib/services/ServiceFactory';
export default async function HomePage() {
const baseUrl =
process.env.API_BASE_URL ??
process.env.NEXT_PUBLIC_API_BASE_URL ??
'http://api:3000';
const baseUrl = getWebsiteApiBaseUrl();
const serviceFactory = new ServiceFactory(baseUrl);
const sessionService = serviceFactory.createSessionService();
const landingService = serviceFactory.createLandingService();

View File

@@ -1,5 +1,4 @@
import { ApiClient } from './api/index';
import { getWebsiteApiBaseUrl } from './config/apiBaseUrl';
const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001';
export const apiClient = new ApiClient(API_BASE_URL);
export const apiClient = new ApiClient(getWebsiteApiBaseUrl());

View File

@@ -0,0 +1,36 @@
function normalizeBaseUrl(raw: string): string {
const trimmed = raw.trim();
return trimmed.endsWith('/') ? trimmed.slice(0, -1) : trimmed;
}
export function getWebsiteApiBaseUrl(): string {
const isBrowser = typeof window !== 'undefined';
const configured = isBrowser
? process.env.NEXT_PUBLIC_API_BASE_URL
: process.env.API_BASE_URL ?? process.env.NEXT_PUBLIC_API_BASE_URL;
if (configured && configured.trim()) {
return normalizeBaseUrl(configured);
}
const isTestLike =
process.env.NODE_ENV === 'test' ||
process.env.CI === 'true' ||
process.env.DOCKER === 'true';
if (isTestLike) {
throw new Error(
isBrowser
? 'Missing NEXT_PUBLIC_API_BASE_URL. In Docker/CI/test we do not allow falling back to localhost.'
: 'Missing API_BASE_URL. In Docker/CI/test we do not allow falling back to localhost.',
);
}
const fallback =
process.env.NODE_ENV === 'development'
? 'http://localhost:3001'
: 'http://api:3000';
return normalizeBaseUrl(fallback);
}

View File

@@ -12,6 +12,7 @@ import { DashboardApiClient } from '../api/dashboard/DashboardApiClient';
import { PolicyApiClient } from '../api/policy/PolicyApiClient';
import { ProtestsApiClient } from '../api/protests/ProtestsApiClient';
import { PenaltiesApiClient } from '../api/penalties/PenaltiesApiClient';
import { getWebsiteApiBaseUrl } from '../config/apiBaseUrl';
import { PenaltyService } from './penalties/PenaltyService';
import { ConsoleErrorReporter } from '../infrastructure/logging/ConsoleErrorReporter';
import { ConsoleLogger } from '../infrastructure/logging/ConsoleLogger';
@@ -102,7 +103,7 @@ export class ServiceFactory {
private static getDefaultInstance(): ServiceFactory {
if (!this.defaultInstance) {
this.defaultInstance = new ServiceFactory(process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001');
this.defaultInstance = new ServiceFactory(getWebsiteApiBaseUrl());
}
return this.defaultInstance;
}

View File

@@ -2,6 +2,7 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { createContext, ReactNode, useContext, useMemo } from 'react';
import { getWebsiteApiBaseUrl } from '../config/apiBaseUrl';
import { ServiceFactory } from './ServiceFactory';
// Import all service types
@@ -82,7 +83,7 @@ interface ServiceProviderProps {
export function ServiceProvider({ children }: ServiceProviderProps) {
const services = useMemo(() => {
const serviceFactory = new ServiceFactory(process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001');
const serviceFactory = new ServiceFactory(getWebsiteApiBaseUrl());
return {
raceService: serviceFactory.createRaceService(),