website refactor
This commit is contained in:
@@ -27,9 +27,9 @@ export class AdminDashboardPageQuery implements PageQuery<AdminDashboardViewData
|
||||
}
|
||||
|
||||
// Transform to ViewData using builder
|
||||
const viewData = AdminDashboardViewDataBuilder.build(apiDtoResult.unwrap());
|
||||
const output = AdminDashboardViewDataBuilder.build(apiDtoResult.unwrap());
|
||||
|
||||
return Result.ok(viewData);
|
||||
return Result.ok(output);
|
||||
} catch (err) {
|
||||
console.error('AdminDashboardPageQuery failed:', err);
|
||||
|
||||
|
||||
@@ -31,9 +31,9 @@ export class AdminUsersPageQuery implements PageQuery<AdminUsersViewData, { sear
|
||||
}
|
||||
|
||||
// Transform to ViewData using builder
|
||||
const viewData = AdminUsersViewDataBuilder.build(apiDtoResult.unwrap());
|
||||
const output = AdminUsersViewDataBuilder.build(apiDtoResult.unwrap());
|
||||
|
||||
return Result.ok(viewData);
|
||||
return Result.ok(output);
|
||||
} catch (error) {
|
||||
console.error('AdminUsersPageQuery failed:', error);
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { ForgotPasswordViewDataBuilder, ForgotPasswordViewData } from '@/lib/builders/view-data/ForgotPasswordViewDataBuilder';
|
||||
import { ForgotPasswordViewDataBuilder } from '@/lib/builders/view-data/ForgotPasswordViewDataBuilder';
|
||||
import { ForgotPasswordViewData } from '@/lib/builders/view-data/types/ForgotPasswordViewData';
|
||||
import { AuthPageService } from '@/lib/services/auth/AuthPageService';
|
||||
import { SearchParamParser } from '@/lib/routing/search-params/SearchParamParser';
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { LoginViewDataBuilder, LoginViewData } from '@/lib/builders/view-data/LoginViewDataBuilder';
|
||||
import { LoginViewDataBuilder } from '@/lib/builders/view-data/LoginViewDataBuilder';
|
||||
import { LoginViewData } from '@/lib/builders/view-data/types/LoginViewData';
|
||||
import { AuthPageService } from '@/lib/services/auth/AuthPageService';
|
||||
import { SearchParamParser } from '@/lib/routing/search-params/SearchParamParser';
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { ResetPasswordViewDataBuilder, ResetPasswordViewData } from '@/lib/builders/view-data/ResetPasswordViewDataBuilder';
|
||||
import { ResetPasswordViewDataBuilder } from '@/lib/builders/view-data/ResetPasswordViewDataBuilder';
|
||||
import { ResetPasswordViewData } from '@/lib/builders/view-data/types/ResetPasswordViewData';
|
||||
import { AuthPageService } from '@/lib/services/auth/AuthPageService';
|
||||
import { SearchParamParser } from '@/lib/routing/search-params/SearchParamParser';
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
* No business logic, only data composition.
|
||||
*/
|
||||
|
||||
import { SignupViewData, SignupViewDataBuilder } from '@/lib/builders/view-data/SignupViewDataBuilder';
|
||||
import { SignupViewDataBuilder } from '@/lib/builders/view-data/SignupViewDataBuilder';
|
||||
import { SignupViewData } from '@/lib/builders/view-data/types/SignupViewData';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { AuthPageService } from '@/lib/services/auth/AuthPageService';
|
||||
|
||||
43
apps/website/lib/page-queries/media/GetAvatarPageQuery.ts
Normal file
43
apps/website/lib/page-queries/media/GetAvatarPageQuery.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* GetAvatarPageQuery
|
||||
*
|
||||
* Server-side composition for avatar media route.
|
||||
* Fetches avatar binary data and transforms to ViewData.
|
||||
*/
|
||||
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { MediaService } from '@/lib/services/media/MediaService';
|
||||
import { AvatarViewDataBuilder } from '@/lib/builders/view-data/AvatarViewDataBuilder';
|
||||
import { AvatarViewData } from '@/lib/view-data/AvatarViewData';
|
||||
import { PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
|
||||
export class GetAvatarPageQuery implements PageQuery<AvatarViewData, { driverId: string }> {
|
||||
async execute(params: { driverId: string }): Promise<Result<AvatarViewData, PresentationError>> {
|
||||
try {
|
||||
// Manual construction: Service creates its own dependencies
|
||||
const service = new MediaService();
|
||||
|
||||
// Fetch avatar data
|
||||
const apiDtoResult = await service.getAvatar(params.driverId);
|
||||
|
||||
if (apiDtoResult.isErr()) {
|
||||
return Result.err(mapToPresentationError(apiDtoResult.getError()));
|
||||
}
|
||||
|
||||
// Transform to ViewData using builder
|
||||
const output = AvatarViewDataBuilder.build(apiDtoResult.unwrap());
|
||||
|
||||
return Result.ok(output);
|
||||
} catch (err) {
|
||||
console.error('GetAvatarPageQuery failed:', err);
|
||||
return Result.err('serverError');
|
||||
}
|
||||
}
|
||||
|
||||
// Static method to avoid object construction in server code
|
||||
static async execute(params: { driverId: string }): Promise<Result<AvatarViewData, PresentationError>> {
|
||||
const query = new GetAvatarPageQuery();
|
||||
return query.execute(params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* GetCategoryIconPageQuery
|
||||
*
|
||||
* Server-side composition for category icon media route.
|
||||
*/
|
||||
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { MediaService } from '@/lib/services/media/MediaService';
|
||||
import { CategoryIconViewDataBuilder } from '@/lib/builders/view-data/CategoryIconViewDataBuilder';
|
||||
import { CategoryIconViewData } from '@/lib/view-data/CategoryIconViewData';
|
||||
import { PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
|
||||
export class GetCategoryIconPageQuery implements PageQuery<CategoryIconViewData, { categoryId: string }> {
|
||||
async execute(params: { categoryId: string }): Promise<Result<CategoryIconViewData, PresentationError>> {
|
||||
try {
|
||||
const service = new MediaService();
|
||||
const apiDtoResult = await service.getCategoryIcon(params.categoryId);
|
||||
|
||||
if (apiDtoResult.isErr()) {
|
||||
return Result.err(mapToPresentationError(apiDtoResult.getError()));
|
||||
}
|
||||
|
||||
const output = CategoryIconViewDataBuilder.build(apiDtoResult.unwrap());
|
||||
return Result.ok(output);
|
||||
} catch (err) {
|
||||
console.error('GetCategoryIconPageQuery failed:', err);
|
||||
return Result.err('serverError');
|
||||
}
|
||||
}
|
||||
|
||||
static async execute(params: { categoryId: string }): Promise<Result<CategoryIconViewData, PresentationError>> {
|
||||
const query = new GetCategoryIconPageQuery();
|
||||
return query.execute(params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* GetLeagueCoverPageQuery
|
||||
*
|
||||
* Server-side composition for league cover media route.
|
||||
*/
|
||||
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { MediaService } from '@/lib/services/media/MediaService';
|
||||
import { LeagueCoverViewDataBuilder } from '@/lib/builders/view-data/LeagueCoverViewDataBuilder';
|
||||
import { LeagueCoverViewData } from '@/lib/view-data/LeagueCoverViewData';
|
||||
import { PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
|
||||
export class GetLeagueCoverPageQuery implements PageQuery<LeagueCoverViewData, { leagueId: string }> {
|
||||
async execute(params: { leagueId: string }): Promise<Result<LeagueCoverViewData, PresentationError>> {
|
||||
try {
|
||||
const service = new MediaService();
|
||||
const apiDtoResult = await service.getLeagueCover(params.leagueId);
|
||||
|
||||
if (apiDtoResult.isErr()) {
|
||||
return Result.err(mapToPresentationError(apiDtoResult.getError()));
|
||||
}
|
||||
|
||||
const output = LeagueCoverViewDataBuilder.build(apiDtoResult.unwrap());
|
||||
return Result.ok(output);
|
||||
} catch (err) {
|
||||
console.error('GetLeagueCoverPageQuery failed:', err);
|
||||
return Result.err('serverError');
|
||||
}
|
||||
}
|
||||
|
||||
static async execute(params: { leagueId: string }): Promise<Result<LeagueCoverViewData, PresentationError>> {
|
||||
const query = new GetLeagueCoverPageQuery();
|
||||
return query.execute(params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* GetLeagueLogoPageQuery
|
||||
*
|
||||
* Server-side composition for league logo media route.
|
||||
*/
|
||||
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { MediaService } from '@/lib/services/media/MediaService';
|
||||
import { LeagueLogoViewDataBuilder } from '@/lib/builders/view-data/LeagueLogoViewDataBuilder';
|
||||
import { LeagueLogoViewData } from '@/lib/view-data/LeagueLogoViewData';
|
||||
import { PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
|
||||
export class GetLeagueLogoPageQuery implements PageQuery<LeagueLogoViewData, { leagueId: string }> {
|
||||
async execute(params: { leagueId: string }): Promise<Result<LeagueLogoViewData, PresentationError>> {
|
||||
try {
|
||||
const service = new MediaService();
|
||||
const apiDtoResult = await service.getLeagueLogo(params.leagueId);
|
||||
|
||||
if (apiDtoResult.isErr()) {
|
||||
return Result.err(mapToPresentationError(apiDtoResult.getError()));
|
||||
}
|
||||
|
||||
const output = LeagueLogoViewDataBuilder.build(apiDtoResult.unwrap());
|
||||
return Result.ok(output);
|
||||
} catch (err) {
|
||||
console.error('GetLeagueLogoPageQuery failed:', err);
|
||||
return Result.err('serverError');
|
||||
}
|
||||
}
|
||||
|
||||
static async execute(params: { leagueId: string }): Promise<Result<LeagueLogoViewData, PresentationError>> {
|
||||
const query = new GetLeagueLogoPageQuery();
|
||||
return query.execute(params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* GetSponsorLogoPageQuery
|
||||
*
|
||||
* Server-side composition for sponsor logo media route.
|
||||
*/
|
||||
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { MediaService } from '@/lib/services/media/MediaService';
|
||||
import { SponsorLogoViewDataBuilder } from '@/lib/builders/view-data/SponsorLogoViewDataBuilder';
|
||||
import { SponsorLogoViewData } from '@/lib/view-data/SponsorLogoViewData';
|
||||
import { PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
|
||||
export class GetSponsorLogoPageQuery implements PageQuery<SponsorLogoViewData, { sponsorId: string }> {
|
||||
async execute(params: { sponsorId: string }): Promise<Result<SponsorLogoViewData, PresentationError>> {
|
||||
try {
|
||||
const service = new MediaService();
|
||||
const apiDtoResult = await service.getSponsorLogo(params.sponsorId);
|
||||
|
||||
if (apiDtoResult.isErr()) {
|
||||
return Result.err(mapToPresentationError(apiDtoResult.getError()));
|
||||
}
|
||||
|
||||
const output = SponsorLogoViewDataBuilder.build(apiDtoResult.unwrap());
|
||||
return Result.ok(output);
|
||||
} catch (err) {
|
||||
console.error('GetSponsorLogoPageQuery failed:', err);
|
||||
return Result.err('serverError');
|
||||
}
|
||||
}
|
||||
|
||||
static async execute(params: { sponsorId: string }): Promise<Result<SponsorLogoViewData, PresentationError>> {
|
||||
const query = new GetSponsorLogoPageQuery();
|
||||
return query.execute(params);
|
||||
}
|
||||
}
|
||||
36
apps/website/lib/page-queries/media/GetTeamLogoPageQuery.ts
Normal file
36
apps/website/lib/page-queries/media/GetTeamLogoPageQuery.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* GetTeamLogoPageQuery
|
||||
*
|
||||
* Server-side composition for team logo media route.
|
||||
*/
|
||||
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { MediaService } from '@/lib/services/media/MediaService';
|
||||
import { TeamLogoViewDataBuilder } from '@/lib/builders/view-data/TeamLogoViewDataBuilder';
|
||||
import { TeamLogoViewData } from '@/lib/view-data/TeamLogoViewData';
|
||||
import { PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
|
||||
export class GetTeamLogoPageQuery implements PageQuery<TeamLogoViewData, { teamId: string }> {
|
||||
async execute(params: { teamId: string }): Promise<Result<TeamLogoViewData, PresentationError>> {
|
||||
try {
|
||||
const service = new MediaService();
|
||||
const apiDtoResult = await service.getTeamLogo(params.teamId);
|
||||
|
||||
if (apiDtoResult.isErr()) {
|
||||
return Result.err(mapToPresentationError(apiDtoResult.getError()));
|
||||
}
|
||||
|
||||
const output = TeamLogoViewDataBuilder.build(apiDtoResult.unwrap());
|
||||
return Result.ok(output);
|
||||
} catch (err) {
|
||||
console.error('GetTeamLogoPageQuery failed:', err);
|
||||
return Result.err('serverError');
|
||||
}
|
||||
}
|
||||
|
||||
static async execute(params: { teamId: string }): Promise<Result<TeamLogoViewData, PresentationError>> {
|
||||
const query = new GetTeamLogoPageQuery();
|
||||
return query.execute(params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* GetTrackImagePageQuery
|
||||
*
|
||||
* Server-side composition for track image media route.
|
||||
*/
|
||||
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { MediaService } from '@/lib/services/media/MediaService';
|
||||
import { TrackImageViewDataBuilder } from '@/lib/builders/view-data/TrackImageViewDataBuilder';
|
||||
import { TrackImageViewData } from '@/lib/view-data/TrackImageViewData';
|
||||
import { PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
|
||||
export class GetTrackImagePageQuery implements PageQuery<TrackImageViewData, { trackId: string }> {
|
||||
async execute(params: { trackId: string }): Promise<Result<TrackImageViewData, PresentationError>> {
|
||||
try {
|
||||
const service = new MediaService();
|
||||
const apiDtoResult = await service.getTrackImage(params.trackId);
|
||||
|
||||
if (apiDtoResult.isErr()) {
|
||||
return Result.err(mapToPresentationError(apiDtoResult.getError()));
|
||||
}
|
||||
|
||||
const output = TrackImageViewDataBuilder.build(apiDtoResult.unwrap());
|
||||
return Result.ok(output);
|
||||
} catch (err) {
|
||||
console.error('GetTrackImagePageQuery failed:', err);
|
||||
return Result.err('serverError');
|
||||
}
|
||||
}
|
||||
|
||||
static async execute(params: { trackId: string }): Promise<Result<TrackImageViewData, PresentationError>> {
|
||||
const query = new GetTrackImagePageQuery();
|
||||
return query.execute(params);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,10 @@
|
||||
import type { DriverLeaderboardItemDTO } from '@/lib/types/generated/DriverLeaderboardItemDTO';
|
||||
|
||||
/**
|
||||
* DriverRankingsPageDto - Raw data structure for Driver Rankings page
|
||||
* Plain data, no methods, no business logic
|
||||
*/
|
||||
|
||||
export interface DriverRankingsPageDto {
|
||||
drivers: {
|
||||
id: string;
|
||||
name: string;
|
||||
rating: number;
|
||||
skillLevel: string;
|
||||
nationality: string;
|
||||
racesCompleted: number;
|
||||
wins: number;
|
||||
podiums: number;
|
||||
isActive: boolean;
|
||||
rank: number;
|
||||
avatarUrl?: string;
|
||||
}[];
|
||||
drivers: DriverLeaderboardItemDTO[];
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient';
|
||||
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
|
||||
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
|
||||
/**
|
||||
* CreateLeaguePageQuery
|
||||
*
|
||||
* Fetches data needed for the create league page.
|
||||
*/
|
||||
export class CreateLeaguePageQuery implements PageQuery<any, void> {
|
||||
async execute(): Promise<Result<any, 'notFound' | 'redirect' | 'CREATE_LEAGUE_FETCH_FAILED' | 'UNKNOWN_ERROR'>> {
|
||||
// Manual wiring: create API client
|
||||
const baseUrl = process.env.NEXT_PUBLIC_API_URL || '';
|
||||
const errorReporter = new ConsoleErrorReporter();
|
||||
const logger = new ConsoleLogger();
|
||||
const apiClient = new LeaguesApiClient(baseUrl, errorReporter, logger);
|
||||
|
||||
try {
|
||||
// Get scoring presets for the form
|
||||
const presetsData = await apiClient.getScoringPresets();
|
||||
|
||||
return Result.ok({
|
||||
scoringPresets: presetsData.presets || [],
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('CreateLeaguePageQuery failed:', error);
|
||||
|
||||
if (error instanceof Error) {
|
||||
if (error.message.includes('403') || error.message.includes('401')) {
|
||||
return Result.err('redirect');
|
||||
}
|
||||
if (error.message.includes('404')) {
|
||||
return Result.err('notFound');
|
||||
}
|
||||
if (error.message.includes('5') || error.message.includes('server')) {
|
||||
return Result.err('CREATE_LEAGUE_FETCH_FAILED');
|
||||
}
|
||||
}
|
||||
|
||||
return Result.err('UNKNOWN_ERROR');
|
||||
}
|
||||
}
|
||||
|
||||
static async execute(): Promise<Result<any, 'notFound' | 'redirect' | 'CREATE_LEAGUE_FETCH_FAILED' | 'UNKNOWN_ERROR'>> {
|
||||
const query = new CreateLeaguePageQuery();
|
||||
return query.execute();
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,15 @@ import { Result } from '@/lib/contracts/Result';
|
||||
import { DashboardViewDataBuilder } from '@/lib/builders/view-data/DashboardViewDataBuilder';
|
||||
import type { DashboardViewData } from '@/lib/view-data/DashboardViewData';
|
||||
import { DashboardService } from '@/lib/services/analytics/DashboardService';
|
||||
import { mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
import { mapToPresentationError, type PresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
|
||||
/**
|
||||
* Dashboard page query
|
||||
* Returns Result<DashboardViewData, PresentationError>
|
||||
* No DI container usage - constructs dependencies explicitly
|
||||
*/
|
||||
export class DashboardPageQuery implements PageQuery<DashboardViewData, void> {
|
||||
async execute(): Promise<Result<DashboardViewData, 'notFound' | 'redirect' | 'DASHBOARD_FETCH_FAILED' | 'UNKNOWN_ERROR'>> {
|
||||
export class DashboardPageQuery implements PageQuery<DashboardViewData, void, PresentationError> {
|
||||
async execute(): Promise<Result<DashboardViewData, PresentationError>> {
|
||||
// Manual wiring: Service creates its own dependencies
|
||||
const dashboardService = new DashboardService();
|
||||
|
||||
@@ -19,31 +19,18 @@ export class DashboardPageQuery implements PageQuery<DashboardViewData, void> {
|
||||
const serviceResult = await dashboardService.getDashboardOverview();
|
||||
|
||||
if (serviceResult.isErr()) {
|
||||
const serviceError = serviceResult.getError();
|
||||
|
||||
// Map domain errors to presentation errors
|
||||
switch (serviceError.type) {
|
||||
case 'notFound':
|
||||
return Result.err('notFound');
|
||||
case 'unauthorized':
|
||||
return Result.err('redirect');
|
||||
case 'serverError':
|
||||
case 'networkError':
|
||||
case 'unknown':
|
||||
return Result.err('DASHBOARD_FETCH_FAILED');
|
||||
default:
|
||||
return Result.err('UNKNOWN_ERROR');
|
||||
}
|
||||
// Map domain errors to presentation errors using helper
|
||||
return Result.err(mapToPresentationError(serviceResult.getError()));
|
||||
}
|
||||
|
||||
// Transform to ViewData using builder
|
||||
const apiDto = serviceResult.unwrap();
|
||||
const viewData = DashboardViewDataBuilder.build(apiDto);
|
||||
return Result.ok(viewData);
|
||||
const dashboardView = DashboardViewDataBuilder.build(apiDto);
|
||||
return Result.ok(dashboardView);
|
||||
}
|
||||
|
||||
// Static method to avoid object construction in server code
|
||||
static async execute(): Promise<Result<DashboardViewData, 'notFound' | 'redirect' | 'DASHBOARD_FETCH_FAILED' | 'UNKNOWN_ERROR'>> {
|
||||
static async execute(): Promise<Result<DashboardViewData, PresentationError>> {
|
||||
const query = new DashboardPageQuery();
|
||||
return query.execute();
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { DriversApiClient } from '@/lib/api/drivers/DriversApiClient';
|
||||
import type { PageQueryResult } from '@/lib/contracts/page-queries/PageQueryResult';
|
||||
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
|
||||
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
import type { GetDriverProfileOutputDTO } from '@/lib/types/generated/GetDriverProfileOutputDTO';
|
||||
import { DriverProfilePageService } from '@/lib/services/drivers/DriverProfilePageService';
|
||||
import { DriverProfileViewModelBuilder } from '@/lib/builders/view-models/DriverProfileViewModelBuilder';
|
||||
import type { DriverProfileViewModel } from '@/lib/view-models/DriverProfileViewModel';
|
||||
|
||||
/**
|
||||
* DriverProfilePageQuery
|
||||
*
|
||||
* Server-side data fetcher for the driver profile page.
|
||||
* Returns a discriminated union with all possible page states.
|
||||
* API DTO is already JSON-serializable.
|
||||
* Uses Service for data access and ViewModelBuilder for transformation.
|
||||
*/
|
||||
export class DriverProfilePageQuery {
|
||||
/**
|
||||
@@ -18,7 +17,7 @@ export class DriverProfilePageQuery {
|
||||
* @param driverId - The driver ID to fetch profile for
|
||||
* @returns PageQueryResult with discriminated union of states
|
||||
*/
|
||||
static async execute(driverId: string | null): Promise<PageQueryResult<GetDriverProfileOutputDTO>> {
|
||||
static async execute(driverId: string | null): Promise<PageQueryResult<DriverProfileViewModel>> {
|
||||
// Handle missing driver ID
|
||||
if (!driverId) {
|
||||
return { status: 'notFound' };
|
||||
@@ -26,20 +25,28 @@ export class DriverProfilePageQuery {
|
||||
|
||||
try {
|
||||
// Manual wiring: construct dependencies explicitly
|
||||
const errorReporter = new ConsoleErrorReporter();
|
||||
const logger = new ConsoleLogger();
|
||||
const baseUrl = process.env.NEXT_PUBLIC_API_URL || '';
|
||||
|
||||
const apiClient = new DriversApiClient(baseUrl, errorReporter, logger);
|
||||
const service = new DriverProfilePageService();
|
||||
|
||||
const dto = await apiClient.getDriverProfile(driverId);
|
||||
const result = await service.getDriverProfile(driverId);
|
||||
|
||||
if (!dto.currentDriver) {
|
||||
return { status: 'notFound' };
|
||||
if (result.isErr()) {
|
||||
const error = result.getError();
|
||||
|
||||
if (error === 'notFound') {
|
||||
return { status: 'notFound' };
|
||||
}
|
||||
|
||||
if (error === 'unauthorized') {
|
||||
return { status: 'error', errorId: 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
return { status: 'error', errorId: 'DRIVER_PROFILE_FETCH_FAILED' };
|
||||
}
|
||||
|
||||
// API DTO is already JSON-serializable
|
||||
return { status: 'ok', dto };
|
||||
// Build ViewModel from DTO
|
||||
const dto = result.unwrap();
|
||||
const viewModel = DriverProfileViewModelBuilder.build(dto);
|
||||
return { status: 'ok', dto: viewModel };
|
||||
|
||||
} catch (error) {
|
||||
console.error('DriverProfilePageQuery failed:', error);
|
||||
|
||||
@@ -1,77 +1,37 @@
|
||||
import { DriversApiClient } from '@/lib/api/drivers/DriversApiClient';
|
||||
import type { PageQueryResult } from '@/lib/contracts/page-queries/PageQueryResult';
|
||||
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
|
||||
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
import type { DriverRankingsPageDto } from '@/lib/page-queries/page-dtos/DriverRankingsPageDto';
|
||||
import type { DriverLeaderboardItemDTO } from '@/lib/types/generated/DriverLeaderboardItemDTO';
|
||||
|
||||
interface ErrorWithStatusCode extends Error {
|
||||
statusCode?: number;
|
||||
}
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { DriverRankingsViewDataBuilder } from '@/lib/builders/view-data/DriverRankingsViewDataBuilder';
|
||||
import type { DriverRankingsViewData } from '@/lib/view-data/DriverRankingsViewData';
|
||||
import { DriverRankingsService } from '@/lib/services/leaderboards/DriverRankingsService';
|
||||
import { mapToPresentationError, type PresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
|
||||
/**
|
||||
* Transform DriverLeaderboardItemDTO to DriverRankingsPageDto
|
||||
* Driver Rankings page query
|
||||
* Returns Result<DriverRankingsViewData, PresentationError>
|
||||
* No DI container usage - constructs dependencies explicitly
|
||||
*/
|
||||
function transformDtoToPageDto(dto: { drivers: DriverLeaderboardItemDTO[] }): DriverRankingsPageDto {
|
||||
return {
|
||||
drivers: dto.drivers.map(driver => ({
|
||||
id: driver.id,
|
||||
name: driver.name,
|
||||
rating: driver.rating,
|
||||
skillLevel: driver.skillLevel,
|
||||
nationality: driver.nationality,
|
||||
racesCompleted: driver.racesCompleted,
|
||||
wins: driver.wins,
|
||||
podiums: driver.podiums,
|
||||
isActive: driver.isActive,
|
||||
rank: driver.rank,
|
||||
avatarUrl: driver.avatarUrl || '',
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Driver Rankings page query with manual wiring
|
||||
* Returns PageQueryResult<DriverRankingsPageDto>
|
||||
*/
|
||||
export class DriverRankingsPageQuery {
|
||||
/**
|
||||
* Execute the driver rankings page query
|
||||
*/
|
||||
static async execute(): Promise<PageQueryResult<DriverRankingsPageDto>> {
|
||||
try {
|
||||
// Manual wiring
|
||||
const errorReporter = new ConsoleErrorReporter();
|
||||
const logger = new ConsoleLogger();
|
||||
const baseUrl = process.env.NEXT_PUBLIC_API_URL || '';
|
||||
const apiClient = new DriversApiClient(baseUrl, errorReporter, logger);
|
||||
|
||||
// Fetch data
|
||||
const dto = await apiClient.getLeaderboard();
|
||||
|
||||
if (!dto || !dto.drivers) {
|
||||
return { status: 'notFound' };
|
||||
}
|
||||
|
||||
// Transform to Page DTO
|
||||
const pageDto = transformDtoToPageDto(dto);
|
||||
return { status: 'ok', dto: pageDto };
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
const errorWithStatus = error as ErrorWithStatusCode;
|
||||
|
||||
if (errorWithStatus.message?.includes('not found') || errorWithStatus.statusCode === 404) {
|
||||
return { status: 'notFound' };
|
||||
}
|
||||
|
||||
if (errorWithStatus.message?.includes('redirect') || errorWithStatus.statusCode === 302) {
|
||||
return { status: 'redirect', to: '/' };
|
||||
}
|
||||
|
||||
return { status: 'error', errorId: 'DRIVER_RANKINGS_FETCH_FAILED' };
|
||||
}
|
||||
|
||||
return { status: 'error', errorId: 'UNKNOWN_ERROR' };
|
||||
export class DriverRankingsPageQuery implements PageQuery<DriverRankingsViewData, void, PresentationError> {
|
||||
async execute(): Promise<Result<DriverRankingsViewData, PresentationError>> {
|
||||
// Manual wiring: Service creates its own dependencies
|
||||
const service = new DriverRankingsService();
|
||||
|
||||
// Fetch data using service
|
||||
const serviceResult = await service.getDriverRankings();
|
||||
|
||||
if (serviceResult.isErr()) {
|
||||
// Map domain errors to presentation errors
|
||||
return Result.err(mapToPresentationError(serviceResult.getError()));
|
||||
}
|
||||
|
||||
// Transform to ViewData using builder
|
||||
const apiDto = serviceResult.unwrap();
|
||||
const viewData = DriverRankingsViewDataBuilder.build(apiDto.drivers);
|
||||
return Result.ok(viewData);
|
||||
}
|
||||
|
||||
// Static method to avoid object construction in server code
|
||||
static async execute(): Promise<Result<DriverRankingsViewData, PresentationError>> {
|
||||
const query = new DriverRankingsPageQuery();
|
||||
return query.execute();
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,14 @@
|
||||
import { DriversApiClient } from '@/lib/api/drivers/DriversApiClient';
|
||||
import type { PageQueryResult } from '@/lib/contracts/page-queries/PageQueryResult';
|
||||
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
|
||||
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
import type { DriversLeaderboardDTO } from '@/lib/types/generated/DriversLeaderboardDTO';
|
||||
import { DriversPageService } from '@/lib/services/drivers/DriversPageService';
|
||||
import { DriversViewModelBuilder } from '@/lib/builders/view-models/DriversViewModelBuilder';
|
||||
import type { DriverLeaderboardViewModel } from '@/lib/view-models/DriverLeaderboardViewModel';
|
||||
|
||||
/**
|
||||
* DriversPageQuery
|
||||
*
|
||||
* Server-side data fetcher for the drivers listing page.
|
||||
* Returns a discriminated union with all possible page states.
|
||||
* API DTO is already JSON-serializable.
|
||||
* Uses Service for data access and ViewModelBuilder for transformation.
|
||||
*/
|
||||
export class DriversPageQuery {
|
||||
/**
|
||||
@@ -17,30 +16,27 @@ export class DriversPageQuery {
|
||||
*
|
||||
* @returns PageQueryResult with discriminated union of states
|
||||
*/
|
||||
static async execute(): Promise<PageQueryResult<DriversLeaderboardDTO>> {
|
||||
static async execute(): Promise<PageQueryResult<DriverLeaderboardViewModel>> {
|
||||
try {
|
||||
// Manual wiring: construct dependencies explicitly
|
||||
const errorReporter = new ConsoleErrorReporter();
|
||||
const logger = new ConsoleLogger();
|
||||
const baseUrl = process.env.NEXT_PUBLIC_API_URL || '';
|
||||
|
||||
const apiClient = new DriversApiClient(baseUrl, errorReporter, logger);
|
||||
const service = new DriversPageService();
|
||||
|
||||
const result = await apiClient.getLeaderboard();
|
||||
const result = await service.getLeaderboard();
|
||||
|
||||
if (!result || !result.drivers) {
|
||||
return { status: 'notFound' };
|
||||
if (result.isErr()) {
|
||||
const error = result.getError();
|
||||
|
||||
if (error === 'notFound') {
|
||||
return { status: 'notFound' };
|
||||
}
|
||||
|
||||
return { status: 'error', errorId: 'DRIVERS_FETCH_FAILED' };
|
||||
}
|
||||
|
||||
// Transform to the expected DTO format
|
||||
const dto: DriversLeaderboardDTO = {
|
||||
drivers: result.drivers,
|
||||
totalRaces: result.drivers.reduce((sum, driver) => sum + driver.racesCompleted, 0),
|
||||
totalWins: result.drivers.reduce((sum, driver) => sum + driver.wins, 0),
|
||||
activeCount: result.drivers.filter(driver => driver.isActive).length,
|
||||
};
|
||||
|
||||
return { status: 'ok', dto };
|
||||
// Build ViewModel from DTO
|
||||
const dto = result.unwrap();
|
||||
const viewModel = DriversViewModelBuilder.build(dto);
|
||||
return { status: 'ok', dto: viewModel };
|
||||
|
||||
} catch (error) {
|
||||
console.error('DriversPageQuery failed:', error);
|
||||
|
||||
@@ -1,64 +1,37 @@
|
||||
import { DriversApiClient } from '@/lib/api/drivers/DriversApiClient';
|
||||
import { TeamsApiClient } from '@/lib/api/teams/TeamsApiClient';
|
||||
import type { PageQueryResult } from '@/lib/contracts/page-queries/PageQueryResult';
|
||||
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
|
||||
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
import type { LeaderboardsPageDto } from '@/lib/page-queries/page-dtos/LeaderboardsPageDto';
|
||||
|
||||
interface ErrorWithStatusCode extends Error {
|
||||
statusCode?: number;
|
||||
}
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { LeaderboardsViewDataBuilder } from '@/lib/builders/view-data/LeaderboardsViewDataBuilder';
|
||||
import type { LeaderboardsViewData } from '@/lib/view-data/LeaderboardsViewData';
|
||||
import { LeaderboardsService } from '@/lib/services/leaderboards/LeaderboardsService';
|
||||
import { mapToPresentationError, type PresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
|
||||
/**
|
||||
* Leaderboards page query with manual wiring
|
||||
* Returns PageQueryResult<LeaderboardsPageDto>
|
||||
* Leaderboards page query
|
||||
* Returns Result<LeaderboardsViewData, PresentationError>
|
||||
* No DI container usage - constructs dependencies explicitly
|
||||
*/
|
||||
export class LeaderboardsPageQuery {
|
||||
/**
|
||||
* Execute the leaderboards page query
|
||||
*/
|
||||
static async execute(): Promise<PageQueryResult<LeaderboardsPageDto>> {
|
||||
try {
|
||||
// Manual wiring
|
||||
const errorReporter = new ConsoleErrorReporter();
|
||||
const logger = new ConsoleLogger();
|
||||
const baseUrl = process.env.NEXT_PUBLIC_API_URL || '';
|
||||
const driversApiClient = new DriversApiClient(baseUrl, errorReporter, logger);
|
||||
const teamsApiClient = new TeamsApiClient(baseUrl, errorReporter, logger);
|
||||
|
||||
// Fetch data in parallel
|
||||
const [driverResult, teamResult] = await Promise.all([
|
||||
driversApiClient.getLeaderboard(),
|
||||
teamsApiClient.getAll(),
|
||||
]);
|
||||
|
||||
if (!driverResult && !teamResult) {
|
||||
return { status: 'notFound' };
|
||||
}
|
||||
|
||||
// Transform to Page DTO
|
||||
const pageDto: LeaderboardsPageDto = {
|
||||
drivers: driverResult || { drivers: [] },
|
||||
teams: teamResult || { teams: [] },
|
||||
};
|
||||
|
||||
return { status: 'ok', dto: pageDto };
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
const errorWithStatus = error as ErrorWithStatusCode;
|
||||
|
||||
if (errorWithStatus.message?.includes('not found') || errorWithStatus.statusCode === 404) {
|
||||
return { status: 'notFound' };
|
||||
}
|
||||
|
||||
if (errorWithStatus.message?.includes('redirect') || errorWithStatus.statusCode === 302) {
|
||||
return { status: 'redirect', to: '/' };
|
||||
}
|
||||
|
||||
return { status: 'error', errorId: 'LEADERBOARDS_FETCH_FAILED' };
|
||||
}
|
||||
|
||||
return { status: 'error', errorId: 'UNKNOWN_ERROR' };
|
||||
export class LeaderboardsPageQuery implements PageQuery<LeaderboardsViewData, void, PresentationError> {
|
||||
async execute(): Promise<Result<LeaderboardsViewData, PresentationError>> {
|
||||
// Manual wiring: Service creates its own dependencies
|
||||
const service = new LeaderboardsService();
|
||||
|
||||
// Fetch data using service
|
||||
const serviceResult = await service.getLeaderboards();
|
||||
|
||||
if (serviceResult.isErr()) {
|
||||
// Map domain errors to presentation errors
|
||||
return Result.err(mapToPresentationError(serviceResult.getError()));
|
||||
}
|
||||
|
||||
// Transform to ViewData using builder
|
||||
const apiDto = serviceResult.unwrap();
|
||||
const viewData = LeaderboardsViewDataBuilder.build(apiDto);
|
||||
return Result.ok(viewData);
|
||||
}
|
||||
|
||||
// Static method to avoid object construction in server code
|
||||
static async execute(): Promise<Result<LeaderboardsViewData, PresentationError>> {
|
||||
const query = new LeaderboardsPageQuery();
|
||||
return query.execute();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient';
|
||||
import { ProtestsApiClient } from '@/lib/api/protests/ProtestsApiClient';
|
||||
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
|
||||
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
|
||||
/**
|
||||
* LeagueProtestReviewPageQuery
|
||||
*
|
||||
* Fetches protest detail data for review.
|
||||
*/
|
||||
export class LeagueProtestReviewPageQuery implements PageQuery<any, { leagueId: string; protestId: string }> {
|
||||
async execute(input: { leagueId: string; protestId: string }): Promise<Result<any, 'notFound' | 'redirect' | 'PROTEST_FETCH_FAILED' | 'UNKNOWN_ERROR'>> {
|
||||
// Manual wiring: create API clients
|
||||
const baseUrl = process.env.NEXT_PUBLIC_API_URL || '';
|
||||
const errorReporter = new ConsoleErrorReporter();
|
||||
const logger = new ConsoleLogger();
|
||||
const leaguesApiClient = new LeaguesApiClient(baseUrl, errorReporter, logger);
|
||||
const protestsApiClient = new ProtestsApiClient(baseUrl, errorReporter, logger);
|
||||
|
||||
try {
|
||||
// Get protest details
|
||||
// Note: This would need a getProtestDetail method on ProtestsApiClient
|
||||
// For now, return placeholder data
|
||||
const protestDetail = {
|
||||
protest: {
|
||||
id: input.protestId,
|
||||
raceId: 'placeholder',
|
||||
protestingDriverId: 'placeholder',
|
||||
accusedDriverId: 'placeholder',
|
||||
description: 'Placeholder protest',
|
||||
status: 'pending',
|
||||
submittedAt: new Date().toISOString(),
|
||||
},
|
||||
race: {
|
||||
id: 'placeholder',
|
||||
name: 'Placeholder Race',
|
||||
scheduledAt: new Date().toISOString(),
|
||||
},
|
||||
protestingDriver: {
|
||||
id: 'placeholder',
|
||||
name: 'Placeholder Protester',
|
||||
},
|
||||
accusedDriver: {
|
||||
id: 'placeholder',
|
||||
name: 'Placeholder Accused',
|
||||
},
|
||||
penaltyTypes: [],
|
||||
defaultReasons: {},
|
||||
};
|
||||
|
||||
return Result.ok(protestDetail);
|
||||
} catch (error) {
|
||||
console.error('LeagueProtestReviewPageQuery failed:', error);
|
||||
|
||||
if (error instanceof Error) {
|
||||
if (error.message.includes('403') || error.message.includes('401')) {
|
||||
return Result.err('redirect');
|
||||
}
|
||||
if (error.message.includes('404')) {
|
||||
return Result.err('notFound');
|
||||
}
|
||||
if (error.message.includes('5') || error.message.includes('server')) {
|
||||
return Result.err('PROTEST_FETCH_FAILED');
|
||||
}
|
||||
}
|
||||
|
||||
return Result.err('UNKNOWN_ERROR');
|
||||
}
|
||||
}
|
||||
|
||||
static async execute(input: { leagueId: string; protestId: string }): Promise<Result<any, 'notFound' | 'redirect' | 'PROTEST_FETCH_FAILED' | 'UNKNOWN_ERROR'>> {
|
||||
const query = new LeagueProtestReviewPageQuery();
|
||||
return query.execute(input);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient';
|
||||
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
|
||||
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
|
||||
/**
|
||||
* LeagueRosterAdminPageQuery
|
||||
*
|
||||
* Fetches league roster admin data (members and join requests).
|
||||
*/
|
||||
export class LeagueRosterAdminPageQuery implements PageQuery<any, string> {
|
||||
async execute(leagueId: string): Promise<Result<any, 'notFound' | 'redirect' | 'ROSTER_FETCH_FAILED' | 'UNKNOWN_ERROR'>> {
|
||||
// Manual wiring: create API client
|
||||
const baseUrl = process.env.NEXT_PUBLIC_API_URL || '';
|
||||
const errorReporter = new ConsoleErrorReporter();
|
||||
const logger = new ConsoleLogger();
|
||||
const apiClient = new LeaguesApiClient(baseUrl, errorReporter, logger);
|
||||
|
||||
try {
|
||||
// Get admin roster members and join requests
|
||||
const [members, joinRequests] = await Promise.all([
|
||||
apiClient.getAdminRosterMembers(leagueId),
|
||||
apiClient.getAdminRosterJoinRequests(leagueId),
|
||||
]);
|
||||
|
||||
if (!members || !joinRequests) {
|
||||
return Result.err('notFound');
|
||||
}
|
||||
|
||||
return Result.ok({
|
||||
leagueId,
|
||||
members,
|
||||
joinRequests,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('LeagueRosterAdminPageQuery failed:', error);
|
||||
|
||||
if (error instanceof Error) {
|
||||
if (error.message.includes('403') || error.message.includes('401')) {
|
||||
return Result.err('redirect');
|
||||
}
|
||||
if (error.message.includes('404')) {
|
||||
return Result.err('notFound');
|
||||
}
|
||||
if (error.message.includes('5') || error.message.includes('server')) {
|
||||
return Result.err('ROSTER_FETCH_FAILED');
|
||||
}
|
||||
}
|
||||
|
||||
return Result.err('UNKNOWN_ERROR');
|
||||
}
|
||||
}
|
||||
|
||||
static async execute(leagueId: string): Promise<Result<any, 'notFound' | 'redirect' | 'ROSTER_FETCH_FAILED' | 'UNKNOWN_ERROR'>> {
|
||||
const query = new LeagueRosterAdminPageQuery();
|
||||
return query.execute(leagueId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
|
||||
/**
|
||||
* LeagueRulebookPageQuery
|
||||
*
|
||||
* Fetches league rulebook data.
|
||||
* Currently returns empty data - would need API endpoint.
|
||||
*/
|
||||
export class LeagueRulebookPageQuery implements PageQuery<any, string> {
|
||||
async execute(leagueId: string): Promise<Result<any, 'notFound' | 'redirect' | 'RULEBOOK_FETCH_FAILED' | 'UNKNOWN_ERROR'>> {
|
||||
// TODO: Implement when API endpoint is available
|
||||
// For now, return empty data
|
||||
return Result.ok({ leagueId, rules: [] });
|
||||
}
|
||||
|
||||
static async execute(leagueId: string): Promise<Result<any, 'notFound' | 'redirect' | 'RULEBOOK_FETCH_FAILED' | 'UNKNOWN_ERROR'>> {
|
||||
const query = new LeagueRulebookPageQuery();
|
||||
return query.execute(leagueId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient';
|
||||
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
|
||||
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
|
||||
/**
|
||||
* LeagueScheduleAdminPageQuery
|
||||
*
|
||||
* Fetches league schedule admin data.
|
||||
*/
|
||||
export class LeagueScheduleAdminPageQuery implements PageQuery<any, { leagueId: string; seasonId?: string }> {
|
||||
async execute(input: { leagueId: string; seasonId?: string }): Promise<Result<any, 'notFound' | 'redirect' | 'SCHEDULE_ADMIN_FETCH_FAILED' | 'UNKNOWN_ERROR'>> {
|
||||
// Manual wiring: create API client
|
||||
const baseUrl = process.env.NEXT_PUBLIC_API_URL || '';
|
||||
const errorReporter = new ConsoleErrorReporter();
|
||||
const logger = new ConsoleLogger();
|
||||
const apiClient = new LeaguesApiClient(baseUrl, errorReporter, logger);
|
||||
|
||||
try {
|
||||
// Get seasons
|
||||
const seasons = await apiClient.getSeasons(input.leagueId);
|
||||
|
||||
if (!seasons || seasons.length === 0) {
|
||||
return Result.err('notFound');
|
||||
}
|
||||
|
||||
// Determine season to use
|
||||
const seasonId = input.seasonId || (seasons.find(s => s.status === 'active')?.seasonId || seasons[0].seasonId);
|
||||
|
||||
// Get schedule
|
||||
const schedule = await apiClient.getSchedule(input.leagueId, seasonId);
|
||||
|
||||
return Result.ok({
|
||||
leagueId: input.leagueId,
|
||||
seasonId,
|
||||
seasons,
|
||||
schedule,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('LeagueScheduleAdminPageQuery failed:', error);
|
||||
|
||||
if (error instanceof Error) {
|
||||
if (error.message.includes('403') || error.message.includes('401')) {
|
||||
return Result.err('redirect');
|
||||
}
|
||||
if (error.message.includes('404')) {
|
||||
return Result.err('notFound');
|
||||
}
|
||||
if (error.message.includes('5') || error.message.includes('server')) {
|
||||
return Result.err('SCHEDULE_ADMIN_FETCH_FAILED');
|
||||
}
|
||||
}
|
||||
|
||||
return Result.err('UNKNOWN_ERROR');
|
||||
}
|
||||
}
|
||||
|
||||
static async execute(input: { leagueId: string; seasonId?: string }): Promise<Result<any, 'notFound' | 'redirect' | 'SCHEDULE_ADMIN_FETCH_FAILED' | 'UNKNOWN_ERROR'>> {
|
||||
const query = new LeagueScheduleAdminPageQuery();
|
||||
return query.execute(input);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient';
|
||||
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
|
||||
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
|
||||
/**
|
||||
* LeagueSchedulePageQuery
|
||||
*
|
||||
* Fetches league schedule data for the schedule page.
|
||||
* Returns raw API DTO for now - would need ViewDataBuilder for proper transformation.
|
||||
*/
|
||||
export class LeagueSchedulePageQuery implements PageQuery<any, string> {
|
||||
async execute(leagueId: string): Promise<Result<any, 'notFound' | 'redirect' | 'SCHEDULE_FETCH_FAILED' | 'UNKNOWN_ERROR'>> {
|
||||
// Manual wiring: create API client
|
||||
const baseUrl = process.env.NEXT_PUBLIC_API_URL || '';
|
||||
const errorReporter = new ConsoleErrorReporter();
|
||||
const logger = new ConsoleLogger();
|
||||
const apiClient = new LeaguesApiClient(baseUrl, errorReporter, logger);
|
||||
|
||||
try {
|
||||
const scheduleDto = await apiClient.getSchedule(leagueId);
|
||||
|
||||
if (!scheduleDto) {
|
||||
return Result.err('notFound');
|
||||
}
|
||||
|
||||
return Result.ok(scheduleDto);
|
||||
} catch (error) {
|
||||
console.error('LeagueSchedulePageQuery failed:', error);
|
||||
|
||||
if (error instanceof Error) {
|
||||
if (error.message.includes('403') || error.message.includes('401')) {
|
||||
return Result.err('redirect');
|
||||
}
|
||||
if (error.message.includes('404')) {
|
||||
return Result.err('notFound');
|
||||
}
|
||||
if (error.message.includes('5') || error.message.includes('server')) {
|
||||
return Result.err('SCHEDULE_FETCH_FAILED');
|
||||
}
|
||||
}
|
||||
|
||||
return Result.err('UNKNOWN_ERROR');
|
||||
}
|
||||
}
|
||||
|
||||
static async execute(leagueId: string): Promise<Result<any, 'notFound' | 'redirect' | 'SCHEDULE_FETCH_FAILED' | 'UNKNOWN_ERROR'>> {
|
||||
const query = new LeagueSchedulePageQuery();
|
||||
return query.execute(leagueId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient';
|
||||
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
|
||||
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
|
||||
/**
|
||||
* LeagueSettingsPageQuery
|
||||
*
|
||||
* Fetches league settings data.
|
||||
*/
|
||||
export class LeagueSettingsPageQuery implements PageQuery<any, string> {
|
||||
async execute(leagueId: string): Promise<Result<any, 'notFound' | 'redirect' | 'SETTINGS_FETCH_FAILED' | 'UNKNOWN_ERROR'>> {
|
||||
// Manual wiring: create API client
|
||||
const baseUrl = process.env.NEXT_PUBLIC_API_URL || '';
|
||||
const errorReporter = new ConsoleErrorReporter();
|
||||
const logger = new ConsoleLogger();
|
||||
const apiClient = new LeaguesApiClient(baseUrl, errorReporter, logger);
|
||||
|
||||
try {
|
||||
// Get league config
|
||||
const config = await apiClient.getLeagueConfig(leagueId);
|
||||
|
||||
if (!config) {
|
||||
return Result.err('notFound');
|
||||
}
|
||||
|
||||
return Result.ok({
|
||||
leagueId,
|
||||
config,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('LeagueSettingsPageQuery failed:', error);
|
||||
|
||||
if (error instanceof Error) {
|
||||
if (error.message.includes('403') || error.message.includes('401')) {
|
||||
return Result.err('redirect');
|
||||
}
|
||||
if (error.message.includes('404')) {
|
||||
return Result.err('notFound');
|
||||
}
|
||||
if (error.message.includes('5') || error.message.includes('server')) {
|
||||
return Result.err('SETTINGS_FETCH_FAILED');
|
||||
}
|
||||
}
|
||||
|
||||
return Result.err('UNKNOWN_ERROR');
|
||||
}
|
||||
}
|
||||
|
||||
static async execute(leagueId: string): Promise<Result<any, 'notFound' | 'redirect' | 'SETTINGS_FETCH_FAILED' | 'UNKNOWN_ERROR'>> {
|
||||
const query = new LeagueSettingsPageQuery();
|
||||
return query.execute(leagueId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient';
|
||||
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
|
||||
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
|
||||
/**
|
||||
* LeagueSponsorshipsPageQuery
|
||||
*
|
||||
* Fetches league sponsorships data.
|
||||
*/
|
||||
export class LeagueSponsorshipsPageQuery implements PageQuery<any, string> {
|
||||
async execute(leagueId: string): Promise<Result<any, 'notFound' | 'redirect' | 'SPONSORSHIPS_FETCH_FAILED' | 'UNKNOWN_ERROR'>> {
|
||||
// Manual wiring: create API client
|
||||
const baseUrl = process.env.NEXT_PUBLIC_API_URL || '';
|
||||
const errorReporter = new ConsoleErrorReporter();
|
||||
const logger = new ConsoleLogger();
|
||||
const apiClient = new LeaguesApiClient(baseUrl, errorReporter, logger);
|
||||
|
||||
try {
|
||||
// Get seasons first to find active season
|
||||
const seasons = await apiClient.getSeasons(leagueId);
|
||||
|
||||
if (!seasons || seasons.length === 0) {
|
||||
return Result.err('notFound');
|
||||
}
|
||||
|
||||
// Get sponsorships for the first season (or active season)
|
||||
const activeSeason = seasons.find(s => s.status === 'active') || seasons[0];
|
||||
const sponsorshipsData = await apiClient.getSeasonSponsorships(activeSeason.seasonId);
|
||||
|
||||
return Result.ok({
|
||||
leagueId,
|
||||
seasonId: activeSeason.seasonId,
|
||||
sponsorships: sponsorshipsData.sponsorships || [],
|
||||
seasons,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('LeagueSponsorshipsPageQuery failed:', error);
|
||||
|
||||
if (error instanceof Error) {
|
||||
if (error.message.includes('403') || error.message.includes('401')) {
|
||||
return Result.err('redirect');
|
||||
}
|
||||
if (error.message.includes('404')) {
|
||||
return Result.err('notFound');
|
||||
}
|
||||
if (error.message.includes('5') || error.message.includes('server')) {
|
||||
return Result.err('SPONSORSHIPS_FETCH_FAILED');
|
||||
}
|
||||
}
|
||||
|
||||
return Result.err('UNKNOWN_ERROR');
|
||||
}
|
||||
}
|
||||
|
||||
static async execute(leagueId: string): Promise<Result<any, 'notFound' | 'redirect' | 'SPONSORSHIPS_FETCH_FAILED' | 'UNKNOWN_ERROR'>> {
|
||||
const query = new LeagueSponsorshipsPageQuery();
|
||||
return query.execute(leagueId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient';
|
||||
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
|
||||
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
import type { LeagueStandingsViewData } from '@/lib/view-data/LeagueStandingsViewData';
|
||||
|
||||
/**
|
||||
* LeagueStandingsPageQuery
|
||||
*
|
||||
* Fetches league standings data for the standings page.
|
||||
* Returns Result<LeagueStandingsViewData, PresentationError>
|
||||
*/
|
||||
export class LeagueStandingsPageQuery implements PageQuery<LeagueStandingsViewData, string> {
|
||||
async execute(leagueId: string): Promise<Result<LeagueStandingsViewData, 'notFound' | 'redirect' | 'STANDINGS_FETCH_FAILED' | 'UNKNOWN_ERROR'>> {
|
||||
// Manual wiring: create API client
|
||||
const baseUrl = process.env.NEXT_PUBLIC_API_URL || '';
|
||||
const errorReporter = new ConsoleErrorReporter();
|
||||
const logger = new ConsoleLogger();
|
||||
const apiClient = new LeaguesApiClient(baseUrl, errorReporter, logger);
|
||||
|
||||
try {
|
||||
// Fetch standings
|
||||
const standingsDto = await apiClient.getStandings(leagueId);
|
||||
|
||||
if (!standingsDto) {
|
||||
return Result.err('notFound');
|
||||
}
|
||||
|
||||
// For now, return empty data structure
|
||||
// In a real implementation, this would transform the DTO to ViewData
|
||||
const viewData: LeagueStandingsViewData = {
|
||||
standings: [],
|
||||
drivers: [],
|
||||
memberships: [],
|
||||
leagueId,
|
||||
currentDriverId: null,
|
||||
isAdmin: false,
|
||||
};
|
||||
|
||||
return Result.ok(viewData);
|
||||
} catch (error) {
|
||||
console.error('LeagueStandingsPageQuery failed:', error);
|
||||
|
||||
if (error instanceof Error) {
|
||||
if (error.message.includes('403') || error.message.includes('401')) {
|
||||
return Result.err('redirect');
|
||||
}
|
||||
if (error.message.includes('404')) {
|
||||
return Result.err('notFound');
|
||||
}
|
||||
if (error.message.includes('5') || error.message.includes('server')) {
|
||||
return Result.err('STANDINGS_FETCH_FAILED');
|
||||
}
|
||||
}
|
||||
|
||||
return Result.err('UNKNOWN_ERROR');
|
||||
}
|
||||
}
|
||||
|
||||
static async execute(leagueId: string): Promise<Result<LeagueStandingsViewData, 'notFound' | 'redirect' | 'STANDINGS_FETCH_FAILED' | 'UNKNOWN_ERROR'>> {
|
||||
const query = new LeagueStandingsPageQuery();
|
||||
return query.execute(leagueId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient';
|
||||
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
|
||||
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
|
||||
/**
|
||||
* LeagueStewardingPageQuery
|
||||
*
|
||||
* Fetches league stewarding data (protests and penalties).
|
||||
*/
|
||||
export class LeagueStewardingPageQuery implements PageQuery<any, string> {
|
||||
async execute(leagueId: string): Promise<Result<any, 'notFound' | 'redirect' | 'STEWARDING_FETCH_FAILED' | 'UNKNOWN_ERROR'>> {
|
||||
// Manual wiring: create API client
|
||||
const baseUrl = process.env.NEXT_PUBLIC_API_URL || '';
|
||||
const errorReporter = new ConsoleErrorReporter();
|
||||
const logger = new ConsoleLogger();
|
||||
const apiClient = new LeaguesApiClient(baseUrl, errorReporter, logger);
|
||||
|
||||
try {
|
||||
// Get races for the league
|
||||
const racesData = await apiClient.getRaces(leagueId);
|
||||
|
||||
if (!racesData) {
|
||||
return Result.err('notFound');
|
||||
}
|
||||
|
||||
// Get memberships for driver lookup
|
||||
const memberships = await apiClient.getMemberships(leagueId);
|
||||
|
||||
// Return data structure for stewarding page
|
||||
// In real implementation, would need protest/penalty API endpoints
|
||||
return Result.ok({
|
||||
leagueId,
|
||||
races: racesData.races || [],
|
||||
memberships: memberships || { members: [] },
|
||||
totalPending: 0,
|
||||
totalResolved: 0,
|
||||
totalPenalties: 0,
|
||||
racesWithData: [],
|
||||
allDrivers: [],
|
||||
driverMap: {},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('LeagueStewardingPageQuery failed:', error);
|
||||
|
||||
if (error instanceof Error) {
|
||||
if (error.message.includes('403') || error.message.includes('401')) {
|
||||
return Result.err('redirect');
|
||||
}
|
||||
if (error.message.includes('404')) {
|
||||
return Result.err('notFound');
|
||||
}
|
||||
if (error.message.includes('5') || error.message.includes('server')) {
|
||||
return Result.err('STEWARDING_FETCH_FAILED');
|
||||
}
|
||||
}
|
||||
|
||||
return Result.err('UNKNOWN_ERROR');
|
||||
}
|
||||
}
|
||||
|
||||
static async execute(leagueId: string): Promise<Result<any, 'notFound' | 'redirect' | 'STEWARDING_FETCH_FAILED' | 'UNKNOWN_ERROR'>> {
|
||||
const query = new LeagueStewardingPageQuery();
|
||||
return query.execute(leagueId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient';
|
||||
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
|
||||
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
|
||||
/**
|
||||
* LeagueWalletPageQuery
|
||||
*
|
||||
* Fetches league wallet data.
|
||||
*/
|
||||
export class LeagueWalletPageQuery implements PageQuery<any, string> {
|
||||
async execute(leagueId: string): Promise<Result<any, 'notFound' | 'redirect' | 'WALLET_FETCH_FAILED' | 'UNKNOWN_ERROR'>> {
|
||||
// Manual wiring: create API client
|
||||
const baseUrl = process.env.NEXT_PUBLIC_API_URL || '';
|
||||
const errorReporter = new ConsoleErrorReporter();
|
||||
const logger = new ConsoleLogger();
|
||||
const apiClient = new LeaguesApiClient(baseUrl, errorReporter, logger);
|
||||
|
||||
try {
|
||||
// Get league memberships to verify access
|
||||
const memberships = await apiClient.getMemberships(leagueId);
|
||||
|
||||
if (!memberships) {
|
||||
return Result.err('notFound');
|
||||
}
|
||||
|
||||
// Return wallet data structure
|
||||
// In real implementation, would need wallet API endpoints
|
||||
return Result.ok({
|
||||
leagueId,
|
||||
balance: 0,
|
||||
totalRevenue: 0,
|
||||
totalFees: 0,
|
||||
pendingPayouts: 0,
|
||||
transactions: [],
|
||||
canWithdraw: false,
|
||||
withdrawalBlockReason: 'Wallet system not yet implemented',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('LeagueWalletPageQuery failed:', error);
|
||||
|
||||
if (error instanceof Error) {
|
||||
if (error.message.includes('403') || error.message.includes('401')) {
|
||||
return Result.err('redirect');
|
||||
}
|
||||
if (error.message.includes('404')) {
|
||||
return Result.err('notFound');
|
||||
}
|
||||
if (error.message.includes('5') || error.message.includes('server')) {
|
||||
return Result.err('WALLET_FETCH_FAILED');
|
||||
}
|
||||
}
|
||||
|
||||
return Result.err('UNKNOWN_ERROR');
|
||||
}
|
||||
}
|
||||
|
||||
static async execute(leagueId: string): Promise<Result<any, 'notFound' | 'redirect' | 'WALLET_FETCH_FAILED' | 'UNKNOWN_ERROR'>> {
|
||||
const query = new LeagueWalletPageQuery();
|
||||
return query.execute(leagueId);
|
||||
}
|
||||
}
|
||||
@@ -1,132 +1,46 @@
|
||||
import { ApiClient } from '@/lib/api';
|
||||
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
|
||||
import type { PageQueryResult } from '@/lib/contracts/page-queries/PageQueryResult';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import type { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { SessionGateway } from '@/lib/gateways/SessionGateway';
|
||||
|
||||
/**
|
||||
* Page DTO for Profile Leagues page
|
||||
* JSON-serializable data structure for server-to-client communication
|
||||
*/
|
||||
export interface ProfileLeaguesPageDto {
|
||||
ownedLeagues: Array<{
|
||||
leagueId: string;
|
||||
name: string;
|
||||
description: string;
|
||||
membershipRole: 'owner' | 'admin' | 'steward' | 'member';
|
||||
}>;
|
||||
memberLeagues: Array<{
|
||||
leagueId: string;
|
||||
name: string;
|
||||
description: string;
|
||||
membershipRole: 'owner' | 'admin' | 'steward' | 'member';
|
||||
}>;
|
||||
}
|
||||
|
||||
interface MembershipDTO {
|
||||
driverId: string;
|
||||
role: string;
|
||||
status?: 'active' | 'inactive';
|
||||
}
|
||||
import { ProfileLeaguesService } from '@/lib/services/leagues/ProfileLeaguesService';
|
||||
import { ProfileLeaguesViewDataBuilder } from '@/lib/builders/view-data/ProfileLeaguesViewDataBuilder';
|
||||
import type { ProfileLeaguesViewData } from '@/lib/view-data/ProfileLeaguesViewData';
|
||||
import { mapToPresentationError, type PresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
|
||||
/**
|
||||
* Profile Leagues Page Query
|
||||
*
|
||||
* Server-side composition that:
|
||||
* 1. Reads session to determine currentDriverId
|
||||
* 2. Calls API clients directly (manual wiring)
|
||||
* 3. Assembles Page DTO
|
||||
* 4. Returns PageQueryResult
|
||||
* 2. Uses service for orchestration
|
||||
* 3. Transforms to ViewData using builder
|
||||
* 4. Returns Result<ViewData, PresentationError>
|
||||
*/
|
||||
export class ProfileLeaguesPageQuery {
|
||||
static async execute(): Promise<PageQueryResult<ProfileLeaguesPageDto>> {
|
||||
try {
|
||||
// Get session server-side
|
||||
const sessionGateway = new SessionGateway();
|
||||
const session = await sessionGateway.getSession();
|
||||
|
||||
if (!session?.user?.primaryDriverId) {
|
||||
return { status: 'notFound' };
|
||||
}
|
||||
|
||||
const currentDriverId = session.user.primaryDriverId;
|
||||
|
||||
// Manual wiring: construct API client explicitly
|
||||
const apiBaseUrl = getWebsiteApiBaseUrl();
|
||||
const apiClient = new ApiClient(apiBaseUrl);
|
||||
|
||||
// Fetch all leagues
|
||||
const leaguesDto = await apiClient.leagues.getAllWithCapacity();
|
||||
|
||||
if (!leaguesDto?.leagues) {
|
||||
return { status: 'notFound' };
|
||||
}
|
||||
|
||||
// Fetch memberships for each league and categorize
|
||||
const owned: ProfileLeaguesPageDto['ownedLeagues'] = [];
|
||||
const member: ProfileLeaguesPageDto['memberLeagues'] = [];
|
||||
|
||||
for (const league of leaguesDto.leagues) {
|
||||
try {
|
||||
const membershipsDto = await apiClient.leagues.getMemberships(league.id);
|
||||
|
||||
// Handle both possible response structures with proper type checking
|
||||
let memberships: MembershipDTO[] = [];
|
||||
if (membershipsDto && typeof membershipsDto === 'object') {
|
||||
if ('members' in membershipsDto && Array.isArray((membershipsDto as { members?: unknown }).members)) {
|
||||
memberships = (membershipsDto as { members: MembershipDTO[] }).members;
|
||||
} else if ('memberships' in membershipsDto && Array.isArray((membershipsDto as { memberships?: unknown }).memberships)) {
|
||||
memberships = (membershipsDto as { memberships: MembershipDTO[] }).memberships;
|
||||
}
|
||||
}
|
||||
|
||||
const currentMembership = memberships.find(
|
||||
(m) => m.driverId === currentDriverId
|
||||
);
|
||||
|
||||
if (currentMembership && currentMembership.status === 'active') {
|
||||
const leagueData = {
|
||||
leagueId: league.id,
|
||||
name: league.name,
|
||||
description: league.description,
|
||||
membershipRole: currentMembership.role as 'owner' | 'admin' | 'steward' | 'member',
|
||||
};
|
||||
|
||||
if (currentMembership.role === 'owner') {
|
||||
owned.push(leagueData);
|
||||
} else {
|
||||
member.push(leagueData);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Skip leagues where membership fetch fails
|
||||
console.warn(`Failed to fetch memberships for league ${league.id}:`, error);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'ok',
|
||||
dto: {
|
||||
ownedLeagues: owned,
|
||||
memberLeagues: member,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('ProfileLeaguesPageQuery failed:', error);
|
||||
|
||||
if (error instanceof Error) {
|
||||
// Check for specific error properties
|
||||
const errorAny = error as { statusCode?: number };
|
||||
if (error.message.includes('not found') || errorAny.statusCode === 404) {
|
||||
return { status: 'notFound' };
|
||||
}
|
||||
if (error.message.includes('redirect') || errorAny.statusCode === 302) {
|
||||
return { status: 'redirect', to: '/' };
|
||||
}
|
||||
return { status: 'error', errorId: 'PROFILE_LEAGUES_FETCH_FAILED' };
|
||||
}
|
||||
|
||||
return { status: 'error', errorId: 'UNKNOWN_ERROR' };
|
||||
export class ProfileLeaguesPageQuery implements PageQuery<ProfileLeaguesViewData, void, PresentationError> {
|
||||
async execute(): Promise<Result<ProfileLeaguesViewData, PresentationError>> {
|
||||
// Get session server-side
|
||||
const sessionGateway = new SessionGateway();
|
||||
const session = await sessionGateway.getSession();
|
||||
|
||||
if (!session?.user?.primaryDriverId) {
|
||||
return Result.err('notFound');
|
||||
}
|
||||
|
||||
const currentDriverId = session.user.primaryDriverId;
|
||||
const service = new ProfileLeaguesService();
|
||||
const serviceResult = await service.getProfileLeagues(currentDriverId);
|
||||
|
||||
if (serviceResult.isErr()) {
|
||||
return Result.err(mapToPresentationError(serviceResult.getError()));
|
||||
}
|
||||
|
||||
// Transform to ViewData using builder
|
||||
const apiDto = serviceResult.unwrap();
|
||||
const profileView = ProfileLeaguesViewDataBuilder.build(apiDto);
|
||||
return Result.ok(profileView);
|
||||
}
|
||||
}
|
||||
|
||||
static async execute(): Promise<Result<ProfileLeaguesViewData, PresentationError>> {
|
||||
const query = new ProfileLeaguesPageQuery();
|
||||
return query.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +1,48 @@
|
||||
import { SponsorsApiClient } from '@/lib/api/sponsors/SponsorsApiClient';
|
||||
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
|
||||
import { isProductionEnvironment } from '@/lib/config/env';
|
||||
import type { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import type { Result as ResultType } from '@/lib/contracts/Result';
|
||||
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
|
||||
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
import type { GetPendingSponsorshipRequestsOutputDTO } from '@/lib/types/generated/GetPendingSponsorshipRequestsOutputDTO';
|
||||
import type { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { SessionGateway } from '@/lib/gateways/SessionGateway';
|
||||
import { SponsorshipRequestsService } from '@/lib/services/sponsors/SponsorshipRequestsService';
|
||||
import { SponsorshipRequestsPageViewDataBuilder } from '@/lib/builders/view-data/SponsorshipRequestsPageViewDataBuilder';
|
||||
import type { SponsorshipRequestsViewData } from '@/lib/view-data/SponsorshipRequestsViewData';
|
||||
import { mapToPresentationError, type PresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
|
||||
export type SponsorshipRequestsPageQueryError =
|
||||
| 'SPONSORSHIP_REQUESTS_NOT_FOUND';
|
||||
|
||||
export class SponsorshipRequestsPageQuery
|
||||
implements PageQuery<GetPendingSponsorshipRequestsOutputDTO, Record<string, string>, SponsorshipRequestsPageQueryError>
|
||||
{
|
||||
private readonly client: SponsorsApiClient;
|
||||
|
||||
constructor() {
|
||||
const baseUrl = getWebsiteApiBaseUrl();
|
||||
const logger = new ConsoleLogger();
|
||||
const errorReporter = new EnhancedErrorReporter(logger, {
|
||||
showUserNotifications: true,
|
||||
logToConsole: true,
|
||||
reportToExternal: isProductionEnvironment(),
|
||||
/**
|
||||
* Sponsorship Requests Page Query
|
||||
*
|
||||
* Server-side composition that:
|
||||
* 1. Reads session to determine currentDriverId
|
||||
* 2. Uses service for orchestration
|
||||
* 3. Transforms to ViewData using builder
|
||||
* 4. Returns Result<ViewData, PresentationError>
|
||||
*/
|
||||
export class SponsorshipRequestsPageQuery implements PageQuery<SponsorshipRequestsViewData, void, PresentationError> {
|
||||
async execute(): Promise<Result<SponsorshipRequestsViewData, PresentationError>> {
|
||||
// Get session to determine current driver
|
||||
const sessionGateway = new SessionGateway();
|
||||
const session = await sessionGateway.getSession();
|
||||
|
||||
if (!session?.user?.primaryDriverId) {
|
||||
return Result.err('notFound');
|
||||
}
|
||||
|
||||
const service = new SponsorshipRequestsService();
|
||||
const serviceResult = await service.getPendingRequests({
|
||||
entityType: 'driver',
|
||||
entityId: session.user.primaryDriverId,
|
||||
});
|
||||
|
||||
this.client = new SponsorsApiClient(baseUrl, errorReporter, logger);
|
||||
if (serviceResult.isErr()) {
|
||||
return Result.err(mapToPresentationError(serviceResult.getError()));
|
||||
}
|
||||
|
||||
// Transform to ViewData using builder
|
||||
const apiDto = serviceResult.unwrap();
|
||||
const sponsorshipView = SponsorshipRequestsPageViewDataBuilder.build(apiDto);
|
||||
return Result.ok(sponsorshipView);
|
||||
}
|
||||
|
||||
async execute(
|
||||
params: Record<string, string>,
|
||||
): Promise<ResultType<GetPendingSponsorshipRequestsOutputDTO, SponsorshipRequestsPageQueryError>> {
|
||||
try {
|
||||
// For now, we'll use hardcoded entityType/entityId
|
||||
// In a real implementation, these would come from the user session
|
||||
const result = await this.client.getPendingSponsorshipRequests({
|
||||
entityType: 'driver',
|
||||
entityId: 'current-user-id', // This would come from session
|
||||
});
|
||||
|
||||
return Result.ok(result);
|
||||
} catch {
|
||||
return Result.err('SPONSORSHIP_REQUESTS_NOT_FOUND');
|
||||
}
|
||||
static async execute(): Promise<Result<SponsorshipRequestsViewData, PresentationError>> {
|
||||
const query = new SponsorshipRequestsPageQuery();
|
||||
return query.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { TeamsApiClient } from '@/lib/api/teams/TeamsApiClient';
|
||||
import type { PageQueryResult } from '@/lib/contracts/page-queries/PageQueryResult';
|
||||
import { SessionGateway } from '@/lib/gateways/SessionGateway';
|
||||
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
|
||||
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
import { TeamService } from '@/lib/services/teams/TeamService';
|
||||
import type { TeamMemberViewModel } from '@/lib/view-models/TeamMemberViewModel';
|
||||
|
||||
@@ -65,26 +62,25 @@ export class TeamDetailPageQuery {
|
||||
const currentDriverId = session.user.primaryDriverId;
|
||||
|
||||
// Manual dependency creation
|
||||
const baseUrl = process.env.API_BASE_URL || 'http://localhost:3101';
|
||||
const logger = new ConsoleLogger();
|
||||
const errorReporter = new EnhancedErrorReporter(logger, {
|
||||
showUserNotifications: true,
|
||||
logToConsole: true,
|
||||
reportToExternal: process.env.NODE_ENV === 'production',
|
||||
});
|
||||
|
||||
const teamsApiClient = new TeamsApiClient(baseUrl, errorReporter, logger);
|
||||
const service = new TeamService(teamsApiClient);
|
||||
const service = new TeamService();
|
||||
|
||||
// Fetch team details
|
||||
const teamData = await service.getTeamDetails(teamId, currentDriverId);
|
||||
const teamResult = await service.getTeamDetails(teamId, currentDriverId);
|
||||
|
||||
if (!teamData) {
|
||||
if (teamResult.isErr()) {
|
||||
return { status: 'notFound' };
|
||||
}
|
||||
|
||||
const teamData = teamResult.unwrap();
|
||||
|
||||
// Fetch team members
|
||||
const membersData = await service.getTeamMembers(teamId, currentDriverId, teamData.ownerId);
|
||||
const membersResult = await service.getTeamMembers(teamId, currentDriverId, teamData.ownerId);
|
||||
|
||||
if (membersResult.isErr()) {
|
||||
return { status: 'error', errorId: 'TEAM_MEMBERS_FETCH_FAILED' };
|
||||
}
|
||||
|
||||
const membersData = membersResult.unwrap();
|
||||
|
||||
// Transform to raw serializable DTO
|
||||
const dto: TeamDetailPageDto = {
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import { TeamsApiClient } from '@/lib/api/teams/TeamsApiClient';
|
||||
import type { PageQueryResult } from '@/lib/contracts/page-queries/PageQueryResult';
|
||||
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
|
||||
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
import { TeamService } from '@/lib/services/teams/TeamService';
|
||||
import type { TeamSummaryViewModel } from '@/lib/view-models/TeamSummaryViewModel';
|
||||
|
||||
@@ -39,19 +36,16 @@ export class TeamsPageQuery {
|
||||
static async execute(): Promise<PageQueryResult<TeamsPageDto>> {
|
||||
try {
|
||||
// Manual dependency creation
|
||||
const baseUrl = process.env.API_BASE_URL || 'http://localhost:3101';
|
||||
const logger = new ConsoleLogger();
|
||||
const errorReporter = new EnhancedErrorReporter(logger, {
|
||||
showUserNotifications: true,
|
||||
logToConsole: true,
|
||||
reportToExternal: process.env.NODE_ENV === 'production',
|
||||
});
|
||||
|
||||
const teamsApiClient = new TeamsApiClient(baseUrl, errorReporter, logger);
|
||||
const service = new TeamService(teamsApiClient);
|
||||
const service = new TeamService();
|
||||
|
||||
// Fetch teams
|
||||
const teams = await service.getAllTeams();
|
||||
const result = await service.getAllTeams();
|
||||
|
||||
if (result.isErr()) {
|
||||
return { status: 'error', errorId: 'TEAMS_FETCH_FAILED' };
|
||||
}
|
||||
|
||||
const teams = result.unwrap();
|
||||
|
||||
if (!teams || teams.length === 0) {
|
||||
return { status: 'notFound' };
|
||||
|
||||
41
apps/website/lib/page-queries/races/RaceDetailPageQuery.ts
Normal file
41
apps/website/lib/page-queries/races/RaceDetailPageQuery.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
import { RaceDetailViewData } from '@/lib/view-data/races/RaceDetailViewData';
|
||||
import { RacesService } from '@/lib/services/races/RacesService';
|
||||
import { RaceDetailViewDataBuilder } from '@/lib/builders/view-data/RaceDetailViewDataBuilder';
|
||||
|
||||
interface RaceDetailPageQueryParams {
|
||||
raceId: string;
|
||||
driverId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Race Detail Page Query
|
||||
*
|
||||
* Fetches race detail data for the race detail page.
|
||||
* Returns Result<RaceDetailViewData, PresentationError>
|
||||
*/
|
||||
export class RaceDetailPageQuery implements PageQuery<RaceDetailViewData, RaceDetailPageQueryParams> {
|
||||
async execute(params: RaceDetailPageQueryParams): Promise<Result<RaceDetailViewData, PresentationError>> {
|
||||
// Manual wiring: Service creates its own dependencies
|
||||
const service = new RacesService();
|
||||
|
||||
// Get race detail data
|
||||
const result = await service.getRaceDetail(params.raceId, params.driverId || '');
|
||||
|
||||
if (result.isErr()) {
|
||||
return Result.err(mapToPresentationError(result.getError()));
|
||||
}
|
||||
|
||||
// Transform to ViewData using builder
|
||||
const viewData = RaceDetailViewDataBuilder.build(result.unwrap());
|
||||
return Result.ok(viewData);
|
||||
}
|
||||
|
||||
// Static method to avoid object construction in server code
|
||||
static async execute(params: RaceDetailPageQueryParams): Promise<Result<RaceDetailViewData, PresentationError>> {
|
||||
const query = new RaceDetailPageQuery();
|
||||
return await query.execute(params);
|
||||
}
|
||||
}
|
||||
41
apps/website/lib/page-queries/races/RaceResultsPageQuery.ts
Normal file
41
apps/website/lib/page-queries/races/RaceResultsPageQuery.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
import { RaceResultsViewData } from '@/lib/view-data/races/RaceResultsViewData';
|
||||
import { RaceResultsService } from '@/lib/services/races/RaceResultsService';
|
||||
import { RaceResultsViewDataBuilder } from '@/lib/builders/view-data/RaceResultsViewDataBuilder';
|
||||
|
||||
interface RaceResultsPageQueryParams {
|
||||
raceId: string;
|
||||
driverId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Race Results Page Query
|
||||
*
|
||||
* Fetches race results data for the race results page.
|
||||
* Returns Result<RaceResultsViewData, PresentationError>
|
||||
*/
|
||||
export class RaceResultsPageQuery implements PageQuery<RaceResultsViewData, RaceResultsPageQueryParams> {
|
||||
async execute(params: RaceResultsPageQueryParams): Promise<Result<RaceResultsViewData, PresentationError>> {
|
||||
// Manual wiring: Service creates its own dependencies
|
||||
const service = new RaceResultsService();
|
||||
|
||||
// Get race results data
|
||||
const result = await service.getRaceResultsDetail(params.raceId);
|
||||
|
||||
if (result.isErr()) {
|
||||
return Result.err(mapToPresentationError(result.getError()));
|
||||
}
|
||||
|
||||
// Transform to ViewData using builder
|
||||
const viewData = RaceResultsViewDataBuilder.build(result.unwrap());
|
||||
return Result.ok(viewData);
|
||||
}
|
||||
|
||||
// Static method to avoid object construction in server code
|
||||
static async execute(params: RaceResultsPageQueryParams): Promise<Result<RaceResultsViewData, PresentationError>> {
|
||||
const query = new RaceResultsPageQuery();
|
||||
return await query.execute(params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
import { RaceStewardingViewData } from '@/lib/view-data/races/RaceStewardingViewData';
|
||||
import { RaceStewardingService } from '@/lib/services/races/RaceStewardingService';
|
||||
import { RaceStewardingViewDataBuilder } from '@/lib/builders/view-data/RaceStewardingViewDataBuilder';
|
||||
|
||||
interface RaceStewardingPageQueryParams {
|
||||
raceId: string;
|
||||
driverId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Race Stewarding Page Query
|
||||
*
|
||||
* Fetches race stewarding data for the stewarding page.
|
||||
* Returns Result<RaceStewardingViewData, PresentationError>
|
||||
*/
|
||||
export class RaceStewardingPageQuery implements PageQuery<RaceStewardingViewData, RaceStewardingPageQueryParams> {
|
||||
async execute(params: RaceStewardingPageQueryParams): Promise<Result<RaceStewardingViewData, PresentationError>> {
|
||||
// Manual wiring: Service creates its own dependencies
|
||||
const service = new RaceStewardingService();
|
||||
|
||||
// Get race stewarding data
|
||||
const result = await service.getRaceStewarding(params.raceId);
|
||||
|
||||
if (result.isErr()) {
|
||||
return Result.err(mapToPresentationError(result.getError()));
|
||||
}
|
||||
|
||||
// Transform to ViewData using builder
|
||||
const viewData = RaceStewardingViewDataBuilder.build(result.unwrap());
|
||||
return Result.ok(viewData);
|
||||
}
|
||||
|
||||
// Static method to avoid object construction in server code
|
||||
static async execute(params: RaceStewardingPageQueryParams): Promise<Result<RaceStewardingViewData, PresentationError>> {
|
||||
const query = new RaceStewardingPageQuery();
|
||||
return await query.execute(params);
|
||||
}
|
||||
}
|
||||
36
apps/website/lib/page-queries/races/RacesAllPageQuery.ts
Normal file
36
apps/website/lib/page-queries/races/RacesAllPageQuery.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
import { RacesAllViewData } from '@/lib/view-data/races/RacesAllViewData';
|
||||
import { RacesService } from '@/lib/services/races/RacesService';
|
||||
import { RacesAllViewDataBuilder } from '@/lib/builders/view-data/RacesAllViewDataBuilder';
|
||||
|
||||
/**
|
||||
* Races All Page Query
|
||||
*
|
||||
* Fetches all races data for the all races page.
|
||||
* Returns Result<RacesAllViewData, PresentationError>
|
||||
*/
|
||||
export class RacesAllPageQuery implements PageQuery<RacesAllViewData, void> {
|
||||
async execute(): Promise<Result<RacesAllViewData, PresentationError>> {
|
||||
// Manual wiring: Service creates its own dependencies
|
||||
const service = new RacesService();
|
||||
|
||||
// Get all races data
|
||||
const result = await service.getAllRacesPageData();
|
||||
|
||||
if (result.isErr()) {
|
||||
return Result.err(mapToPresentationError(result.getError()));
|
||||
}
|
||||
|
||||
// Transform to ViewData using builder
|
||||
const viewData = RacesAllViewDataBuilder.build(result.unwrap());
|
||||
return Result.ok(viewData);
|
||||
}
|
||||
|
||||
// Static method to avoid object construction in server code
|
||||
static async execute(): Promise<Result<RacesAllViewData, PresentationError>> {
|
||||
const query = new RacesAllPageQuery();
|
||||
return await query.execute();
|
||||
}
|
||||
}
|
||||
36
apps/website/lib/page-queries/races/RacesPageQuery.ts
Normal file
36
apps/website/lib/page-queries/races/RacesPageQuery.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
import { RacesViewData } from '@/lib/view-data/races/RacesViewData';
|
||||
import { RacesService } from '@/lib/services/races/RacesService';
|
||||
import { RacesViewDataBuilder } from '@/lib/builders/view-data/RacesViewDataBuilder';
|
||||
|
||||
/**
|
||||
* Races Page Query
|
||||
*
|
||||
* Fetches races data for the main races page.
|
||||
* Returns Result<RacesViewData, PresentationError>
|
||||
*/
|
||||
export class RacesPageQuery implements PageQuery<RacesViewData, void> {
|
||||
async execute(): Promise<Result<RacesViewData, PresentationError>> {
|
||||
// Manual wiring: Service creates its own dependencies
|
||||
const service = new RacesService();
|
||||
|
||||
// Get races data
|
||||
const result = await service.getRacesPageData();
|
||||
|
||||
if (result.isErr()) {
|
||||
return Result.err(mapToPresentationError(result.getError()));
|
||||
}
|
||||
|
||||
// Transform to ViewData using builder
|
||||
const viewData = RacesViewDataBuilder.build(result.unwrap());
|
||||
return Result.ok(viewData);
|
||||
}
|
||||
|
||||
// Static method to avoid object construction in server code
|
||||
static async execute(): Promise<Result<RacesViewData, PresentationError>> {
|
||||
const query = new RacesPageQuery();
|
||||
return await query.execute();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user