website refactor

This commit is contained in:
2026-01-16 01:00:03 +01:00
parent ce7be39155
commit a98e3e3166
286 changed files with 5522 additions and 5261 deletions

View File

@@ -6,14 +6,15 @@
*/
import { Result } from '@/lib/contracts/Result';
import { DomainError, Service } from '@/lib/contracts/services/Service';
import { LoginPageDTO } from './types/LoginPageDTO';
import { ForgotPasswordPageDTO } from './types/ForgotPasswordPageDTO';
import { ResetPasswordPageDTO } from './types/ResetPasswordPageDTO';
import { SignupPageDTO } from './types/SignupPageDTO';
import { AuthPageParams } from './AuthPageParams';
export class AuthPageService {
async processLoginParams(params: AuthPageParams): Promise<Result<LoginPageDTO, string>> {
export class AuthPageService implements Service {
async processLoginParams(params: AuthPageParams): Promise<Result<LoginPageDTO, DomainError>> {
try {
const returnTo = params.returnTo ?? '/dashboard';
const hasInsufficientPermissions = params.returnTo !== null;
@@ -23,38 +24,38 @@ export class AuthPageService {
hasInsufficientPermissions,
});
} catch (error) {
return Result.err('Failed to process login parameters');
return Result.err({ type: 'unknown', message: 'Failed to process login parameters' });
}
}
async processForgotPasswordParams(params: AuthPageParams): Promise<Result<ForgotPasswordPageDTO, string>> {
async processForgotPasswordParams(params: AuthPageParams): Promise<Result<ForgotPasswordPageDTO, DomainError>> {
try {
const returnTo = params.returnTo ?? '/auth/login';
return Result.ok({ returnTo });
} catch (error) {
return Result.err('Failed to process forgot password parameters');
return Result.err({ type: 'unknown', message: 'Failed to process forgot password parameters' });
}
}
async processResetPasswordParams(params: AuthPageParams): Promise<Result<ResetPasswordPageDTO, string>> {
async processResetPasswordParams(params: AuthPageParams): Promise<Result<ResetPasswordPageDTO, DomainError>> {
try {
const token = params.token;
if (!token) {
return Result.err('Missing reset token');
return Result.err({ type: 'validation', message: 'Missing reset token' });
}
const returnTo = params.returnTo ?? '/auth/login';
return Result.ok({ token, returnTo });
} catch (error) {
return Result.err('Failed to process reset password parameters');
return Result.err({ type: 'unknown', message: 'Failed to process reset password parameters' });
}
}
async processSignupParams(params: AuthPageParams): Promise<Result<SignupPageDTO, string>> {
async processSignupParams(params: AuthPageParams): Promise<Result<SignupPageDTO, DomainError>> {
try {
const returnTo = params.returnTo ?? '/onboarding';
return Result.ok({ returnTo });
} catch (error) {
return Result.err('Failed to process signup parameters');
return Result.err({ type: 'unknown', message: 'Failed to process signup parameters' });
}
}
}
}

View File

@@ -1,53 +1,87 @@
import { AuthApiClient } from '@/lib/api/auth/AuthApiClient';
import { Result } from '@/lib/contracts/Result';
import { DomainError, Service } from '@/lib/contracts/services/Service';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
import type { AuthSessionDTO } from '@/lib/types/generated/AuthSessionDTO';
import type { LoginParamsDTO } from '@/lib/types/generated/LoginParamsDTO';
import type { SignupParamsDTO } from '@/lib/types/generated/SignupParamsDTO';
import type { ForgotPasswordDTO } from '@/lib/types/generated/ForgotPasswordDTO';
import type { ResetPasswordDTO } from '@/lib/types/generated/ResetPasswordDTO';
import { isProductionEnvironment } from '@/lib/config/env';
/**
* Auth Service
*
* Orchestrates authentication operations.
* Calls AuthApiClient for API calls.
* Returns raw API DTOs. No ViewModels or UX logic.
*/
import { AuthApiClient } from '@/lib/api/auth/AuthApiClient';
import { SessionViewModel } from '@/lib/view-models/SessionViewModel';
import { AuthSessionDTO } from '@/lib/types/generated/AuthSessionDTO';
import { LoginParamsDTO } from '@/lib/types/generated/LoginParamsDTO';
import { SignupParamsDTO } from '@/lib/types/generated/SignupParamsDTO';
import { ForgotPasswordDTO } from '@/lib/types/generated/ForgotPasswordDTO';
import { ResetPasswordDTO } from '@/lib/types/generated/ResetPasswordDTO';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
export class AuthService {
export class AuthService implements Service {
private apiClient: AuthApiClient;
constructor() {
const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001';
const baseUrl = getWebsiteApiBaseUrl();
const logger = new ConsoleLogger();
const errorReporter = new EnhancedErrorReporter(logger, {
showUserNotifications: false,
logToConsole: true,
reportToExternal: process.env.NODE_ENV === 'production',
reportToExternal: isProductionEnvironment(),
});
this.apiClient = new AuthApiClient(baseUrl, errorReporter, logger);
}
async login(params: LoginParamsDTO): Promise<SessionViewModel> {
const dto = await this.apiClient.login(params);
return new SessionViewModel(dto.user);
async login(params: LoginParamsDTO): Promise<Result<AuthSessionDTO, DomainError>> {
try {
const dto = await this.apiClient.login(params);
return Result.ok(dto);
} catch (error: unknown) {
return Result.err({ type: 'unauthorized', message: (error as Error).message || 'Login failed' });
}
}
async signup(params: SignupParamsDTO): Promise<SessionViewModel> {
const dto = await this.apiClient.signup(params);
return new SessionViewModel(dto.user);
async signup(params: SignupParamsDTO): Promise<Result<AuthSessionDTO, DomainError>> {
try {
const dto = await this.apiClient.signup(params);
return Result.ok(dto);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Signup failed' });
}
}
async logout(): Promise<void> {
await this.apiClient.logout();
async logout(): Promise<Result<void, DomainError>> {
try {
await this.apiClient.logout();
return Result.ok(undefined);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Logout failed' });
}
}
async forgotPassword(params: ForgotPasswordDTO): Promise<{ message: string; magicLink?: string }> {
return await this.apiClient.forgotPassword(params);
async forgotPassword(params: ForgotPasswordDTO): Promise<Result<{ message: string; magicLink?: string }, DomainError>> {
try {
const result = await this.apiClient.forgotPassword(params);
return Result.ok(result);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Forgot password request failed' });
}
}
async resetPassword(params: ResetPasswordDTO): Promise<{ message: string }> {
return await this.apiClient.resetPassword(params);
async resetPassword(params: ResetPasswordDTO): Promise<Result<{ message: string }, DomainError>> {
try {
const result = await this.apiClient.resetPassword(params);
return Result.ok(result);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Reset password failed' });
}
}
async getSession(): Promise<Result<AuthSessionDTO | null, DomainError>> {
try {
const dto = await this.apiClient.getSession();
return Result.ok(dto);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to fetch session' });
}
}
}

View File

@@ -1,23 +1,25 @@
import { AuthApiClient } from '@/lib/api/auth/AuthApiClient';
import { Result } from '@/lib/contracts/Result';
import { DomainError, Service } from '@/lib/contracts/services/Service';
import { AuthService } from './AuthService';
import type { AuthSessionDTO } from '@/lib/types/generated/AuthSessionDTO';
import { SessionViewModel } from '@/lib/view-models/SessionViewModel';
/**
* Session Service
*
* Returns SessionViewModel for client consumption.
* Orchestrates session-related operations.
* Returns raw API DTOs. No ViewModels or UX logic.
*/
export class SessionService {
constructor(
private readonly apiClient: AuthApiClient
) {}
export class SessionService implements Service {
private authService: AuthService;
constructor() {
this.authService = new AuthService();
}
/**
* Get current user session (returns ViewModel)
* Get current user session
*/
async getSession(): Promise<SessionViewModel | null> {
const dto = await this.apiClient.getSession();
if (!dto) return null;
return new SessionViewModel(dto.user);
async getSession(): Promise<Result<AuthSessionDTO | null, DomainError>> {
return this.authService.getSession();
}
}

View File

@@ -4,6 +4,11 @@ import type { GetDriverOutputDTO } from '@/lib/types/generated/GetDriverOutputDT
import type { CompleteOnboardingOutputDTO } from '@/lib/types/generated/CompleteOnboardingOutputDTO';
import type { DriverDTO } from '@/lib/types/generated/DriverDTO';
import type { GetDriverProfileOutputDTO } from '@/lib/types/generated/GetDriverProfileOutputDTO';
import { Result } from '@/lib/contracts/Result';
import { DomainError, Service } from '@/lib/contracts/services/Service';
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
/**
* Driver Service - DTO Only
@@ -11,58 +16,97 @@ import type { GetDriverProfileOutputDTO } from '@/lib/types/generated/GetDriverP
* Returns raw API DTOs. No ViewModels or UX logic.
* All client-side presentation logic must be handled by hooks/components.
*/
export class DriverService {
constructor(
private readonly apiClient: DriversApiClient
) {}
export class DriverService implements Service {
private readonly apiClient: DriversApiClient;
constructor() {
const baseUrl = getWebsiteApiBaseUrl();
const logger = new ConsoleLogger();
const errorReporter = new EnhancedErrorReporter(logger);
this.apiClient = new DriversApiClient(baseUrl, errorReporter, logger);
}
/**
* Get driver leaderboard (returns DTO)
*/
async getDriverLeaderboard() {
return this.apiClient.getLeaderboard();
async getDriverLeaderboard(): Promise<Result<unknown, DomainError>> {
try {
const data = await this.apiClient.getLeaderboard();
return Result.ok(data);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to get leaderboard' });
}
}
/**
* Complete driver onboarding (returns DTO)
*/
async completeDriverOnboarding(input: CompleteOnboardingInputDTO): Promise<CompleteOnboardingOutputDTO> {
return this.apiClient.completeOnboarding(input);
async completeDriverOnboarding(input: CompleteOnboardingInputDTO): Promise<Result<CompleteOnboardingOutputDTO, DomainError>> {
try {
const data = await this.apiClient.completeOnboarding(input);
return Result.ok(data);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to complete onboarding' });
}
}
/**
* Get current driver (returns DTO)
*/
async getCurrentDriver(): Promise<DriverDTO | null> {
return this.apiClient.getCurrent();
async getCurrentDriver(): Promise<Result<DriverDTO | null, DomainError>> {
try {
const data = await this.apiClient.getCurrent();
return Result.ok(data);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to get current driver' });
}
}
/**
* Get driver profile (returns DTO)
*/
async getDriverProfile(driverId: string): Promise<GetDriverProfileOutputDTO> {
return this.apiClient.getDriverProfile(driverId);
async getDriverProfile(driverId: string): Promise<Result<GetDriverProfileOutputDTO, DomainError>> {
try {
const data = await this.apiClient.getDriverProfile(driverId);
return Result.ok(data);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to get driver profile' });
}
}
/**
* Update current driver profile (returns DTO)
*/
async updateProfile(updates: { bio?: string; country?: string }): Promise<DriverDTO> {
return this.apiClient.updateProfile(updates);
async updateProfile(updates: { bio?: string; country?: string }): Promise<Result<DriverDTO, DomainError>> {
try {
const data = await this.apiClient.updateProfile(updates);
return Result.ok(data);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to update profile' });
}
}
/**
* Find driver by ID (returns DTO)
*/
async findById(id: string): Promise<GetDriverOutputDTO | null> {
return this.apiClient.getDriver(id);
async findById(id: string): Promise<Result<GetDriverOutputDTO | null, DomainError>> {
try {
const data = await this.apiClient.getDriver(id);
return Result.ok(data);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to find driver' });
}
}
/**
* Find multiple drivers by IDs (returns DTOs)
*/
async findByIds(ids: string[]): Promise<GetDriverOutputDTO[]> {
const drivers = await Promise.all(ids.map(id => this.apiClient.getDriver(id)));
return drivers.filter((d): d is GetDriverOutputDTO => d !== null);
async findByIds(ids: string[]): Promise<Result<GetDriverOutputDTO[], DomainError>> {
try {
const drivers = await Promise.all(ids.map(id => this.apiClient.getDriver(id)));
return Result.ok(drivers.filter((d): d is GetDriverOutputDTO => d !== null));
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to find drivers' });
}
}
}

View File

@@ -1,5 +1,5 @@
import { Result } from '@/lib/contracts/Result';
import type { Service } from '@/lib/contracts/services/Service';
import type { DomainError, Service } from '@/lib/contracts/services/Service';
import type { GetLiveriesOutputDTO } from '@/lib/types/tbd/GetLiveriesOutputDTO';
/**
@@ -8,7 +8,7 @@ import type { GetLiveriesOutputDTO } from '@/lib/types/tbd/GetLiveriesOutputDTO'
* Provides livery management functionality.
*/
export class LiveryService implements Service {
async getLiveries(driverId: string): Promise<Result<GetLiveriesOutputDTO, string>> {
async getLiveries(_: string): Promise<Result<GetLiveriesOutputDTO, DomainError>> {
// Mock data for now
const mockLiveries: GetLiveriesOutputDTO = {
liveries: [
@@ -31,7 +31,7 @@ export class LiveryService implements Service {
return Result.ok(mockLiveries);
}
async uploadLivery(driverId: string, file: File): Promise<Result<{ liveryId: string }, string>> {
async uploadLivery(__: string, ___: File): Promise<Result<{ liveryId: string }, DomainError>> {
// Mock implementation
return Result.ok({ liveryId: 'new-livery-id' });
}

View File

@@ -1,6 +1,9 @@
import { getGlobalErrorHandler } from '@/lib/infrastructure/GlobalErrorHandler';
import { getGlobalApiLogger } from '@/lib/infrastructure/ApiRequestLogger';
import { ApiError } from '@/lib/api/base/ApiError';
import { Result } from '@/lib/contracts/Result';
import { DomainError, Service } from '@/lib/contracts/services/Service';
import { getWebsiteServerEnv } from '@/lib/config/env';
export interface ErrorStats {
totalErrors: number;
@@ -26,59 +29,71 @@ export interface ErrorStats {
};
}
export function getErrorAnalyticsStats(): ErrorStats {
const globalHandler = getGlobalErrorHandler();
const apiLogger = getGlobalApiLogger();
export class ErrorAnalyticsService implements Service {
static getErrorAnalyticsStats(): ErrorStats {
const globalHandler = getGlobalErrorHandler();
const apiLogger = getGlobalApiLogger();
const errorHistory = globalHandler.getErrorHistory();
const errorStats = globalHandler.getStats();
const apiStats = apiLogger.getStats();
const errorHistory = globalHandler.getErrorHistory();
const errorStats = globalHandler.getStats();
const apiStats = apiLogger.getStats();
// Group errors by time (last 10 minutes)
const timeGroups = new Map<string, number>();
const now = Date.now();
const tenMinutesAgo = now - (10 * 60 * 1000);
// Group errors by time (last 10 minutes)
const timeGroups = new Map<string, number>();
const now = Date.now();
const tenMinutesAgo = now - (10 * 60 * 1000);
errorHistory.forEach(entry => {
const entryTime = new Date(entry.timestamp).getTime();
if (entryTime >= tenMinutesAgo) {
const timeKey = new Date(entry.timestamp).toLocaleTimeString();
timeGroups.set(timeKey, (timeGroups.get(timeKey) || 0) + 1);
errorHistory.forEach(entry => {
const entryTime = new Date(entry.timestamp).getTime();
if (entryTime >= tenMinutesAgo) {
const timeKey = new Date(entry.timestamp).toLocaleTimeString();
timeGroups.set(timeKey, (timeGroups.get(timeKey) || 0) + 1);
}
});
const errorsByTime = Array.from(timeGroups.entries())
.map(([time, count]) => ({ time, count }))
.sort((a, b) => a.time.localeCompare(b.time));
const recentErrors = errorHistory.slice(-10).reverse().map(entry => ({
timestamp: entry.timestamp,
message: entry.error.message,
type: entry.error instanceof ApiError ? entry.error.type : entry.error.name || 'Error',
context: entry.context,
}));
const slowestRequests = apiLogger.getSlowestRequests(5).map(log => ({
url: log.url,
duration: log.response?.duration || 0,
}));
const env = getWebsiteServerEnv();
return {
totalErrors: errorStats.total,
errorsByType: errorStats.byType,
errorsByTime,
recentErrors,
apiStats: {
totalRequests: apiStats.total,
successful: apiStats.successful,
failed: apiStats.failed,
averageDuration: apiStats.averageDuration,
slowestRequests,
},
environment: {
mode: env.NODE_ENV || 'unknown',
version: process.env.NEXT_PUBLIC_APP_VERSION,
buildTime: process.env.NEXT_PUBLIC_BUILD_TIME,
},
};
}
async getErrorAnalyticsStats(): Promise<Result<ErrorStats, DomainError>> {
try {
return Result.ok(ErrorAnalyticsService.getErrorAnalyticsStats());
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to get error analytics stats' });
}
});
const errorsByTime = Array.from(timeGroups.entries())
.map(([time, count]) => ({ time, count }))
.sort((a, b) => a.time.localeCompare(b.time));
const recentErrors = errorHistory.slice(-10).reverse().map(entry => ({
timestamp: entry.timestamp,
message: entry.error.message,
type: entry.error instanceof ApiError ? entry.error.type : entry.error.name || 'Error',
context: entry.context,
}));
const slowestRequests = apiLogger.getSlowestRequests(5).map(log => ({
url: log.url,
duration: log.response?.duration || 0,
}));
return {
totalErrors: errorStats.total,
errorsByType: errorStats.byType,
errorsByTime,
recentErrors,
apiStats: {
totalRequests: apiStats.total,
successful: apiStats.successful,
failed: apiStats.failed,
averageDuration: apiStats.averageDuration,
slowestRequests,
},
environment: {
mode: process.env.NODE_ENV || 'unknown',
version: process.env.NEXT_PUBLIC_APP_VERSION,
buildTime: process.env.NEXT_PUBLIC_BUILD_TIME,
},
};
}
}

View File

@@ -0,0 +1,75 @@
import { FeatureFlagService } from '@/lib/feature/FeatureFlagService';
// API Clients
import { RacesApiClient } from '@/lib/api/races/RacesApiClient';
import { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient';
import { TeamsApiClient } from '@/lib/api/teams/TeamsApiClient';
// Services
import { SessionService } from '@/lib/services/auth/SessionService';
// Infrastructure
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
// DTO types
import type { HomeViewData } from '@/templates/HomeTemplate';
import { Result } from '@/lib/contracts/Result';
export class HomeService {
async getHomeData(): Promise<Result<HomeViewData, Error>> {
try {
// Manual wiring: construct dependencies explicitly
const baseUrl = getWebsiteApiBaseUrl();
const errorReporter = new ConsoleErrorReporter();
const logger = new ConsoleLogger();
// Construct API clients
const racesApiClient = new RacesApiClient(baseUrl, errorReporter, logger);
const leaguesApiClient = new LeaguesApiClient(baseUrl, errorReporter, logger);
const teamsApiClient = new TeamsApiClient(baseUrl, errorReporter, logger);
// Get feature flags
const featureService = await FeatureFlagService.fromAPI();
const isAlpha = featureService.isEnabled('alpha_features');
// Get home discovery data (manual implementation)
const [racesDto, leaguesDto, teamsDto] = await Promise.all([
racesApiClient.getPageData(),
leaguesApiClient.getAllWithCapacity(),
teamsApiClient.getAll(),
]);
// Return DTOs directly (no ViewModels)
return Result.ok({
isAlpha,
upcomingRaces: racesDto.races.slice(0, 4).map(r => ({
id: r.id,
track: r.track,
car: r.car,
formattedDate: new Date(r.scheduledAt).toLocaleDateString(),
})),
topLeagues: leaguesDto.leagues.slice(0, 4).map(l => ({
id: l.id,
name: l.name,
description: l.description,
})),
teams: teamsDto.teams.slice(0, 4).map(t => ({
id: t.id,
name: t.name,
description: t.description,
logoUrl: t.logoUrl,
})),
});
} catch (err) {
return Result.err(err instanceof Error ? err : new Error('Failed to get home data'));
}
}
async shouldRedirectToDashboard(): Promise<boolean> {
const sessionService = new SessionService();
const sessionResult = await sessionService.getSession();
return sessionResult.isOk() && !!sessionResult.unwrap();
}
}

View File

@@ -1,69 +0,0 @@
import { redirect } from 'next/navigation';
import { routes } from '@/lib/routing/RouteConfig';
import { FeatureFlagService } from '@/lib/feature/FeatureFlagService';
// API Clients
import { AuthApiClient } from '@/lib/api/auth/AuthApiClient';
import { RacesApiClient } from '@/lib/api/races/RacesApiClient';
import { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient';
import { TeamsApiClient } from '@/lib/api/teams/TeamsApiClient';
// Services
import { SessionService } from '@/lib/services/auth/SessionService';
// Infrastructure
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
// DTO types
import type { RacesPageDataRaceDTO } from '@/lib/types/generated/RacesPageDataRaceDTO';
import type { LeagueWithCapacityDTO } from '@/lib/types/generated/LeagueWithCapacityDTO';
import type { TeamListItemDTO } from '@/lib/types/generated/TeamListItemDTO';
export interface HomeDataDTO {
isAlpha: boolean;
upcomingRaces: RacesPageDataRaceDTO[];
topLeagues: LeagueWithCapacityDTO[];
teams: TeamListItemDTO[];
}
export async function getHomeData(): Promise<HomeDataDTO> {
// Manual wiring: construct dependencies explicitly
const baseUrl = process.env.NEXT_PUBLIC_API_URL || '';
const errorReporter = new ConsoleErrorReporter();
const logger = new ConsoleLogger();
// Construct API clients
const authApiClient = new AuthApiClient(baseUrl, errorReporter, logger);
const racesApiClient = new RacesApiClient(baseUrl, errorReporter, logger);
const leaguesApiClient = new LeaguesApiClient(baseUrl, errorReporter, logger);
const teamsApiClient = new TeamsApiClient(baseUrl, errorReporter, logger);
// Construct services
const sessionService = new SessionService(authApiClient);
// Check session and redirect if logged in
const session = await sessionService.getSession();
if (session) {
redirect(routes.protected.dashboard);
}
// Get feature flags
const featureService = await FeatureFlagService.fromAPI();
const isAlpha = featureService.isEnabled('alpha_features');
// Get home discovery data (manual implementation)
const [racesDto, leaguesDto, teamsDto] = await Promise.all([
racesApiClient.getPageData(),
leaguesApiClient.getAllWithCapacity(),
teamsApiClient.getAll(),
]);
// Return DTOs directly (no ViewModels)
return {
isAlpha,
upcomingRaces: racesDto.races.slice(0, 4),
topLeagues: leaguesDto.leagues.slice(0, 4),
teams: teamsDto.teams.slice(0, 4),
};
}

View File

@@ -1,5 +1,13 @@
import { Result } from '@/lib/contracts/Result';
import { DomainError } from '@/lib/contracts/services/Service';
import { DomainError, Service } from '@/lib/contracts/services/Service';
import { RacesApiClient } from '@/lib/api/races/RacesApiClient';
import { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient';
import { TeamsApiClient } from '@/lib/api/teams/TeamsApiClient';
import { AuthApiClient } from '@/lib/api/auth/AuthApiClient';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
import { isProductionEnvironment } from '@/lib/config/env';
/**
* Landing Service - DTO Only
@@ -7,14 +15,34 @@ import { DomainError } from '@/lib/contracts/services/Service';
* Returns raw API DTOs. No ViewModels or UX logic.
* All client-side presentation logic must be handled by hooks/components.
*/
export class LandingService {
constructor(private readonly apiClient: any) {}
export class LandingService implements Service {
private racesApi: RacesApiClient;
private leaguesApi: LeaguesApiClient;
private teamsApi: TeamsApiClient;
private authApi: AuthApiClient;
async getLandingData(): Promise<any> {
return { featuredLeagues: [], stats: {} };
constructor() {
const baseUrl = getWebsiteApiBaseUrl();
const logger = new ConsoleLogger();
const errorReporter = new EnhancedErrorReporter(logger, {
showUserNotifications: true,
logToConsole: true,
reportToExternal: isProductionEnvironment(),
});
this.racesApi = new RacesApiClient(baseUrl, errorReporter, logger);
this.leaguesApi = new LeaguesApiClient(baseUrl, errorReporter, logger);
this.teamsApi = new TeamsApiClient(baseUrl, errorReporter, logger);
this.authApi = new AuthApiClient(baseUrl, errorReporter, logger);
}
async signup(email: string): Promise<Result<void, DomainError>> {
async getLandingData(): Promise<Result<{ featuredLeagues: unknown[]; stats: Record<string, unknown> }, DomainError>> {
return Result.ok({ featuredLeagues: [], stats: {} });
}
async signup(_email: string): Promise<Result<void, DomainError>> {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const email = _email;
return Result.err({ type: 'notImplemented', message: 'Email signup endpoint' });
}
}
}

View File

@@ -1,41 +1,45 @@
import type { LeagueActivity } from '@/components/leagues/LeagueActivityFeed';
import { Result } from '@/lib/contracts/Result';
import { DomainError, Service } from '@/lib/contracts/services/Service';
export function processLeagueActivities(raceList: any[], limit: number): LeagueActivity[] {
const activities: LeagueActivity[] = [];
const completedRaces = raceList
.filter((r) => r.status === 'completed')
.sort((a, b) => new Date(b.scheduledAt).getTime() - new Date(a.scheduledAt).getTime())
.slice(0, 5);
export class LeagueActivityService implements Service {
processLeagueActivities(raceList: any[], limit: number): Result<LeagueActivity[], DomainError> {
const activities: LeagueActivity[] = [];
const completedRaces = raceList
.filter((r) => r.status === 'completed')
.sort((a, b) => new Date(b.scheduledAt).getTime() - new Date(a.scheduledAt).getTime())
.slice(0, 5);
const upcomingRaces = raceList
.filter((r) => r.status === 'scheduled')
.sort((a, b) => new Date(b.scheduledAt).getTime() - new Date(a.scheduledAt).getTime())
.slice(0, 3);
const upcomingRaces = raceList
.filter((r) => r.status === 'scheduled')
.sort((a, b) => new Date(b.scheduledAt).getTime() - new Date(a.scheduledAt).getTime())
.slice(0, 3);
for (const race of completedRaces) {
activities.push({
type: 'race_completed',
raceId: race.id,
raceName: `${race.track} - ${race.car}`,
timestamp: new Date(race.scheduledAt),
});
for (const race of completedRaces) {
activities.push({
type: 'race_completed',
raceId: race.id,
raceName: `${race.track} - ${race.car}`,
timestamp: new Date(race.scheduledAt),
});
}
for (const race of upcomingRaces) {
activities.push({
type: 'race_scheduled',
raceId: race.id,
raceName: `${race.track} - ${race.car}`,
timestamp: new Date(new Date(race.scheduledAt).getTime() - 7 * 24 * 60 * 60 * 1000), // Simulate schedule announcement
});
}
// Sort all activities by timestamp
activities.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
if (activities.length > limit) {
activities.splice(limit);
}
return Result.ok(activities);
}
for (const race of upcomingRaces) {
activities.push({
type: 'race_scheduled',
raceId: race.id,
raceName: `${race.track} - ${race.car}`,
timestamp: new Date(new Date(race.scheduledAt).getTime() - 7 * 24 * 60 * 60 * 1000), // Simulate schedule announcement
});
}
// Sort all activities by timestamp
activities.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
if (activities.length > limit) {
activities.splice(limit);
}
return activities;
}

View File

@@ -1,166 +1,130 @@
import { ApiClient } from '@/lib/api';
import type { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient';
import { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient';
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
import type { LeagueMemberDTO } from '@/lib/types/generated/LeagueMemberDTO';
import type { LeagueMembershipsDTO } from '@/lib/types/generated/LeagueMembershipsDTO';
import type { LeagueMembership } from '@/lib/types/LeagueMembership';
import { Result } from '@/lib/contracts/Result';
import { Service, type DomainError } from '@/lib/contracts/services/Service';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
import { isProductionEnvironment } from '@/lib/config/env';
import type { LeagueRosterMemberDTO } from '@/lib/types/generated/LeagueRosterMemberDTO';
import type { LeagueRosterJoinRequestDTO } from '@/lib/types/generated/LeagueRosterJoinRequestDTO';
import type { LeagueMembershipDTO } from '@/lib/types/generated/LeagueMembershipDTO';
let cachedLeaguesApiClient: LeaguesApiClient | undefined;
function getDefaultLeaguesApiClient(): LeaguesApiClient {
if (cachedLeaguesApiClient) return cachedLeaguesApiClient;
const api = new ApiClient(getWebsiteApiBaseUrl());
cachedLeaguesApiClient = api.leagues;
return cachedLeaguesApiClient;
export interface LeagueRosterAdminData {
leagueId: string;
members: LeagueRosterMemberDTO[];
joinRequests: LeagueRosterJoinRequestDTO[];
}
export class LeagueMembershipService {
// In-memory cache for memberships (populated via API calls)
private static leagueMemberships = new Map<string, LeagueMembership[]>();
export class LeagueMembershipService implements Service {
private apiClient: LeaguesApiClient;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private static cachedMemberships = new Map<string, any[]>();
constructor(private readonly leaguesApiClient?: LeaguesApiClient) {}
private getClient(): LeaguesApiClient {
return this.leaguesApiClient ?? getDefaultLeaguesApiClient();
constructor() {
const baseUrl = getWebsiteApiBaseUrl();
const logger = new ConsoleLogger();
const errorReporter = new EnhancedErrorReporter(logger, {
showUserNotifications: false,
logToConsole: true,
reportToExternal: isProductionEnvironment(),
});
this.apiClient = new LeaguesApiClient(baseUrl, errorReporter, logger);
}
async getLeagueMemberships(leagueId: string, currentUserId: string): Promise<LeagueMembershipsDTO> {
const dto = await this.getClient().getMemberships(leagueId);
return dto;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static getMembership(leagueId: string, driverId: string): any | null {
const members = this.cachedMemberships.get(leagueId);
if (!members) return null;
return members.find(m => m.driverId === driverId) || null;
}
async removeMember(leagueId: string, performerDriverId: string, targetDriverId: string): Promise<{ success: boolean }> {
return this.getClient().removeMember(leagueId, performerDriverId, targetDriverId);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static getLeagueMembers(leagueId: string): any[] {
return this.cachedMemberships.get(leagueId) || [];
}
/**
* Get a specific membership from cache.
*/
static getMembership(leagueId: string, driverId: string): LeagueMembership | null {
const list = this.leagueMemberships.get(leagueId);
if (!list) return null;
return list.find((m) => m.driverId === driverId) ?? null;
}
/**
* Get all members of a league from cache.
*/
static getLeagueMembers(leagueId: string): LeagueMembership[] {
return [...(this.leagueMemberships.get(leagueId) ?? [])];
}
/**
* Fetch and cache memberships for a league via API.
*/
static async fetchLeagueMemberships(leagueId: string): Promise<LeagueMembership[]> {
try {
const result = await getDefaultLeaguesApiClient().getMemberships(leagueId);
const memberships: LeagueMembership[] = (result.members ?? []).map((member) => ({
id: `${member.driverId}-${leagueId}`, // Generate ID since API doesn't provide it
leagueId,
driverId: member.driverId,
role: member.role as 'owner' | 'admin' | 'steward' | 'member',
status: 'active', // Assume active since API returns current members
joinedAt: member.joinedAt,
}));
this.setLeagueMemberships(leagueId, memberships);
return memberships;
} catch (error) {
console.error('Failed to fetch league memberships:', error);
return [];
}
}
/**
* Join a league.
*
* NOTE: The API currently exposes membership mutations via league member management endpoints.
* For now we keep the website decoupled by consuming only the API through this service.
*/
async joinLeague(leagueId: string, driverId: string): Promise<void> {
// Temporary: no join endpoint exposed yet in API.
// Keep behavior predictable for UI.
throw new Error('Joining leagues is not available in this build.');
}
/**
* Leave a league.
*/
async leaveLeague(leagueId: string, driverId: string): Promise<void> {
// Temporary: no leave endpoint exposed yet in API.
throw new Error('Leaving leagues is not available in this build.');
}
/**
* Set memberships in cache (for use after API calls).
*/
static setLeagueMemberships(leagueId: string, memberships: LeagueMembership[]): void {
this.leagueMemberships.set(leagueId, memberships);
}
/**
* Clear cached memberships for a league.
*/
static clearLeagueMemberships(leagueId: string): void {
this.leagueMemberships.delete(leagueId);
}
/**
* Get iterator for cached memberships (for utility functions).
*/
static getCachedMembershipsIterator(): IterableIterator<[string, LeagueMembership[]]> {
return this.leagueMemberships.entries();
}
/**
* Get all memberships for a specific driver across all leagues.
*/
static getAllMembershipsForDriver(driverId: string): LeagueMembership[] {
const allMemberships: LeagueMembership[] = [];
for (const [leagueId, memberships] of this.leagueMemberships.entries()) {
const driverMembership = memberships.find((m) => m.driverId === driverId);
if (driverMembership) {
allMemberships.push(driverMembership);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static getAllMembershipsForDriver(driverId: string): any[] {
const allMemberships: any[] = [];
for (const [leagueId, members] of this.cachedMemberships.entries()) {
const membership = members.find(m => m.driverId === driverId);
if (membership) {
allMemberships.push({ ...membership, leagueId });
}
}
return allMemberships;
}
// Instance methods that delegate to static methods for consistency with service pattern
async fetchLeagueMemberships(leagueId: string): Promise<void> {
try {
const members = await this.apiClient.getMemberships(leagueId);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
LeagueMembershipService.cachedMemberships.set(leagueId, (members as any).members || []);
} catch (error) {
console.error('Failed to fetch memberships', error);
}
}
/**
* Get a specific membership from cache.
*/
getMembership(leagueId: string, driverId: string): LeagueMembership | null {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getMembership(leagueId: string, driverId: string): any | null {
return LeagueMembershipService.getMembership(leagueId, driverId);
}
/**
* Get all members of a league from cache.
*/
getLeagueMembers(leagueId: string): LeagueMembership[] {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getLeagueMembers(leagueId: string): any[] {
return LeagueMembershipService.getLeagueMembers(leagueId);
}
/**
* Fetch and cache memberships for a league via API.
*/
async fetchLeagueMemberships(leagueId: string): Promise<LeagueMembership[]> {
return LeagueMembershipService.fetchLeagueMemberships(leagueId);
async joinLeague(_: string, __: string): Promise<Result<void, DomainError>> {
return Result.err({ type: 'notImplemented', message: 'joinLeague' });
}
/**
* Set memberships in cache (for use after API calls).
*/
setLeagueMemberships(leagueId: string, memberships: LeagueMembership[]): void {
LeagueMembershipService.setLeagueMemberships(leagueId, memberships);
async leaveLeague(_: string, __: string): Promise<Result<void, DomainError>> {
return Result.err({ type: 'notImplemented', message: 'leaveLeague' });
}
/**
* Clear cached memberships for a league.
*/
clearLeagueMemberships(leagueId: string): void {
LeagueMembershipService.clearLeagueMemberships(leagueId);
async getRosterAdminData(leagueId: string): Promise<Result<LeagueRosterAdminData, DomainError>> {
try {
const [members, joinRequests] = await Promise.all([
this.apiClient.getAdminRosterMembers(leagueId),
this.apiClient.getAdminRosterJoinRequests(leagueId),
]);
return Result.ok({
leagueId,
members,
joinRequests,
});
} catch (error: unknown) {
console.error('LeagueMembershipService.getRosterAdminData failed:', error);
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to fetch roster data' });
}
}
}
async removeMember(leagueId: string, targetDriverId: string): Promise<Result<{ success: boolean }, DomainError>> {
try {
const dto = await this.apiClient.removeRosterMember(leagueId, targetDriverId);
return Result.ok({ success: dto.success });
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to remove member' });
}
}
async approveJoinRequest(leagueId: string, joinRequestId: string): Promise<Result<{ success: boolean }, DomainError>> {
try {
const dto = await this.apiClient.approveRosterJoinRequest(leagueId, joinRequestId);
return Result.ok({ success: dto.success });
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to approve join request' });
}
}
async rejectJoinRequest(leagueId: string, joinRequestId: string): Promise<Result<{ success: boolean }, DomainError>> {
try {
const dto = await this.apiClient.rejectRosterJoinRequest(leagueId, joinRequestId);
return Result.ok({ success: dto.success });
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to reject join request' });
}
}
}

View File

@@ -1,9 +1,9 @@
import { Result } from '@/lib/contracts/Result';
import { Service } from '@/lib/contracts/services/Service';
import { Service, DomainError } from '@/lib/contracts/services/Service';
import { RulebookApiDto } from '@/lib/types/tbd/RulebookApiDto';
export class LeagueRulebookService implements Service {
async getRulebookData(leagueId: string): Promise<Result<RulebookApiDto, never>> {
async getRulebookData(leagueId: string): Promise<Result<RulebookApiDto, DomainError>> {
// Mock data since backend not implemented
const mockData: RulebookApiDto = {
leagueId,
@@ -27,4 +27,4 @@ export class LeagueRulebookService implements Service {
};
return Result.ok(mockData);
}
}
}

View File

@@ -1,9 +1,9 @@
import { Result } from '@/lib/contracts/Result';
import { Service } from '@/lib/contracts/services/Service';
import { Service, DomainError } from '@/lib/contracts/services/Service';
import { LeagueScheduleApiDto } from '@/lib/types/tbd/LeagueScheduleApiDto';
export class LeagueScheduleService implements Service {
async getScheduleData(leagueId: string): Promise<Result<LeagueScheduleApiDto, never>> {
async getScheduleData(leagueId: string): Promise<Result<LeagueScheduleApiDto, DomainError>> {
// Mock data since backend not implemented
const mockData: LeagueScheduleApiDto = {
leagueId,
@@ -36,4 +36,4 @@ export class LeagueScheduleService implements Service {
};
return Result.ok(mockData);
}
}
}

View File

@@ -21,8 +21,27 @@ import { DomainError, Service } from '@/lib/contracts/services/Service';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
import { getWebsiteServerEnv } from '@/lib/config/env';
import { isProductionEnvironment } from '@/lib/config/env';
import { AllLeaguesWithCapacityAndScoringDTO } from '@/lib/types/AllLeaguesWithCapacityAndScoringDTO';
import type { LeagueWithCapacityAndScoringDTO } from '@/lib/types/generated/LeagueWithCapacityAndScoringDTO';
export interface LeagueScheduleAdminData {
leagueId: string;
seasonId: string;
seasons: LeagueSeasonSummaryDTO[];
schedule: LeagueScheduleDTO;
}
export interface LeagueRosterAdminData {
leagueId: string;
members: LeagueRosterMemberDTO[];
joinRequests: LeagueRosterJoinRequestDTO[];
}
export interface LeagueDetailData {
league: LeagueWithCapacityAndScoringDTO;
apiDto: AllLeaguesWithCapacityAndScoringDTO;
}
/**
* League Service - DTO Only
@@ -40,11 +59,10 @@ export class LeagueService implements Service {
constructor() {
const baseUrl = getWebsiteApiBaseUrl();
const logger = new ConsoleLogger();
const { NODE_ENV } = getWebsiteServerEnv();
const errorReporter = new EnhancedErrorReporter(logger, {
showUserNotifications: false,
logToConsole: true,
reportToExternal: NODE_ENV === 'production',
reportToExternal: isProductionEnvironment(),
});
this.apiClient = new LeaguesApiClient(baseUrl, errorReporter, logger);
// Optional clients can be initialized if needed
@@ -54,9 +72,73 @@ export class LeagueService implements Service {
try {
const dto = await this.apiClient.getAllWithCapacityAndScoring();
return Result.ok(dto);
} catch (error) {
} catch (error: unknown) {
console.error('LeagueService.getAllLeagues failed:', error);
return Result.err({ type: 'serverError', message: 'Failed to fetch leagues' });
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to fetch leagues' });
}
}
async getLeagueDetailData(leagueId: string): Promise<Result<LeagueDetailData, DomainError>> {
try {
const apiDto = await this.apiClient.getAllWithCapacityAndScoring();
if (!apiDto || !apiDto.leagues) {
return Result.err({ type: 'notFound', message: 'Leagues not found' });
}
const league = apiDto.leagues.find(l => l.id === leagueId);
if (!league) {
return Result.err({ type: 'notFound', message: 'League not found' });
}
return Result.ok({
league,
apiDto,
});
} catch (error: unknown) {
console.error('LeagueService.getLeagueDetailData failed:', error);
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to fetch league detail' });
}
}
async getScheduleAdminData(leagueId: string, seasonId?: string): Promise<Result<LeagueScheduleAdminData, DomainError>> {
try {
const seasons = await this.apiClient.getSeasons(leagueId);
if (!seasons || seasons.length === 0) {
return Result.err({ type: 'notFound', message: 'No seasons found for league' });
}
const targetSeasonId = seasonId || (seasons.find(s => s.status === 'active')?.seasonId || seasons[0].seasonId);
const schedule = await this.apiClient.getSchedule(leagueId, targetSeasonId);
return Result.ok({
leagueId,
seasonId: targetSeasonId,
seasons,
schedule,
});
} catch (error: unknown) {
console.error('LeagueService.getScheduleAdminData failed:', error);
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to fetch schedule admin data' });
}
}
async getRosterAdminData(leagueId: string): Promise<Result<LeagueRosterAdminData, DomainError>> {
try {
const [members, joinRequests] = await Promise.all([
this.apiClient.getAdminRosterMembers(leagueId),
this.apiClient.getAdminRosterJoinRequests(leagueId),
]);
return Result.ok({
leagueId,
members,
joinRequests,
});
} catch (error: unknown) {
console.error('LeagueService.getRosterAdminData failed:', error);
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to fetch roster data' });
}
}
@@ -64,41 +146,76 @@ export class LeagueService implements Service {
return Result.err({ type: 'notImplemented', message: 'League standings endpoint not implemented' });
}
async getLeagueStats(): Promise<TotalLeaguesDTO> {
return this.apiClient.getTotal();
async getLeagueStats(): Promise<Result<TotalLeaguesDTO, DomainError>> {
try {
const data = await this.apiClient.getTotal();
return Result.ok(data);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to fetch league stats' });
}
}
async getLeagueSchedule(leagueId: string): Promise<LeagueScheduleDTO> {
return this.apiClient.getSchedule(leagueId);
async getLeagueSchedule(leagueId: string): Promise<Result<LeagueScheduleDTO, DomainError>> {
try {
const data = await this.apiClient.getSchedule(leagueId);
return Result.ok(data);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to fetch league schedule' });
}
}
async getLeagueSeasons(leagueId: string): Promise<LeagueSeasonSummaryDTO[]> {
return this.apiClient.getSeasons(leagueId);
async getLeagueSeasons(leagueId: string): Promise<Result<LeagueSeasonSummaryDTO[], DomainError>> {
try {
const data = await this.apiClient.getSeasons(leagueId);
return Result.ok(data);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to fetch league seasons' });
}
}
async getLeagueSeasonSummaries(leagueId: string): Promise<LeagueSeasonSummaryDTO[]> {
return this.apiClient.getSeasons(leagueId);
async getLeagueSeasonSummaries(leagueId: string): Promise<Result<LeagueSeasonSummaryDTO[], DomainError>> {
return this.getLeagueSeasons(leagueId);
}
async getAdminSchedule(leagueId: string, seasonId: string): Promise<LeagueScheduleDTO> {
return this.apiClient.getSchedule(leagueId, seasonId);
async getAdminSchedule(leagueId: string, seasonId: string): Promise<Result<LeagueScheduleDTO, DomainError>> {
try {
const data = await this.apiClient.getSchedule(leagueId, seasonId);
return Result.ok(data);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to fetch admin schedule' });
}
}
async publishAdminSchedule(leagueId: string, seasonId: string): Promise<LeagueSeasonSchedulePublishOutputDTO> {
return this.apiClient.publishSeasonSchedule(leagueId, seasonId);
async publishAdminSchedule(leagueId: string, seasonId: string): Promise<Result<LeagueSeasonSchedulePublishOutputDTO, DomainError>> {
try {
const data = await this.apiClient.publishSeasonSchedule(leagueId, seasonId);
return Result.ok(data);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to publish schedule' });
}
}
async unpublishAdminSchedule(leagueId: string, seasonId: string): Promise<LeagueSeasonSchedulePublishOutputDTO> {
return this.apiClient.unpublishSeasonSchedule(leagueId, seasonId);
async unpublishAdminSchedule(leagueId: string, seasonId: string): Promise<Result<LeagueSeasonSchedulePublishOutputDTO, DomainError>> {
try {
const data = await this.apiClient.unpublishSeasonSchedule(leagueId, seasonId);
return Result.ok(data);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to unpublish schedule' });
}
}
async createAdminScheduleRace(
leagueId: string,
seasonId: string,
input: { track: string; car: string; scheduledAtIso: string },
): Promise<CreateLeagueScheduleRaceOutputDTO> {
const payload: CreateLeagueScheduleRaceInputDTO = { ...input, example: '' };
return this.apiClient.createSeasonScheduleRace(leagueId, seasonId, payload);
): Promise<Result<CreateLeagueScheduleRaceOutputDTO, DomainError>> {
try {
const payload: CreateLeagueScheduleRaceInputDTO = { ...input, example: '' };
const data = await this.apiClient.createSeasonScheduleRace(leagueId, seasonId, payload);
return Result.ok(data);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to create race' });
}
}
async updateAdminScheduleRace(
@@ -106,33 +223,48 @@ export class LeagueService implements Service {
seasonId: string,
raceId: string,
input: Partial<{ track: string; car: string; scheduledAtIso: string }>,
): Promise<LeagueScheduleRaceMutationSuccessDTO> {
const payload: UpdateLeagueScheduleRaceInputDTO = { ...input, example: '' };
return this.apiClient.updateSeasonScheduleRace(leagueId, seasonId, raceId, payload);
): Promise<Result<LeagueScheduleRaceMutationSuccessDTO, DomainError>> {
try {
const payload: UpdateLeagueScheduleRaceInputDTO = { ...input, example: '' };
const data = await this.apiClient.updateSeasonScheduleRace(leagueId, seasonId, raceId, payload);
return Result.ok(data);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to update race' });
}
}
async deleteAdminScheduleRace(leagueId: string, seasonId: string, raceId: string): Promise<LeagueScheduleRaceMutationSuccessDTO> {
return this.apiClient.deleteSeasonScheduleRace(leagueId, seasonId, raceId);
async deleteAdminScheduleRace(leagueId: string, seasonId: string, raceId: string): Promise<Result<LeagueScheduleRaceMutationSuccessDTO, DomainError>> {
try {
const data = await this.apiClient.deleteSeasonScheduleRace(leagueId, seasonId, raceId);
return Result.ok(data);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to delete race' });
}
}
async getLeagueScheduleDto(leagueId: string, seasonId: string): Promise<LeagueScheduleDTO> {
return this.apiClient.getSchedule(leagueId, seasonId);
async getLeagueScheduleDto(leagueId: string, seasonId: string): Promise<Result<LeagueScheduleDTO, DomainError>> {
return this.getAdminSchedule(leagueId, seasonId);
}
async publishLeagueSeasonSchedule(leagueId: string, seasonId: string): Promise<LeagueSeasonSchedulePublishOutputDTO> {
return this.apiClient.publishSeasonSchedule(leagueId, seasonId);
async publishLeagueSeasonSchedule(leagueId: string, seasonId: string): Promise<Result<LeagueSeasonSchedulePublishOutputDTO, DomainError>> {
return this.publishAdminSchedule(leagueId, seasonId);
}
async unpublishLeagueSeasonSchedule(leagueId: string, seasonId: string): Promise<LeagueSeasonSchedulePublishOutputDTO> {
return this.apiClient.unpublishSeasonSchedule(leagueId, seasonId);
async unpublishLeagueSeasonSchedule(leagueId: string, seasonId: string): Promise<Result<LeagueSeasonSchedulePublishOutputDTO, DomainError>> {
return this.unpublishAdminSchedule(leagueId, seasonId);
}
async createLeagueSeasonScheduleRace(
leagueId: string,
seasonId: string,
input: CreateLeagueScheduleRaceInputDTO,
): Promise<CreateLeagueScheduleRaceOutputDTO> {
return this.apiClient.createSeasonScheduleRace(leagueId, seasonId, input);
): Promise<Result<CreateLeagueScheduleRaceOutputDTO, DomainError>> {
try {
const data = await this.apiClient.createSeasonScheduleRace(leagueId, seasonId, input);
return Result.ok(data);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to create race' });
}
}
async updateLeagueSeasonScheduleRace(
@@ -140,52 +272,93 @@ export class LeagueService implements Service {
seasonId: string,
raceId: string,
input: UpdateLeagueScheduleRaceInputDTO,
): Promise<LeagueScheduleRaceMutationSuccessDTO> {
return this.apiClient.updateSeasonScheduleRace(leagueId, seasonId, raceId, input);
): Promise<Result<LeagueScheduleRaceMutationSuccessDTO, DomainError>> {
try {
const data = await this.apiClient.updateSeasonScheduleRace(leagueId, seasonId, raceId, input);
return Result.ok(data);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to update race' });
}
}
async deleteLeagueSeasonScheduleRace(
leagueId: string,
seasonId: string,
raceId: string,
): Promise<LeagueScheduleRaceMutationSuccessDTO> {
return this.apiClient.deleteSeasonScheduleRace(leagueId, seasonId, raceId);
): Promise<Result<LeagueScheduleRaceMutationSuccessDTO, DomainError>> {
return this.deleteAdminScheduleRace(leagueId, seasonId, raceId);
}
async getLeagueMemberships(leagueId: string): Promise<LeagueMembershipsDTO> {
return this.apiClient.getMemberships(leagueId);
async getLeagueMemberships(leagueId: string): Promise<Result<LeagueMembershipsDTO, DomainError>> {
try {
const data = await this.apiClient.getMemberships(leagueId);
return Result.ok(data);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to fetch memberships' });
}
}
async createLeague(input: CreateLeagueInputDTO): Promise<CreateLeagueOutputDTO> {
return this.apiClient.create(input);
async createLeague(input: CreateLeagueInputDTO): Promise<Result<CreateLeagueOutputDTO, DomainError>> {
try {
const data = await this.apiClient.create(input);
return Result.ok(data);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to create league' });
}
}
async removeMember(leagueId: string, targetDriverId: string): Promise<{ success: boolean }> {
const dto = await this.apiClient.removeRosterMember(leagueId, targetDriverId);
return { success: dto.success };
async removeMember(leagueId: string, targetDriverId: string): Promise<Result<{ success: boolean }, DomainError>> {
try {
const dto = await this.apiClient.removeRosterMember(leagueId, targetDriverId);
return Result.ok({ success: dto.success });
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to remove member' });
}
}
async updateMemberRole(leagueId: string, targetDriverId: string, newRole: MembershipRole): Promise<{ success: boolean }> {
const dto = await this.apiClient.updateRosterMemberRole(leagueId, targetDriverId, newRole);
return { success: dto.success };
async updateMemberRole(leagueId: string, targetDriverId: string, newRole: MembershipRole): Promise<Result<{ success: boolean }, DomainError>> {
try {
const dto = await this.apiClient.updateRosterMemberRole(leagueId, targetDriverId, newRole);
return Result.ok({ success: dto.success });
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to update member role' });
}
}
async getAdminRosterMembers(leagueId: string): Promise<LeagueRosterMemberDTO[]> {
return this.apiClient.getAdminRosterMembers(leagueId);
async getAdminRosterMembers(leagueId: string): Promise<Result<LeagueRosterMemberDTO[], DomainError>> {
try {
const data = await this.apiClient.getAdminRosterMembers(leagueId);
return Result.ok(data);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to fetch roster members' });
}
}
async getAdminRosterJoinRequests(leagueId: string): Promise<LeagueRosterJoinRequestDTO[]> {
return this.apiClient.getAdminRosterJoinRequests(leagueId);
async getAdminRosterJoinRequests(leagueId: string): Promise<Result<LeagueRosterJoinRequestDTO[], DomainError>> {
try {
const data = await this.apiClient.getAdminRosterJoinRequests(leagueId);
return Result.ok(data);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to fetch join requests' });
}
}
async approveJoinRequest(leagueId: string, joinRequestId: string): Promise<{ success: boolean }> {
const dto = await this.apiClient.approveRosterJoinRequest(leagueId, joinRequestId);
return { success: dto.success };
async approveJoinRequest(leagueId: string, joinRequestId: string): Promise<Result<{ success: boolean }, DomainError>> {
try {
const dto = await this.apiClient.approveRosterJoinRequest(leagueId, joinRequestId);
return Result.ok({ success: dto.success });
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to approve join request' });
}
}
async rejectJoinRequest(leagueId: string, joinRequestId: string): Promise<{ success: boolean }> {
const dto = await this.apiClient.rejectRosterJoinRequest(leagueId, joinRequestId);
return { success: dto.success };
async rejectJoinRequest(leagueId: string, joinRequestId: string): Promise<Result<{ success: boolean }, DomainError>> {
try {
const dto = await this.apiClient.rejectRosterJoinRequest(leagueId, joinRequestId);
return Result.ok({ success: dto.success });
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to reject join request' });
}
}
async getLeagueDetail(): Promise<Result<never, DomainError>> {
@@ -199,4 +372,4 @@ export class LeagueService implements Service {
async getScoringPresets(): Promise<Result<never, DomainError>> {
return Result.err({ type: 'notImplemented', message: 'Scoring presets endpoint not implemented' });
}
}
}

View File

@@ -1,9 +1,11 @@
import { Result } from '@/lib/contracts/Result';
import { Service } from '@/lib/contracts/services/Service';
import { LeagueSettingsApiDto } from '@/lib/types/tbd/LeagueSettingsApiDto';
import { Service, type DomainError } from '@/lib/contracts/services/Service';
import { type LeagueSettingsApiDto } from '@/lib/types/tbd/LeagueSettingsApiDto';
export class LeagueSettingsService implements Service {
async getSettingsData(leagueId: string): Promise<Result<LeagueSettingsApiDto, never>> {
private static cachedMemberships = new Map<string, unknown[]>();
async getSettingsData(leagueId: string): Promise<Result<LeagueSettingsApiDto, DomainError>> {
// Mock data since backend not implemented
const mockData: LeagueSettingsApiDto = {
leagueId,
@@ -25,4 +27,14 @@ export class LeagueSettingsService implements Service {
};
return Result.ok(mockData);
}
}
static getCachedMembershipsIterator(): IterableIterator<[string, unknown[]]> {
return this.cachedMemberships.entries();
}
static getMembership(leagueId: string, driverId: string): unknown | null {
const members = this.cachedMemberships.get(leagueId);
if (!members) return null;
return members.find((m: any) => m.driverId === driverId) || null;
}
}

View File

@@ -1,9 +1,9 @@
import { Result } from '@/lib/contracts/Result';
import { Service } from '@/lib/contracts/services/Service';
import { Service, DomainError } from '@/lib/contracts/services/Service';
import { LeagueSponsorshipsApiDto } from '@/lib/types/tbd/LeagueSponsorshipsApiDto';
export class LeagueSponsorshipsService implements Service {
async getSponsorshipsData(leagueId: string): Promise<Result<LeagueSponsorshipsApiDto, never>> {
async getSponsorshipsData(leagueId: string): Promise<Result<LeagueSponsorshipsApiDto, DomainError>> {
// Mock data since backend not implemented
const mockData: LeagueSponsorshipsApiDto = {
leagueId,
@@ -56,4 +56,4 @@ export class LeagueSponsorshipsService implements Service {
};
return Result.ok(mockData);
}
}
}

View File

@@ -1,6 +1,6 @@
import { Result } from '@/lib/contracts/Result';
import { Service } from '@/lib/contracts/services/Service';
import { LeagueStandingsApiDto, LeagueMembershipsApiDto } from '@/lib/types/tbd/LeagueStandingsApiDto';
import { Service, type DomainError } from '@/lib/contracts/services/Service';
import { type LeagueStandingsApiDto, type LeagueMembershipsApiDto } from '@/lib/types/tbd/LeagueStandingsApiDto';
import { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient';
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
@@ -18,7 +18,7 @@ export class LeagueStandingsService implements Service {
);
}
async getStandingsData(leagueId: string): Promise<Result<{ standings: LeagueStandingsApiDto; memberships: LeagueMembershipsApiDto }, never>> {
async getStandingsData(_: string): Promise<Result<{ standings: LeagueStandingsApiDto; memberships: LeagueMembershipsApiDto }, DomainError>> {
// Mock data since backend may not be implemented
const mockStandings: LeagueStandingsApiDto = {
standings: [
@@ -86,4 +86,4 @@ export class LeagueStandingsService implements Service {
return Result.ok({ standings: mockStandings, memberships: mockMemberships });
}
}
}

View File

@@ -1,9 +1,9 @@
import { Result } from '@/lib/contracts/Result';
import { Service } from '@/lib/contracts/services/Service';
import { StewardingApiDto } from '@/lib/types/tbd/StewardingApiDto';
import { Service, type DomainError } from '@/lib/contracts/services/Service';
import { type StewardingApiDto } from '@/lib/types/tbd/StewardingApiDto';
export class LeagueStewardingService implements Service {
async getStewardingData(leagueId: string): Promise<Result<StewardingApiDto, never>> {
async getStewardingData(leagueId: string): Promise<Result<StewardingApiDto, DomainError>> {
// Mock data since backend not implemented
const mockData: StewardingApiDto = {
leagueId,
@@ -15,4 +15,8 @@ export class LeagueStewardingService implements Service {
};
return Result.ok(mockData);
}
}
async getProtestDetailViewModel(_: string, __: string): Promise<Result<any, DomainError>> {
return Result.err({ type: 'notImplemented', message: 'getProtestDetailViewModel' });
}
}

View File

@@ -1,9 +1,26 @@
import { Result } from '@/lib/contracts/Result';
import { Service } from '@/lib/contracts/services/Service';
import { Service, DomainError } from '@/lib/contracts/services/Service';
import { LeagueWalletApiDto } from '@/lib/types/tbd/LeagueWalletApiDto';
export class LeagueWalletService implements Service {
async getWalletData(leagueId: string): Promise<Result<LeagueWalletApiDto, never>> {
async getWalletForLeague(leagueId: string): Promise<LeagueWalletApiDto> {
const result = await this.getWalletData(leagueId);
if (result.isErr()) throw new Error(result.getError().message);
return result.unwrap();
}
async withdraw(
leagueId: string,
amount: number,
currency: string,
seasonId: string,
destinationId: string
): Promise<{ success: boolean; message?: string }> {
// Mock implementation
return { success: true };
}
async getWalletData(leagueId: string): Promise<Result<LeagueWalletApiDto, DomainError>> {
// Mock data since backend not implemented
const mockData: LeagueWalletApiDto = {
leagueId,
@@ -46,4 +63,4 @@ export class LeagueWalletService implements Service {
};
return Result.ok(mockData);
}
}
}

View File

@@ -1,11 +1,9 @@
import { ApiClient } from '@/lib/api';
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
import { Result } from '@/lib/contracts/Result';
import type { Service } from '@/lib/contracts/services/Service';
import { Service, type DomainError } from '@/lib/contracts/services/Service';
type ProfileLeaguesServiceError = 'notFound' | 'redirect' | 'unknown';
interface ProfileLeaguesPageDto {
export interface ProfileLeaguesPageDto {
ownedLeagues: Array<{
leagueId: string;
name: string;
@@ -27,7 +25,7 @@ interface MembershipDTO {
}
export class ProfileLeaguesService implements Service {
async getProfileLeagues(driverId: string): Promise<Result<ProfileLeaguesPageDto, ProfileLeaguesServiceError>> {
async getProfileLeagues(driverId: string): Promise<Result<ProfileLeaguesPageDto, DomainError>> {
try {
const baseUrl = getWebsiteApiBaseUrl();
const apiClient = new ApiClient(baseUrl);
@@ -35,7 +33,7 @@ export class ProfileLeaguesService implements Service {
const leaguesDto = await apiClient.leagues.getAllWithCapacity();
if (!leaguesDto?.leagues) {
return Result.err('notFound');
return Result.err({ type: 'notFound', message: 'Leagues not found' });
}
// Fetch all memberships in parallel
@@ -80,18 +78,18 @@ export class ProfileLeaguesService implements Service {
ownedLeagues,
memberLeagues,
});
} catch (error) {
} catch (error: any) {
const errorAny = error as { statusCode?: number; message?: string };
if (errorAny.statusCode === 404 || errorAny.message?.toLowerCase().includes('not found')) {
return Result.err('notFound');
return Result.err({ type: 'notFound', message: 'Profile leagues not found' });
}
if (errorAny.statusCode === 302 || errorAny.message?.toLowerCase().includes('redirect')) {
return Result.err('redirect');
return Result.err({ type: 'unauthorized', message: 'Unauthorized access' });
}
return Result.err('unknown');
return Result.err({ type: 'unknown', message: error.message || 'Failed to fetch profile leagues' });
}
}
}

View File

@@ -1,9 +1,9 @@
import { Result } from '@/lib/contracts/Result';
import { Service } from '@/lib/contracts/services/Service';
import { Service, DomainError } from '@/lib/contracts/services/Service';
import { ProtestDetailApiDto } from '@/lib/types/tbd/ProtestDetailApiDto';
export class ProtestDetailService implements Service {
async getProtestDetail(leagueId: string, protestId: string): Promise<Result<ProtestDetailApiDto, never>> {
async getProtestDetail(leagueId: string, protestId: string): Promise<Result<ProtestDetailApiDto, DomainError>> {
// Mock data since backend not implemented
const mockData: ProtestDetailApiDto = {
id: protestId,
@@ -35,4 +35,4 @@ export class ProtestDetailService implements Service {
};
return Result.ok(mockData);
}
}
}

View File

@@ -1,13 +1,16 @@
import { Result } from '@/lib/contracts/Result';
import { DomainError, Service } from '@/lib/contracts/services/Service';
/**
* Payment Service - DTO Only
*
* Returns raw API DTOs. No ViewModels or UX logic.
* All client-side presentation logic must be handled by hooks/components.
*/
export class PaymentService {
constructor(private readonly apiClient: any) {}
export class PaymentService implements Service {
constructor() {}
async getPaymentById(paymentId: string): Promise<any> {
return { id: paymentId, amount: 0 };
async getPaymentById(paymentId: string): Promise<Result<{ id: string; amount: number }, DomainError>> {
return Result.ok({ id: paymentId, amount: 0 });
}
}
}

View File

@@ -1,13 +1,16 @@
import { Result } from '@/lib/contracts/Result';
import { DomainError, Service } from '@/lib/contracts/services/Service';
/**
* Wallet Service - DTO Only
*
* Returns raw API DTOs. No ViewModels or UX logic.
* All client-side presentation logic must be handled by hooks/components.
*/
export class WalletService {
constructor(private readonly apiClient: any) {}
export class WalletService implements Service {
constructor() {}
async getWalletBalance(driverId: string): Promise<any> {
return { balance: 0, currency: 'USD' };
async getWalletBalance(_: string): Promise<Result<{ balance: number; currency: string }, DomainError>> {
return Result.ok({ balance: 0, currency: 'USD' });
}
}
}

View File

@@ -1,5 +1,10 @@
import { PenaltiesApiClient } from '@/lib/api/penalties/PenaltiesApiClient';
import type { PenaltyTypesReferenceDTO } from '@/lib/types/PenaltyTypesReferenceDTO';
import { Result } from '@/lib/contracts/Result';
import { DomainError, Service } from '@/lib/contracts/services/Service';
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
/**
* Penalty Service
@@ -7,30 +12,50 @@ import type { PenaltyTypesReferenceDTO } from '@/lib/types/PenaltyTypesReference
* Orchestrates penalty operations by coordinating API calls and view model creation.
* All dependencies are injected via constructor.
*/
export class PenaltyService {
constructor(
private readonly apiClient: PenaltiesApiClient
) {}
export class PenaltyService implements Service {
private readonly apiClient: PenaltiesApiClient;
constructor() {
const baseUrl = getWebsiteApiBaseUrl();
const logger = new ConsoleLogger();
const errorReporter = new EnhancedErrorReporter(logger);
this.apiClient = new PenaltiesApiClient(baseUrl, errorReporter, logger);
}
/**
* Find penalties by race ID
*/
async findByRaceId(raceId: string): Promise<any[]> {
const dto = await this.apiClient.getRacePenalties(raceId);
return dto.penalties;
async findByRaceId(raceId: string): Promise<Result<unknown[], DomainError>> {
try {
const dto = await this.apiClient.getRacePenalties(raceId);
return Result.ok(dto.penalties);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to find penalties' });
}
}
/**
* Get allowed penalty types and semantics
*/
async getPenaltyTypesReference(): Promise<PenaltyTypesReferenceDTO> {
return this.apiClient.getPenaltyTypesReference();
async getPenaltyTypesReference(): Promise<Result<PenaltyTypesReferenceDTO, DomainError>> {
try {
const data = await this.apiClient.getPenaltyTypesReference();
return Result.ok(data);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to get penalty types' });
}
}
/**
* Apply a penalty
*/
async applyPenalty(input: any): Promise<void> {
await this.apiClient.applyPenalty(input);
async applyPenalty(input: unknown): Promise<Result<void, DomainError>> {
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
await this.apiClient.applyPenalty(input as any);
return Result.ok(undefined);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to apply penalty' });
}
}
}
}

View File

@@ -1,4 +1,9 @@
import type { FeatureState, PolicyApiClient, PolicySnapshotDto } from '@/lib/api/policy/PolicyApiClient';
import { PolicyApiClient, type FeatureState, type PolicySnapshotDto } from '@/lib/api/policy/PolicyApiClient';
import { Result } from '@/lib/contracts/Result';
import { DomainError, Service } from '@/lib/contracts/services/Service';
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
export interface CapabilityEvaluationResult {
isLoading: boolean;
@@ -8,11 +13,23 @@ export interface CapabilityEvaluationResult {
shouldShowComingSoon: boolean;
}
export class PolicyService {
constructor(private readonly apiClient: PolicyApiClient) {}
export class PolicyService implements Service {
private readonly apiClient: PolicyApiClient;
getSnapshot(): Promise<PolicySnapshotDto> {
return this.apiClient.getSnapshot();
constructor() {
const baseUrl = getWebsiteApiBaseUrl();
const logger = new ConsoleLogger();
const errorReporter = new EnhancedErrorReporter(logger);
this.apiClient = new PolicyApiClient(baseUrl, errorReporter, logger);
}
async getSnapshot(): Promise<Result<PolicySnapshotDto, DomainError>> {
try {
const data = await this.apiClient.getSnapshot();
return Result.ok(data);
} catch (error: any) {
return Result.err({ type: 'serverError', message: error.message || 'Failed to get policy snapshot' });
}
}
getCapabilityState(snapshot: PolicySnapshotDto, capabilityKey: string): FeatureState {
@@ -79,4 +96,4 @@ export class PolicyService {
return fallback;
}
}
}

View File

@@ -1,10 +1,12 @@
import { ProtestsApiClient } from '@/lib/api/protests/ProtestsApiClient';
import type { ProtestViewModel } from '@/lib/view-models/ProtestViewModel';
import type { RaceViewModel } from '@/lib/view-models/RaceViewModel';
import type { ProtestDriverViewModel } from '@/lib/view-models/ProtestDriverViewModel';
import type { ApplyPenaltyCommandDTO } from '@/lib/types/generated/ApplyPenaltyCommandDTO';
import type { RequestProtestDefenseCommandDTO } from '@/lib/types/generated/RequestProtestDefenseCommandDTO';
import type { ReviewProtestCommandDTO } from '@/lib/types/generated/ReviewProtestCommandDTO';
import { Result } from '@/lib/contracts/Result';
import { DomainError, Service } from '@/lib/contracts/services/Service';
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
/**
* Protest Service - DTO Only
@@ -12,30 +14,67 @@ import type { ReviewProtestCommandDTO } from '@/lib/types/generated/ReviewProtes
* Returns raw API DTOs. No ViewModels or UX logic.
* All client-side presentation logic must be handled by hooks/components.
*/
export class ProtestService {
constructor(private readonly apiClient: ProtestsApiClient) {}
export class ProtestService implements Service {
private readonly apiClient: ProtestsApiClient;
async getLeagueProtests(leagueId: string): Promise<any> {
return this.apiClient.getLeagueProtests(leagueId);
constructor() {
const baseUrl = getWebsiteApiBaseUrl();
const logger = new ConsoleLogger();
const errorReporter = new EnhancedErrorReporter(logger);
this.apiClient = new ProtestsApiClient(baseUrl, errorReporter, logger);
}
async getProtestById(leagueId: string, protestId: string): Promise<any> {
return this.apiClient.getLeagueProtest(leagueId, protestId);
async getLeagueProtests(leagueId: string): Promise<Result<unknown, DomainError>> {
try {
const data = await this.apiClient.getLeagueProtests(leagueId);
return Result.ok(data);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to get league protests' });
}
}
async applyPenalty(input: ApplyPenaltyCommandDTO): Promise<void> {
return this.apiClient.applyPenalty(input);
async getProtestById(leagueId: string, protestId: string): Promise<Result<unknown, DomainError>> {
try {
const data = await this.apiClient.getLeagueProtest(leagueId, protestId);
return Result.ok(data);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to get protest' });
}
}
async requestDefense(input: RequestProtestDefenseCommandDTO): Promise<void> {
return this.apiClient.requestDefense(input);
async applyPenalty(input: ApplyPenaltyCommandDTO): Promise<Result<void, DomainError>> {
try {
await this.apiClient.applyPenalty(input);
return Result.ok(undefined);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to apply penalty' });
}
}
async reviewProtest(input: ReviewProtestCommandDTO): Promise<void> {
return this.apiClient.reviewProtest(input);
async requestDefense(input: RequestProtestDefenseCommandDTO): Promise<Result<void, DomainError>> {
try {
await this.apiClient.requestDefense(input);
return Result.ok(undefined);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to request defense' });
}
}
async findByRaceId(raceId: string): Promise<any> {
return this.apiClient.getRaceProtests(raceId);
async reviewProtest(input: ReviewProtestCommandDTO): Promise<Result<void, DomainError>> {
try {
await this.apiClient.reviewProtest(input);
return Result.ok(undefined);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to review protest' });
}
}
}
async findByRaceId(raceId: string): Promise<Result<unknown, DomainError>> {
try {
const data = await this.apiClient.getRaceProtests(raceId);
return Result.ok(data);
} catch (error: unknown) {
return Result.err({ type: 'serverError', message: (error as Error).message || 'Failed to find protests' });
}
}
}

View File

@@ -1,6 +1,6 @@
import { RacesApiClient } from '@/lib/api/races/RacesApiClient';
import { Result } from '@/lib/contracts/Result';
import { DomainError } from '@/lib/contracts/services/Service';
import { DomainError, Service } from '@/lib/contracts/services/Service';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
@@ -12,7 +12,7 @@ import { ApiError } from '@/lib/api/base/ApiError';
* Orchestration service for race results operations.
* Returns raw API DTOs. No ViewModels or UX logic.
*/
export class RaceResultsService {
export class RaceResultsService implements Service {
private apiClient: RacesApiClient;
constructor() {
@@ -28,11 +28,11 @@ export class RaceResultsService {
* Get race results detail
* Returns results for a specific race
*/
async getRaceResultsDetail(raceId: string): Promise<Result<any, DomainError>> {
async getRaceResultsDetail(raceId: string): Promise<Result<unknown, DomainError>> {
try {
const data = await this.apiClient.getResultsDetail(raceId);
return Result.ok(data);
} catch (error) {
} catch (error: unknown) {
if (error instanceof ApiError) {
return Result.err({
type: this.mapApiErrorType(error.type),
@@ -41,7 +41,7 @@ export class RaceResultsService {
}
return Result.err({
type: 'unknown',
message: 'Failed to fetch race results'
message: (error as Error).message || 'Failed to fetch race results'
});
}
}
@@ -50,11 +50,11 @@ export class RaceResultsService {
* Get race with strength of field
* Returns race data with SOF calculation
*/
async getWithSOF(raceId: string): Promise<Result<any, DomainError>> {
async getWithSOF(raceId: string): Promise<Result<unknown, DomainError>> {
try {
const data = await this.apiClient.getWithSOF(raceId);
return Result.ok(data);
} catch (error) {
} catch (error: unknown) {
if (error instanceof ApiError) {
return Result.err({
type: this.mapApiErrorType(error.type),
@@ -63,7 +63,7 @@ export class RaceResultsService {
}
return Result.err({
type: 'unknown',
message: 'Failed to fetch race SOF'
message: (error as Error).message || 'Failed to fetch race SOF'
});
}
}
@@ -84,4 +84,4 @@ export class RaceResultsService {
return 'unknown';
}
}
}
}

View File

@@ -1,4 +1,10 @@
import { RacesApiClient } from '@/lib/api/races/RacesApiClient';
import { Result } from '@/lib/contracts/Result';
import { DomainError, Service } from '@/lib/contracts/services/Service';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
import { ApiError } from '@/lib/api/base/ApiError';
/**
* Race Service - DTO Only
@@ -6,20 +12,83 @@ import { RacesApiClient } from '@/lib/api/races/RacesApiClient';
* Returns raw API DTOs. No ViewModels or UX logic.
* All client-side presentation logic must be handled by hooks/components.
*/
export class RaceService {
constructor(private readonly apiClient: RacesApiClient) {}
export class RaceService implements Service {
private apiClient: RacesApiClient;
async getRaceById(raceId: string): Promise<any> {
// This would need a driverId, but for now we'll use a placeholder
return this.apiClient.getDetail(raceId, 'placeholder-driver-id');
constructor() {
const baseUrl = getWebsiteApiBaseUrl();
const logger = new ConsoleLogger();
const errorReporter = new ConsoleErrorReporter();
this.apiClient = new RacesApiClient(baseUrl, errorReporter, logger);
}
async getRacesByLeagueId(leagueId: string): Promise<any> {
return this.apiClient.getPageData(leagueId);
async getRaceById(raceId: string): Promise<Result<unknown, DomainError>> {
try {
// This would need a driverId, but for now we'll use a placeholder
const data = await this.apiClient.getDetail(raceId, 'placeholder-driver-id');
return Result.ok(data);
} catch (error: unknown) {
return Result.err(this.mapError(error, 'Failed to fetch race by ID'));
}
}
async findByLeagueId(leagueId: string): Promise<any[]> {
const result = await this.apiClient.getPageData(leagueId);
return result.races || [];
async getRacesByLeagueId(leagueId: string): Promise<Result<unknown, DomainError>> {
try {
const data = await this.apiClient.getPageData(leagueId);
return Result.ok(data);
} catch (error: unknown) {
return Result.err(this.mapError(error, 'Failed to fetch races by league ID'));
}
}
}
async findByLeagueId(leagueId: string): Promise<Result<unknown[], DomainError>> {
try {
const result = await this.apiClient.getPageData(leagueId);
return Result.ok(result.races || []);
} catch (error: unknown) {
return Result.err(this.mapError(error, 'Failed to find races by league ID'));
}
}
async registerForRace(_: string, __: string, ___: string): Promise<Result<void, DomainError>> {
return Result.err({ type: 'notImplemented', message: 'registerForRace' });
}
async withdrawFromRace(_: string, __: string): Promise<Result<void, DomainError>> {
return Result.err({ type: 'notImplemented', message: 'withdrawFromRace' });
}
async fileProtest(__: unknown): Promise<Result<void, DomainError>> {
return Result.err({ type: 'notImplemented', message: 'fileProtest' });
}
private mapError(error: unknown, defaultMessage: string): DomainError {
if (error instanceof ApiError) {
return {
type: this.mapApiErrorType(error.type),
message: error.message
};
}
return {
type: 'unknown',
message: defaultMessage
};
}
private mapApiErrorType(apiErrorType: string): DomainError['type'] {
switch (apiErrorType) {
case 'NOT_FOUND':
return 'notFound';
case 'AUTH_ERROR':
return 'unauthorized';
case 'VALIDATION_ERROR':
return 'validation';
case 'SERVER_ERROR':
return 'serverError';
case 'NETWORK_ERROR':
return 'networkError';
default:
return 'unknown';
}
}
}

View File

@@ -2,7 +2,7 @@ import { RacesApiClient } from '@/lib/api/races/RacesApiClient';
import { ProtestsApiClient } from '@/lib/api/protests/ProtestsApiClient';
import { PenaltiesApiClient } from '@/lib/api/penalties/PenaltiesApiClient';
import { Result } from '@/lib/contracts/Result';
import { DomainError } from '@/lib/contracts/services/Service';
import { DomainError, Service } from '@/lib/contracts/services/Service';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
@@ -14,7 +14,7 @@ import { ApiError } from '@/lib/api/base/ApiError';
* Orchestration service for race stewarding operations.
* Returns raw API DTOs. No ViewModels or UX logic.
*/
export class RaceStewardingService {
export class RaceStewardingService implements Service {
private racesApiClient: RacesApiClient;
private protestsApiClient: ProtestsApiClient;
private penaltiesApiClient: PenaltiesApiClient;
@@ -34,7 +34,7 @@ export class RaceStewardingService {
* Get race stewarding data
* Returns protests and penalties for a race
*/
async getRaceStewarding(raceId: string): Promise<Result<any, DomainError>> {
async getRaceStewarding(raceId: string): Promise<Result<unknown, DomainError>> {
try {
// Fetch data in parallel
const [raceDetail, protests, penalties] = await Promise.all([
@@ -44,6 +44,7 @@ export class RaceStewardingService {
]);
// Transform data to match view model structure
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const protestsData = protests.protests.map((p: any) => ({
id: p.id,
protestingDriverId: p.protestingDriverId,
@@ -56,7 +57,9 @@ export class RaceStewardingService {
status: p.status,
}));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const pendingProtests = protestsData.filter((p: any) => p.status === 'pending' || p.status === 'under_review');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const resolvedProtests = protestsData.filter((p: any) =>
p.status === 'upheld' ||
p.status === 'dismissed' ||
@@ -77,7 +80,7 @@ export class RaceStewardingService {
};
return Result.ok(data);
} catch (error) {
} catch (error: unknown) {
if (error instanceof ApiError) {
return Result.err({
type: this.mapApiErrorType(error.type),
@@ -86,7 +89,7 @@ export class RaceStewardingService {
}
return Result.err({
type: 'unknown',
message: 'Failed to fetch stewarding data'
message: (error as Error).message || 'Failed to fetch stewarding data'
});
}
}
@@ -107,4 +110,4 @@ export class RaceStewardingService {
return 'unknown';
}
}
}
}

View File

@@ -42,8 +42,8 @@ export class SponsorService implements Service {
return Result.err({ type: 'notFound', message: 'Sponsor not found' });
}
return Result.ok(result);
} catch (error) {
return Result.err({ type: 'unknown', message: 'Failed to get sponsor' });
} catch (error: unknown) {
return Result.err({ type: 'unknown', message: (error as Error).message || 'Failed to get sponsor' });
}
}
@@ -54,8 +54,8 @@ export class SponsorService implements Service {
return Result.err({ type: 'notFound', message: 'Dashboard not found' });
}
return Result.ok(result);
} catch (error) {
return Result.err({ type: 'notImplemented', message: 'getSponsorDashboard' });
} catch (error: unknown) {
return Result.err({ type: 'notImplemented', message: (error as Error).message || 'getSponsorDashboard' });
}
}
@@ -66,12 +66,12 @@ export class SponsorService implements Service {
return Result.err({ type: 'notFound', message: 'Sponsorships not found' });
}
return Result.ok(result);
} catch (error) {
return Result.err({ type: 'notImplemented', message: 'getSponsorSponsorships' });
} catch (error: unknown) {
return Result.err({ type: 'notImplemented', message: (error as Error).message || 'getSponsorSponsorships' });
}
}
async getBilling(): Promise<Result<SponsorBillingDTO, DomainError>> {
async getBilling(_: string): Promise<Result<SponsorBillingDTO, DomainError>> {
return Result.err({ type: 'notImplemented', message: 'getBilling' });
}
@@ -95,8 +95,8 @@ export class SponsorService implements Service {
try {
await this.apiClient.acceptSponsorshipRequest(requestId, { respondedBy: sponsorId });
return Result.ok(undefined);
} catch (error) {
return Result.err({ type: 'unknown', message: 'Failed to accept sponsorship request' });
} catch (error: unknown) {
return Result.err({ type: 'unknown', message: (error as Error).message || 'Failed to accept sponsorship request' });
}
}
@@ -104,8 +104,8 @@ export class SponsorService implements Service {
try {
await this.apiClient.rejectSponsorshipRequest(requestId, { respondedBy: sponsorId, reason });
return Result.ok(undefined);
} catch (error) {
return Result.err({ type: 'unknown', message: 'Failed to reject sponsorship request' });
} catch (error: unknown) {
return Result.err({ type: 'unknown', message: (error as Error).message || 'Failed to reject sponsorship request' });
}
}
@@ -113,8 +113,8 @@ export class SponsorService implements Service {
try {
const result = await this.apiClient.getPendingSponsorshipRequests(input);
return Result.ok(result);
} catch (error) {
return Result.err({ type: 'notImplemented', message: 'getPendingSponsorshipRequests' });
} catch (error: unknown) {
return Result.err({ type: 'notImplemented', message: (error as Error).message || 'getPendingSponsorshipRequests' });
}
}
}
}

View File

@@ -2,7 +2,7 @@ import { SponsorsApiClient } from '@/lib/api/sponsors/SponsorsApiClient';
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
import { isProductionEnvironment } from '@/lib/config/env';
import { Result } from '@/lib/contracts/Result';
import type { Service } from '@/lib/contracts/services/Service';
import type { Service, DomainError } from '@/lib/contracts/services/Service';
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
@@ -23,8 +23,6 @@ export interface SponsorshipRequestsReadApiDto {
}>;
}
export type SponsorshipRequestsReadServiceError = 'notFound' | 'unauthorized' | 'serverError';
export class SponsorshipRequestsReadService implements Service {
private readonly client: SponsorsApiClient;
@@ -42,7 +40,7 @@ export class SponsorshipRequestsReadService implements Service {
async getPendingRequestsForDriver(
driverId: string,
): Promise<Result<SponsorshipRequestsReadApiDto, SponsorshipRequestsReadServiceError>> {
): Promise<Result<SponsorshipRequestsReadApiDto, DomainError>> {
try {
const response = await this.client.getPendingSponsorshipRequests({
entityType: 'driver',
@@ -79,11 +77,11 @@ export class SponsorshipRequestsReadService implements Service {
},
],
});
} catch (error) {
} catch (error: any) {
const errorAny = error as { statusCode?: number; message?: string };
if (errorAny.statusCode === 401) return Result.err('unauthorized');
if (errorAny.statusCode === 404) return Result.err('notFound');
return Result.err('serverError');
if (errorAny.statusCode === 401) return Result.err({ type: 'unauthorized', message: 'Unauthorized' });
if (errorAny.statusCode === 404) return Result.err({ type: 'notFound', message: 'Not found' });
return Result.err({ type: 'serverError', message: error.message || 'Server error' });
}
}
}

View File

@@ -2,7 +2,7 @@ import { SponsorsApiClient } from '@/lib/api/sponsors/SponsorsApiClient';
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
import { isProductionEnvironment } from '@/lib/config/env';
import { Result } from '@/lib/contracts/Result';
import type { Service } from '@/lib/contracts/services/Service';
import { Service, DomainError } from '@/lib/contracts/services/Service';
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import type { GetPendingSponsorshipRequestsOutputDTO } from '@/lib/types/generated/GetPendingSponsorshipRequestsOutputDTO';
@@ -31,7 +31,7 @@ export class SponsorshipRequestsService implements Service {
async getPendingRequests(
input: GetPendingRequestsInput,
): Promise<Result<GetPendingSponsorshipRequestsOutputDTO, 'GET_PENDING_REQUESTS_FAILED'>> {
): Promise<Result<GetPendingSponsorshipRequestsOutputDTO, DomainError>> {
try {
const result = await this.client.getPendingSponsorshipRequests({
entityType: input.entityType,
@@ -40,13 +40,13 @@ export class SponsorshipRequestsService implements Service {
return Result.ok(result);
} catch {
return Result.err('GET_PENDING_REQUESTS_FAILED');
return Result.err({ type: 'serverError', message: 'Failed to fetch pending requests' });
}
}
async acceptRequest(
command: { requestId: string; actorDriverId: string },
): Promise<Result<void, 'ACCEPT_SPONSORSHIP_REQUEST_FAILED'>> {
): Promise<Result<void, DomainError>> {
try {
const input: AcceptSponsorshipRequestInputDTO = {
respondedBy: command.actorDriverId,
@@ -55,13 +55,13 @@ export class SponsorshipRequestsService implements Service {
return Result.ok(undefined);
} catch {
return Result.err('ACCEPT_SPONSORSHIP_REQUEST_FAILED');
return Result.err({ type: 'serverError', message: 'Failed to accept sponsorship request' });
}
}
async rejectRequest(
command: { requestId: string; actorDriverId: string; reason: string | null },
): Promise<Result<void, 'REJECT_SPONSORSHIP_REQUEST_FAILED'>> {
): Promise<Result<void, DomainError>> {
try {
const input: RejectSponsorshipRequestInputDTO = {
respondedBy: command.actorDriverId,
@@ -71,7 +71,7 @@ export class SponsorshipRequestsService implements Service {
return Result.ok(undefined);
} catch {
return Result.err('REJECT_SPONSORSHIP_REQUEST_FAILED');
return Result.err({ type: 'serverError', message: 'Failed to reject sponsorship request' });
}
}
}

View File

@@ -5,6 +5,7 @@ import { DomainError, Service } from '@/lib/contracts/services/Service';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
import { isProductionEnvironment } from '@/lib/config/env';
/**
* Team Join Service - ViewModels
@@ -21,28 +22,27 @@ export class TeamJoinService implements Service {
const errorReporter = new EnhancedErrorReporter(logger, {
showUserNotifications: true,
logToConsole: true,
reportToExternal: process.env.NODE_ENV === 'production',
reportToExternal: isProductionEnvironment(),
});
this.apiClient = new TeamsApiClient(baseUrl, errorReporter, logger);
}
async getJoinRequests(teamId: string, currentDriverId: string, isOwner: boolean): Promise<TeamJoinRequestViewModel[]> {
async getJoinRequests(teamId: string, currentDriverId: string, isOwner: boolean): Promise<Result<TeamJoinRequestViewModel[], DomainError>> {
try {
const result = await this.apiClient.getJoinRequests(teamId);
return result.requests.map(request =>
return Result.ok(result.requests.map(request =>
new TeamJoinRequestViewModel(request, currentDriverId, isOwner)
);
} catch (error) {
// Return empty array on error
return [];
));
} catch (error: any) {
return Result.err({ type: 'serverError', message: error.message || 'Failed to fetch join requests' });
}
}
async approveJoinRequest(): Promise<void> {
throw new Error('Not implemented: API endpoint for approving join requests');
async approveJoinRequest(): Promise<Result<void, DomainError>> {
return Result.err({ type: 'notImplemented', message: 'Not implemented: API endpoint for approving join requests' });
}
async rejectJoinRequest(): Promise<void> {
throw new Error('Not implemented: API endpoint for rejecting join requests');
async rejectJoinRequest(): Promise<Result<void, DomainError>> {
return Result.err({ type: 'notImplemented', message: 'Not implemented: API endpoint for rejecting join requests' });
}
}
}

View File

@@ -1,11 +1,12 @@
import { TeamsApiClient } from '@/lib/api/teams/TeamsApiClient';
import { TeamSummaryViewModel } from '@/lib/view-models/TeamSummaryViewModel';
import type { TeamListItemDTO } from '@/lib/types/generated/TeamListItemDTO';
import { TeamMemberViewModel } from '@/lib/view-models/TeamMemberViewModel';
import { Result } from '@/lib/contracts/Result';
import { DomainError, Service } from '@/lib/contracts/services/Service';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
import { isProductionEnvironment } from '@/lib/config/env';
/**
* Team Service - DTO Only
@@ -22,7 +23,7 @@ export class TeamService implements Service {
const errorReporter = new EnhancedErrorReporter(logger, {
showUserNotifications: true,
logToConsole: true,
reportToExternal: process.env.NODE_ENV === 'production',
reportToExternal: isProductionEnvironment(),
});
this.apiClient = new TeamsApiClient(baseUrl, errorReporter, logger);
}
@@ -31,20 +32,20 @@ export class TeamService implements Service {
try {
const result = await this.apiClient.getAll();
return Result.ok(result.teams);
} catch (error) {
return Result.err({ type: 'unknown', message: 'Failed to fetch teams' });
} catch (error: unknown) {
return Result.err({ type: 'unknown', message: (error as Error).message || 'Failed to fetch teams' });
}
}
async getTeamDetails(_teamId: string, _currentDriverId: string): Promise<Result<any, DomainError>> {
async getTeamDetails(teamId: string, _: string): Promise<Result<unknown, DomainError>> {
try {
const result = await this.apiClient.getDetails(_teamId);
const result = await this.apiClient.getDetails(teamId);
if (!result) {
return Result.err({ type: 'notFound', message: 'Team not found' });
}
return Result.ok(result);
} catch (error) {
return Result.err({ type: 'unknown', message: 'Failed to fetch team details' });
} catch (error: unknown) {
return Result.err({ type: 'unknown', message: (error as Error).message || 'Failed to fetch team details' });
}
}
@@ -52,28 +53,28 @@ export class TeamService implements Service {
try {
const result = await this.apiClient.getMembers(teamId);
return Result.ok(result.members.map(member => new TeamMemberViewModel(member, currentDriverId, ownerId)));
} catch (error) {
return Result.err({ type: 'unknown', message: 'Failed to fetch team members' });
} catch (error: unknown) {
return Result.err({ type: 'unknown', message: (error as Error).message || 'Failed to fetch team members' });
}
}
async getTeamJoinRequests(_teamId: string): Promise<Result<any, DomainError>> {
async getTeamJoinRequests(_: string): Promise<Result<unknown, DomainError>> {
return Result.err({ type: 'notImplemented', message: 'getTeamJoinRequests' });
}
async createTeam(_input: any): Promise<Result<any, DomainError>> {
async createTeam(__: unknown): Promise<Result<unknown, DomainError>> {
return Result.err({ type: 'notImplemented', message: 'createTeam' });
}
async updateTeam(_teamId: string, _input: any): Promise<Result<any, DomainError>> {
async updateTeam(_: string, __: unknown): Promise<Result<unknown, DomainError>> {
return Result.err({ type: 'notImplemented', message: 'updateTeam' });
}
async getDriverTeam(_driverId: string): Promise<Result<any, DomainError>> {
async getDriverTeam(_: string): Promise<Result<unknown, DomainError>> {
return Result.err({ type: 'notImplemented', message: 'getDriverTeam' });
}
async getMembership(_teamId: string, _driverId: string): Promise<Result<any, DomainError>> {
async getMembership(_: string, __: string): Promise<Result<unknown, DomainError>> {
return Result.err({ type: 'notImplemented', message: 'getMembership' });
}
}
}