3 Commits

Author SHA1 Message Date
1b0a1f4aee view data fixes
Some checks failed
Contract Testing / contract-tests (pull_request) Failing after 7m11s
Contract Testing / contract-snapshot (pull_request) Has been skipped
2026-01-24 23:29:55 +01:00
c1750a33dd view data fixes 2026-01-24 12:47:49 +01:00
6749fe326b view data fixes 2026-01-24 12:44:57 +01:00
265 changed files with 10939 additions and 905 deletions

View File

@@ -426,6 +426,16 @@
"no-restricted-syntax": "error"
}
},
{
"files": [
"apps/website/**/*.test.ts",
"apps/website/**/*.test.tsx"
],
"rules": {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": "off"
}
},
{
"files": [
"tests/**/*.ts"

View File

@@ -107,45 +107,49 @@ export class GetDashboardStatsUseCase {
// User growth (last 7 days)
const userGrowth: DashboardStatsResult['userGrowth'] = [];
for (let i = 6; i >= 0; i--) {
const date = new Date();
date.setDate(date.getDate() - i);
const dateStr = date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
const count = allUsers.filter((u: AdminUser) => {
const userDate = new Date(u.createdAt);
return userDate.toDateString() === date.toDateString();
}).length;
userGrowth.push({
label: dateStr,
value: count,
color: 'text-primary-blue',
});
if (allUsers.length > 0) {
for (let i = 6; i >= 0; i--) {
const date = new Date();
date.setDate(date.getDate() - i);
const dateStr = date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
const count = allUsers.filter((u: AdminUser) => {
const userDate = u.createdAt;
return userDate.toDateString() === date.toDateString();
}).length;
userGrowth.push({
label: dateStr,
value: count,
color: 'text-primary-blue',
});
}
}
// Activity timeline (last 7 days)
const activityTimeline: DashboardStatsResult['activityTimeline'] = [];
for (let i = 6; i >= 0; i--) {
const date = new Date();
date.setDate(date.getDate() - i);
const dateStr = date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
const newUsers = allUsers.filter((u: AdminUser) => {
const userDate = new Date(u.createdAt);
return userDate.toDateString() === date.toDateString();
}).length;
if (allUsers.length > 0) {
for (let i = 6; i >= 0; i--) {
const date = new Date();
date.setDate(date.getDate() - i);
const dateStr = date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
const newUsers = allUsers.filter((u: AdminUser) => {
const userDate = u.createdAt;
return userDate.toDateString() === date.toDateString();
}).length;
const logins = allUsers.filter((u: AdminUser) => {
const loginDate = u.lastLoginAt;
return loginDate && loginDate.toDateString() === date.toDateString();
}).length;
const logins = allUsers.filter((u: AdminUser) => {
const loginDate = u.lastLoginAt;
return loginDate && loginDate.toDateString() === date.toDateString();
}).length;
activityTimeline.push({
date: dateStr,
newUsers,
logins,
});
activityTimeline.push({
date: dateStr,
newUsers,
logins,
});
}
}
const result: DashboardStatsResult = {

View File

@@ -1,14 +1,14 @@
'use server';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
import { Result } from '@/lib/contracts/Result';
import { ScheduleAdminMutation } from '@/lib/mutations/leagues/ScheduleAdminMutation';
import { RacesApiClient } from '@/lib/api/races/RacesApiClient';
import { routes } from '@/lib/routing/RouteConfig';
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
import { Result } from '@/lib/contracts/Result';
import { RacesApiClient } from '@/lib/gateways/api/races/RacesApiClient';
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { ScheduleAdminMutation } from '@/lib/mutations/leagues/ScheduleAdminMutation';
import { routes } from '@/lib/routing/RouteConfig';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
// eslint-disable-next-line gridpilot-rules/server-actions-interface
export async function publishScheduleAction(leagueId: string, seasonId: string): Promise<Result<void, string>> {
@@ -124,16 +124,16 @@ export async function withdrawFromRaceAction(raceId: string, driverId: string, l
}
// eslint-disable-next-line gridpilot-rules/server-actions-interface
export async function navigateToEditRaceAction(raceId: string, leagueId: string): Promise<void> {
export async function navigateToEditRaceAction(leagueId: string): Promise<void> {
redirect(routes.league.scheduleAdmin(leagueId));
}
// eslint-disable-next-line gridpilot-rules/server-actions-interface
export async function navigateToRescheduleRaceAction(raceId: string, leagueId: string): Promise<void> {
export async function navigateToRescheduleRaceAction(leagueId: string): Promise<void> {
redirect(routes.league.scheduleAdmin(leagueId));
}
// eslint-disable-next-line gridpilot-rules/server-actions-interface
export async function navigateToRaceResultsAction(raceId: string, leagueId: string): Promise<void> {
export async function navigateToRaceResultsAction(raceId: string): Promise<void> {
redirect(routes.race.results(raceId));
}

View File

@@ -32,14 +32,42 @@ export default async function LeagueLayout({
leagueId,
name: 'Error',
description: 'Failed to load league',
info: { name: 'Error', membersCount: 0, racesCount: 0, avgSOF: 0, structure: '', scoring: '', createdAt: '' },
info: { name: 'Error', description: 'Error', membersCount: 0, racesCount: 0, avgSOF: 0, structure: '', scoring: '', createdAt: '' },
runningRaces: [],
sponsors: [],
ownerSummary: null,
adminSummaries: [],
stewardSummaries: [],
memberSummaries: [],
sponsorInsights: null
sponsorInsights: null,
league: {
id: leagueId,
name: 'Error',
game: 'Unknown',
tier: 'starter',
season: 'Unknown',
description: 'Error',
drivers: 0,
races: 0,
completedRaces: 0,
totalImpressions: 0,
avgViewsPerRace: 0,
engagement: 0,
rating: 0,
seasonStatus: 'completed',
seasonDates: { start: '', end: '' },
sponsorSlots: {
main: { price: 0, status: 'occupied' },
secondary: { price: 0, total: 0, occupied: 0 }
}
},
drivers: [],
races: [],
seasonProgress: { completedRaces: 0, totalRaces: 0, percentage: 0 },
recentResults: [],
walletBalance: 0,
pendingProtestsCount: 0,
pendingJoinRequestsCount: 0
}}
tabs={[]}
>

View File

@@ -22,22 +22,50 @@ export default async function LeagueSettingsPage({ params }: Props) {
}
// For serverError, show the template with empty data
return <LeagueSettingsTemplate viewData={{
leagueId,
league: {
id: leagueId,
name: 'Unknown League',
description: 'League information unavailable',
visibility: 'private',
ownerId: 'unknown',
createdAt: '1970-01-01T00:00:00Z',
updatedAt: '1970-01-01T00:00:00Z',
},
config: {
maxDrivers: 0,
scoringPresetId: 'unknown',
allowLateJoin: false,
requireApproval: false,
basics: {
name: 'Unknown League',
description: 'League information unavailable',
visibility: 'private',
gameId: 'unknown',
},
structure: {
mode: 'solo',
maxDrivers: 0,
},
championships: {
enableDriverChampionship: true,
enableTeamChampionship: false,
enableNationsChampionship: false,
enableTrophyChampionship: false,
},
scoring: {
patternId: 'unknown',
},
dropPolicy: {
strategy: 'none',
},
timings: {},
stewarding: {
decisionMode: 'single_steward',
requireDefense: false,
defenseTimeLimit: 24,
voteTimeLimit: 24,
protestDeadlineHours: 24,
stewardingClosesHours: 48,
notifyAccusedOnProtest: true,
notifyOnVoteRequired: true,
},
},
presets: [],
owner: null,
members: [],
}} />;
}

View File

@@ -29,6 +29,7 @@ export default async function Page({ params }: Props) {
leagueId,
currentDriverId: null,
isAdmin: false,
isTeamChampionship: false,
}}
/>;
}

View File

@@ -33,6 +33,8 @@ export default async function LeagueWalletPage({ params }: Props) {
formattedPendingPayouts: '$0.00',
currency: 'USD',
transactions: [],
totalWithdrawals: 0,
canWithdraw: false,
}} />;
}

View File

@@ -1,11 +1,11 @@
import { notFound } from 'next/navigation';
import { PageWrapper } from '@/components/shared/state/PageWrapper';
import { SponsorLeagueDetailPageClient } from '@/client-wrapper/SponsorLeagueDetailPageClient';
import { SponsorsApiClient } from '@/lib/api/sponsors/SponsorsApiClient';
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { PageWrapper } from '@/components/shared/state/PageWrapper';
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
import { getWebsiteServerEnv } from '@/lib/config/env';
import { SponsorsApiClient } from '@/lib/gateways/api/sponsors/SponsorsApiClient';
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { notFound } from 'next/navigation';
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;

View File

@@ -1,10 +1,10 @@
import { PageWrapper } from '@/components/shared/state/PageWrapper';
import { SponsorLeaguesPageClient } from '@/client-wrapper/SponsorLeaguesPageClient';
import { SponsorsApiClient } from '@/lib/api/sponsors/SponsorsApiClient';
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { PageWrapper } from '@/components/shared/state/PageWrapper';
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
import { getWebsiteServerEnv } from '@/lib/config/env';
import { SponsorsApiClient } from '@/lib/gateways/api/sponsors/SponsorsApiClient';
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
export default async function Page() {
// Manual wiring: create dependencies

View File

@@ -1,9 +1,9 @@
'use client';
import { ActionItem } from '@/lib/queries/ActionsPageQuery';
import { ActionStatusBadge } from './ActionStatusBadge';
import { Table, TableHead, TableBody, TableRow, TableHeader, TableCell } from '@/ui/Table';
import { ActionItem } from '@/lib/page-queries/ActionsPageQuery';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/ui/Table';
import { Text } from '@/ui/Text';
import { ActionStatusBadge } from './ActionStatusBadge';
interface ActionListProps {
actions: ActionItem[];

View File

@@ -3,10 +3,10 @@
import { useNotifications } from '@/components/notifications/NotificationProvider';
import type { NotificationVariant } from '@/components/notifications/notificationTypes';
import { useEffectiveDriverId } from "@/hooks/useEffectiveDriverId";
import { ApiConnectionMonitor } from '@/lib/api/base/ApiConnectionMonitor';
import { CircuitBreakerRegistry } from '@/lib/api/base/RetryHandler';
import { ApiConnectionMonitor } from '@/lib/gateways/api/base/ApiConnectionMonitor';
import { CircuitBreakerRegistry } from '@/lib/gateways/api/base/RetryHandler';
import { getGlobalErrorHandler } from '@/lib/infrastructure/GlobalErrorHandler';
import { Activity, AlertTriangle, ChevronDown, ChevronUp, MessageSquare, Wrench, X } from 'lucide-react';
import { ChevronUp, Wrench, X } from 'lucide-react';
import { useEffect, useState } from 'react';
// Import our new components
@@ -15,8 +15,8 @@ import { Badge } from '@/ui/Badge';
import { Button } from '@/ui/Button';
import { Icon } from '@/ui/Icon';
import { IconButton } from '@/ui/IconButton';
import { Text } from '@/ui/Text';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { Toolbar } from '@/ui/Toolbar';
import { APIStatusSection } from './sections/APIStatusSection';
import { NotificationSendSection } from './sections/NotificationSendSection';

View File

@@ -1,10 +1,10 @@
'use client';
import React, { Component, ReactNode, useState } from 'react';
import { ApiError } from '@/lib/api/base/ApiError';
import { connectionMonitor } from '@/lib/api/base/ApiConnectionMonitor';
import { ErrorDisplay } from '@/components/shared/ErrorDisplay';
import { DevErrorPanel } from '@/components/shared/DevErrorPanel';
import { ErrorDisplay } from '@/components/shared/ErrorDisplay';
import { connectionMonitor } from '@/lib/gateways/api/base/ApiConnectionMonitor';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import { Component, ReactNode, useState } from 'react';
interface Props {
children: ReactNode;

View File

@@ -1,10 +1,10 @@
'use client';
import React, { Component, ReactNode, ErrorInfo, useState, version } from 'react';
import { ApiError } from '@/lib/api/base/ApiError';
import { getGlobalErrorHandler } from '@/lib/infrastructure/GlobalErrorHandler';
import { DevErrorPanel } from '@/components/shared/DevErrorPanel';
import { ErrorDisplay } from '@/components/shared/ErrorDisplay';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import { getGlobalErrorHandler } from '@/lib/infrastructure/GlobalErrorHandler';
import React, { Component, ErrorInfo, ReactNode, useState, version } from 'react';
interface Props {
children: ReactNode;

View File

@@ -1,6 +1,6 @@
'use client';
import { ApiError } from '@/lib/api/base/ApiError';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import { getErrorSeverity, isConnectivityError, isRetryable, parseApiError } from '@/lib/utils/errorUtils';
import { Button } from '@/ui/Button';
import { Icon } from '@/ui/Icon';
@@ -9,15 +9,15 @@ import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { AnimatePresence, motion } from 'framer-motion';
import {
AlertCircle,
AlertTriangle,
Bug,
ChevronDown,
ChevronUp,
Info,
RefreshCw,
Wifi,
X
AlertCircle,
AlertTriangle,
Bug,
ChevronDown,
ChevronUp,
Info,
RefreshCw,
Wifi,
X
} from 'lucide-react';
import { useState } from 'react';

View File

@@ -1,8 +1,7 @@
'use client';
import React from 'react';
import { ApiError } from '@/lib/api/base/ApiError';
import { ErrorDisplay as UiErrorDisplay } from '@/components/shared/ErrorDisplay';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
interface ErrorDisplayProps {
error: ApiError;

View File

@@ -1,9 +1,9 @@
'use client';
import { useEffect, useState } from 'react';
import { useNotifications } from '@/components/notifications/NotificationProvider';
import { ApiError } from '@/lib/api/base/ApiError';
import { connectionMonitor } from '@/lib/api/base/ApiConnectionMonitor';
import { connectionMonitor } from '@/lib/gateways/api/base/ApiConnectionMonitor';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import { useEffect, useState } from 'react';
/**
* Integration component that listens for API errors and shows notifications

View File

@@ -1,6 +1,6 @@
import { connectionMonitor } from '@/lib/api/base/ApiConnectionMonitor';
import { ApiError } from '@/lib/api/base/ApiError';
import { CircuitBreakerRegistry } from '@/lib/api/base/RetryHandler';
import { connectionMonitor } from '@/lib/gateways/api/base/ApiConnectionMonitor';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import { CircuitBreakerRegistry } from '@/lib/gateways/api/base/RetryHandler';
import { Badge } from '@/ui/Badge';
import { Box } from '@/ui/Box';
import { Button } from '@/ui/Button';

View File

@@ -1,4 +1,4 @@
import { ApiError } from '@/lib/api/base/ApiError';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import { Box } from '@/ui/Box';
import { Button } from '@/ui/Button';
import { Heading } from '@/ui/Heading';

View File

@@ -1,7 +1,7 @@
import { EmptyState } from '@/ui/EmptyState';
import { ErrorDisplay } from '@/components/shared/ErrorDisplay';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import { EmptyState } from '@/ui/EmptyState';
import { LoadingWrapper } from '@/ui/LoadingWrapper';
import { ApiError } from '@/lib/api/base/ApiError';
import { Inbox, List, LucideIcon } from 'lucide-react';
import React, { ReactNode } from 'react';

View File

@@ -1,9 +1,9 @@
import { useQuery, UseQueryOptions } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { SESSION_SERVICE_TOKEN } from '@/lib/di/tokens';
import { enhanceQueryResult } from '@/lib/di/hooks/useReactQueryWithApiError';
import { ApiError } from '@/lib/api/base/ApiError';
import { SESSION_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import { SessionViewModel } from '@/lib/view-models/SessionViewModel';
import { useQuery, UseQueryOptions } from '@tanstack/react-query';
export function useCurrentSession(
options?: Omit<UseQueryOptions<SessionViewModel | null, ApiError>, 'queryKey' | 'queryFn'> & { initialData?: SessionViewModel | null }

View File

@@ -1,8 +1,8 @@
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { AUTH_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/api/base/ApiError';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import type { ForgotPasswordDTO } from '@/lib/types/generated/ForgotPasswordDTO';
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
export function useForgotPassword(
options?: Omit<UseMutationOptions<{ message: string; magicLink?: string }, ApiError, ForgotPasswordDTO>, 'mutationFn'>

View File

@@ -1,9 +1,9 @@
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { AUTH_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/api/base/ApiError';
import { SessionViewModel } from '@/lib/view-models/SessionViewModel';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import type { LoginParamsDTO } from '@/lib/types/generated/LoginParamsDTO';
import { SessionViewModel } from '@/lib/view-models/SessionViewModel';
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
export function useLogin(
options?: Omit<UseMutationOptions<SessionViewModel, ApiError, LoginParamsDTO>, 'mutationFn'>

View File

@@ -1,7 +1,7 @@
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { AUTH_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/api/base/ApiError';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
export function useLogout(
options?: Omit<UseMutationOptions<void, ApiError, void>, 'mutationFn'>

View File

@@ -1,8 +1,8 @@
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { AUTH_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/api/base/ApiError';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import type { ResetPasswordDTO } from '@/lib/types/generated/ResetPasswordDTO';
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
export function useResetPassword(
options?: Omit<UseMutationOptions<{ message: string }, ApiError, ResetPasswordDTO>, 'mutationFn'>

View File

@@ -1,9 +1,9 @@
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { AUTH_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/api/base/ApiError';
import { SessionViewModel } from '@/lib/view-models/SessionViewModel';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import type { SignupParamsDTO } from '@/lib/types/generated/SignupParamsDTO';
import { SessionViewModel } from '@/lib/view-models/SessionViewModel';
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
export function useSignup(
options?: Omit<UseMutationOptions<SessionViewModel, ApiError, SignupParamsDTO>, 'mutationFn'>

View File

@@ -1,11 +1,11 @@
'use client';
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { DRIVER_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/api/base/ApiError';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import type { CompleteOnboardingInputDTO } from '@/lib/types/generated/CompleteOnboardingInputDTO';
import { CompleteOnboardingViewModel } from '@/lib/view-models/CompleteOnboardingViewModel';
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
export function useCreateDriver(
options?: Omit<UseMutationOptions<CompleteOnboardingViewModel, ApiError, CompleteOnboardingInputDTO>, 'mutationFn'>

View File

@@ -1,10 +1,10 @@
import { useQuery, UseQueryOptions } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { DRIVER_SERVICE_TOKEN } from '@/lib/di/tokens';
import { enhanceQueryResult } from '@/lib/di/hooks/useReactQueryWithApiError';
import { ApiError } from '@/lib/api/base/ApiError';
import type { DriverDTO } from '@/lib/types/generated/DriverDTO';
import { useAuth } from '@/components/auth/AuthContext';
import { useInject } from '@/lib/di/hooks/useInject';
import { enhanceQueryResult } from '@/lib/di/hooks/useReactQueryWithApiError';
import { DRIVER_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import type { DriverDTO } from '@/lib/types/generated/DriverDTO';
import { useQuery, UseQueryOptions } from '@tanstack/react-query';
export function useCurrentDriver(
options?: Omit<UseQueryOptions<DriverDTO | null, ApiError>, 'queryKey' | 'queryFn'>

View File

@@ -1,9 +1,9 @@
import { useQuery, UseQueryOptions } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { DRIVER_SERVICE_TOKEN } from '@/lib/di/tokens';
import { enhanceQueryResult } from '@/lib/di/hooks/useReactQueryWithApiError';
import { ApiError } from '@/lib/api/base/ApiError';
import { DRIVER_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import { DriverProfileViewModel, type DriverProfileViewModelData } from '@/lib/view-models/DriverProfileViewModel';
import { useQuery, UseQueryOptions } from '@tanstack/react-query';
export function useDriverProfile(
driverId: string,
@@ -19,7 +19,7 @@ export function useDriverProfile(
const error = result.getError();
throw new ApiError(error.message, 'SERVER_ERROR', { timestamp: new globalThis.Date().toISOString() });
}
return new DriverProfileViewModel(result.unwrap() as unknown as DriverProfileViewModelData);
return new DriverProfileViewModel(result.unwrap());
},
enabled: !!driverId,
...options,

View File

@@ -1,9 +1,9 @@
import { useQuery, UseQueryOptions } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { DRIVER_SERVICE_TOKEN } from '@/lib/di/tokens';
import { enhanceQueryResult } from '@/lib/di/hooks/useReactQueryWithApiError';
import { ApiError } from '@/lib/api/base/ApiError';
import { DRIVER_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import type { GetDriverOutputDTO } from '@/lib/types/generated/GetDriverOutputDTO';
import { useQuery, UseQueryOptions } from '@tanstack/react-query';
export function useFindDriverById(
driverId: string,

View File

@@ -1,10 +1,10 @@
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
import { DriverProfileViewModelBuilder } from '@/lib/builders/view-models/DriverProfileViewModelBuilder';
import { useInject } from '@/lib/di/hooks/useInject';
import { DRIVER_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/api/base/ApiError';
import { DriverProfileViewModel } from '@/lib/view-models/DriverProfileViewModel';
import { DriverProfileViewModelBuilder } from '@/lib/builders/view-models/DriverProfileViewModelBuilder';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import type { GetDriverProfileOutputDTO } from '@/lib/types/generated/GetDriverProfileOutputDTO';
import { DriverProfileViewModel } from '@/lib/view-models/DriverProfileViewModel';
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
export function useUpdateDriverProfile(
options?: Omit<UseMutationOptions<DriverProfileViewModel, ApiError, { bio?: string; country?: string }>, 'mutationFn'>

View File

@@ -1,9 +1,9 @@
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { LEAGUE_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/api/base/ApiError';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import { CreateLeagueInputDTO } from '@/lib/types/generated/CreateLeagueInputDTO';
import { CreateLeagueOutputDTO } from '@/lib/types/generated/CreateLeagueOutputDTO';
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
export interface CreateLeagueInput {
name: string;

View File

@@ -1,15 +1,10 @@
import { useQuery, UseQueryOptions } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { LEAGUE_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/api/base/ApiError';
import type { LeagueWithCapacityAndScoringDTO } from '@/lib/types/generated/LeagueWithCapacityAndScoringDTO';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import type { LeagueMembershipsDTO } from '@/lib/types/generated/LeagueMembershipsDTO';
import type { AllLeaguesWithCapacityAndScoringDTO } from '@/lib/types/AllLeaguesWithCapacityAndScoringDTO';
import type { LeagueWithCapacityAndScoringDTO } from '@/lib/types/generated/LeagueWithCapacityAndScoringDTO';
import { useQuery, UseQueryOptions } from '@tanstack/react-query';
interface UseLeagueDetailOptions {
leagueId: string;
queryOptions?: UseQueryOptions<LeagueWithCapacityAndScoringDTO, ApiError>;
}
interface UseLeagueMembershipsOptions {
leagueId: string;

View File

@@ -1,7 +1,7 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { LEAGUE_MEMBERSHIP_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/api/base/ApiError';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import { useMutation, useQueryClient } from '@tanstack/react-query';
export function useLeagueMembershipMutation() {
const leagueMembershipService = useInject(LEAGUE_MEMBERSHIP_SERVICE_TOKEN);

View File

@@ -1,9 +1,9 @@
import { useMutation, useQuery, UseMutationOptions, UseQueryOptions } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { LEAGUE_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/api/base/ApiError';
import type { LeagueRosterMemberDTO } from '@/lib/types/generated/LeagueRosterMemberDTO';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import type { LeagueRosterJoinRequestDTO } from '@/lib/types/generated/LeagueRosterJoinRequestDTO';
import type { LeagueRosterMemberDTO } from '@/lib/types/generated/LeagueRosterMemberDTO';
import { useMutation, UseMutationOptions, useQuery, UseQueryOptions } from '@tanstack/react-query';
interface UpdateMemberRoleInput {
leagueId: string;

View File

@@ -1,9 +1,9 @@
import { useQuery, UseQueryOptions } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { LEAGUE_SETTINGS_SERVICE_TOKEN } from '@/lib/di/tokens';
import { enhanceQueryResult } from '@/lib/di/hooks/useReactQueryWithApiError';
import { ApiError } from '@/lib/api/base/ApiError';
import { LEAGUE_SETTINGS_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import type { LeagueSettingsViewModel } from '@/lib/view-models/LeagueSettingsViewModel';
import { useQuery, UseQueryOptions } from '@tanstack/react-query';
export function useLeagueSettings(
leagueId: string,

View File

@@ -1,8 +1,8 @@
import { useQuery } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { LEAGUE_STEWARDING_SERVICE_TOKEN } from '@/lib/di/tokens';
import { enhanceQueryResult } from '@/lib/di/hooks/useReactQueryWithApiError';
import { ApiError } from '@/lib/api/base/ApiError';
import { LEAGUE_STEWARDING_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import { useQuery } from '@tanstack/react-query';
export function useProtestDetail(leagueId: string, protestId: string, enabled: boolean = true) {
const leagueStewardingService = useInject(LEAGUE_STEWARDING_SERVICE_TOKEN);

View File

@@ -1,8 +1,8 @@
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { RACE_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/api/base/ApiError';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import type { FileProtestCommandDTO } from '@/lib/types/generated/FileProtestCommandDTO';
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
export function useFileProtest(
options?: Omit<UseMutationOptions<void, ApiError, FileProtestCommandDTO>, 'mutationFn'>

View File

@@ -1,7 +1,7 @@
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { RACE_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/api/base/ApiError';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
interface RegisterForRaceParams {
raceId: string;

View File

@@ -1,7 +1,7 @@
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { RACE_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/api/base/ApiError';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
interface WithdrawFromRaceParams {
raceId: string;

View File

@@ -1,7 +1,7 @@
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { TEAM_JOIN_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/api/base/ApiError';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
export function useApproveJoinRequest(options?: Omit<UseMutationOptions<void, ApiError, void>, 'mutationFn'>) {
const teamJoinService = useInject(TEAM_JOIN_SERVICE_TOKEN);

View File

@@ -1,9 +1,9 @@
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { TEAM_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/api/base/ApiError';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import type { CreateTeamInputDTO } from '@/lib/types/generated/CreateTeamInputDTO';
import type { CreateTeamOutputDTO } from '@/lib/types/generated/CreateTeamOutputDTO';
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
export function useCreateTeam(options?: UseMutationOptions<CreateTeamOutputDTO, ApiError, CreateTeamInputDTO>) {
const teamService = useInject(TEAM_SERVICE_TOKEN);

View File

@@ -1,5 +1,5 @@
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
import { ApiError } from '@/lib/api/base/ApiError';
interface JoinTeamParams {
teamId: string;

View File

@@ -1,5 +1,5 @@
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
import { ApiError } from '@/lib/api/base/ApiError';
interface LeaveTeamParams {
teamId: string;

View File

@@ -1,7 +1,7 @@
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { TEAM_JOIN_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/api/base/ApiError';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
export function useRejectJoinRequest(options?: Omit<UseMutationOptions<void, ApiError, void>, 'mutationFn'>) {
const teamJoinService = useInject(TEAM_JOIN_SERVICE_TOKEN);

View File

@@ -1,8 +1,8 @@
import { useQuery } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { TEAM_JOIN_SERVICE_TOKEN } from '@/lib/di/tokens';
import { enhanceQueryResult } from '@/lib/di/hooks/useReactQueryWithApiError';
import { ApiError } from '@/lib/api/base/ApiError';
import { TEAM_JOIN_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import { useQuery } from '@tanstack/react-query';
export function useTeamJoinRequests(teamId: string, currentUserId: string, isOwner: boolean) {
const teamJoinService = useInject(TEAM_JOIN_SERVICE_TOKEN);

View File

@@ -1,9 +1,9 @@
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { TEAM_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/api/base/ApiError';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import type { UpdateTeamInputDTO } from '@/lib/types/generated/UpdateTeamInputDTO';
import type { UpdateTeamOutputDTO } from '@/lib/types/generated/UpdateTeamOutputDTO';
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
export function useUpdateTeam(options?: UseMutationOptions<UpdateTeamOutputDTO, ApiError, { teamId: string; input: UpdateTeamInputDTO }>) {
const teamService = useInject(TEAM_SERVICE_TOKEN);

View File

@@ -1,9 +1,9 @@
import { useQuery, UseQueryOptions } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { POLICY_SERVICE_TOKEN } from '@/lib/di/tokens';
import { enhanceQueryResult } from '@/lib/di/hooks/useReactQueryWithApiError';
import { ApiError } from '@/lib/api/base/ApiError';
import { PolicySnapshotDto } from '@/lib/api/policy/PolicyApiClient';
import { POLICY_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import { PolicySnapshotDto } from '@/lib/gateways/api/policy/PolicyApiClient';
import { useQuery, UseQueryOptions } from '@tanstack/react-query';
export function useCapability(
capabilityKey: string,

View File

@@ -10,6 +10,8 @@ import { Result } from '@/lib/contracts/Result';
import { DomainError } from '@/lib/contracts/services/Service';
import { MediaBinaryDTO } from '@/lib/types/MediaBinaryDTO';
// TODO why is this an adapter?
/**
* MediaAdapter
*

View File

@@ -1,5 +1,5 @@
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import { UseQueryResult } from '@tanstack/react-query';
import { ApiError } from '@/lib/api/base/ApiError';
/**
* Converts React-Query error to ApiError for StateContainer compatibility

View File

@@ -1,37 +1,37 @@
import { ContainerModule } from 'inversify';
import { RacesApiClient } from '../../api/races/RacesApiClient';
import { DriversApiClient } from '../../api/drivers/DriversApiClient';
import { TeamsApiClient } from '../../api/teams/TeamsApiClient';
import { LeaguesApiClient } from '../../api/leagues/LeaguesApiClient';
import { SponsorsApiClient } from '../../api/sponsors/SponsorsApiClient';
import { PaymentsApiClient } from '../../api/payments/PaymentsApiClient';
import { WalletsApiClient } from '../../api/wallets/WalletsApiClient';
import { AuthApiClient } from '../../api/auth/AuthApiClient';
import { AnalyticsApiClient } from '../../api/analytics/AnalyticsApiClient';
import { MediaApiClient } from '../../api/media/MediaApiClient';
import { DashboardApiClient } from '../../api/dashboard/DashboardApiClient';
import { PolicyApiClient } from '../../api/policy/PolicyApiClient';
import { ProtestsApiClient } from '../../api/protests/ProtestsApiClient';
import { PenaltiesApiClient } from '../../api/penalties/PenaltiesApiClient';
import { AnalyticsApiClient } from '../../gateways/api/analytics/AnalyticsApiClient';
import { AuthApiClient } from '../../gateways/api/auth/AuthApiClient';
import { DashboardApiClient } from '../../gateways/api/dashboard/DashboardApiClient';
import { DriversApiClient } from '../../gateways/api/drivers/DriversApiClient';
import { LeaguesApiClient } from '../../gateways/api/leagues/LeaguesApiClient';
import { MediaApiClient } from '../../gateways/api/media/MediaApiClient';
import { PaymentsApiClient } from '../../gateways/api/payments/PaymentsApiClient';
import { PenaltiesApiClient } from '../../gateways/api/penalties/PenaltiesApiClient';
import { PolicyApiClient } from '../../gateways/api/policy/PolicyApiClient';
import { ProtestsApiClient } from '../../gateways/api/protests/ProtestsApiClient';
import { RacesApiClient } from '../../gateways/api/races/RacesApiClient';
import { SponsorsApiClient } from '../../gateways/api/sponsors/SponsorsApiClient';
import { TeamsApiClient } from '../../gateways/api/teams/TeamsApiClient';
import { WalletsApiClient } from '../../gateways/api/wallets/WalletsApiClient';
import {
LOGGER_TOKEN,
ERROR_REPORTER_TOKEN,
CONFIG_TOKEN,
LEAGUE_API_CLIENT_TOKEN,
DRIVER_API_CLIENT_TOKEN,
TEAM_API_CLIENT_TOKEN,
RACE_API_CLIENT_TOKEN,
SPONSOR_API_CLIENT_TOKEN,
PAYMENT_API_CLIENT_TOKEN,
WALLET_API_CLIENT_TOKEN,
AUTH_API_CLIENT_TOKEN,
import {
ANALYTICS_API_CLIENT_TOKEN,
MEDIA_API_CLIENT_TOKEN,
AUTH_API_CLIENT_TOKEN,
CONFIG_TOKEN,
DASHBOARD_API_CLIENT_TOKEN,
DRIVER_API_CLIENT_TOKEN,
ERROR_REPORTER_TOKEN,
LEAGUE_API_CLIENT_TOKEN,
LOGGER_TOKEN,
MEDIA_API_CLIENT_TOKEN,
PAYMENT_API_CLIENT_TOKEN,
PENALTY_API_CLIENT_TOKEN,
POLICY_API_CLIENT_TOKEN,
PROTEST_API_CLIENT_TOKEN,
PENALTY_API_CLIENT_TOKEN
RACE_API_CLIENT_TOKEN,
SPONSOR_API_CLIENT_TOKEN,
TEAM_API_CLIENT_TOKEN,
WALLET_API_CLIENT_TOKEN
} from '../tokens';
export const ApiModule = new ContainerModule((options) => {

View File

@@ -1,3 +1,5 @@
import { ErrorReporter } from '../../interfaces/ErrorReporter';
import { Logger } from '../../interfaces/Logger';
import { AdminApiClient } from './admin/AdminApiClient';
import { AnalyticsApiClient } from './analytics/AnalyticsApiClient';
import { AuthApiClient } from './auth/AuthApiClient';
@@ -13,10 +15,8 @@ import { RacesApiClient } from './races/RacesApiClient';
import { SponsorsApiClient } from './sponsors/SponsorsApiClient';
import { TeamsApiClient } from './teams/TeamsApiClient';
import { WalletsApiClient } from './wallets/WalletsApiClient';
import { ErrorReporter } from '../interfaces/ErrorReporter';
import { Logger } from '../interfaces/Logger';
import { ConsoleLogger } from '../infrastructure/logging/ConsoleLogger';
import { ConsoleLogger } from '../../infrastructure/logging/ConsoleLogger';
export class ApiClient {
public readonly admin: AdminApiClient;

View File

@@ -1,10 +1,10 @@
import { GetAnalyticsMetricsOutputDTO } from '../../../types/generated/GetAnalyticsMetricsOutputDTO';
import { GetDashboardDataOutputDTO } from '../../../types/generated/GetDashboardDataOutputDTO';
import { RecordEngagementInputDTO } from '../../../types/generated/RecordEngagementInputDTO';
import { RecordEngagementOutputDTO } from '../../../types/generated/RecordEngagementOutputDTO';
import { RecordPageViewInputDTO } from '../../../types/generated/RecordPageViewInputDTO';
import { RecordPageViewOutputDTO } from '../../../types/generated/RecordPageViewOutputDTO';
import { BaseApiClient } from '../base/BaseApiClient';
import { RecordPageViewOutputDTO } from '../../types/generated/RecordPageViewOutputDTO';
import { RecordEngagementOutputDTO } from '../../types/generated/RecordEngagementOutputDTO';
import { GetDashboardDataOutputDTO } from '../../types/generated/GetDashboardDataOutputDTO';
import { GetAnalyticsMetricsOutputDTO } from '../../types/generated/GetAnalyticsMetricsOutputDTO';
import { RecordPageViewInputDTO } from '../../types/generated/RecordPageViewInputDTO';
import { RecordEngagementInputDTO } from '../../types/generated/RecordEngagementInputDTO';
/**
* Analytics API Client

View File

@@ -1,9 +1,9 @@
import { AuthSessionDTO } from '../../../types/generated/AuthSessionDTO';
import { ForgotPasswordDTO } from '../../../types/generated/ForgotPasswordDTO';
import { LoginParamsDTO } from '../../../types/generated/LoginParamsDTO';
import { ResetPasswordDTO } from '../../../types/generated/ResetPasswordDTO';
import { SignupParamsDTO } from '../../../types/generated/SignupParamsDTO';
import { BaseApiClient } from '../base/BaseApiClient';
import { AuthSessionDTO } from '../../types/generated/AuthSessionDTO';
import { LoginParamsDTO } from '../../types/generated/LoginParamsDTO';
import { SignupParamsDTO } from '../../types/generated/SignupParamsDTO';
import { ForgotPasswordDTO } from '../../types/generated/ForgotPasswordDTO';
import { ResetPasswordDTO } from '../../types/generated/ResetPasswordDTO';
/**
* Auth API Client

View File

@@ -5,12 +5,12 @@
* error handling, authentication, retry logic, and circuit breaker.
*/
import { Logger } from '../../interfaces/Logger';
import { ErrorReporter } from '../../interfaces/ErrorReporter';
import { ApiError, ApiErrorType } from './ApiError';
import { RetryHandler, CircuitBreakerRegistry, DEFAULT_RETRY_CONFIG } from './RetryHandler';
import { ApiConnectionMonitor } from './ApiConnectionMonitor';
import { getGlobalApiLogger } from '@/lib/infrastructure/ApiRequestLogger';
import { ErrorReporter } from '../../../interfaces/ErrorReporter';
import { Logger } from '../../../interfaces/Logger';
import { ApiConnectionMonitor } from './ApiConnectionMonitor';
import { ApiError, ApiErrorType } from './ApiError';
import { CircuitBreakerRegistry, DEFAULT_RETRY_CONFIG, RetryHandler } from './RetryHandler';
export interface BaseApiClientOptions {
timeout?: number;

View File

@@ -1,5 +1,5 @@
import type { DashboardOverviewDTO } from '../../../types/generated/DashboardOverviewDTO';
import { BaseApiClient } from '../base/BaseApiClient';
import type { DashboardOverviewDTO } from '../../types/generated/DashboardOverviewDTO';
/**
* Dashboard API Client

View File

@@ -1,10 +1,10 @@
import type { CompleteOnboardingInputDTO } from '../../../types/generated/CompleteOnboardingInputDTO';
import type { CompleteOnboardingOutputDTO } from '../../../types/generated/CompleteOnboardingOutputDTO';
import type { DriverLeaderboardItemDTO } from '../../../types/generated/DriverLeaderboardItemDTO';
import type { DriverRegistrationStatusDTO } from '../../../types/generated/DriverRegistrationStatusDTO';
import type { GetDriverOutputDTO } from '../../../types/generated/GetDriverOutputDTO';
import type { GetDriverProfileOutputDTO } from '../../../types/generated/GetDriverProfileOutputDTO';
import { BaseApiClient } from '../base/BaseApiClient';
import type { CompleteOnboardingInputDTO } from '../../types/generated/CompleteOnboardingInputDTO';
import type { CompleteOnboardingOutputDTO } from '../../types/generated/CompleteOnboardingOutputDTO';
import type { DriverRegistrationStatusDTO } from '../../types/generated/DriverRegistrationStatusDTO';
import type { DriverLeaderboardItemDTO } from '../../types/generated/DriverLeaderboardItemDTO';
import type { GetDriverOutputDTO } from '../../types/generated/GetDriverOutputDTO';
import type { GetDriverProfileOutputDTO } from '../../types/generated/GetDriverProfileOutputDTO';
type DriversLeaderboardDto = {
drivers: DriverLeaderboardItemDTO[];

View File

@@ -1,28 +1,28 @@
import type { AllLeaguesWithCapacityAndScoringDTO } from '../../../types/AllLeaguesWithCapacityAndScoringDTO';
import type { AllLeaguesWithCapacityDTO } from '../../../types/generated/AllLeaguesWithCapacityDTO';
import type { ApproveJoinRequestOutputDTO } from '../../../types/generated/ApproveJoinRequestOutputDTO';
import type { CreateLeagueInputDTO } from '../../../types/generated/CreateLeagueInputDTO';
import type { CreateLeagueOutputDTO } from '../../../types/generated/CreateLeagueOutputDTO';
import type { CreateLeagueScheduleRaceInputDTO } from '../../../types/generated/CreateLeagueScheduleRaceInputDTO';
import type { CreateLeagueScheduleRaceOutputDTO } from '../../../types/generated/CreateLeagueScheduleRaceOutputDTO';
import type { GetLeagueAdminConfigOutputDTO } from '../../../types/generated/GetLeagueAdminConfigOutputDTO';
import type { LeagueMembershipsDTO } from '../../../types/generated/LeagueMembershipsDTO';
import type { LeagueRosterJoinRequestDTO } from '../../../types/generated/LeagueRosterJoinRequestDTO';
import type { LeagueRosterMemberDTO } from '../../../types/generated/LeagueRosterMemberDTO';
import type { LeagueScheduleDTO } from '../../../types/generated/LeagueScheduleDTO';
import type { LeagueScheduleRaceMutationSuccessDTO } from '../../../types/generated/LeagueScheduleRaceMutationSuccessDTO';
import type { LeagueScoringPresetDTO } from '../../../types/generated/LeagueScoringPresetDTO';
import type { LeagueSeasonSchedulePublishOutputDTO } from '../../../types/generated/LeagueSeasonSchedulePublishOutputDTO';
import type { LeagueSeasonSummaryDTO } from '../../../types/generated/LeagueSeasonSummaryDTO';
import type { LeagueStandingsDTO } from '../../../types/generated/LeagueStandingsDTO';
import type { RaceDTO } from '../../../types/generated/RaceDTO';
import type { RejectJoinRequestOutputDTO } from '../../../types/generated/RejectJoinRequestOutputDTO';
import type { RemoveLeagueMemberOutputDTO } from '../../../types/generated/RemoveLeagueMemberOutputDTO';
import type { SponsorshipDetailDTO } from '../../../types/generated/SponsorshipDetailDTO';
import type { TotalLeaguesDTO } from '../../../types/generated/TotalLeaguesDTO';
import type { UpdateLeagueMemberRoleOutputDTO } from '../../../types/generated/UpdateLeagueMemberRoleOutputDTO';
import type { UpdateLeagueScheduleRaceInputDTO } from '../../../types/generated/UpdateLeagueScheduleRaceInputDTO';
import { BaseApiClient } from '../base/BaseApiClient';
import type { AllLeaguesWithCapacityDTO } from '../../types/generated/AllLeaguesWithCapacityDTO';
import type { TotalLeaguesDTO } from '../../types/generated/TotalLeaguesDTO';
import type { LeagueStandingsDTO } from '../../types/generated/LeagueStandingsDTO';
import type { LeagueScheduleDTO } from '../../types/generated/LeagueScheduleDTO';
import type { LeagueMembershipsDTO } from '../../types/generated/LeagueMembershipsDTO';
import type { CreateLeagueInputDTO } from '../../types/generated/CreateLeagueInputDTO';
import type { CreateLeagueOutputDTO } from '../../types/generated/CreateLeagueOutputDTO';
import type { SponsorshipDetailDTO } from '../../types/generated/SponsorshipDetailDTO';
import type { RaceDTO } from '../../types/generated/RaceDTO';
import type { GetLeagueAdminConfigOutputDTO } from '../../types/generated/GetLeagueAdminConfigOutputDTO';
import type { LeagueScoringPresetDTO } from '../../types/generated/LeagueScoringPresetDTO';
import type { LeagueSeasonSummaryDTO } from '../../types/generated/LeagueSeasonSummaryDTO';
import type { CreateLeagueScheduleRaceInputDTO } from '../../types/generated/CreateLeagueScheduleRaceInputDTO';
import type { CreateLeagueScheduleRaceOutputDTO } from '../../types/generated/CreateLeagueScheduleRaceOutputDTO';
import type { UpdateLeagueScheduleRaceInputDTO } from '../../types/generated/UpdateLeagueScheduleRaceInputDTO';
import type { LeagueScheduleRaceMutationSuccessDTO } from '../../types/generated/LeagueScheduleRaceMutationSuccessDTO';
import type { LeagueSeasonSchedulePublishOutputDTO } from '../../types/generated/LeagueSeasonSchedulePublishOutputDTO';
import type { LeagueRosterMemberDTO } from '../../types/generated/LeagueRosterMemberDTO';
import type { LeagueRosterJoinRequestDTO } from '../../types/generated/LeagueRosterJoinRequestDTO';
import type { ApproveJoinRequestOutputDTO } from '../../types/generated/ApproveJoinRequestOutputDTO';
import type { RejectJoinRequestOutputDTO } from '../../types/generated/RejectJoinRequestOutputDTO';
import type { UpdateLeagueMemberRoleOutputDTO } from '../../types/generated/UpdateLeagueMemberRoleOutputDTO';
import type { RemoveLeagueMemberOutputDTO } from '../../types/generated/RemoveLeagueMemberOutputDTO';
import type { AllLeaguesWithCapacityAndScoringDTO } from '../../types/AllLeaguesWithCapacityAndScoringDTO';
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null;

View File

@@ -1,13 +1,13 @@
import type { DeleteMediaOutputDTO } from '../../types/generated/DeleteMediaOutputDTO';
import type { GetAvatarOutputDTO } from '../../types/generated/GetAvatarOutputDTO';
import type { GetMediaOutputDTO } from '../../types/generated/GetMediaOutputDTO';
import type { RequestAvatarGenerationInputDTO } from '../../types/generated/RequestAvatarGenerationInputDTO';
import type { RequestAvatarGenerationOutputDTO } from '../../types/generated/RequestAvatarGenerationOutputDTO';
import type { UpdateAvatarInputDTO } from '../../types/generated/UpdateAvatarInputDTO';
import type { UpdateAvatarOutputDTO } from '../../types/generated/UpdateAvatarOutputDTO';
import type { UploadMediaOutputDTO } from '../../types/generated/UploadMediaOutputDTO';
import type { ValidateFaceInputDTO } from '../../types/generated/ValidateFaceInputDTO';
import type { ValidateFaceOutputDTO } from '../../types/generated/ValidateFaceOutputDTO';
import type { DeleteMediaOutputDTO } from '../../../types/generated/DeleteMediaOutputDTO';
import type { GetAvatarOutputDTO } from '../../../types/generated/GetAvatarOutputDTO';
import type { GetMediaOutputDTO } from '../../../types/generated/GetMediaOutputDTO';
import type { RequestAvatarGenerationInputDTO } from '../../../types/generated/RequestAvatarGenerationInputDTO';
import type { RequestAvatarGenerationOutputDTO } from '../../../types/generated/RequestAvatarGenerationOutputDTO';
import type { UpdateAvatarInputDTO } from '../../../types/generated/UpdateAvatarInputDTO';
import type { UpdateAvatarOutputDTO } from '../../../types/generated/UpdateAvatarOutputDTO';
import type { UploadMediaOutputDTO } from '../../../types/generated/UploadMediaOutputDTO';
import type { ValidateFaceInputDTO } from '../../../types/generated/ValidateFaceInputDTO';
import type { ValidateFaceOutputDTO } from '../../../types/generated/ValidateFaceOutputDTO';
import { BaseApiClient } from '../base/BaseApiClient';
/**

View File

@@ -1,11 +1,11 @@
import type { MemberPaymentDTO } from '../../../types/generated/MemberPaymentDTO';
import type { MembershipFeeDTO } from '../../../types/generated/MembershipFeeDTO';
import type { PaymentDTO } from '../../../types/generated/PaymentDTO';
import type { PrizeDTO } from '../../../types/generated/PrizeDTO';
import type { TransactionDTO } from '../../../types/generated/TransactionDTO';
import type { UpdatePaymentStatusInputDTO } from '../../../types/generated/UpdatePaymentStatusInputDTO';
import type { WalletDTO } from '../../../types/generated/WalletDTO';
import { BaseApiClient } from '../base/BaseApiClient';
import type { MembershipFeeDTO } from '../../types/generated/MembershipFeeDTO';
import type { MemberPaymentDTO } from '../../types/generated/MemberPaymentDTO';
import type { PaymentDTO } from '../../types/generated/PaymentDTO';
import type { PrizeDTO } from '../../types/generated/PrizeDTO';
import type { TransactionDTO } from '../../types/generated/TransactionDTO';
import type { UpdatePaymentStatusInputDTO } from '../../types/generated/UpdatePaymentStatusInputDTO';
import type { WalletDTO } from '../../types/generated/WalletDTO';
// Define missing types that are not fully generated
type GetPaymentsOutputDto = { payments: PaymentDTO[] };

View File

@@ -1,7 +1,7 @@
import { ApplyPenaltyCommandDTO } from '../../../types/generated/ApplyPenaltyCommandDTO';
import { RacePenaltiesDTO } from '../../../types/generated/RacePenaltiesDTO';
import type { PenaltyTypesReferenceDTO } from '../../../types/PenaltyTypesReferenceDTO';
import { BaseApiClient } from '../base/BaseApiClient';
import { RacePenaltiesDTO } from '../../types/generated/RacePenaltiesDTO';
import { ApplyPenaltyCommandDTO } from '../../types/generated/ApplyPenaltyCommandDTO';
import type { PenaltyTypesReferenceDTO } from '../../types/PenaltyTypesReferenceDTO';
/**
* Penalties API Client

View File

@@ -1,6 +1,6 @@
import type { ErrorReporter } from '../../../interfaces/ErrorReporter';
import type { Logger } from '../../../interfaces/Logger';
import { BaseApiClient } from '../base/BaseApiClient';
import type { ErrorReporter } from '../../interfaces/ErrorReporter';
import type { Logger } from '../../interfaces/Logger';
export type OperationalMode = 'normal' | 'maintenance' | 'test';
export type FeatureState = 'enabled' | 'disabled' | 'coming_soon' | 'hidden';

View File

@@ -1,9 +1,9 @@
import type { ApplyPenaltyCommandDTO } from '../../../types/generated/ApplyPenaltyCommandDTO';
import type { LeagueAdminProtestsDTO } from '../../../types/generated/LeagueAdminProtestsDTO';
import type { RaceProtestsDTO } from '../../../types/generated/RaceProtestsDTO';
import type { RequestProtestDefenseCommandDTO } from '../../../types/generated/RequestProtestDefenseCommandDTO';
import type { ReviewProtestCommandDTO } from '../../../types/generated/ReviewProtestCommandDTO';
import { BaseApiClient } from '../base/BaseApiClient';
import type { ApplyPenaltyCommandDTO } from '../../types/generated/ApplyPenaltyCommandDTO';
import type { LeagueAdminProtestsDTO } from '../../types/generated/LeagueAdminProtestsDTO';
import type { RaceProtestsDTO } from '../../types/generated/RaceProtestsDTO';
import type { RequestProtestDefenseCommandDTO } from '../../types/generated/RequestProtestDefenseCommandDTO';
import type { ReviewProtestCommandDTO } from '../../types/generated/ReviewProtestCommandDTO';
/**
* Protests API Client

View File

@@ -1,17 +1,17 @@
import type { FileProtestCommandDTO } from '../../../types/generated/FileProtestCommandDTO';
import type { ImportRaceResultsDTO } from '../../../types/generated/ImportRaceResultsDTO';
import type { RaceDetailEntryDTO } from '../../../types/generated/RaceDetailEntryDTO';
import type { RaceDetailLeagueDTO } from '../../../types/generated/RaceDetailLeagueDTO';
import type { RaceDetailRaceDTO } from '../../../types/generated/RaceDetailRaceDTO';
import type { RaceDetailRegistrationDTO } from '../../../types/generated/RaceDetailRegistrationDTO';
import type { RaceDetailUserResultDTO } from '../../../types/generated/RaceDetailUserResultDTO';
import type { RaceResultsDetailDTO } from '../../../types/generated/RaceResultsDetailDTO';
import type { RaceStatsDTO } from '../../../types/generated/RaceStatsDTO';
import type { RaceWithSOFDTO } from '../../../types/generated/RaceWithSOFDTO';
import type { RacesPageDataRaceDTO } from '../../../types/generated/RacesPageDataRaceDTO';
import type { RegisterForRaceParamsDTO } from '../../../types/generated/RegisterForRaceParamsDTO';
import type { WithdrawFromRaceParamsDTO } from '../../../types/generated/WithdrawFromRaceParamsDTO';
import { BaseApiClient } from '../base/BaseApiClient';
import type { RaceStatsDTO } from '../../types/generated/RaceStatsDTO';
import type { RacesPageDataRaceDTO } from '../../types/generated/RacesPageDataRaceDTO';
import type { RaceResultsDetailDTO } from '../../types/generated/RaceResultsDetailDTO';
import type { RaceWithSOFDTO } from '../../types/generated/RaceWithSOFDTO';
import type { RegisterForRaceParamsDTO } from '../../types/generated/RegisterForRaceParamsDTO';
import type { ImportRaceResultsDTO } from '../../types/generated/ImportRaceResultsDTO';
import type { WithdrawFromRaceParamsDTO } from '../../types/generated/WithdrawFromRaceParamsDTO';
import type { RaceDetailRaceDTO } from '../../types/generated/RaceDetailRaceDTO';
import type { RaceDetailLeagueDTO } from '../../types/generated/RaceDetailLeagueDTO';
import type { RaceDetailEntryDTO } from '../../types/generated/RaceDetailEntryDTO';
import type { RaceDetailRegistrationDTO } from '../../types/generated/RaceDetailRegistrationDTO';
import type { RaceDetailUserResultDTO } from '../../types/generated/RaceDetailUserResultDTO';
import type { FileProtestCommandDTO } from '../../types/generated/FileProtestCommandDTO';
// Define missing types
export type RacesPageDataDTO = { races: RacesPageDataRaceDTO[] };

View File

@@ -1,12 +1,12 @@
import type { AcceptSponsorshipRequestInputDTO } from '../../../types/generated/AcceptSponsorshipRequestInputDTO';
import type { CreateSponsorInputDTO } from '../../../types/generated/CreateSponsorInputDTO';
import type { GetPendingSponsorshipRequestsOutputDTO } from '../../../types/generated/GetPendingSponsorshipRequestsOutputDTO';
import type { GetSponsorOutputDTO } from '../../../types/generated/GetSponsorOutputDTO';
import type { RejectSponsorshipRequestInputDTO } from '../../../types/generated/RejectSponsorshipRequestInputDTO';
import type { SponsorDashboardDTO } from '../../../types/generated/SponsorDashboardDTO';
import type { SponsorDTO } from '../../../types/generated/SponsorDTO';
import type { SponsorSponsorshipsDTO } from '../../../types/generated/SponsorSponsorshipsDTO';
import { BaseApiClient } from '../base/BaseApiClient';
import type { CreateSponsorInputDTO } from '../../types/generated/CreateSponsorInputDTO';
import type { SponsorDashboardDTO } from '../../types/generated/SponsorDashboardDTO';
import type { SponsorSponsorshipsDTO } from '../../types/generated/SponsorSponsorshipsDTO';
import type { GetPendingSponsorshipRequestsOutputDTO } from '../../types/generated/GetPendingSponsorshipRequestsOutputDTO';
import type { AcceptSponsorshipRequestInputDTO } from '../../types/generated/AcceptSponsorshipRequestInputDTO';
import type { RejectSponsorshipRequestInputDTO } from '../../types/generated/RejectSponsorshipRequestInputDTO';
import type { GetSponsorOutputDTO } from '../../types/generated/GetSponsorOutputDTO';
import type { SponsorDTO } from '../../types/generated/SponsorDTO';
// Types that are not yet generated
export type CreateSponsorOutputDto = { id: string; name: string };

View File

@@ -1,4 +1,4 @@
import { getWebsiteApiBaseUrl } from '../config/apiBaseUrl';
import { ApiClient } from './api/ApiClient';
import { getWebsiteApiBaseUrl } from './config/apiBaseUrl';
export const apiClient = new ApiClient(getWebsiteApiBaseUrl());

View File

@@ -2,10 +2,10 @@
* Enhanced Error Reporter with user notifications and environment-specific handling
*/
import { connectionMonitor } from '../gateways/api/base/ApiConnectionMonitor';
import { ApiError } from '../gateways/api/base/ApiError';
import { ErrorReporter } from '../interfaces/ErrorReporter';
import { Logger } from '../interfaces/Logger';
import { ApiError } from '../api/base/ApiError';
import { connectionMonitor } from '../api/base/ApiConnectionMonitor';
// Import notification system (will be used if available)
try {

View File

@@ -3,9 +3,9 @@
* Allows developers to replay errors with the exact same context
*/
import { getGlobalErrorHandler } from './GlobalErrorHandler';
import { ApiError } from '../gateways/api/base/ApiError';
import { getGlobalApiLogger } from './ApiRequestLogger';
import { ApiError } from '../api/base/ApiError';
import { getGlobalErrorHandler } from './GlobalErrorHandler';
export interface ReplayContext {
timestamp: string;
@@ -178,7 +178,7 @@ export class ErrorReplaySystem {
const error = replay.error.type === 'ApiError'
? new ApiError(
replay.error.message,
((replay.error.context as { type?: string } | undefined)?.type as import('../api/base/ApiError').ApiErrorType) || 'UNKNOWN_ERROR',
((replay.error.context as { type?: string } | undefined)?.type as import('../gateways/api/base/ApiError').ApiErrorType) || 'UNKNOWN_ERROR',
{
timestamp: replay.timestamp,
...(replay.error.context as Record<string, unknown> | undefined),

View File

@@ -3,10 +3,9 @@
* Captures all uncaught errors, promise rejections, and React errors
*/
import { ApiError } from '../api/base/ApiError';
import { getGlobalErrorReporter } from './EnhancedErrorReporter';
import { ConsoleLogger } from './logging/ConsoleLogger';
import { ApiError } from '../gateways/api/base/ApiError';
import { getGlobalReplaySystem } from './ErrorReplay';
import { ConsoleLogger } from './logging/ConsoleLogger';
export interface GlobalErrorHandlerOptions {
/**
@@ -38,7 +37,6 @@ export interface GlobalErrorHandlerOptions {
export class GlobalErrorHandler {
private options: GlobalErrorHandlerOptions;
private logger: ConsoleLogger;
private errorReporter: ReturnType<typeof getGlobalErrorReporter>;
private errorHistory: Array<{
error: Error | ApiError;
timestamp: string;
@@ -58,7 +56,6 @@ export class GlobalErrorHandler {
};
this.logger = new ConsoleLogger();
this.errorReporter = getGlobalErrorReporter();
}
/**

View File

@@ -0,0 +1,193 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { DeleteUserMutation } from './DeleteUserMutation';
import { AdminService } from '@/lib/services/admin/AdminService';
import { Result } from '@/lib/contracts/Result';
// Mock dependencies
vi.mock('@/lib/services/admin/AdminService', () => {
return {
AdminService: vi.fn(),
};
});
vi.mock('@/lib/config/apiBaseUrl', () => ({
getWebsiteApiBaseUrl: () => 'http://localhost:3000',
}));
vi.mock('@/lib/config/env', () => ({
getWebsiteServerEnv: () => ({ NODE_ENV: 'test' }),
}));
describe('DeleteUserMutation', () => {
let mutation: DeleteUserMutation;
let mockServiceInstance: any;
beforeEach(() => {
vi.clearAllMocks();
mutation = new DeleteUserMutation();
mockServiceInstance = {
deleteUser: vi.fn(),
};
// Use mockImplementation to return the instance
(AdminService as any).mockImplementation(function() {
return mockServiceInstance;
});
});
describe('execute', () => {
describe('happy paths', () => {
it('should successfully delete a user', async () => {
// Arrange
const input = { userId: 'user-123' };
mockServiceInstance.deleteUser.mockResolvedValue(Result.ok(undefined));
// Act
const result = await mutation.execute(input);
// Assert
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
expect(mockServiceInstance.deleteUser).toHaveBeenCalledTimes(1);
});
it('should handle deletion without userId parameter', async () => {
// Arrange
const input = { userId: 'user-456' };
mockServiceInstance.deleteUser.mockResolvedValue(Result.ok(undefined));
// Act
const result = await mutation.execute(input);
// Assert
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
expect(mockServiceInstance.deleteUser).toHaveBeenCalledWith();
});
});
describe('failure modes', () => {
it('should handle service failure during deletion', async () => {
// Arrange
const input = { userId: 'user-123' };
const serviceError = new Error('Service error');
mockServiceInstance.deleteUser.mockRejectedValue(serviceError);
// Act
const result = await mutation.execute(input);
// Assert
expect(result.isErr()).toBe(true);
expect(result.getError()).toBe('deleteFailed');
expect(mockServiceInstance.deleteUser).toHaveBeenCalledTimes(1);
});
it('should handle service returning error result', async () => {
// Arrange
const input = { userId: 'user-123' };
const domainError = { type: 'serverError', message: 'Database connection failed' };
mockServiceInstance.deleteUser.mockResolvedValue(Result.err(domainError));
// Act
const result = await mutation.execute(input);
// Assert
expect(result.isErr()).toBe(true);
expect(result.getError()).toBe('serverError');
expect(mockServiceInstance.deleteUser).toHaveBeenCalledTimes(1);
});
it('should handle service returning userNotFound error', async () => {
// Arrange
const input = { userId: 'user-999' };
const domainError = { type: 'notFound', message: 'User not found' };
mockServiceInstance.deleteUser.mockResolvedValue(Result.err(domainError));
// Act
const result = await mutation.execute(input);
// Assert
expect(result.isErr()).toBe(true);
expect(result.getError()).toBe('userNotFound');
expect(mockServiceInstance.deleteUser).toHaveBeenCalledTimes(1);
});
it('should handle service returning noPermission error', async () => {
// Arrange
const input = { userId: 'user-123' };
const domainError = { type: 'unauthorized', message: 'Insufficient permissions' };
mockServiceInstance.deleteUser.mockResolvedValue(Result.err(domainError));
// Act
const result = await mutation.execute(input);
// Assert
expect(result.isErr()).toBe(true);
expect(result.getError()).toBe('noPermission');
expect(mockServiceInstance.deleteUser).toHaveBeenCalledTimes(1);
});
});
describe('error mapping', () => {
it('should map various domain errors to mutation errors', async () => {
// Arrange
const input = { userId: 'user-123' };
const testCases = [
{ domainError: { type: 'notFound' }, expectedError: 'userNotFound' },
{ domainError: { type: 'unauthorized' }, expectedError: 'noPermission' },
{ domainError: { type: 'validationError' }, expectedError: 'invalidData' },
{ domainError: { type: 'serverError' }, expectedError: 'serverError' },
{ domainError: { type: 'networkError' }, expectedError: 'networkError' },
{ domainError: { type: 'notImplemented' }, expectedError: 'notImplemented' },
{ domainError: { type: 'unknown' }, expectedError: 'unknown' },
];
for (const testCase of testCases) {
mockServiceInstance.deleteUser.mockResolvedValue(Result.err(testCase.domainError));
const result = await mutation.execute(input);
expect(result.isErr()).toBe(true);
expect(result.getError()).toBe(testCase.expectedError);
}
});
});
describe('input validation', () => {
it('should accept valid userId input', async () => {
// Arrange
const input = { userId: 'user-123' };
mockServiceInstance.deleteUser.mockResolvedValue(Result.ok(undefined));
// Act
const result = await mutation.execute(input);
// Assert
expect(result.isOk()).toBe(true);
expect(mockServiceInstance.deleteUser).toHaveBeenCalledTimes(1);
});
it('should handle empty userId gracefully', async () => {
// Arrange
const input = { userId: '' };
mockServiceInstance.deleteUser.mockResolvedValue(Result.ok(undefined));
// Act
const result = await mutation.execute(input);
// Assert
expect(result.isOk()).toBe(true);
expect(mockServiceInstance.deleteUser).toHaveBeenCalledTimes(1);
});
});
describe('service instantiation', () => {
it('should create AdminService instance', () => {
// Arrange & Act
const mutation = new DeleteUserMutation();
// Assert
expect(mutation).toBeInstanceOf(DeleteUserMutation);
});
});
});
});

View File

@@ -0,0 +1,331 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { UpdateUserStatusMutation } from './UpdateUserStatusMutation';
import { AdminService } from '@/lib/services/admin/AdminService';
import { Result } from '@/lib/contracts/Result';
// Mock dependencies
vi.mock('@/lib/services/admin/AdminService', () => {
return {
AdminService: vi.fn(),
};
});
vi.mock('@/lib/config/apiBaseUrl', () => ({
getWebsiteApiBaseUrl: () => 'http://localhost:3000',
}));
vi.mock('@/lib/config/env', () => ({
getWebsiteServerEnv: () => ({ NODE_ENV: 'test' }),
}));
describe('UpdateUserStatusMutation', () => {
let mutation: UpdateUserStatusMutation;
let mockServiceInstance: any;
beforeEach(() => {
vi.clearAllMocks();
mutation = new UpdateUserStatusMutation();
mockServiceInstance = {
updateUserStatus: vi.fn(),
};
// Use mockImplementation to return the instance
(AdminService as any).mockImplementation(function() {
return mockServiceInstance;
});
});
describe('execute', () => {
describe('happy paths', () => {
it('should successfully update user status to active', async () => {
// Arrange
const input = { userId: 'user-123', status: 'active' };
mockServiceInstance.updateUserStatus.mockResolvedValue(
Result.ok({
id: 'user-123',
email: 'mock@example.com',
displayName: 'Mock User',
roles: ['user'],
status: 'active',
isSystemAdmin: false,
createdAt: '2024-01-01T00:00:00.000Z',
updatedAt: '2024-01-01T00:00:00.000Z',
})
);
// Act
const result = await mutation.execute(input);
// Assert
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
expect(mockServiceInstance.updateUserStatus).toHaveBeenCalledWith('user-123', 'active');
expect(mockServiceInstance.updateUserStatus).toHaveBeenCalledTimes(1);
});
it('should successfully update user status to suspended', async () => {
// Arrange
const input = { userId: 'user-456', status: 'suspended' };
mockServiceInstance.updateUserStatus.mockResolvedValue(
Result.ok({
id: 'user-456',
email: 'mock@example.com',
displayName: 'Mock User',
roles: ['user'],
status: 'suspended',
isSystemAdmin: false,
createdAt: '2024-01-01T00:00:00.000Z',
updatedAt: '2024-01-01T00:00:00.000Z',
})
);
// Act
const result = await mutation.execute(input);
// Assert
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
expect(mockServiceInstance.updateUserStatus).toHaveBeenCalledWith('user-456', 'suspended');
expect(mockServiceInstance.updateUserStatus).toHaveBeenCalledTimes(1);
});
it('should successfully update user status to deleted', async () => {
// Arrange
const input = { userId: 'user-789', status: 'deleted' };
mockServiceInstance.updateUserStatus.mockResolvedValue(
Result.ok({
id: 'user-789',
email: 'mock@example.com',
displayName: 'Mock User',
roles: ['user'],
status: 'deleted',
isSystemAdmin: false,
createdAt: '2024-01-01T00:00:00.000Z',
updatedAt: '2024-01-01T00:00:00.000Z',
})
);
// Act
const result = await mutation.execute(input);
// Assert
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
expect(mockServiceInstance.updateUserStatus).toHaveBeenCalledWith('user-789', 'deleted');
expect(mockServiceInstance.updateUserStatus).toHaveBeenCalledTimes(1);
});
it('should handle different status values', async () => {
// Arrange
const statuses = ['active', 'suspended', 'deleted', 'pending'];
const userId = 'user-123';
for (const status of statuses) {
mockServiceInstance.updateUserStatus.mockResolvedValue(
Result.ok({
id: userId,
email: 'mock@example.com',
displayName: 'Mock User',
roles: ['user'],
status,
isSystemAdmin: false,
createdAt: '2024-01-01T00:00:00.000Z',
updatedAt: '2024-01-01T00:00:00.000Z',
})
);
const result = await mutation.execute({ userId, status });
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
expect(mockServiceInstance.updateUserStatus).toHaveBeenCalledWith(userId, status);
}
});
});
describe('failure modes', () => {
it('should handle service failure during status update', async () => {
// Arrange
const input = { userId: 'user-123', status: 'suspended' };
const serviceError = new Error('Service error');
mockServiceInstance.updateUserStatus.mockRejectedValue(serviceError);
// Act
const result = await mutation.execute(input);
// Assert
expect(result.isErr()).toBe(true);
expect(result.getError()).toBe('updateFailed');
expect(mockServiceInstance.updateUserStatus).toHaveBeenCalledTimes(1);
});
it('should handle service returning error result', async () => {
// Arrange
const input = { userId: 'user-123', status: 'suspended' };
const domainError = { type: 'serverError', message: 'Database connection failed' };
mockServiceInstance.updateUserStatus.mockResolvedValue(Result.err(domainError));
// Act
const result = await mutation.execute(input);
// Assert
expect(result.isErr()).toBe(true);
expect(result.getError()).toBe('serverError');
expect(mockServiceInstance.updateUserStatus).toHaveBeenCalledTimes(1);
});
it('should handle service returning userNotFound error', async () => {
// Arrange
const input = { userId: 'user-999', status: 'suspended' };
const domainError = { type: 'notFound', message: 'User not found' };
mockServiceInstance.updateUserStatus.mockResolvedValue(Result.err(domainError));
// Act
const result = await mutation.execute(input);
// Assert
expect(result.isErr()).toBe(true);
expect(result.getError()).toBe('userNotFound');
expect(mockServiceInstance.updateUserStatus).toHaveBeenCalledTimes(1);
});
it('should handle service returning noPermission error', async () => {
// Arrange
const input = { userId: 'user-123', status: 'suspended' };
const domainError = { type: 'unauthorized', message: 'Insufficient permissions' };
mockServiceInstance.updateUserStatus.mockResolvedValue(Result.err(domainError));
// Act
const result = await mutation.execute(input);
// Assert
expect(result.isErr()).toBe(true);
expect(result.getError()).toBe('noPermission');
expect(mockServiceInstance.updateUserStatus).toHaveBeenCalledTimes(1);
});
it('should handle service returning invalidData error', async () => {
// Arrange
const input = { userId: 'user-123', status: 'invalid-status' };
const domainError = { type: 'validationError', message: 'Invalid status value' };
mockServiceInstance.updateUserStatus.mockResolvedValue(Result.err(domainError));
// Act
const result = await mutation.execute(input);
// Assert
expect(result.isErr()).toBe(true);
expect(result.getError()).toBe('invalidData');
expect(mockServiceInstance.updateUserStatus).toHaveBeenCalledTimes(1);
});
});
describe('error mapping', () => {
it('should map various domain errors to mutation errors', async () => {
// Arrange
const input = { userId: 'user-123', status: 'suspended' };
const testCases = [
{ domainError: { type: 'notFound' }, expectedError: 'userNotFound' },
{ domainError: { type: 'unauthorized' }, expectedError: 'noPermission' },
{ domainError: { type: 'validationError' }, expectedError: 'invalidData' },
{ domainError: { type: 'serverError' }, expectedError: 'serverError' },
{ domainError: { type: 'networkError' }, expectedError: 'networkError' },
{ domainError: { type: 'notImplemented' }, expectedError: 'notImplemented' },
{ domainError: { type: 'unknown' }, expectedError: 'unknown' },
];
for (const testCase of testCases) {
mockServiceInstance.updateUserStatus.mockResolvedValue(Result.err(testCase.domainError));
const result = await mutation.execute(input);
expect(result.isErr()).toBe(true);
expect(result.getError()).toBe(testCase.expectedError);
}
});
});
describe('input validation', () => {
it('should accept valid userId and status input', async () => {
// Arrange
const input = { userId: 'user-123', status: 'active' };
mockServiceInstance.updateUserStatus.mockResolvedValue(
Result.ok({
id: 'user-123',
email: 'mock@example.com',
displayName: 'Mock User',
roles: ['user'],
status: 'active',
isSystemAdmin: false,
createdAt: '2024-01-01T00:00:00.000Z',
updatedAt: '2024-01-01T00:00:00.000Z',
})
);
// Act
const result = await mutation.execute(input);
// Assert
expect(result.isOk()).toBe(true);
expect(mockServiceInstance.updateUserStatus).toHaveBeenCalledTimes(1);
});
it('should handle empty userId gracefully', async () => {
// Arrange
const input = { userId: '', status: 'active' };
mockServiceInstance.updateUserStatus.mockResolvedValue(
Result.ok({
id: '',
email: 'mock@example.com',
displayName: 'Mock User',
roles: ['user'],
status: 'active',
isSystemAdmin: false,
createdAt: '2024-01-01T00:00:00.000Z',
updatedAt: '2024-01-01T00:00:00.000Z',
})
);
// Act
const result = await mutation.execute(input);
// Assert
expect(result.isOk()).toBe(true);
expect(mockServiceInstance.updateUserStatus).toHaveBeenCalledWith('', 'active');
});
it('should handle empty status gracefully', async () => {
// Arrange
const input = { userId: 'user-123', status: '' };
mockServiceInstance.updateUserStatus.mockResolvedValue(
Result.ok({
id: 'user-123',
email: 'mock@example.com',
displayName: 'Mock User',
roles: ['user'],
status: '',
isSystemAdmin: false,
createdAt: '2024-01-01T00:00:00.000Z',
updatedAt: '2024-01-01T00:00:00.000Z',
})
);
// Act
const result = await mutation.execute(input);
// Assert
expect(result.isOk()).toBe(true);
expect(mockServiceInstance.updateUserStatus).toHaveBeenCalledWith('user-123', '');
});
});
describe('service instantiation', () => {
it('should create AdminService instance', () => {
// Arrange & Act
const mutation = new UpdateUserStatusMutation();
// Assert
expect(mutation).toBeInstanceOf(UpdateUserStatusMutation);
});
});
});
});

View File

@@ -0,0 +1,189 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { ForgotPasswordMutation } from './ForgotPasswordMutation';
import { AuthService } from '@/lib/services/auth/AuthService';
import { Result } from '@/lib/contracts/Result';
// Mock dependencies
vi.mock('@/lib/services/auth/AuthService', () => {
return {
AuthService: vi.fn(),
};
});
describe('ForgotPasswordMutation', () => {
let mutation: ForgotPasswordMutation;
let mockServiceInstance: { forgotPassword: ReturnType<typeof vi.fn> };
beforeEach(() => {
vi.clearAllMocks();
mutation = new ForgotPasswordMutation();
mockServiceInstance = {
forgotPassword: vi.fn(),
};
// Use mockImplementation to return the instance
(AuthService as any).mockImplementation(function() {
return mockServiceInstance;
});
});
describe('execute', () => {
describe('happy paths', () => {
it('should successfully send forgot password request', async () => {
// Arrange
const input = { email: 'test@example.com' };
const serviceOutput = { message: 'Reset link sent', magicLink: 'https://example.com/reset' };
mockServiceInstance.forgotPassword.mockResolvedValue(Result.ok(serviceOutput));
// Act
const result = await mutation.execute(input);
// Assert
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toEqual(serviceOutput);
expect(mockServiceInstance.forgotPassword).toHaveBeenCalledWith(input);
expect(mockServiceInstance.forgotPassword).toHaveBeenCalledTimes(1);
});
it('should handle forgot password request without magicLink', async () => {
// Arrange
const input = { email: 'test@example.com' };
const serviceOutput = { message: 'Reset link sent' };
mockServiceInstance.forgotPassword.mockResolvedValue(Result.ok(serviceOutput));
// Act
const result = await mutation.execute(input);
// Assert
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toEqual(serviceOutput);
expect(mockServiceInstance.forgotPassword).toHaveBeenCalledWith(input);
});
});
describe('failure modes', () => {
it('should handle service failure during forgot password request', async () => {
// Arrange
const input = { email: 'test@example.com' };
const serviceError = new Error('Service error');
mockServiceInstance.forgotPassword.mockRejectedValue(serviceError);
// Act
const result = await mutation.execute(input);
// Assert
expect(result.isErr()).toBe(true);
expect(result.getError()).toBe('Service error');
expect(mockServiceInstance.forgotPassword).toHaveBeenCalledTimes(1);
});
it('should handle service returning error result', async () => {
// Arrange
const input = { email: 'test@example.com' };
const domainError = { type: 'serverError', message: 'Email not found' };
mockServiceInstance.forgotPassword.mockResolvedValue(Result.err(domainError));
// Act
const result = await mutation.execute(input);
// Assert
expect(result.isErr()).toBe(true);
expect(result.getError()).toBe('Email not found');
expect(mockServiceInstance.forgotPassword).toHaveBeenCalledTimes(1);
});
it('should handle service returning validation error', async () => {
// Arrange
const input = { email: 'invalid-email' };
const domainError = { type: 'validationError', message: 'Invalid email format' };
mockServiceInstance.forgotPassword.mockResolvedValue(Result.err(domainError));
// Act
const result = await mutation.execute(input);
// Assert
expect(result.isErr()).toBe(true);
expect(result.getError()).toBe('Invalid email format');
expect(mockServiceInstance.forgotPassword).toHaveBeenCalledTimes(1);
});
it('should handle service returning rate limit error', async () => {
// Arrange
const input = { email: 'test@example.com' };
const domainError = { type: 'rateLimit', message: 'Too many requests' };
mockServiceInstance.forgotPassword.mockResolvedValue(Result.err(domainError));
// Act
const result = await mutation.execute(input);
// Assert
expect(result.isErr()).toBe(true);
expect(result.getError()).toBe('Too many requests');
expect(mockServiceInstance.forgotPassword).toHaveBeenCalledTimes(1);
});
});
describe('error mapping', () => {
it('should map various domain errors to mutation errors', async () => {
// Arrange
const input = { email: 'test@example.com' };
const testCases = [
{ domainError: { type: 'serverError', message: 'Server error' }, expectedError: 'Server error' },
{ domainError: { type: 'validationError', message: 'Validation error' }, expectedError: 'Validation error' },
{ domainError: { type: 'notFound', message: 'Not found' }, expectedError: 'Not found' },
{ domainError: { type: 'rateLimit', message: 'Rate limit exceeded' }, expectedError: 'Rate limit exceeded' },
];
for (const testCase of testCases) {
mockServiceInstance.forgotPassword.mockResolvedValue(Result.err(testCase.domainError));
const result = await mutation.execute(input);
expect(result.isErr()).toBe(true);
expect(result.getError()).toBe(testCase.expectedError);
}
});
});
describe('input validation', () => {
it('should accept valid email input', async () => {
// Arrange
const input = { email: 'test@example.com' };
mockServiceInstance.forgotPassword.mockResolvedValue(
Result.ok({ message: 'Reset link sent' })
);
// Act
const result = await mutation.execute(input);
// Assert
expect(result.isOk()).toBe(true);
expect(mockServiceInstance.forgotPassword).toHaveBeenCalledTimes(1);
});
it('should handle empty email gracefully', async () => {
// Arrange
const input = { email: '' };
mockServiceInstance.forgotPassword.mockResolvedValue(
Result.ok({ message: 'Reset link sent' })
);
// Act
const result = await mutation.execute(input);
// Assert
expect(result.isOk()).toBe(true);
expect(mockServiceInstance.forgotPassword).toHaveBeenCalledWith(input);
});
});
describe('service instantiation', () => {
it('should create AuthService instance', () => {
// Arrange & Act
const mutation = new ForgotPasswordMutation();
// Assert
expect(mutation).toBeInstanceOf(ForgotPasswordMutation);
});
});
});
});

Some files were not shown because too many files have changed in this diff Show More