website refactor
This commit is contained in:
@@ -13,32 +13,22 @@ import { PresentationError, mapToPresentationError } from '@/lib/contracts/page-
|
||||
*
|
||||
* Follows Clean Architecture: Uses builders for transformation.
|
||||
*/
|
||||
export class AdminDashboardPageQuery implements PageQuery<AdminDashboardViewData, void> {
|
||||
export class AdminDashboardPageQuery implements PageQuery<AdminDashboardViewData, void, PresentationError> {
|
||||
async execute(): Promise<Result<AdminDashboardViewData, PresentationError>> {
|
||||
try {
|
||||
// Manual construction: Service creates its own dependencies
|
||||
const adminService = new AdminService();
|
||||
// Manual construction: Service creates its own dependencies
|
||||
const adminService = new AdminService();
|
||||
|
||||
// Fetch dashboard stats
|
||||
const apiDtoResult = await adminService.getDashboardStats();
|
||||
// Fetch dashboard stats
|
||||
const apiDtoResult = await adminService.getDashboardStats();
|
||||
|
||||
if (apiDtoResult.isErr()) {
|
||||
return Result.err(mapToPresentationError(apiDtoResult.getError()));
|
||||
}
|
||||
|
||||
// Transform to ViewData using builder
|
||||
const output = AdminDashboardViewDataBuilder.build(apiDtoResult.unwrap());
|
||||
|
||||
return Result.ok(output);
|
||||
} catch (err) {
|
||||
console.error('AdminDashboardPageQuery failed:', err);
|
||||
|
||||
if (err instanceof Error && (err.message.includes('403') || err.message.includes('401'))) {
|
||||
return Result.err('notFound');
|
||||
}
|
||||
|
||||
return Result.err('serverError');
|
||||
if (apiDtoResult.isErr()) {
|
||||
return Result.err(mapToPresentationError(apiDtoResult.getError()));
|
||||
}
|
||||
|
||||
// Transform to ViewData using builder
|
||||
const output = AdminDashboardViewDataBuilder.build(apiDtoResult.unwrap());
|
||||
|
||||
return Result.ok(output);
|
||||
}
|
||||
|
||||
// Static method to avoid object construction in server code
|
||||
@@ -46,4 +36,4 @@ export class AdminDashboardPageQuery implements PageQuery<AdminDashboardViewData
|
||||
const query = new AdminDashboardPageQuery();
|
||||
return query.execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
*
|
||||
* 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'>> {
|
||||
export class CreateLeaguePageQuery implements PageQuery<unknown, void> {
|
||||
async execute(): Promise<Result<unknown, '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();
|
||||
@@ -43,7 +43,7 @@ export class CreateLeaguePageQuery implements PageQuery<any, void> {
|
||||
}
|
||||
}
|
||||
|
||||
static async execute(): Promise<Result<any, 'notFound' | 'redirect' | 'CREATE_LEAGUE_FETCH_FAILED' | 'UNKNOWN_ERROR'>> {
|
||||
static async execute(): Promise<Result<unknown, 'notFound' | 'redirect' | 'CREATE_LEAGUE_FETCH_FAILED' | 'UNKNOWN_ERROR'>> {
|
||||
const query = new CreateLeaguePageQuery();
|
||||
return query.execute();
|
||||
}
|
||||
28
apps/website/lib/page-queries/LeagueDetailPageQuery.ts
Normal file
28
apps/website/lib/page-queries/LeagueDetailPageQuery.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { LeagueService } from '@/lib/services/leagues/LeagueService';
|
||||
import { type PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
|
||||
/**
|
||||
* LeagueDetail page query
|
||||
* Returns the raw API DTO for the league detail page
|
||||
* No DI container usage - constructs dependencies explicitly
|
||||
*/
|
||||
export class LeagueDetailPageQuery implements PageQuery<unknown, string, PresentationError> {
|
||||
async execute(leagueId: string): Promise<Result<unknown, PresentationError>> {
|
||||
const service = new LeagueService();
|
||||
const result = await service.getLeagueDetailData(leagueId);
|
||||
|
||||
if (result.isErr()) {
|
||||
return Result.err(mapToPresentationError(result.getError()));
|
||||
}
|
||||
|
||||
return Result.ok(result.unwrap());
|
||||
}
|
||||
|
||||
// Static method to avoid object construction in server code
|
||||
static async execute(leagueId: string): Promise<Result<unknown, PresentationError>> {
|
||||
const query = new LeagueDetailPageQuery();
|
||||
return query.execute(leagueId);
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,7 @@ import { Result } from '@/lib/contracts/Result';
|
||||
import { ProtestDetailService } from '@/lib/services/leagues/ProtestDetailService';
|
||||
import { ProtestDetailViewDataBuilder } from '@/lib/builders/view-data/ProtestDetailViewDataBuilder';
|
||||
import { ProtestDetailViewData } from '@/lib/view-data/leagues/ProtestDetailViewData';
|
||||
|
||||
interface PresentationError {
|
||||
type: 'notFound' | 'forbidden' | 'serverError';
|
||||
message: string;
|
||||
}
|
||||
import { type PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
|
||||
export class LeagueProtestDetailPageQuery implements PageQuery<ProtestDetailViewData, { leagueId: string; protestId: string }, PresentationError> {
|
||||
async execute(params: { leagueId: string; protestId: string }): Promise<Result<ProtestDetailViewData, PresentationError>> {
|
||||
@@ -15,7 +11,7 @@ export class LeagueProtestDetailPageQuery implements PageQuery<ProtestDetailView
|
||||
const result = await service.getProtestDetail(params.leagueId, params.protestId);
|
||||
|
||||
if (result.isErr()) {
|
||||
return Result.err({ type: 'serverError', message: 'Failed to load protest details' });
|
||||
return Result.err(mapToPresentationError(result.getError()));
|
||||
}
|
||||
|
||||
const viewData = ProtestDetailViewDataBuilder.build(result.unwrap());
|
||||
@@ -10,14 +10,14 @@ import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
*
|
||||
* 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'>> {
|
||||
export class LeagueProtestReviewPageQuery implements PageQuery<unknown, { leagueId: string; protestId: string }> {
|
||||
async execute(input: { leagueId: string; protestId: string }): Promise<Result<unknown, '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);
|
||||
new LeaguesApiClient(baseUrl, errorReporter, logger);
|
||||
new ProtestsApiClient(baseUrl, errorReporter, logger);
|
||||
|
||||
try {
|
||||
// Get protest details
|
||||
@@ -70,7 +70,7 @@ export class LeagueProtestReviewPageQuery implements PageQuery<any, { leagueId:
|
||||
}
|
||||
}
|
||||
|
||||
static async execute(input: { leagueId: string; protestId: string }): Promise<Result<any, 'notFound' | 'redirect' | 'PROTEST_FETCH_FAILED' | 'UNKNOWN_ERROR'>> {
|
||||
static async execute(input: { leagueId: string; protestId: string }): Promise<Result<unknown, 'notFound' | 'redirect' | 'PROTEST_FETCH_FAILED' | 'UNKNOWN_ERROR'>> {
|
||||
const query = new LeagueProtestReviewPageQuery();
|
||||
return query.execute(input);
|
||||
}
|
||||
29
apps/website/lib/page-queries/LeagueRosterAdminPageQuery.ts
Normal file
29
apps/website/lib/page-queries/LeagueRosterAdminPageQuery.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { LeagueService } from '@/lib/services/leagues/LeagueService';
|
||||
import { LeagueRosterAdminViewDataBuilder } from '@/lib/builders/view-data/LeagueRosterAdminViewDataBuilder';
|
||||
import { type PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
|
||||
/**
|
||||
* LeagueRosterAdminPageQuery
|
||||
*
|
||||
* Fetches league roster admin data (members and join requests).
|
||||
*/
|
||||
export class LeagueRosterAdminPageQuery implements PageQuery<unknown, string, PresentationError> {
|
||||
async execute(leagueId: string): Promise<Result<unknown, PresentationError>> {
|
||||
const service = new LeagueService();
|
||||
const result = await service.getRosterAdminData(leagueId);
|
||||
|
||||
if (result.isErr()) {
|
||||
return Result.err(mapToPresentationError(result.getError()));
|
||||
}
|
||||
|
||||
const viewData = LeagueRosterAdminViewDataBuilder.build(result.unwrap());
|
||||
return Result.ok(viewData);
|
||||
}
|
||||
|
||||
static async execute(leagueId: string): Promise<Result<unknown, PresentationError>> {
|
||||
const query = new LeagueRosterAdminPageQuery();
|
||||
return query.execute(leagueId);
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,7 @@ import { Result } from '@/lib/contracts/Result';
|
||||
import { LeagueRulebookService } from '@/lib/services/leagues/LeagueRulebookService';
|
||||
import { RulebookViewDataBuilder } from '@/lib/builders/view-data/RulebookViewDataBuilder';
|
||||
import { RulebookViewData } from '@/lib/view-data/leagues/RulebookViewData';
|
||||
|
||||
interface PresentationError {
|
||||
type: 'notFound' | 'forbidden' | 'serverError';
|
||||
message: string;
|
||||
}
|
||||
import { type PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
|
||||
export class LeagueRulebookPageQuery implements PageQuery<RulebookViewData, string, PresentationError> {
|
||||
async execute(leagueId: string): Promise<Result<RulebookViewData, PresentationError>> {
|
||||
@@ -15,7 +11,7 @@ export class LeagueRulebookPageQuery implements PageQuery<RulebookViewData, stri
|
||||
const result = await service.getRulebookData(leagueId);
|
||||
|
||||
if (result.isErr()) {
|
||||
return Result.err({ type: 'serverError', message: 'Failed to load rulebook data' });
|
||||
return Result.err(mapToPresentationError(result.getError()));
|
||||
}
|
||||
|
||||
const viewData = RulebookViewDataBuilder.build(result.unwrap());
|
||||
@@ -26,4 +22,4 @@ export class LeagueRulebookPageQuery implements PageQuery<RulebookViewData, stri
|
||||
const query = new LeagueRulebookPageQuery();
|
||||
return query.execute(leagueId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { LeagueService } from '@/lib/services/leagues/LeagueService';
|
||||
import { LeagueScheduleViewDataBuilder } from '@/lib/builders/view-data/LeagueScheduleViewDataBuilder';
|
||||
import { type PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
|
||||
/**
|
||||
* LeagueScheduleAdminPageQuery
|
||||
*
|
||||
* Fetches league schedule admin data.
|
||||
*/
|
||||
export class LeagueScheduleAdminPageQuery implements PageQuery<unknown, { leagueId: string; seasonId?: string }> {
|
||||
async execute(input: { leagueId: string; seasonId?: string }): Promise<Result<unknown, PresentationError>> {
|
||||
const service = new LeagueService();
|
||||
const result = await service.getScheduleAdminData(input.leagueId, input.seasonId);
|
||||
|
||||
if (result.isErr()) {
|
||||
return Result.err(mapToPresentationError(result.getError()));
|
||||
}
|
||||
|
||||
const data = result.unwrap();
|
||||
const viewData = LeagueScheduleViewDataBuilder.build({
|
||||
leagueId: data.leagueId,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
races: data.schedule.races.map((r: any) => ({
|
||||
id: r.id,
|
||||
name: r.name,
|
||||
date: r.scheduledAt,
|
||||
track: r.track,
|
||||
car: r.car,
|
||||
sessionType: r.sessionType,
|
||||
})),
|
||||
});
|
||||
return Result.ok(viewData);
|
||||
}
|
||||
|
||||
static async execute(input: { leagueId: string; seasonId?: string }): Promise<Result<unknown, PresentationError>> {
|
||||
const query = new LeagueScheduleAdminPageQuery();
|
||||
return query.execute(input);
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,7 @@ import { Result } from '@/lib/contracts/Result';
|
||||
import { LeagueScheduleService } from '@/lib/services/leagues/LeagueScheduleService';
|
||||
import { LeagueScheduleViewDataBuilder } from '@/lib/builders/view-data/LeagueScheduleViewDataBuilder';
|
||||
import { LeagueScheduleViewData } from '@/lib/view-data/leagues/LeagueScheduleViewData';
|
||||
|
||||
interface PresentationError {
|
||||
type: 'notFound' | 'forbidden' | 'serverError';
|
||||
message: string;
|
||||
}
|
||||
import { type PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
|
||||
export class LeagueSchedulePageQuery implements PageQuery<LeagueScheduleViewData, string, PresentationError> {
|
||||
async execute(leagueId: string): Promise<Result<LeagueScheduleViewData, PresentationError>> {
|
||||
@@ -15,7 +11,7 @@ export class LeagueSchedulePageQuery implements PageQuery<LeagueScheduleViewData
|
||||
const result = await service.getScheduleData(leagueId);
|
||||
|
||||
if (result.isErr()) {
|
||||
return Result.err({ type: 'serverError', message: 'Failed to load schedule data' });
|
||||
return Result.err(mapToPresentationError(result.getError()));
|
||||
}
|
||||
|
||||
const viewData = LeagueScheduleViewDataBuilder.build(result.unwrap());
|
||||
@@ -26,4 +22,4 @@ export class LeagueSchedulePageQuery implements PageQuery<LeagueScheduleViewData
|
||||
const query = new LeagueSchedulePageQuery();
|
||||
return query.execute(leagueId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,7 @@ import { Result } from '@/lib/contracts/Result';
|
||||
import { LeagueSettingsService } from '@/lib/services/leagues/LeagueSettingsService';
|
||||
import { LeagueSettingsViewDataBuilder } from '@/lib/builders/view-data/LeagueSettingsViewDataBuilder';
|
||||
import { LeagueSettingsViewData } from '@/lib/view-data/leagues/LeagueSettingsViewData';
|
||||
|
||||
interface PresentationError {
|
||||
type: 'notFound' | 'forbidden' | 'serverError';
|
||||
message: string;
|
||||
}
|
||||
import { type PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
|
||||
export class LeagueSettingsPageQuery implements PageQuery<LeagueSettingsViewData, string, PresentationError> {
|
||||
async execute(leagueId: string): Promise<Result<LeagueSettingsViewData, PresentationError>> {
|
||||
@@ -15,7 +11,7 @@ export class LeagueSettingsPageQuery implements PageQuery<LeagueSettingsViewData
|
||||
const result = await service.getSettingsData(leagueId);
|
||||
|
||||
if (result.isErr()) {
|
||||
return Result.err({ type: 'serverError', message: 'Failed to load settings data' });
|
||||
return Result.err(mapToPresentationError(result.getError()));
|
||||
}
|
||||
|
||||
const viewData = LeagueSettingsViewDataBuilder.build(result.unwrap());
|
||||
@@ -26,4 +22,4 @@ export class LeagueSettingsPageQuery implements PageQuery<LeagueSettingsViewData
|
||||
const query = new LeagueSettingsPageQuery();
|
||||
return query.execute(leagueId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,7 @@ import { Result } from '@/lib/contracts/Result';
|
||||
import { LeagueSponsorshipsService } from '@/lib/services/leagues/LeagueSponsorshipsService';
|
||||
import { LeagueSponsorshipsViewDataBuilder } from '@/lib/builders/view-data/LeagueSponsorshipsViewDataBuilder';
|
||||
import { LeagueSponsorshipsViewData } from '@/lib/view-data/leagues/LeagueSponsorshipsViewData';
|
||||
|
||||
interface PresentationError {
|
||||
type: 'notFound' | 'forbidden' | 'serverError';
|
||||
message: string;
|
||||
}
|
||||
import { type PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
|
||||
export class LeagueSponsorshipsPageQuery implements PageQuery<LeagueSponsorshipsViewData, string, PresentationError> {
|
||||
async execute(leagueId: string): Promise<Result<LeagueSponsorshipsViewData, PresentationError>> {
|
||||
@@ -15,7 +11,7 @@ export class LeagueSponsorshipsPageQuery implements PageQuery<LeagueSponsorships
|
||||
const result = await service.getSponsorshipsData(leagueId);
|
||||
|
||||
if (result.isErr()) {
|
||||
return Result.err({ type: 'serverError', message: 'Failed to load sponsorships data' });
|
||||
return Result.err(mapToPresentationError(result.getError()));
|
||||
}
|
||||
|
||||
const viewData = LeagueSponsorshipsViewDataBuilder.build(result.unwrap());
|
||||
@@ -26,4 +22,4 @@ export class LeagueSponsorshipsPageQuery implements PageQuery<LeagueSponsorships
|
||||
const query = new LeagueSponsorshipsPageQuery();
|
||||
return query.execute(leagueId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,7 @@ import { Result } from '@/lib/contracts/Result';
|
||||
import { LeagueStandingsService } from '@/lib/services/leagues/LeagueStandingsService';
|
||||
import { LeagueStandingsViewDataBuilder } from '@/lib/builders/view-data/LeagueStandingsViewDataBuilder';
|
||||
import { LeagueStandingsViewData } from '@/lib/view-data/LeagueStandingsViewData';
|
||||
|
||||
interface PresentationError {
|
||||
type: 'notFound' | 'forbidden' | 'serverError';
|
||||
message: string;
|
||||
}
|
||||
import { type PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
|
||||
export class LeagueStandingsPageQuery implements PageQuery<LeagueStandingsViewData, string, PresentationError> {
|
||||
async execute(leagueId: string): Promise<Result<LeagueStandingsViewData, PresentationError>> {
|
||||
@@ -15,7 +11,7 @@ export class LeagueStandingsPageQuery implements PageQuery<LeagueStandingsViewDa
|
||||
const result = await service.getStandingsData(leagueId);
|
||||
|
||||
if (result.isErr()) {
|
||||
return Result.err({ type: 'serverError', message: 'Failed to load standings data' });
|
||||
return Result.err(mapToPresentationError(result.getError()));
|
||||
}
|
||||
|
||||
const { standings, memberships } = result.unwrap();
|
||||
@@ -27,4 +23,4 @@ export class LeagueStandingsPageQuery implements PageQuery<LeagueStandingsViewDa
|
||||
const query = new LeagueStandingsPageQuery();
|
||||
return query.execute(leagueId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,7 @@ import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { LeagueStewardingService } from '@/lib/services/leagues/LeagueStewardingService';
|
||||
import { StewardingViewDataBuilder } from '@/lib/builders/view-data/StewardingViewDataBuilder';
|
||||
import { StewardingViewData } from '@/lib/view-data/leagues/StewardingViewData';
|
||||
|
||||
interface PresentationError {
|
||||
type: 'notFound' | 'forbidden' | 'notImplemented' | 'serverError';
|
||||
message: string;
|
||||
}
|
||||
import { type PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
|
||||
export class LeagueStewardingPageQuery implements PageQuery<StewardingViewData, string, PresentationError> {
|
||||
async execute(leagueId: string): Promise<Result<StewardingViewData, PresentationError>> {
|
||||
@@ -16,7 +12,7 @@ export class LeagueStewardingPageQuery implements PageQuery<StewardingViewData,
|
||||
|
||||
if (result.isErr()) {
|
||||
// Map domain errors to presentation errors
|
||||
return Result.err({ type: 'serverError', message: 'Failed to load stewarding data' });
|
||||
return Result.err(mapToPresentationError(result.getError()));
|
||||
}
|
||||
|
||||
const viewData = StewardingViewDataBuilder.build(result.unwrap());
|
||||
@@ -28,4 +24,4 @@ export class LeagueStewardingPageQuery implements PageQuery<StewardingViewData,
|
||||
const query = new LeagueStewardingPageQuery();
|
||||
return query.execute(leagueId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,7 @@ import { Result } from '@/lib/contracts/Result';
|
||||
import { LeagueWalletService } from '@/lib/services/leagues/LeagueWalletService';
|
||||
import { LeagueWalletViewDataBuilder } from '@/lib/builders/view-data/LeagueWalletViewDataBuilder';
|
||||
import { LeagueWalletViewData } from '@/lib/view-data/leagues/LeagueWalletViewData';
|
||||
|
||||
interface PresentationError {
|
||||
type: 'notFound' | 'forbidden' | 'serverError';
|
||||
message: string;
|
||||
}
|
||||
import { type PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
|
||||
export class LeagueWalletPageQuery implements PageQuery<LeagueWalletViewData, string, PresentationError> {
|
||||
async execute(leagueId: string): Promise<Result<LeagueWalletViewData, PresentationError>> {
|
||||
@@ -15,7 +11,7 @@ export class LeagueWalletPageQuery implements PageQuery<LeagueWalletViewData, st
|
||||
const result = await service.getWalletData(leagueId);
|
||||
|
||||
if (result.isErr()) {
|
||||
return Result.err({ type: 'serverError', message: 'Failed to load wallet data' });
|
||||
return Result.err(mapToPresentationError(result.getError()));
|
||||
}
|
||||
|
||||
const viewData = LeagueWalletViewDataBuilder.build(result.unwrap());
|
||||
@@ -26,4 +22,4 @@ export class LeagueWalletPageQuery implements PageQuery<LeagueWalletViewData, st
|
||||
const query = new LeagueWalletPageQuery();
|
||||
return query.execute(leagueId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ 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';
|
||||
import { type PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
|
||||
/**
|
||||
* Sponsor Dashboard Page Query
|
||||
@@ -10,13 +11,13 @@ import type { SponsorDashboardViewData } from '@/lib/view-data/SponsorDashboardV
|
||||
* 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>> {
|
||||
export class SponsorDashboardPageQuery implements PageQuery<SponsorDashboardViewData, string, PresentationError> {
|
||||
async execute(sponsorId: string): Promise<Result<SponsorDashboardViewData, PresentationError>> {
|
||||
const service = new SponsorService();
|
||||
|
||||
const dashboardResult = await service.getSponsorDashboard(sponsorId);
|
||||
if (dashboardResult.isErr()) {
|
||||
return Result.err(this.mapToPresentationError(dashboardResult.getError()));
|
||||
return Result.err(mapToPresentationError(dashboardResult.getError()));
|
||||
}
|
||||
|
||||
const dto = dashboardResult.unwrap();
|
||||
@@ -25,14 +26,8 @@ export class SponsorDashboardPageQuery implements PageQuery<SponsorDashboardView
|
||||
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';
|
||||
}
|
||||
static async execute(sponsorId: string): Promise<Result<SponsorDashboardViewData, PresentationError>> {
|
||||
const query = new SponsorDashboardPageQuery();
|
||||
return query.execute(sponsorId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
119
apps/website/lib/page-queries/TeamDetailPageQuery.ts
Normal file
119
apps/website/lib/page-queries/TeamDetailPageQuery.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { TeamService } from '@/lib/services/teams/TeamService';
|
||||
import { TeamDetailViewDataBuilder } from '@/lib/builders/view-data/TeamDetailViewDataBuilder';
|
||||
import type { TeamDetailViewData } from '@/lib/view-data/TeamDetailViewData';
|
||||
import { type PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
import { SessionGateway } from '@/lib/gateways/SessionGateway';
|
||||
import type { TeamMemberViewModel } from '@/lib/view-models/TeamMemberViewModel';
|
||||
|
||||
/**
|
||||
* TeamDetailPageDto - Raw serializable data for team detail page
|
||||
* Contains only raw data, no derived/computed properties
|
||||
*/
|
||||
export interface TeamDetailPageDto {
|
||||
team: {
|
||||
id: string;
|
||||
name: string;
|
||||
tag: string;
|
||||
description?: string;
|
||||
ownerId: string;
|
||||
leagues: string[];
|
||||
createdAt?: string;
|
||||
specialization?: string;
|
||||
region?: string;
|
||||
languages?: string[];
|
||||
category?: string;
|
||||
membership?: {
|
||||
role: string;
|
||||
joinedAt: string;
|
||||
isActive: boolean;
|
||||
} | null;
|
||||
canManage: boolean;
|
||||
};
|
||||
memberships: Array<{
|
||||
driverId: string;
|
||||
driverName: string;
|
||||
role: 'owner' | 'manager' | 'member';
|
||||
joinedAt: string;
|
||||
isActive: boolean;
|
||||
avatarUrl: string;
|
||||
}>;
|
||||
currentDriverId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* TeamDetailPageQuery - Server-side composition for team detail page
|
||||
*/
|
||||
export class TeamDetailPageQuery implements PageQuery<TeamDetailViewData, string> {
|
||||
async execute(teamId: string): Promise<Result<TeamDetailViewData, 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 currentDriverId = session.user.primaryDriverId;
|
||||
const service = new TeamService();
|
||||
|
||||
// Fetch team details
|
||||
const teamResult = await service.getTeamDetails(teamId, currentDriverId);
|
||||
|
||||
if (teamResult.isErr()) {
|
||||
return Result.err(mapToPresentationError(teamResult.getError()));
|
||||
}
|
||||
|
||||
const teamData = teamResult.unwrap();
|
||||
|
||||
// Fetch team members
|
||||
const membersResult = await service.getTeamMembers(teamId, currentDriverId, teamData.ownerId);
|
||||
|
||||
if (membersResult.isErr()) {
|
||||
return Result.err(mapToPresentationError(membersResult.getError()));
|
||||
}
|
||||
|
||||
const membersData = membersResult.unwrap();
|
||||
|
||||
// Transform to raw serializable DTO
|
||||
const dto: TeamDetailPageDto = {
|
||||
team: {
|
||||
id: teamData.id,
|
||||
name: teamData.name,
|
||||
tag: teamData.tag,
|
||||
description: teamData.description,
|
||||
ownerId: teamData.ownerId,
|
||||
leagues: teamData.leagues,
|
||||
createdAt: teamData.createdAt,
|
||||
specialization: teamData.specialization,
|
||||
region: teamData.region,
|
||||
languages: teamData.languages,
|
||||
category: teamData.category,
|
||||
membership: teamData.membership ? {
|
||||
role: teamData.membership.role,
|
||||
joinedAt: teamData.membership.joinedAt,
|
||||
isActive: teamData.membership.isActive,
|
||||
} : null,
|
||||
canManage: teamData.canManage,
|
||||
},
|
||||
memberships: membersData.map((member: TeamMemberViewModel) => ({
|
||||
driverId: member.driverId,
|
||||
driverName: member.driverName,
|
||||
role: member.role,
|
||||
joinedAt: member.joinedAt,
|
||||
isActive: member.isActive,
|
||||
avatarUrl: member.avatarUrl,
|
||||
})),
|
||||
currentDriverId,
|
||||
};
|
||||
|
||||
const viewData = TeamDetailViewDataBuilder.build(dto);
|
||||
return Result.ok(viewData);
|
||||
}
|
||||
|
||||
static async execute(teamId: string): Promise<Result<TeamDetailViewData, PresentationError>> {
|
||||
const query = new TeamDetailPageQuery();
|
||||
return query.execute(teamId);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,11 @@ import { PresentationError, mapToPresentationError } from '@/lib/contracts/page-
|
||||
import { TeamService } from '@/lib/services/teams/TeamService';
|
||||
import type { TeamsViewData } from '@/lib/view-data/TeamsViewData';
|
||||
import { TeamsViewDataBuilder } from '@/lib/builders/view-data/TeamsViewDataBuilder';
|
||||
import type { TeamListItemDTO } from '@/lib/types/generated/TeamListItemDTO';
|
||||
|
||||
export interface TeamsPageDto {
|
||||
teams: TeamListItemDTO[];
|
||||
}
|
||||
|
||||
/**
|
||||
* TeamsPageQuery - Server-side composition for teams list page
|
||||
@@ -1,10 +1,3 @@
|
||||
/**
|
||||
* Forgot Password Page Query
|
||||
*
|
||||
* Composes data for the forgot password page using RSC pattern.
|
||||
* No business logic, only data composition.
|
||||
*/
|
||||
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { ForgotPasswordViewDataBuilder } from '@/lib/builders/view-data/ForgotPasswordViewDataBuilder';
|
||||
@@ -28,14 +21,14 @@ export class ForgotPasswordPageQuery implements PageQuery<ForgotPasswordViewData
|
||||
const serviceResult = await authService.processForgotPasswordParams({ returnTo, token });
|
||||
|
||||
if (serviceResult.isErr()) {
|
||||
return Result.err(serviceResult.getError());
|
||||
return Result.err(serviceResult.getError().message);
|
||||
}
|
||||
|
||||
// Transform to ViewData using builder
|
||||
const viewData = ForgotPasswordViewDataBuilder.build(serviceResult.unwrap());
|
||||
return Result.ok(viewData);
|
||||
} catch (error) {
|
||||
return Result.err('Failed to execute forgot password page query');
|
||||
} catch (error: any) {
|
||||
return Result.err(error.message || 'Failed to execute forgot password page query');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,4 +37,4 @@ export class ForgotPasswordPageQuery implements PageQuery<ForgotPasswordViewData
|
||||
const query = new ForgotPasswordPageQuery();
|
||||
return query.execute(searchParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
/**
|
||||
* Login Page Query
|
||||
*
|
||||
* Composes data for the login page using RSC pattern.
|
||||
* No business logic, only data composition.
|
||||
*/
|
||||
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { LoginViewDataBuilder } from '@/lib/builders/view-data/LoginViewDataBuilder';
|
||||
@@ -28,14 +21,14 @@ export class LoginPageQuery implements PageQuery<LoginViewData, URLSearchParams>
|
||||
const serviceResult = await authService.processLoginParams({ returnTo, token });
|
||||
|
||||
if (serviceResult.isErr()) {
|
||||
return Result.err(serviceResult.getError());
|
||||
return Result.err(serviceResult.getError().message);
|
||||
}
|
||||
|
||||
// Transform to ViewData using builder
|
||||
const viewData = LoginViewDataBuilder.build(serviceResult.unwrap());
|
||||
return Result.ok(viewData);
|
||||
} catch (error) {
|
||||
return Result.err('Failed to execute login page query');
|
||||
} catch (error: any) {
|
||||
return Result.err(error.message || 'Failed to execute login page query');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,4 +37,4 @@ export class LoginPageQuery implements PageQuery<LoginViewData, URLSearchParams>
|
||||
const query = new LoginPageQuery();
|
||||
return query.execute(searchParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
/**
|
||||
* Reset Password Page Query
|
||||
*
|
||||
* Composes data for the reset password page using RSC pattern.
|
||||
* No business logic, only data composition.
|
||||
*/
|
||||
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { ResetPasswordViewDataBuilder } from '@/lib/builders/view-data/ResetPasswordViewDataBuilder';
|
||||
@@ -28,14 +21,14 @@ export class ResetPasswordPageQuery implements PageQuery<ResetPasswordViewData,
|
||||
const serviceResult = await authService.processResetPasswordParams({ returnTo, token });
|
||||
|
||||
if (serviceResult.isErr()) {
|
||||
return Result.err(serviceResult.getError());
|
||||
return Result.err(serviceResult.getError().message);
|
||||
}
|
||||
|
||||
// Transform to ViewData using builder
|
||||
const viewData = ResetPasswordViewDataBuilder.build(serviceResult.unwrap());
|
||||
return Result.ok(viewData);
|
||||
} catch (error) {
|
||||
return Result.err('Failed to execute reset password page query');
|
||||
} catch (error: any) {
|
||||
return Result.err(error.message || 'Failed to execute reset password page query');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,4 +37,4 @@ export class ResetPasswordPageQuery implements PageQuery<ResetPasswordViewData,
|
||||
const query = new ResetPasswordPageQuery();
|
||||
return query.execute(searchParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
/**
|
||||
* Signup Page Query
|
||||
*
|
||||
* Composes data for the signup page using RSC pattern.
|
||||
* No business logic, only data composition.
|
||||
*/
|
||||
|
||||
import { SignupViewDataBuilder } from '@/lib/builders/view-data/SignupViewDataBuilder';
|
||||
import { SignupViewData } from '@/lib/builders/view-data/types/SignupViewData';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
@@ -28,14 +21,14 @@ export class SignupPageQuery implements PageQuery<SignupViewData, URLSearchParam
|
||||
const serviceResult = await authService.processSignupParams({ returnTo, token });
|
||||
|
||||
if (serviceResult.isErr()) {
|
||||
return Result.err(serviceResult.getError());
|
||||
return Result.err(serviceResult.getError().message);
|
||||
}
|
||||
|
||||
// Transform to ViewData using builder
|
||||
const viewData = SignupViewDataBuilder.build(serviceResult.unwrap());
|
||||
return Result.ok(viewData);
|
||||
} catch (error) {
|
||||
return Result.err('Failed to execute signup page query');
|
||||
} catch (error: any) {
|
||||
return Result.err(error.message || 'Failed to execute signup page query');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,4 +37,4 @@ export class SignupPageQuery implements PageQuery<SignupViewData, URLSearchParam
|
||||
const query = new SignupPageQuery();
|
||||
return query.execute(searchParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
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: DriverLeaderboardItemDTO[];
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import type { DriverLeaderboardItemDTO } from '@/lib/types/generated/DriverLeaderboardItemDTO';
|
||||
import type { TeamListItemDTO } from '@/lib/types/generated/TeamListItemDTO';
|
||||
|
||||
export interface LeaderboardsPageDto {
|
||||
drivers: { drivers: DriverLeaderboardItemDTO[] };
|
||||
teams: { teams: TeamListItemDTO[] };
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
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';
|
||||
|
||||
/**
|
||||
* LeagueDetail page query
|
||||
* Returns the raw API DTO for the league detail page
|
||||
* No DI container usage - constructs dependencies explicitly
|
||||
*/
|
||||
export class LeagueDetailPageQuery implements PageQuery<any, string> {
|
||||
async execute(leagueId: string): Promise<Result<any, 'notFound' | 'redirect' | '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);
|
||||
|
||||
// Fetch data using API client
|
||||
try {
|
||||
const apiDto = await apiClient.getAllWithCapacityAndScoring();
|
||||
|
||||
if (!apiDto || !apiDto.leagues) {
|
||||
return Result.err('notFound');
|
||||
}
|
||||
|
||||
// Find the specific league
|
||||
const league = apiDto.leagues.find(l => l.id === leagueId);
|
||||
if (!league) {
|
||||
return Result.err('notFound');
|
||||
}
|
||||
|
||||
// Return the raw DTO - the page will handle ViewModel conversion
|
||||
return Result.ok({
|
||||
league,
|
||||
apiDto,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('LeagueDetailPageQuery 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('LEAGUE_FETCH_FAILED');
|
||||
}
|
||||
}
|
||||
|
||||
return Result.err('UNKNOWN_ERROR');
|
||||
}
|
||||
}
|
||||
|
||||
// Static method to avoid object construction in server code
|
||||
static async execute(leagueId: string): Promise<Result<any, 'notFound' | 'redirect' | 'LEAGUE_FETCH_FAILED' | 'UNKNOWN_ERROR'>> {
|
||||
const query = new LeagueDetailPageQuery();
|
||||
return query.execute(leagueId);
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
import type { PageQueryResult } from '@/lib/contracts/page-queries/PageQueryResult';
|
||||
import { SessionGateway } from '@/lib/gateways/SessionGateway';
|
||||
import { TeamService } from '@/lib/services/teams/TeamService';
|
||||
import type { TeamMemberViewModel } from '@/lib/view-models/TeamMemberViewModel';
|
||||
|
||||
/**
|
||||
* TeamDetailPageDto - Raw serializable data for team detail page
|
||||
* Contains only raw data, no derived/computed properties
|
||||
*/
|
||||
export interface TeamDetailPageDto {
|
||||
team: {
|
||||
id: string;
|
||||
name: string;
|
||||
tag: string;
|
||||
description?: string;
|
||||
ownerId: string;
|
||||
leagues: string[];
|
||||
createdAt?: string;
|
||||
specialization?: string;
|
||||
region?: string;
|
||||
languages?: string[];
|
||||
category?: string;
|
||||
membership?: {
|
||||
role: string;
|
||||
joinedAt: string;
|
||||
isActive: boolean;
|
||||
} | null;
|
||||
canManage: boolean;
|
||||
};
|
||||
memberships: Array<{
|
||||
driverId: string;
|
||||
driverName: string;
|
||||
role: 'owner' | 'manager' | 'member';
|
||||
joinedAt: string;
|
||||
isActive: boolean;
|
||||
avatarUrl: string;
|
||||
}>;
|
||||
currentDriverId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* TeamDetailPageQuery - Server-side composition for team detail page
|
||||
* Manual wiring only; no ContainerManager; no PageDataFetcher
|
||||
* Returns raw serializable DTO
|
||||
*/
|
||||
export class TeamDetailPageQuery {
|
||||
static async execute(teamId: string): Promise<PageQueryResult<TeamDetailPageDto>> {
|
||||
try {
|
||||
// Validate teamId
|
||||
if (!teamId) {
|
||||
return { status: 'notFound' };
|
||||
}
|
||||
|
||||
// Get session to determine current driver
|
||||
const sessionGateway = new SessionGateway();
|
||||
const session = await sessionGateway.getSession();
|
||||
|
||||
if (!session?.user?.primaryDriverId) {
|
||||
return { status: 'notFound' };
|
||||
}
|
||||
|
||||
const currentDriverId = session.user.primaryDriverId;
|
||||
|
||||
// Manual dependency creation
|
||||
const service = new TeamService();
|
||||
|
||||
// Fetch team details
|
||||
const teamResult = await service.getTeamDetails(teamId, currentDriverId);
|
||||
|
||||
if (teamResult.isErr()) {
|
||||
return { status: 'notFound' };
|
||||
}
|
||||
|
||||
const teamData = teamResult.unwrap();
|
||||
|
||||
// Fetch team members
|
||||
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 = {
|
||||
team: {
|
||||
id: teamData.id,
|
||||
name: teamData.name,
|
||||
tag: teamData.tag,
|
||||
description: teamData.description,
|
||||
ownerId: teamData.ownerId,
|
||||
leagues: teamData.leagues,
|
||||
createdAt: teamData.createdAt,
|
||||
specialization: teamData.specialization,
|
||||
region: teamData.region,
|
||||
languages: teamData.languages,
|
||||
category: teamData.category,
|
||||
membership: teamData.membership ? {
|
||||
role: teamData.membership.role,
|
||||
joinedAt: teamData.membership.joinedAt,
|
||||
isActive: teamData.membership.isActive,
|
||||
} : null,
|
||||
canManage: teamData.canManage,
|
||||
},
|
||||
memberships: membersData.map((member: TeamMemberViewModel) => ({
|
||||
driverId: member.driverId,
|
||||
driverName: member.driverName,
|
||||
role: member.role,
|
||||
joinedAt: member.joinedAt,
|
||||
isActive: member.isActive,
|
||||
avatarUrl: member.avatarUrl,
|
||||
})),
|
||||
currentDriverId,
|
||||
};
|
||||
|
||||
return { status: 'ok', dto };
|
||||
} catch (error) {
|
||||
// Handle specific error types
|
||||
if (error instanceof Error) {
|
||||
const errorAny = error as { statusCode?: number; message?: string };
|
||||
if (errorAny.message?.includes('not found') || errorAny.statusCode === 404) {
|
||||
return { status: 'notFound' };
|
||||
}
|
||||
if (errorAny.message?.includes('redirect') || errorAny.statusCode === 302) {
|
||||
return { status: 'redirect', to: '/' };
|
||||
}
|
||||
return { status: 'error', errorId: 'TEAM_DETAIL_FETCH_FAILED' };
|
||||
}
|
||||
return { status: 'error', errorId: 'UNKNOWN_ERROR' };
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user