website refactor

This commit is contained in:
2026-01-14 16:28:39 +01:00
parent 85e09b6f4d
commit 4b7d82ab43
119 changed files with 2403 additions and 1615 deletions

View File

@@ -11,20 +11,14 @@ import { PresentationError, mapToPresentationError } from '@/lib/contracts/page-
* Server-side composition for admin users page.
* Fetches user list from API and transforms to ViewData.
*/
export class AdminUsersPageQuery implements PageQuery<AdminUsersViewData, { search?: string; role?: string; status?: string; page?: number; limit?: number }> {
async execute(query: { search?: string; role?: string; status?: string; page?: number; limit?: number }): Promise<Result<AdminUsersViewData, PresentationError>> {
export class AdminUsersPageQuery implements PageQuery<AdminUsersViewData, void> {
async execute(): Promise<Result<AdminUsersViewData, PresentationError>> {
try {
// Manual construction: Service creates its own dependencies
const adminService = new AdminService();
// Fetch user list via service
const apiDtoResult = await adminService.listUsers({
search: query.search,
role: query.role,
status: query.status,
page: query.page || 1,
limit: query.limit || 50,
});
const apiDtoResult = await adminService.listUsers();
if (apiDtoResult.isErr()) {
return Result.err(mapToPresentationError(apiDtoResult.getError()));
@@ -46,8 +40,8 @@ export class AdminUsersPageQuery implements PageQuery<AdminUsersViewData, { sear
}
// Static method to avoid object construction in server code
static async execute(query: { search?: string; role?: string; status?: string; page?: number; limit?: number }): Promise<Result<AdminUsersViewData, PresentationError>> {
static async execute(): Promise<Result<AdminUsersViewData, PresentationError>> {
const queryInstance = new AdminUsersPageQuery();
return queryInstance.execute(query);
return queryInstance.execute();
}
}

View File

@@ -0,0 +1,38 @@
import { Result } from '@/lib/contracts/Result';
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
import { SponsorService } from '@/lib/services/sponsors/SponsorService';
import { SponsorDashboardViewDataBuilder } from '@/lib/builders/view-data/SponsorDashboardViewDataBuilder';
import type { SponsorDashboardViewData } from '@/lib/view-data/SponsorDashboardViewData';
/**
* Sponsor Dashboard Page Query
*
* Composes data for the sponsor dashboard page.
* Maps domain errors to presentation errors.
*/
export class SponsorDashboardPageQuery implements PageQuery<SponsorDashboardViewData, string> {
async execute(sponsorId: string): Promise<Result<SponsorDashboardViewData, string>> {
const service = new SponsorService();
const dashboardResult = await service.getSponsorDashboard(sponsorId);
if (dashboardResult.isErr()) {
return Result.err(this.mapToPresentationError(dashboardResult.getError()));
}
const dto = dashboardResult.unwrap();
const viewData = SponsorDashboardViewDataBuilder.build(dto);
return Result.ok(viewData);
}
private mapToPresentationError(domainError: { type: string }): string {
switch (domainError.type) {
case 'notFound':
return 'Dashboard not found';
case 'notImplemented':
return 'Dashboard feature not yet implemented';
default:
return 'Failed to load dashboard';
}
}
}

View File

@@ -1,26 +1,25 @@
import type { PageQueryResult } from '@/lib/contracts/page-queries/PageQueryResult';
import { Result } from '@/lib/contracts/Result';
import { DriverProfilePageService } from '@/lib/services/drivers/DriverProfilePageService';
import { DriverProfileViewModelBuilder } from '@/lib/builders/view-models/DriverProfileViewModelBuilder';
import type { DriverProfileViewModel } from '@/lib/view-models/DriverProfileViewModel';
import { DriverProfileViewDataBuilder } from '@/lib/builders/view-data/DriverProfileViewDataBuilder';
import type { DriverProfileViewData } from '@/lib/types/view-data/DriverProfileViewData';
/**
* DriverProfilePageQuery
*
* Server-side data fetcher for the driver profile page.
* Returns a discriminated union with all possible page states.
* Uses Service for data access and ViewModelBuilder for transformation.
* Returns Result<ViewData, PresentationError>
* Uses Service for data access and ViewDataBuilder for transformation.
*/
export class DriverProfilePageQuery {
/**
* Execute the driver profile page query
*
* @param driverId - The driver ID to fetch profile for
* @returns PageQueryResult with discriminated union of states
* @returns Result with ViewData or error
*/
static async execute(driverId: string | null): Promise<PageQueryResult<DriverProfileViewModel>> {
// Handle missing driver ID
static async execute(driverId: string | null): Promise<Result<DriverProfileViewData, string>> {
if (!driverId) {
return { status: 'notFound' };
return Result.err('NotFound');
}
try {
@@ -31,26 +30,26 @@ export class DriverProfilePageQuery {
if (result.isErr()) {
const error = result.getError();
if (error === 'notFound') {
return { status: 'notFound' };
return Result.err('NotFound');
}
if (error === 'unauthorized') {
return { status: 'error', errorId: 'UNAUTHORIZED' };
return Result.err('Unauthorized');
}
return { status: 'error', errorId: 'DRIVER_PROFILE_FETCH_FAILED' };
return Result.err('Error');
}
// Build ViewModel from DTO
// Build ViewData from DTO
const dto = result.unwrap();
const viewModel = DriverProfileViewModelBuilder.build(dto);
return { status: 'ok', dto: viewModel };
const viewData = DriverProfileViewDataBuilder.build(dto);
return Result.ok(viewData);
} catch (error) {
console.error('DriverProfilePageQuery failed:', error);
return { status: 'error', errorId: 'DRIVER_PROFILE_FETCH_FAILED' };
return Result.err('Error');
}
}
}

View File

@@ -1,22 +1,22 @@
import type { PageQueryResult } from '@/lib/contracts/page-queries/PageQueryResult';
import { Result } from '@/lib/contracts/Result';
import { DriversPageService } from '@/lib/services/drivers/DriversPageService';
import { DriversViewModelBuilder } from '@/lib/builders/view-models/DriversViewModelBuilder';
import type { DriverLeaderboardViewModel } from '@/lib/view-models/DriverLeaderboardViewModel';
import { DriversViewDataBuilder } from '@/lib/builders/view-data/DriversViewDataBuilder';
import type { DriversViewData } from '@/lib/types/view-data/DriversViewData';
/**
* DriversPageQuery
*
* Server-side data fetcher for the drivers listing page.
* Returns a discriminated union with all possible page states.
* Uses Service for data access and ViewModelBuilder for transformation.
* Returns Result<ViewData, PresentationError>
* Uses Service for data access and ViewDataBuilder for transformation.
*/
export class DriversPageQuery {
/**
* Execute the drivers page query
*
* @returns PageQueryResult with discriminated union of states
* @returns Result with ViewData or error
*/
static async execute(): Promise<PageQueryResult<DriverLeaderboardViewModel>> {
static async execute(): Promise<Result<DriversViewData, string>> {
try {
// Manual wiring: construct dependencies explicitly
const service = new DriversPageService();
@@ -25,22 +25,22 @@ export class DriversPageQuery {
if (result.isErr()) {
const error = result.getError();
if (error === 'notFound') {
return { status: 'notFound' };
return Result.err('NotFound');
}
return { status: 'error', errorId: 'DRIVERS_FETCH_FAILED' };
return Result.err('Error');
}
// Build ViewModel from DTO
// Build ViewData from DTO
const dto = result.unwrap();
const viewModel = DriversViewModelBuilder.build(dto);
return { status: 'ok', dto: viewModel };
const viewData = DriversViewDataBuilder.build(dto);
return Result.ok(viewData);
} catch (error) {
console.error('DriversPageQuery failed:', error);
return { status: 'error', errorId: 'DRIVERS_FETCH_FAILED' };
return Result.err('Error');
}
}
}

View File

@@ -2,10 +2,7 @@ import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
import { Result } from '@/lib/contracts/Result';
import { LeaguesViewDataBuilder } from '@/lib/builders/view-data/LeaguesViewDataBuilder';
import type { LeaguesViewData } from '@/lib/view-data/LeaguesViewData';
import { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient';
import { DomainError } from '@/lib/contracts/services/Service';
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { LeagueService } from '@/lib/services/leagues/LeagueService';
/**
* Leagues page query
@@ -14,40 +11,30 @@ import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
*/
export class LeaguesPageQuery implements PageQuery<LeaguesViewData, void> {
async execute(): Promise<Result<LeaguesViewData, 'notFound' | 'redirect' | 'LEAGUES_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);
// Fetch data using API client
try {
const apiDto = await apiClient.getAllWithCapacityAndScoring();
if (!apiDto || !apiDto.leagues) {
return Result.err('notFound');
}
// Transform to ViewData using builder
const viewData = LeaguesViewDataBuilder.build(apiDto);
return Result.ok(viewData);
} catch (error) {
console.error('LeaguesPageQuery failed:', error);
if (error instanceof Error) {
if (error.message.includes('403') || error.message.includes('401')) {
return Result.err('redirect');
}
if (error.message.includes('404')) {
// Manual construction: Service creates its own dependencies
const service = new LeagueService();
// Fetch data using service
const result = await service.getAllLeagues();
if (result.isErr()) {
const error = result.getError();
switch (error.type) {
case 'notFound':
return Result.err('notFound');
}
if (error.message.includes('5') || error.message.includes('server')) {
case 'unauthorized':
case 'forbidden':
return Result.err('redirect');
case 'serverError':
return Result.err('LEAGUES_FETCH_FAILED');
}
default:
return Result.err('UNKNOWN_ERROR');
}
return Result.err('UNKNOWN_ERROR');
}
// Transform to ViewData using builder
const viewData = LeaguesViewDataBuilder.build(result.unwrap());
return Result.ok(viewData);
}
// Static method to avoid object construction in server code