From 11492d1ff2fd74510d45801f464bd0ecf0efbc5f Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Tue, 23 Dec 2025 11:49:47 +0100 Subject: [PATCH] fix issues in core --- .rooignore | 3 +- .../use-cases/CheckAuthenticationUseCase.ts | 2 +- .../use-cases/ClearSessionUseCase.test.ts | 2 +- .../use-cases/ClearSessionUseCase.ts | 2 +- .../CompleteRaceCreationUseCase.test.ts | 2 +- .../use-cases/CompleteRaceCreationUseCase.ts | 2 +- .../use-cases/ConfirmCheckoutUseCase.test.ts | 2 +- .../use-cases/ConfirmCheckoutUseCase.ts | 2 +- .../use-cases/InitiateLoginUseCase.ts | 2 +- .../VerifyAuthenticatedPageUseCase.test.ts | 2 +- .../VerifyAuthenticatedPageUseCase.ts | 2 +- .../domain/services/PageStateValidator.ts | 2 +- .../services/StepTransitionValidator.ts | 2 +- .../automation/CheckoutPriceExtractor.ts | 2 +- .../auth/PlaywrightAuthSessionService.ts | 2 +- ...onService.verifyPageAuthentication.test.ts | 2 +- .../automation/auth/SessionCookieStore.ts | 2 +- .../core/PlaywrightAutomationAdapter.ts | 2 +- .../automation/core/WizardStepOrchestrator.ts | 2 +- .../ElectronCheckoutConfirmationAdapter.ts | 2 +- .../DashboardOverviewUseCase.test.ts | 2 +- .../use-cases/DashboardOverviewUseCase.ts | 47 ++-- .../use-cases/FileProtestUseCase.test.ts | 11 +- core/racing/domain/entities/result/index.ts | 1 - core/racing/index.ts | 2 +- docs/architecture/ADAPTERS.md | 206 ++++++++++++++++++ 26 files changed, 257 insertions(+), 53 deletions(-) delete mode 100644 core/racing/domain/entities/result/index.ts create mode 100644 docs/architecture/ADAPTERS.md diff --git a/.rooignore b/.rooignore index 8d7bf3db6..c95b4df72 100644 --- a/.rooignore +++ b/.rooignore @@ -1 +1,2 @@ -./html-dumps \ No newline at end of file +./html-dumps +apps/companion \ No newline at end of file diff --git a/apps/companion/main/automation/application/use-cases/CheckAuthenticationUseCase.ts b/apps/companion/main/automation/application/use-cases/CheckAuthenticationUseCase.ts index a4c3d0a4c..bb4bdcdcf 100644 --- a/apps/companion/main/automation/application/use-cases/CheckAuthenticationUseCase.ts +++ b/apps/companion/main/automation/application/use-cases/CheckAuthenticationUseCase.ts @@ -1,6 +1,6 @@ import { AuthenticationState } from '../../domain/value-objects/AuthenticationState'; 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 { SessionLifetime } from '../../domain/value-objects/SessionLifetime'; import type { SessionValidatorPort } from '../ports/SessionValidatorPort'; diff --git a/apps/companion/main/automation/application/use-cases/ClearSessionUseCase.test.ts b/apps/companion/main/automation/application/use-cases/ClearSessionUseCase.test.ts index ea992b8c7..e16a56c11 100644 --- a/apps/companion/main/automation/application/use-cases/ClearSessionUseCase.test.ts +++ b/apps/companion/main/automation/application/use-cases/ClearSessionUseCase.test.ts @@ -2,7 +2,7 @@ import { vi, Mock } from 'vitest'; import { ClearSessionUseCase } from './ClearSessionUseCase'; import type { AuthenticationServicePort } from '../ports/AuthenticationServicePort'; import type { Logger } from '@core/shared/application'; -import { Result } from '@gridpilot/shared/application/Result'; +import { Result } from '@core/shared/application/Result'; describe('ClearSessionUseCase', () => { let useCase: ClearSessionUseCase; diff --git a/apps/companion/main/automation/application/use-cases/ClearSessionUseCase.ts b/apps/companion/main/automation/application/use-cases/ClearSessionUseCase.ts index 250e0c8ae..a47c36467 100644 --- a/apps/companion/main/automation/application/use-cases/ClearSessionUseCase.ts +++ b/apps/companion/main/automation/application/use-cases/ClearSessionUseCase.ts @@ -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 { Logger } from '@core/shared/application'; diff --git a/apps/companion/main/automation/application/use-cases/CompleteRaceCreationUseCase.test.ts b/apps/companion/main/automation/application/use-cases/CompleteRaceCreationUseCase.test.ts index f9c9b6d6a..d1f816fe4 100644 --- a/apps/companion/main/automation/application/use-cases/CompleteRaceCreationUseCase.test.ts +++ b/apps/companion/main/automation/application/use-cases/CompleteRaceCreationUseCase.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; 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 { CheckoutPrice } from 'apps/companion/main/automation/domain/value-objects/CheckoutPrice'; import type { CheckoutServicePort } from 'apps/companion/main/automation/application/ports/CheckoutServicePort'; diff --git a/apps/companion/main/automation/application/use-cases/CompleteRaceCreationUseCase.ts b/apps/companion/main/automation/application/use-cases/CompleteRaceCreationUseCase.ts index d21c5c83b..f6af8ed7f 100644 --- a/apps/companion/main/automation/application/use-cases/CompleteRaceCreationUseCase.ts +++ b/apps/companion/main/automation/application/use-cases/CompleteRaceCreationUseCase.ts @@ -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 type { CheckoutServicePort } from '../ports/CheckoutServicePort'; import type { Logger } from '@core/shared/application'; diff --git a/apps/companion/main/automation/application/use-cases/ConfirmCheckoutUseCase.test.ts b/apps/companion/main/automation/application/use-cases/ConfirmCheckoutUseCase.test.ts index 75d473c21..4058d6f81 100644 --- a/apps/companion/main/automation/application/use-cases/ConfirmCheckoutUseCase.test.ts +++ b/apps/companion/main/automation/application/use-cases/ConfirmCheckoutUseCase.test.ts @@ -1,5 +1,5 @@ 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 type { CheckoutServicePort } from 'apps/companion/main/automation/application/ports/CheckoutServicePort'; import type { CheckoutConfirmationPort } from 'apps/companion/main/automation/application/ports/CheckoutConfirmationPort'; diff --git a/apps/companion/main/automation/application/use-cases/ConfirmCheckoutUseCase.ts b/apps/companion/main/automation/application/use-cases/ConfirmCheckoutUseCase.ts index 67584e2b1..0df8b3ef2 100644 --- a/apps/companion/main/automation/application/use-cases/ConfirmCheckoutUseCase.ts +++ b/apps/companion/main/automation/application/use-cases/ConfirmCheckoutUseCase.ts @@ -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 { CheckoutServicePort } from '../ports/CheckoutServicePort'; import type { CheckoutConfirmationPort } from '../ports/CheckoutConfirmationPort'; diff --git a/apps/companion/main/automation/application/use-cases/InitiateLoginUseCase.ts b/apps/companion/main/automation/application/use-cases/InitiateLoginUseCase.ts index 015dda457..7d02e20a0 100644 --- a/apps/companion/main/automation/application/use-cases/InitiateLoginUseCase.ts +++ b/apps/companion/main/automation/application/use-cases/InitiateLoginUseCase.ts @@ -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 { Logger } from '@core/shared/application/Logger'; diff --git a/apps/companion/main/automation/application/use-cases/VerifyAuthenticatedPageUseCase.test.ts b/apps/companion/main/automation/application/use-cases/VerifyAuthenticatedPageUseCase.test.ts index 54d091d5e..fdf4a1439 100644 --- a/apps/companion/main/automation/application/use-cases/VerifyAuthenticatedPageUseCase.test.ts +++ b/apps/companion/main/automation/application/use-cases/VerifyAuthenticatedPageUseCase.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { VerifyAuthenticatedPageUseCase } from 'apps/companion/main/automation/application/use-cases/VerifyAuthenticatedPageUseCase'; 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 { AuthenticationState } from 'apps/companion/main/automation/domain/value-objects/AuthenticationState'; diff --git a/apps/companion/main/automation/application/use-cases/VerifyAuthenticatedPageUseCase.ts b/apps/companion/main/automation/application/use-cases/VerifyAuthenticatedPageUseCase.ts index afccbcf5a..6ce21fff7 100644 --- a/apps/companion/main/automation/application/use-cases/VerifyAuthenticatedPageUseCase.ts +++ b/apps/companion/main/automation/application/use-cases/VerifyAuthenticatedPageUseCase.ts @@ -1,5 +1,5 @@ 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 type { Logger } from '@core/shared/application'; diff --git a/apps/companion/main/automation/domain/services/PageStateValidator.ts b/apps/companion/main/automation/domain/services/PageStateValidator.ts index 1eb01322b..17561f34e 100644 --- a/apps/companion/main/automation/domain/services/PageStateValidator.ts +++ b/apps/companion/main/automation/domain/services/PageStateValidator.ts @@ -1,5 +1,5 @@ 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. diff --git a/apps/companion/main/automation/domain/services/StepTransitionValidator.ts b/apps/companion/main/automation/domain/services/StepTransitionValidator.ts index 25d796737..7e52ee0ad 100644 --- a/apps/companion/main/automation/domain/services/StepTransitionValidator.ts +++ b/apps/companion/main/automation/domain/services/StepTransitionValidator.ts @@ -1,7 +1,7 @@ import { StepId } from '../value-objects/StepId'; 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'; export interface ValidationResult { diff --git a/apps/companion/main/automation/infrastructure/adapters/automation/CheckoutPriceExtractor.ts b/apps/companion/main/automation/infrastructure/adapters/automation/CheckoutPriceExtractor.ts index ed7e3bcad..acb8df2da 100644 --- a/apps/companion/main/automation/infrastructure/adapters/automation/CheckoutPriceExtractor.ts +++ b/apps/companion/main/automation/infrastructure/adapters/automation/CheckoutPriceExtractor.ts @@ -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 { CheckoutState } from '../../../domain/value-objects/CheckoutState'; import type { CheckoutInfoDTO } from '../../../application/dto/CheckoutInfoDTO'; diff --git a/apps/companion/main/automation/infrastructure/adapters/automation/auth/PlaywrightAuthSessionService.ts b/apps/companion/main/automation/infrastructure/adapters/automation/auth/PlaywrightAuthSessionService.ts index f22b0c16e..c475953bc 100644 --- a/apps/companion/main/automation/infrastructure/adapters/automation/auth/PlaywrightAuthSessionService.ts +++ b/apps/companion/main/automation/infrastructure/adapters/automation/auth/PlaywrightAuthSessionService.ts @@ -5,7 +5,7 @@ import type { AuthenticationServicePort } from '../../../../application/ports/Au import type { LoggerPort } from '../../../../application/ports/LoggerPort'; import { AuthenticationState } from '../../../../domain/value-objects/AuthenticationState'; 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 { SessionCookieStore } from './SessionCookieStore'; import type { IPlaywrightAuthFlow } from './PlaywrightAuthFlow'; diff --git a/apps/companion/main/automation/infrastructure/adapters/automation/auth/PlaywrightAuthSessionService.verifyPageAuthentication.test.ts b/apps/companion/main/automation/infrastructure/adapters/automation/auth/PlaywrightAuthSessionService.verifyPageAuthentication.test.ts index 04aceea52..3ad46e9a7 100644 --- a/apps/companion/main/automation/infrastructure/adapters/automation/auth/PlaywrightAuthSessionService.verifyPageAuthentication.test.ts +++ b/apps/companion/main/automation/infrastructure/adapters/automation/auth/PlaywrightAuthSessionService.verifyPageAuthentication.test.ts @@ -3,7 +3,7 @@ import type { Page, Locator } from 'playwright'; import { AuthenticationState } from 'apps/companion/main/automation/domain/value-objects/AuthenticationState'; 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 { Result } from '@gridpilot/shared/application/Result'; +import type { Result } from '@core/shared/application/Result'; import { PlaywrightBrowserSession } from '../core/PlaywrightBrowserSession'; import { IPlaywrightAuthFlow } from './PlaywrightAuthFlow'; import { PlaywrightAuthSessionService } from './PlaywrightAuthSessionService'; diff --git a/apps/companion/main/automation/infrastructure/adapters/automation/auth/SessionCookieStore.ts b/apps/companion/main/automation/infrastructure/adapters/automation/auth/SessionCookieStore.ts index ecf74cb32..25689b3ae 100644 --- a/apps/companion/main/automation/infrastructure/adapters/automation/auth/SessionCookieStore.ts +++ b/apps/companion/main/automation/infrastructure/adapters/automation/auth/SessionCookieStore.ts @@ -2,7 +2,7 @@ import * as fs from 'fs/promises'; import * as path from 'path'; import { AuthenticationState } from '../../../../domain/value-objects/AuthenticationState'; 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'; interface Cookie { diff --git a/apps/companion/main/automation/infrastructure/adapters/automation/core/PlaywrightAutomationAdapter.ts b/apps/companion/main/automation/infrastructure/adapters/automation/core/PlaywrightAutomationAdapter.ts index e35d6b452..377e77b6e 100644 --- a/apps/companion/main/automation/infrastructure/adapters/automation/core/PlaywrightAutomationAdapter.ts +++ b/apps/companion/main/automation/infrastructure/adapters/automation/core/PlaywrightAutomationAdapter.ts @@ -16,7 +16,7 @@ import type { ModalResultDTO } from '../../../../application/dto/ModalResultDTO' import type { AutomationResultDTO } from '../../../../application/dto/AutomationResultDTO'; import type { AuthenticationServicePort } from '../../../../application/ports/AuthenticationServicePort'; 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 { SessionCookieStore } from '../auth/SessionCookieStore'; import { PlaywrightBrowserSession } from './PlaywrightBrowserSession'; diff --git a/apps/companion/main/automation/infrastructure/adapters/automation/core/WizardStepOrchestrator.ts b/apps/companion/main/automation/infrastructure/adapters/automation/core/WizardStepOrchestrator.ts index b612329d0..1d4aa839a 100644 --- a/apps/companion/main/automation/infrastructure/adapters/automation/core/WizardStepOrchestrator.ts +++ b/apps/companion/main/automation/infrastructure/adapters/automation/core/WizardStepOrchestrator.ts @@ -18,7 +18,7 @@ import type { PageStateValidation, PageStateValidationResult, } from '../../../../domain/services/PageStateValidator'; -import type { Result } from '@gridpilot/shared/application/Result'; +import type { Result } from '@core/shared/application/Result'; interface WizardStepOrchestratorDeps { config: Required; diff --git a/apps/companion/main/automation/infrastructure/adapters/ipc/ElectronCheckoutConfirmationAdapter.ts b/apps/companion/main/automation/infrastructure/adapters/ipc/ElectronCheckoutConfirmationAdapter.ts index 5138c56e5..cf3eb9dc3 100644 --- a/apps/companion/main/automation/infrastructure/adapters/ipc/ElectronCheckoutConfirmationAdapter.ts +++ b/apps/companion/main/automation/infrastructure/adapters/ipc/ElectronCheckoutConfirmationAdapter.ts @@ -5,7 +5,7 @@ import type { BrowserWindow } 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 { CheckoutConfirmationRequestDTO } from '../../../application/dto/CheckoutConfirmationRequestDTO'; import { CheckoutConfirmation } from '../../../domain/value-objects/CheckoutConfirmation'; diff --git a/core/racing/application/use-cases/DashboardOverviewUseCase.test.ts b/core/racing/application/use-cases/DashboardOverviewUseCase.test.ts index 738911c37..4c7b6df72 100644 --- a/core/racing/application/use-cases/DashboardOverviewUseCase.test.ts +++ b/core/racing/application/use-cases/DashboardOverviewUseCase.test.ts @@ -189,7 +189,7 @@ describe('DashboardOverviewUseCase', () => { getMembership: async (leagueId: string, driverIdParam: string): Promise => { return ( memberships.find( - m => m.leagueId === leagueId && m.driverId === driverIdParam, + m => m.leagueId.toString() === leagueId && m.driverId.toString() === driverIdParam, ) ?? null ); }, diff --git a/core/racing/application/use-cases/DashboardOverviewUseCase.ts b/core/racing/application/use-cases/DashboardOverviewUseCase.ts index 40e95a164..15d403851 100644 --- a/core/racing/application/use-cases/DashboardOverviewUseCase.ts +++ b/core/racing/application/use-cases/DashboardOverviewUseCase.ts @@ -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 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 { 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 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 { driverId: string; @@ -49,7 +48,7 @@ export interface DashboardRaceSummary { export interface DashboardRecentRaceResultSummary { race: Race; league: League | null; - result: RaceResult; + result: Result; } 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); @@ -142,7 +141,7 @@ export class DashboardOverviewUseCase { }; 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 upcomingRaces = allRaces @@ -150,7 +149,7 @@ export class DashboardOverviewUseCase { .sort((a, b) => a.scheduledAt.getTime() - b.scheduledAt.getTime()); const upcomingRacesInDriverLeagues = upcomingRaces.filter(race => - driverLeagueIds.has(race.leagueId), + driverLeagueIds.has(race.leagueId.toString()), ); const { myUpcomingRaces, otherUpcomingRaces } = @@ -226,10 +225,10 @@ export class DashboardOverviewUseCase { for (const league of allLeagues) { const membership = await this.leagueMembershipRepository.getMembership( - league.id, + league.id.toString(), driverId, ); - if (membership && membership.status === 'active') { + if (membership && membership.status.toString() === 'active') { driverLeagues.push(league); } } @@ -284,13 +283,13 @@ export class DashboardOverviewUseCase { driverId: string, ): DashboardRecentRaceResultSummary[] { 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 .map(result => { - const race = raceById.get(result.raceId); + const race = raceById.get(result.raceId.toString()); if (!race) return null; const league = leagueById.get(race.leagueId) ?? null; @@ -321,9 +320,9 @@ export class DashboardOverviewUseCase { const summaries: DashboardLeagueStandingSummary[] = []; 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( - (standing: Standing) => standing.driverId === driverId, + (standing: Standing) => standing.driverId.toString() === driverId, ); summaries.push({ @@ -347,7 +346,7 @@ export class DashboardOverviewUseCase { } for (const standing of leagueStandingsSummaries) { - activeLeagueIds.add(standing.league.id); + activeLeagueIds.add(standing.league.id.toString()); } return activeLeagueIds.size; diff --git a/core/racing/application/use-cases/FileProtestUseCase.test.ts b/core/racing/application/use-cases/FileProtestUseCase.test.ts index f44f14290..4c1816e1b 100644 --- a/core/racing/application/use-cases/FileProtestUseCase.test.ts +++ b/core/racing/application/use-cases/FileProtestUseCase.test.ts @@ -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 { 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', () => { let mockProtestRepo: { diff --git a/core/racing/domain/entities/result/index.ts b/core/racing/domain/entities/result/index.ts deleted file mode 100644 index 88ea629f7..000000000 --- a/core/racing/domain/entities/result/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Result'; \ No newline at end of file diff --git a/core/racing/index.ts b/core/racing/index.ts index d6cab8f6a..042aa2c77 100644 --- a/core/racing/index.ts +++ b/core/racing/index.ts @@ -9,7 +9,7 @@ export * from './domain/entities/Team'; export * from './domain/entities/Track'; export * from './domain/entities/Car'; 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/ILeagueRepository'; diff --git a/docs/architecture/ADAPTERS.md b/docs/architecture/ADAPTERS.md new file mode 100644 index 000000000..fb74baedd --- /dev/null +++ b/docs/architecture/ADAPTERS.md @@ -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. \ No newline at end of file