fix issues in core
This commit is contained in:
@@ -1 +1,2 @@
|
|||||||
./html-dumps
|
./html-dumps
|
||||||
|
apps/companion
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { AuthenticationState } from '../../domain/value-objects/AuthenticationState';
|
import { AuthenticationState } from '../../domain/value-objects/AuthenticationState';
|
||||||
import type { Logger } from '@core/shared/application';
|
import type { Logger } from '@core/shared/application';
|
||||||
import { Result } from '@gridpilot/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import type { AuthenticationServicePort } from '../ports/AuthenticationServicePort';
|
import type { AuthenticationServicePort } from '../ports/AuthenticationServicePort';
|
||||||
import { SessionLifetime } from '../../domain/value-objects/SessionLifetime';
|
import { SessionLifetime } from '../../domain/value-objects/SessionLifetime';
|
||||||
import type { SessionValidatorPort } from '../ports/SessionValidatorPort';
|
import type { SessionValidatorPort } from '../ports/SessionValidatorPort';
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { vi, Mock } from 'vitest';
|
|||||||
import { ClearSessionUseCase } from './ClearSessionUseCase';
|
import { ClearSessionUseCase } from './ClearSessionUseCase';
|
||||||
import type { AuthenticationServicePort } from '../ports/AuthenticationServicePort';
|
import type { AuthenticationServicePort } from '../ports/AuthenticationServicePort';
|
||||||
import type { Logger } from '@core/shared/application';
|
import type { Logger } from '@core/shared/application';
|
||||||
import { Result } from '@gridpilot/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
|
|
||||||
describe('ClearSessionUseCase', () => {
|
describe('ClearSessionUseCase', () => {
|
||||||
let useCase: ClearSessionUseCase;
|
let useCase: ClearSessionUseCase;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Result } from '@gridpilot/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import type { AuthenticationServicePort } from '../ports/AuthenticationServicePort';
|
import type { AuthenticationServicePort } from '../ports/AuthenticationServicePort';
|
||||||
import type { Logger } from '@core/shared/application';
|
import type { Logger } from '@core/shared/application';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
import { CompleteRaceCreationUseCase } from 'apps/companion/main/automation/application/use-cases/CompleteRaceCreationUseCase';
|
import { CompleteRaceCreationUseCase } from 'apps/companion/main/automation/application/use-cases/CompleteRaceCreationUseCase';
|
||||||
import { Result } from '@gridpilot/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import { RaceCreationResult } from 'apps/companion/main/automation/domain/value-objects/RaceCreationResult';
|
import { RaceCreationResult } from 'apps/companion/main/automation/domain/value-objects/RaceCreationResult';
|
||||||
import { CheckoutPrice } from 'apps/companion/main/automation/domain/value-objects/CheckoutPrice';
|
import { CheckoutPrice } from 'apps/companion/main/automation/domain/value-objects/CheckoutPrice';
|
||||||
import type { CheckoutServicePort } from 'apps/companion/main/automation/application/ports/CheckoutServicePort';
|
import type { CheckoutServicePort } from 'apps/companion/main/automation/application/ports/CheckoutServicePort';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Result } from '@gridpilot/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import { RaceCreationResult } from '../../domain/value-objects/RaceCreationResult';
|
import { RaceCreationResult } from '../../domain/value-objects/RaceCreationResult';
|
||||||
import type { CheckoutServicePort } from '../ports/CheckoutServicePort';
|
import type { CheckoutServicePort } from '../ports/CheckoutServicePort';
|
||||||
import type { Logger } from '@core/shared/application';
|
import type { Logger } from '@core/shared/application';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
|
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
|
||||||
import { Result } from '@gridpilot/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import { ConfirmCheckoutUseCase } from 'apps/companion/main/automation/application/use-cases/ConfirmCheckoutUseCase';
|
import { ConfirmCheckoutUseCase } from 'apps/companion/main/automation/application/use-cases/ConfirmCheckoutUseCase';
|
||||||
import type { CheckoutServicePort } from 'apps/companion/main/automation/application/ports/CheckoutServicePort';
|
import type { CheckoutServicePort } from 'apps/companion/main/automation/application/ports/CheckoutServicePort';
|
||||||
import type { CheckoutConfirmationPort } from 'apps/companion/main/automation/application/ports/CheckoutConfirmationPort';
|
import type { CheckoutConfirmationPort } from 'apps/companion/main/automation/application/ports/CheckoutConfirmationPort';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Result } from '@gridpilot/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import type { Logger } from '@core/shared/application';
|
import type { Logger } from '@core/shared/application';
|
||||||
import type { CheckoutServicePort } from '../ports/CheckoutServicePort';
|
import type { CheckoutServicePort } from '../ports/CheckoutServicePort';
|
||||||
import type { CheckoutConfirmationPort } from '../ports/CheckoutConfirmationPort';
|
import type { CheckoutConfirmationPort } from '../ports/CheckoutConfirmationPort';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Result } from '@gridpilot/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import type { AuthenticationServicePort } from '../ports/AuthenticationServicePort';
|
import type { AuthenticationServicePort } from '../ports/AuthenticationServicePort';
|
||||||
import type { Logger } from '@core/shared/application/Logger';
|
import type { Logger } from '@core/shared/application/Logger';
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||||
import { VerifyAuthenticatedPageUseCase } from 'apps/companion/main/automation/application/use-cases/VerifyAuthenticatedPageUseCase';
|
import { VerifyAuthenticatedPageUseCase } from 'apps/companion/main/automation/application/use-cases/VerifyAuthenticatedPageUseCase';
|
||||||
import { AuthenticationServicePort as IAuthenticationService } from 'apps/companion/main/automation/application/ports/AuthenticationServicePort';
|
import { AuthenticationServicePort as IAuthenticationService } from 'apps/companion/main/automation/application/ports/AuthenticationServicePort';
|
||||||
import { Result } from '@gridpilot/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import { BrowserAuthenticationState } from 'apps/companion/main/automation/domain/value-objects/BrowserAuthenticationState';
|
import { BrowserAuthenticationState } from 'apps/companion/main/automation/domain/value-objects/BrowserAuthenticationState';
|
||||||
import { AuthenticationState } from 'apps/companion/main/automation/domain/value-objects/AuthenticationState';
|
import { AuthenticationState } from 'apps/companion/main/automation/domain/value-objects/AuthenticationState';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { AuthenticationServicePort } from '../ports/AuthenticationServicePort';
|
import type { AuthenticationServicePort } from '../ports/AuthenticationServicePort';
|
||||||
import { Result } from '@gridpilot/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import { BrowserAuthenticationState } from '../../domain/value-objects/BrowserAuthenticationState';
|
import { BrowserAuthenticationState } from '../../domain/value-objects/BrowserAuthenticationState';
|
||||||
import type { Logger } from '@core/shared/application';
|
import type { Logger } from '@core/shared/application';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { IDomainValidationService } from '@core/shared/domain';
|
import type { IDomainValidationService } from '@core/shared/domain';
|
||||||
import { Result } from '@gridpilot/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration for page state validation.
|
* Configuration for page state validation.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { StepId } from '../value-objects/StepId';
|
import { StepId } from '../value-objects/StepId';
|
||||||
|
|
||||||
import type { IDomainValidationService } from '@core/shared/domain';
|
import type { IDomainValidationService } from '@core/shared/domain';
|
||||||
import { Result } from '@gridpilot/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import { SessionState } from '../value-objects/SessionState';
|
import { SessionState } from '../value-objects/SessionState';
|
||||||
|
|
||||||
export interface ValidationResult {
|
export interface ValidationResult {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Result } from '@gridpilot/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import { CheckoutPrice } from '../../../domain/value-objects/CheckoutPrice';
|
import { CheckoutPrice } from '../../../domain/value-objects/CheckoutPrice';
|
||||||
import { CheckoutState } from '../../../domain/value-objects/CheckoutState';
|
import { CheckoutState } from '../../../domain/value-objects/CheckoutState';
|
||||||
import type { CheckoutInfoDTO } from '../../../application/dto/CheckoutInfoDTO';
|
import type { CheckoutInfoDTO } from '../../../application/dto/CheckoutInfoDTO';
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type { AuthenticationServicePort } from '../../../../application/ports/Au
|
|||||||
import type { LoggerPort } from '../../../../application/ports/LoggerPort';
|
import type { LoggerPort } from '../../../../application/ports/LoggerPort';
|
||||||
import { AuthenticationState } from '../../../../domain/value-objects/AuthenticationState';
|
import { AuthenticationState } from '../../../../domain/value-objects/AuthenticationState';
|
||||||
import { BrowserAuthenticationState } from '../../../../domain/value-objects/BrowserAuthenticationState';
|
import { BrowserAuthenticationState } from '../../../../domain/value-objects/BrowserAuthenticationState';
|
||||||
import { Result } from '@gridpilot/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import { PlaywrightBrowserSession } from '../core/PlaywrightBrowserSession';
|
import { PlaywrightBrowserSession } from '../core/PlaywrightBrowserSession';
|
||||||
import { SessionCookieStore } from './SessionCookieStore';
|
import { SessionCookieStore } from './SessionCookieStore';
|
||||||
import type { IPlaywrightAuthFlow } from './PlaywrightAuthFlow';
|
import type { IPlaywrightAuthFlow } from './PlaywrightAuthFlow';
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { Page, Locator } from 'playwright';
|
|||||||
import { AuthenticationState } from 'apps/companion/main/automation/domain/value-objects/AuthenticationState';
|
import { AuthenticationState } from 'apps/companion/main/automation/domain/value-objects/AuthenticationState';
|
||||||
import { BrowserAuthenticationState } from 'apps/companion/main/automation/domain/value-objects/BrowserAuthenticationState';
|
import { BrowserAuthenticationState } from 'apps/companion/main/automation/domain/value-objects/BrowserAuthenticationState';
|
||||||
import type { LoggerPort as Logger } from 'apps/companion/main/automation/application/ports/LoggerPort';
|
import type { LoggerPort as Logger } from 'apps/companion/main/automation/application/ports/LoggerPort';
|
||||||
import type { Result } from '@gridpilot/shared/application/Result';
|
import type { Result } from '@core/shared/application/Result';
|
||||||
import { PlaywrightBrowserSession } from '../core/PlaywrightBrowserSession';
|
import { PlaywrightBrowserSession } from '../core/PlaywrightBrowserSession';
|
||||||
import { IPlaywrightAuthFlow } from './PlaywrightAuthFlow';
|
import { IPlaywrightAuthFlow } from './PlaywrightAuthFlow';
|
||||||
import { PlaywrightAuthSessionService } from './PlaywrightAuthSessionService';
|
import { PlaywrightAuthSessionService } from './PlaywrightAuthSessionService';
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import * as fs from 'fs/promises';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { AuthenticationState } from '../../../../domain/value-objects/AuthenticationState';
|
import { AuthenticationState } from '../../../../domain/value-objects/AuthenticationState';
|
||||||
import { CookieConfiguration } from '../../../../domain/value-objects/CookieConfiguration';
|
import { CookieConfiguration } from '../../../../domain/value-objects/CookieConfiguration';
|
||||||
import { Result } from '@gridpilot/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import type { LoggerPort } from '../../../../application/ports/LoggerPort';
|
import type { LoggerPort } from '../../../../application/ports/LoggerPort';
|
||||||
|
|
||||||
interface Cookie {
|
interface Cookie {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import type { ModalResultDTO } from '../../../../application/dto/ModalResultDTO'
|
|||||||
import type { AutomationResultDTO } from '../../../../application/dto/AutomationResultDTO';
|
import type { AutomationResultDTO } from '../../../../application/dto/AutomationResultDTO';
|
||||||
import type { AuthenticationServicePort } from '../../../../application/ports/AuthenticationServicePort';
|
import type { AuthenticationServicePort } from '../../../../application/ports/AuthenticationServicePort';
|
||||||
import type { LoggerPort } from '../../../../application/ports/LoggerPort';
|
import type { LoggerPort } from '../../../../application/ports/LoggerPort';
|
||||||
import { Result } from '@gridpilot/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import { IRACING_SELECTORS, IRACING_URLS, IRACING_TIMEOUTS, BLOCKED_KEYWORDS } from '../dom/IRacingSelectors';
|
import { IRACING_SELECTORS, IRACING_URLS, IRACING_TIMEOUTS, BLOCKED_KEYWORDS } from '../dom/IRacingSelectors';
|
||||||
import { SessionCookieStore } from '../auth/SessionCookieStore';
|
import { SessionCookieStore } from '../auth/SessionCookieStore';
|
||||||
import { PlaywrightBrowserSession } from './PlaywrightBrowserSession';
|
import { PlaywrightBrowserSession } from './PlaywrightBrowserSession';
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import type {
|
|||||||
PageStateValidation,
|
PageStateValidation,
|
||||||
PageStateValidationResult,
|
PageStateValidationResult,
|
||||||
} from '../../../../domain/services/PageStateValidator';
|
} from '../../../../domain/services/PageStateValidator';
|
||||||
import type { Result } from '@gridpilot/shared/application/Result';
|
import type { Result } from '@core/shared/application/Result';
|
||||||
|
|
||||||
interface WizardStepOrchestratorDeps {
|
interface WizardStepOrchestratorDeps {
|
||||||
config: Required<PlaywrightConfig>;
|
config: Required<PlaywrightConfig>;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import type { BrowserWindow } from 'electron';
|
import type { BrowserWindow } from 'electron';
|
||||||
import { ipcMain } from 'electron';
|
import { ipcMain } from 'electron';
|
||||||
import { Result } from '@gridpilot/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import type { CheckoutConfirmationPort } from '../../../application/ports/CheckoutConfirmationPort';
|
import type { CheckoutConfirmationPort } from '../../../application/ports/CheckoutConfirmationPort';
|
||||||
import type { CheckoutConfirmationRequestDTO } from '../../../application/dto/CheckoutConfirmationRequestDTO';
|
import type { CheckoutConfirmationRequestDTO } from '../../../application/dto/CheckoutConfirmationRequestDTO';
|
||||||
import { CheckoutConfirmation } from '../../../domain/value-objects/CheckoutConfirmation';
|
import { CheckoutConfirmation } from '../../../domain/value-objects/CheckoutConfirmation';
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ describe('DashboardOverviewUseCase', () => {
|
|||||||
getMembership: async (leagueId: string, driverIdParam: string): Promise<LeagueMembership | null> => {
|
getMembership: async (leagueId: string, driverIdParam: string): Promise<LeagueMembership | null> => {
|
||||||
return (
|
return (
|
||||||
memberships.find(
|
memberships.find(
|
||||||
m => m.leagueId === leagueId && m.driverId === driverIdParam,
|
m => m.leagueId.toString() === leagueId && m.driverId.toString() === driverIdParam,
|
||||||
) ?? null
|
) ?? null
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
import type { IDriverRepository } from '../../domain/repositories/IDriverRepository';
|
|
||||||
import type { IRaceRepository } from '../../domain/repositories/IRaceRepository';
|
|
||||||
import type { IResultRepository } from '../../domain/repositories/IResultRepository';
|
|
||||||
import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository';
|
|
||||||
import type { IStandingRepository } from '../../domain/repositories/IStandingRepository';
|
|
||||||
import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository';
|
|
||||||
import type { IRaceRegistrationRepository } from '../../domain/repositories/IRaceRegistrationRepository';
|
|
||||||
import type { IFeedRepository } from '@core/social/domain/repositories/IFeedRepository';
|
|
||||||
import type { ISocialGraphRepository } from '@core/social/domain/repositories/ISocialGraphRepository';
|
|
||||||
import { Result } from '@core/shared/application/Result';
|
import { Result } from '@core/shared/application/Result';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||||
|
import type { IFeedRepository } from '@core/social/domain/repositories/IFeedRepository';
|
||||||
|
import type { ISocialGraphRepository } from '@core/social/domain/repositories/ISocialGraphRepository';
|
||||||
|
import type { FeedItem } from '@core/social/domain/types/FeedItem';
|
||||||
|
import { Driver } from '../../domain/entities/Driver';
|
||||||
import { League } from '../../domain/entities/League';
|
import { League } from '../../domain/entities/League';
|
||||||
import { Race } from '../../domain/entities/Race';
|
import { Race } from '../../domain/entities/Race';
|
||||||
import { Result as RaceResult } from '../../domain/entities/Result';
|
|
||||||
import { Driver } from '../../domain/entities/Driver';
|
|
||||||
import { Standing } from '../../domain/entities/Standing';
|
import { Standing } from '../../domain/entities/Standing';
|
||||||
import type { FeedItem } from '@core/social/domain/types/FeedItem';
|
import type { IDriverRepository } from '../../domain/repositories/IDriverRepository';
|
||||||
|
import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository';
|
||||||
|
import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository';
|
||||||
|
import type { IRaceRegistrationRepository } from '../../domain/repositories/IRaceRegistrationRepository';
|
||||||
|
import type { IRaceRepository } from '../../domain/repositories/IRaceRepository';
|
||||||
|
import type { IResultRepository } from '../../domain/repositories/IResultRepository';
|
||||||
|
import type { IStandingRepository } from '../../domain/repositories/IStandingRepository';
|
||||||
|
|
||||||
export interface DashboardOverviewInput {
|
export interface DashboardOverviewInput {
|
||||||
driverId: string;
|
driverId: string;
|
||||||
@@ -49,7 +48,7 @@ export interface DashboardRaceSummary {
|
|||||||
export interface DashboardRecentRaceResultSummary {
|
export interface DashboardRecentRaceResultSummary {
|
||||||
race: Race;
|
race: Race;
|
||||||
league: League | null;
|
league: League | null;
|
||||||
result: RaceResult;
|
result: Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DashboardLeagueStandingSummary {
|
export interface DashboardLeagueStandingSummary {
|
||||||
@@ -126,7 +125,7 @@ export class DashboardOverviewUseCase {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const leagueMap = new Map(allLeagues.map(league => [league.id, league]));
|
const leagueMap = new Map(allLeagues.map(league => [league.id.toString(), league]));
|
||||||
|
|
||||||
const driverStats = this.getDriverStats(driverId);
|
const driverStats = this.getDriverStats(driverId);
|
||||||
|
|
||||||
@@ -142,7 +141,7 @@ export class DashboardOverviewUseCase {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const driverLeagues = await this.getDriverLeagues(allLeagues, driverId);
|
const driverLeagues = await this.getDriverLeagues(allLeagues, driverId);
|
||||||
const driverLeagueIds = new Set(driverLeagues.map(league => league.id));
|
const driverLeagueIds = new Set(driverLeagues.map(league => league.id.toString()));
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const upcomingRaces = allRaces
|
const upcomingRaces = allRaces
|
||||||
@@ -150,7 +149,7 @@ export class DashboardOverviewUseCase {
|
|||||||
.sort((a, b) => a.scheduledAt.getTime() - b.scheduledAt.getTime());
|
.sort((a, b) => a.scheduledAt.getTime() - b.scheduledAt.getTime());
|
||||||
|
|
||||||
const upcomingRacesInDriverLeagues = upcomingRaces.filter(race =>
|
const upcomingRacesInDriverLeagues = upcomingRaces.filter(race =>
|
||||||
driverLeagueIds.has(race.leagueId),
|
driverLeagueIds.has(race.leagueId.toString()),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { myUpcomingRaces, otherUpcomingRaces } =
|
const { myUpcomingRaces, otherUpcomingRaces } =
|
||||||
@@ -226,10 +225,10 @@ export class DashboardOverviewUseCase {
|
|||||||
|
|
||||||
for (const league of allLeagues) {
|
for (const league of allLeagues) {
|
||||||
const membership = await this.leagueMembershipRepository.getMembership(
|
const membership = await this.leagueMembershipRepository.getMembership(
|
||||||
league.id,
|
league.id.toString(),
|
||||||
driverId,
|
driverId,
|
||||||
);
|
);
|
||||||
if (membership && membership.status === 'active') {
|
if (membership && membership.status.toString() === 'active') {
|
||||||
driverLeagues.push(league);
|
driverLeagues.push(league);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -284,13 +283,13 @@ export class DashboardOverviewUseCase {
|
|||||||
driverId: string,
|
driverId: string,
|
||||||
): DashboardRecentRaceResultSummary[] {
|
): DashboardRecentRaceResultSummary[] {
|
||||||
const raceById = new Map(allRaces.map(race => [race.id, race]));
|
const raceById = new Map(allRaces.map(race => [race.id, race]));
|
||||||
const leagueById = new Map(allLeagues.map(league => [league.id, league]));
|
const leagueById = new Map(allLeagues.map(league => [league.id.toString(), league]));
|
||||||
|
|
||||||
const driverResults = allResults.filter(result => result.driverId === driverId);
|
const driverResults = allResults.filter(result => result.driverId.toString() === driverId);
|
||||||
|
|
||||||
const enriched = driverResults
|
const enriched = driverResults
|
||||||
.map(result => {
|
.map(result => {
|
||||||
const race = raceById.get(result.raceId);
|
const race = raceById.get(result.raceId.toString());
|
||||||
if (!race) return null;
|
if (!race) return null;
|
||||||
|
|
||||||
const league = leagueById.get(race.leagueId) ?? null;
|
const league = leagueById.get(race.leagueId) ?? null;
|
||||||
@@ -321,9 +320,9 @@ export class DashboardOverviewUseCase {
|
|||||||
const summaries: DashboardLeagueStandingSummary[] = [];
|
const summaries: DashboardLeagueStandingSummary[] = [];
|
||||||
|
|
||||||
for (const league of driverLeagues.slice(0, 3)) {
|
for (const league of driverLeagues.slice(0, 3)) {
|
||||||
const standings = await this.standingRepository.findByLeagueId(league.id);
|
const standings = await this.standingRepository.findByLeagueId(league.id.toString());
|
||||||
const driverStanding = standings.find(
|
const driverStanding = standings.find(
|
||||||
(standing: Standing) => standing.driverId === driverId,
|
(standing: Standing) => standing.driverId.toString() === driverId,
|
||||||
);
|
);
|
||||||
|
|
||||||
summaries.push({
|
summaries.push({
|
||||||
@@ -347,7 +346,7 @@ export class DashboardOverviewUseCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const standing of leagueStandingsSummaries) {
|
for (const standing of leagueStandingsSummaries) {
|
||||||
activeLeagueIds.add(standing.league.id);
|
activeLeagueIds.add(standing.league.id.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
return activeLeagueIds.size;
|
return activeLeagueIds.size;
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
|
|
||||||
import { FileProtestUseCase, type FileProtestInput, type FileProtestResult, type FileProtestErrorCode } from './FileProtestUseCase';
|
|
||||||
import type { IProtestRepository } from '../../domain/repositories/IProtestRepository';
|
|
||||||
import type { IRaceRepository } from '../../domain/repositories/IRaceRepository';
|
|
||||||
import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository';
|
|
||||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||||
import type { Result } from '@core/shared/application/Result';
|
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
|
||||||
|
import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository';
|
||||||
|
import type { IProtestRepository } from '../../domain/repositories/IProtestRepository';
|
||||||
|
import type { IRaceRepository } from '../../domain/repositories/IRaceRepository';
|
||||||
|
import { FileProtestUseCase, type FileProtestErrorCode, type FileProtestInput, type FileProtestResult } from './FileProtestUseCase';
|
||||||
|
|
||||||
describe('FileProtestUseCase', () => {
|
describe('FileProtestUseCase', () => {
|
||||||
let mockProtestRepo: {
|
let mockProtestRepo: {
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
export * from './Result';
|
|
||||||
@@ -9,7 +9,7 @@ export * from './domain/entities/Team';
|
|||||||
export * from './domain/entities/Track';
|
export * from './domain/entities/Track';
|
||||||
export * from './domain/entities/Car';
|
export * from './domain/entities/Car';
|
||||||
export * from './domain/entities/Protest';
|
export * from './domain/entities/Protest';
|
||||||
export * from './domain/entities/Penalty';
|
export * from './domain/entities/penalty/Penalty';
|
||||||
|
|
||||||
export * from './domain/repositories/IDriverRepository';
|
export * from './domain/repositories/IDriverRepository';
|
||||||
export * from './domain/repositories/ILeagueRepository';
|
export * from './domain/repositories/ILeagueRepository';
|
||||||
|
|||||||
206
docs/architecture/ADAPTERS.md
Normal file
206
docs/architecture/ADAPTERS.md
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
Delivery Adapters vs App Code (Strict Clean Architecture)
|
||||||
|
|
||||||
|
This document clarifies where Controllers live, why they are adapters, and how to structure them cleanly in large systems.
|
||||||
|
|
||||||
|
It resolves the common confusion between:
|
||||||
|
• architectural role (what something is)
|
||||||
|
• physical placement (where something lives in the repo)
|
||||||
|
|
||||||
|
This document is framework-agnostic in its principles and feature-based in its structure.
|
||||||
|
|
||||||
|
⸻
|
||||||
|
|
||||||
|
1. Clean Architecture Layers (Authoritative)
|
||||||
|
|
||||||
|
Clean Architecture defines roles, not folders.
|
||||||
|
|
||||||
|
ENTITIES
|
||||||
|
USE CASES
|
||||||
|
INTERFACE ADAPTERS
|
||||||
|
FRAMEWORKS & DRIVERS
|
||||||
|
|
||||||
|
Everything that follows maps code to these roles.
|
||||||
|
|
||||||
|
⸻
|
||||||
|
|
||||||
|
2. What a Controller Is (Architecturally)
|
||||||
|
|
||||||
|
A Controller is an Interface Adapter.
|
||||||
|
|
||||||
|
Why:
|
||||||
|
• It receives external input (HTTP)
|
||||||
|
• It translates that input into Use Case input
|
||||||
|
• It invokes a Use Case
|
||||||
|
• It performs no business logic
|
||||||
|
|
||||||
|
By definition:
|
||||||
|
|
||||||
|
Anything that translates between external input and the application boundary is an adapter.
|
||||||
|
|
||||||
|
So:
|
||||||
|
|
||||||
|
✅ Controllers are Interface Adapters
|
||||||
|
|
||||||
|
⸻
|
||||||
|
|
||||||
|
3. Why Controllers Do NOT Live in adapters/
|
||||||
|
|
||||||
|
Although Controllers are adapters by role, they are also:
|
||||||
|
• Framework-specific
|
||||||
|
• Delivery-specific
|
||||||
|
• Not reusable outside their app
|
||||||
|
|
||||||
|
They:
|
||||||
|
• use routing decorators
|
||||||
|
• depend on HTTP concepts
|
||||||
|
• depend on a specific framework
|
||||||
|
|
||||||
|
Therefore:
|
||||||
|
|
||||||
|
Controllers belong to the delivery application, not to shared adapters.
|
||||||
|
|
||||||
|
⸻
|
||||||
|
|
||||||
|
4. Adapter vs App — The Key Distinction
|
||||||
|
|
||||||
|
Concept Meaning
|
||||||
|
Adapter Architectural role (translator)
|
||||||
|
App Delivery mechanism (HTTP, UI, CLI)
|
||||||
|
|
||||||
|
“Adapter” answers what it does.
|
||||||
|
“App” answers where it runs.
|
||||||
|
|
||||||
|
Both are correct at the same time.
|
||||||
|
|
||||||
|
⸻
|
||||||
|
|
||||||
|
5. Feature-Based Structure (Mandatory for Scale)
|
||||||
|
|
||||||
|
Flat technical folders do not scale.
|
||||||
|
|
||||||
|
Everything is organized by feature / bounded context.
|
||||||
|
|
||||||
|
⸻
|
||||||
|
|
||||||
|
6. Canonical Project Structure (Strict)
|
||||||
|
|
||||||
|
Root
|
||||||
|
|
||||||
|
core/ # Application + Domain (pure)
|
||||||
|
adapters/ # Reusable infrastructure adapters
|
||||||
|
apps/ # Delivery applications
|
||||||
|
|
||||||
|
|
||||||
|
⸻
|
||||||
|
|
||||||
|
Core (Feature-Based)
|
||||||
|
|
||||||
|
core/
|
||||||
|
└── racing/
|
||||||
|
├── domain/
|
||||||
|
│ ├── entities/
|
||||||
|
│ ├── value-objects/
|
||||||
|
│ └── services/
|
||||||
|
└── application/
|
||||||
|
├── use-cases/
|
||||||
|
├── inputs/
|
||||||
|
├── results/
|
||||||
|
└── ports/
|
||||||
|
├── gateways/
|
||||||
|
└── output/
|
||||||
|
|
||||||
|
• No framework imports
|
||||||
|
• No DTOs
|
||||||
|
• No controllers
|
||||||
|
|
||||||
|
⸻
|
||||||
|
|
||||||
|
Reusable Adapters (Framework-Agnostic Implementations)
|
||||||
|
|
||||||
|
adapters/
|
||||||
|
└── racing/
|
||||||
|
├── persistence/
|
||||||
|
│ ├── TypeOrmRaceRepository.ts
|
||||||
|
│ └── TypeOrmDriverRepository.ts
|
||||||
|
├── presentation/
|
||||||
|
│ └── presenters/
|
||||||
|
│ └── GetDashboardOverviewPresenter.ts
|
||||||
|
├── messaging/
|
||||||
|
│ └── EventPublisher.ts
|
||||||
|
└── logging/
|
||||||
|
└── StructuredLogger.ts
|
||||||
|
|
||||||
|
These adapters:
|
||||||
|
• implement Core ports
|
||||||
|
• are reusable across apps
|
||||||
|
• do not depend on routing or UI
|
||||||
|
|
||||||
|
⸻
|
||||||
|
|
||||||
|
API App (Delivery Application)
|
||||||
|
|
||||||
|
apps/api/
|
||||||
|
└── racing/
|
||||||
|
├── controllers/
|
||||||
|
│ └── DashboardController.ts
|
||||||
|
├── services/
|
||||||
|
│ └── DashboardApplicationService.ts
|
||||||
|
├── dto/
|
||||||
|
│ └── DashboardOverviewResponseDto.ts
|
||||||
|
└── module.ts
|
||||||
|
|
||||||
|
Responsibilities:
|
||||||
|
• Controllers translate HTTP → Input
|
||||||
|
• Application Services orchestrate Use Case + Presenter
|
||||||
|
• DTOs represent HTTP contracts
|
||||||
|
• Module wires dependencies
|
||||||
|
|
||||||
|
⸻
|
||||||
|
|
||||||
|
7. Responsibilities by Layer (No Overlap)
|
||||||
|
|
||||||
|
Controllers
|
||||||
|
• HTTP only
|
||||||
|
• No business logic
|
||||||
|
• No mapping logic
|
||||||
|
• Call Application Services only
|
||||||
|
|
||||||
|
Application Services (API)
|
||||||
|
• Instantiate Output Adapters
|
||||||
|
• Invoke Use Cases
|
||||||
|
• Return response DTOs
|
||||||
|
|
||||||
|
Presenters
|
||||||
|
• Implement Output Ports
|
||||||
|
• Map Result → DTO/ViewModel
|
||||||
|
• Hold state per execution
|
||||||
|
|
||||||
|
⸻
|
||||||
|
|
||||||
|
8. Forbidden Patterns
|
||||||
|
|
||||||
|
❌ Controllers inside adapters/
|
||||||
|
❌ Use Cases inside apps/api
|
||||||
|
❌ DTOs inside core
|
||||||
|
❌ Controllers calling Use Cases directly
|
||||||
|
❌ Business logic in Controllers or Services
|
||||||
|
|
||||||
|
⸻
|
||||||
|
|
||||||
|
9. Final Mental Model
|
||||||
|
|
||||||
|
Controllers are adapters by responsibility.
|
||||||
|
Apps define where adapters live.
|
||||||
|
|
||||||
|
This separation allows:
|
||||||
|
• strict Clean Architecture
|
||||||
|
• multiple delivery mechanisms
|
||||||
|
• feature-level scalability
|
||||||
|
|
||||||
|
⸻
|
||||||
|
|
||||||
|
10. One-Line Summary
|
||||||
|
|
||||||
|
Controller = Adapter (role), App = Delivery (location).
|
||||||
|
|
||||||
|
This document is the authoritative reference for controller placement and adapter roles.
|
||||||
Reference in New Issue
Block a user