189 lines
6.4 KiB
TypeScript
189 lines
6.4 KiB
TypeScript
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
|
|
export type RouteAccess = 'public' | 'auth' | 'admin' | 'sponsor';
|
|
|
|
export type RouteParams = Record<string, string>;
|
|
|
|
export type WebsiteRouteDefinition = {
|
|
pathTemplate: string;
|
|
params?: RouteParams;
|
|
access: RouteAccess;
|
|
expectedPathTemplate?: string;
|
|
allowNotFound?: boolean;
|
|
};
|
|
|
|
function walkDir(rootDir: string): string[] {
|
|
const entries = fs.readdirSync(rootDir, { withFileTypes: true });
|
|
const results: string[] = [];
|
|
|
|
for (const entry of entries) {
|
|
if (entry.name.startsWith('.')) continue;
|
|
|
|
const fullPath = path.join(rootDir, entry.name);
|
|
|
|
if (entry.isDirectory()) {
|
|
results.push(...walkDir(fullPath));
|
|
continue;
|
|
}
|
|
|
|
results.push(fullPath);
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
function toPathTemplate(appDir: string, pageFilePath: string): string {
|
|
const rel = path.relative(appDir, pageFilePath);
|
|
const segments = rel.split(path.sep);
|
|
|
|
// drop trailing "page.tsx"
|
|
segments.pop();
|
|
|
|
// root page.tsx
|
|
if (segments.length === 0) return '/';
|
|
|
|
return `/${segments.join('/')}`;
|
|
}
|
|
|
|
export function listNextAppPageTemplates(appDir?: string): string[] {
|
|
const resolvedAppDir = appDir ?? path.join(process.cwd(), 'apps', 'website', 'app');
|
|
|
|
const files = walkDir(resolvedAppDir);
|
|
const pages = files.filter((f) => path.basename(f) === 'page.tsx');
|
|
|
|
return pages.map((pagePath) => toPathTemplate(resolvedAppDir, pagePath));
|
|
}
|
|
|
|
export function resolvePathTemplate(pathTemplate: string, params: RouteParams = {}): string {
|
|
return pathTemplate.replace(/\[([^\]]+)\]/g, (_match, key: string) => {
|
|
const replacement = params[key];
|
|
if (!replacement) {
|
|
throw new Error(`Missing route param "${key}" for template "${pathTemplate}"`);
|
|
}
|
|
return replacement;
|
|
});
|
|
}
|
|
|
|
// Default IDs used to resolve dynamic routes in smoke tests.
|
|
// These values must be supported by the docker mock API in docker-compose.test.yml.
|
|
const LEAGUE_ID = 'league-1';
|
|
const DRIVER_ID = 'driver-1';
|
|
const TEAM_ID = 'team-1';
|
|
const RACE_ID = 'race-1';
|
|
const PROTEST_ID = 'protest-1';
|
|
|
|
const ROUTE_META: Record<string, Omit<WebsiteRouteDefinition, 'pathTemplate'>> = {
|
|
'/': { access: 'public' },
|
|
|
|
'/404': { access: 'public' },
|
|
'/500': { access: 'public' },
|
|
|
|
'/auth/iracing': { access: 'public' },
|
|
'/auth/login': { access: 'public' },
|
|
'/auth/signup': { access: 'public' },
|
|
|
|
'/dashboard': { access: 'auth' },
|
|
|
|
'/drivers': { access: 'public' },
|
|
'/drivers/[id]': { access: 'public', params: { id: DRIVER_ID } },
|
|
|
|
'/leaderboards': { access: 'public' },
|
|
'/leaderboards/drivers': { access: 'public' },
|
|
|
|
'/leagues': { access: 'public' },
|
|
'/leagues/create': { access: 'auth' },
|
|
'/leagues/[id]': { access: 'public', params: { id: LEAGUE_ID } },
|
|
'/leagues/[id]/roster/admin': { access: 'admin', params: { id: LEAGUE_ID } },
|
|
'/leagues/[id]/rulebook': { access: 'public', params: { id: LEAGUE_ID } },
|
|
'/leagues/[id]/schedule': { access: 'public', params: { id: LEAGUE_ID } },
|
|
'/leagues/[id]/schedule/admin': { access: 'admin', params: { id: LEAGUE_ID } },
|
|
'/leagues/[id]/settings': { access: 'admin', params: { id: LEAGUE_ID } },
|
|
'/leagues/[id]/sponsorships': { access: 'admin', params: { id: LEAGUE_ID } },
|
|
'/leagues/[id]/standings': { access: 'public', params: { id: LEAGUE_ID } },
|
|
'/leagues/[id]/stewarding': { access: 'admin', params: { id: LEAGUE_ID } },
|
|
'/leagues/[id]/stewarding/protests/[protestId]': {
|
|
access: 'admin',
|
|
params: { id: LEAGUE_ID, protestId: PROTEST_ID },
|
|
},
|
|
'/leagues/[id]/wallet': { access: 'admin', params: { id: LEAGUE_ID } },
|
|
|
|
'/onboarding': { access: 'auth' },
|
|
|
|
'/profile': { access: 'auth' },
|
|
'/profile/leagues': { access: 'auth' },
|
|
'/profile/liveries': { access: 'auth' },
|
|
'/profile/liveries/upload': { access: 'auth' },
|
|
'/profile/settings': { access: 'auth' },
|
|
'/profile/sponsorship-requests': { access: 'auth' },
|
|
|
|
'/races': { access: 'public' },
|
|
'/races/all': { access: 'public' },
|
|
'/races/[id]': { access: 'public', params: { id: RACE_ID } },
|
|
'/races/[id]/results': { access: 'public', params: { id: RACE_ID } },
|
|
'/races/[id]/stewarding': { access: 'admin', params: { id: RACE_ID } },
|
|
|
|
'/sponsor': { access: 'sponsor', expectedPathTemplate: '/sponsor/dashboard' },
|
|
'/sponsor/billing': { access: 'sponsor' },
|
|
'/sponsor/campaigns': { access: 'sponsor' },
|
|
'/sponsor/dashboard': { access: 'sponsor' },
|
|
'/sponsor/leagues': { access: 'sponsor' },
|
|
'/sponsor/leagues/[id]': { access: 'sponsor', params: { id: LEAGUE_ID } },
|
|
'/sponsor/settings': { access: 'sponsor' },
|
|
'/sponsor/signup': { access: 'public' },
|
|
|
|
'/teams': { access: 'public' },
|
|
'/teams/leaderboard': { access: 'public' },
|
|
'/teams/[id]': { access: 'public', params: { id: TEAM_ID } },
|
|
};
|
|
|
|
export function getWebsiteRouteInventory(): WebsiteRouteDefinition[] {
|
|
const discovered = listNextAppPageTemplates();
|
|
|
|
const missingMeta = discovered.filter((template) => !ROUTE_META[template]);
|
|
if (missingMeta.length > 0) {
|
|
throw new Error(
|
|
`Missing ROUTE_META entries for discovered pages:\n${missingMeta
|
|
.slice()
|
|
.sort()
|
|
.map((t) => `- ${t}`)
|
|
.join('\n')}`,
|
|
);
|
|
}
|
|
|
|
const extraMeta = Object.keys(ROUTE_META).filter((template) => !discovered.includes(template));
|
|
if (extraMeta.length > 0) {
|
|
throw new Error(
|
|
`ROUTE_META contains templates that are not present as page.tsx routes:\n${extraMeta
|
|
.slice()
|
|
.sort()
|
|
.map((t) => `- ${t}`)
|
|
.join('\n')}`,
|
|
);
|
|
}
|
|
|
|
return discovered
|
|
.slice()
|
|
.sort()
|
|
.map((pathTemplate) => ({ pathTemplate, ...ROUTE_META[pathTemplate] }));
|
|
}
|
|
|
|
export function getWebsiteParamEdgeCases(): WebsiteRouteDefinition[] {
|
|
return [
|
|
{ pathTemplate: '/races/[id]', params: { id: 'does-not-exist' }, access: 'public', allowNotFound: true },
|
|
{ pathTemplate: '/leagues/[id]', params: { id: 'does-not-exist' }, access: 'public', allowNotFound: true },
|
|
];
|
|
}
|
|
|
|
export function getWebsiteFaultInjectionRoutes(): WebsiteRouteDefinition[] {
|
|
return [
|
|
{ pathTemplate: '/leagues/[id]', params: { id: LEAGUE_ID }, access: 'public' },
|
|
{ pathTemplate: '/leagues/[id]/schedule/admin', params: { id: LEAGUE_ID }, access: 'admin' },
|
|
{ pathTemplate: '/sponsor/dashboard', access: 'sponsor' },
|
|
{ pathTemplate: '/races/[id]', params: { id: RACE_ID }, access: 'public' },
|
|
];
|
|
}
|
|
|
|
export function getWebsiteAuthDriftRoutes(): WebsiteRouteDefinition[] {
|
|
return [{ pathTemplate: '/sponsor/dashboard', access: 'sponsor' }];
|
|
} |