Files
gridpilot.gg/apps/website/lib/auth/RouteCatalog.ts
2026-01-03 02:42:47 +01:00

274 lines
7.3 KiB
TypeScript

import { routes, routeMatchers } from '../routing/RouteConfig';
/**
* RouteCatalog exposes route IDs and patterns for matching
*
* Route IDs follow the pattern: 'category.routeName'
* Examples:
* - 'auth.login' → '/auth/login'
* - 'protected.dashboard' → '/dashboard'
* - 'league.detail' → '/leagues/[id]' (pattern)
*/
export class RouteCatalog {
/**
* List all public route IDs
* Public routes are accessible without authentication
*/
listPublicRoutes(): string[] {
return [
'public.home',
'public.leagues',
'public.drivers',
'public.teams',
'public.leaderboards',
'public.races',
'public.sponsorSignup',
'auth.login',
'auth.signup',
'auth.forgotPassword',
'auth.resetPassword',
'auth.iRacingStart',
'auth.iRacingCallback',
'error.notFound',
'error.serverError',
// Parameterized public routes
'league.detail',
'league.rulebook',
'league.schedule',
'league.standings',
'driver.detail',
'team.detail',
'race.detail',
'race.results',
'race.all',
];
}
/**
* List all protected route IDs
* Protected routes require authentication
*/
listProtectedRoutes(): string[] {
return [
'protected.dashboard',
'protected.onboarding',
'protected.profile',
'protected.profileSettings',
'protected.profileLeagues',
'protected.profileLiveries',
'protected.profileLiveryUpload',
'protected.profileSponsorshipRequests',
'sponsor.root',
'sponsor.dashboard',
'sponsor.billing',
'sponsor.campaigns',
'sponsor.leagues',
'sponsor.settings',
'admin.root',
'admin.users',
'league.create',
'race.root',
'team.root',
'team.leaderboard',
];
}
/**
* List all admin route IDs
* Admin routes require admin-level permissions
*/
listAdminRoutes(): string[] {
return [
'admin.root',
'admin.users',
'league.rosterAdmin',
'league.scheduleAdmin',
'league.stewarding',
'league.settings',
'league.sponsorships',
'league.wallet',
'race.stewarding',
];
}
/**
* List all sponsor route IDs
* Sponsor routes require sponsor role
*/
listSponsorRoutes(): string[] {
return [
'sponsor.root',
'sponsor.dashboard',
'sponsor.billing',
'sponsor.campaigns',
'sponsor.leagues',
'sponsor.settings',
];
}
/**
* Get the path pattern for a route ID
* @param routeId - Route ID in format 'category.routeName'
* @returns Path pattern (e.g., '/auth/login' or '/leagues/[id]')
* @throws Error if route ID is unknown
*/
getPattern(routeId: string): string {
const parts = routeId.split('.');
let route: any = routes;
for (const part of parts) {
route = route[part];
if (!route) {
throw new Error(`Unknown route ID: ${routeId}`);
}
}
// Handle parameterized routes
if (typeof route === 'function') {
// Return pattern with placeholder
const paramPattern = route('placeholder');
return paramPattern.replace('/placeholder', '/[id]');
}
return route as string;
}
/**
* Check if a path is an auth page
* @param logicalPath - Path to check
* @returns True if path is an auth page
*/
isAuthPage(logicalPath: string): boolean {
return routeMatchers.isInGroup(logicalPath, 'auth');
}
/**
* Get all route patterns with their IDs
* @returns Array of route patterns with IDs
*/
getAllPatterns(): Array<{ routeId: string; pattern: string }> {
const patterns: Array<{ routeId: string; pattern: string }> = [];
// Helper to traverse routes and build patterns
const traverse = (obj: any, prefix: string) => {
for (const [key, value] of Object.entries(obj)) {
const routeId = prefix ? `${prefix}.${key}` : key;
if (typeof value === 'function') {
// Parameterized route
const pattern = value('placeholder').replace('/placeholder', '/[id]');
patterns.push({ routeId, pattern });
} else if (typeof value === 'object' && value !== null) {
// Nested category
traverse(value, routeId);
} else if (typeof value === 'string') {
// Simple route
patterns.push({ routeId, pattern: value });
}
}
};
traverse(routes, '');
return patterns;
}
/**
* Get route ID by path
* @param path - Path to find
* @returns Route ID or null if not found
*
* Note: This method prioritizes exact matches over parameterized matches.
* For example, '/leagues/create' will match 'league.create' before 'league.detail'.
*/
getRouteIdByPath(path: string): string | null {
const allPatterns = this.getAllPatterns();
// First, try exact matches
for (const { routeId, pattern } of allPatterns) {
if (pattern === path) {
return routeId;
}
}
// Then, try parameterized matches
for (const { routeId, pattern } of allPatterns) {
if (pattern.includes('[')) {
const paramPattern = pattern.replace(/\[([^\]]+)\]/g, '([^/]+)');
const regex = new RegExp(`^${paramPattern}$`);
if (regex.test(path)) {
return routeId;
}
}
}
return null;
}
/**
* Check if a path requires specific role-based access
* @param logicalPath - Path to check
* @returns Array of required roles or null
*/
getRequiredRoles(logicalPath: string): string[] | null {
// Check admin routes
if (routeMatchers.isInGroup(logicalPath, 'admin')) {
return ['system-owner', 'super-admin', 'league-admin'];
}
// Check sponsor routes
if (routeMatchers.isInGroup(logicalPath, 'sponsor')) {
return ['sponsor'];
}
// Check league admin routes (specific patterns)
if (logicalPath.match(/\/leagues\/[^/]+\/(roster\/admin|schedule\/admin|stewarding|settings|sponsorships|wallet)/)) {
return ['system-owner', 'super-admin', 'league-admin'];
}
// Check race stewarding routes
if (logicalPath.match(/\/races\/[^/]+\/stewarding/)) {
return ['system-owner', 'super-admin', 'league-steward'];
}
// Public or auth-only routes (no specific role)
return null;
}
/**
* Get the home path for a specific role
* @param role - The role name
* @returns The logical path for that role's home page
*/
getRoleHome(role: string): string {
const roleHomeMap: Record<string, string> = {
'driver': '/dashboard',
'sponsor': '/sponsor/dashboard',
'league-admin': '/admin',
'league-steward': '/admin',
'league-owner': '/admin',
'system-owner': '/admin',
'super-admin': '/admin',
};
return roleHomeMap[role] || '/dashboard';
}
/**
* Get the route ID for a specific role's home page
* @param role - The role name
* @returns The route ID for that role's home page
*/
getRoleHomeRouteId(role: string): string {
const roleHomeRouteMap: Record<string, string> = {
'driver': 'protected.dashboard',
'sponsor': 'sponsor.dashboard',
'league-admin': 'admin',
'league-steward': 'admin',
'league-owner': 'admin',
'system-owner': 'admin',
'super-admin': 'admin',
};
return roleHomeRouteMap[role] || 'protected.dashboard';
}
}