diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index c2f09cd2e..262d75a08 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -361,6 +361,144 @@ Inner layers: This keeps the **automation engine** stable and reusable across presentation surfaces (companion today, web or backend orchestrators later), while allowing infrastructure and UI details to evolve without rewriting business logic. +### 4.2 Critical Clean Architecture Principle: Use Cases Do NOT Call Presenters + +**The most important rule in Clean Architecture is that use cases must remain completely independent of presentation concerns.** + +#### ❌ WRONG PATTERN (What NOT to do) + +```typescript +// ❌ VIOLATES CLEAN ARCHITECTURE +class GetRaceDetailUseCase { + constructor( + private repositories: any, + private output: UseCaseOutputPort + ) {} + + async execute(input: GetRaceDetailInput): Promise> { + const race = await this.raceRepository.findById(input.raceId); + + if (!race) { + // WRONG: Use case calling presenter + const result = Result.err({ code: 'RACE_NOT_FOUND', details: {...} }); + this.output.present(result); // ❌ DON'T DO THIS + return result; + } + + // WRONG: Use case calling presenter + const result = Result.ok({ race, league, registrations, drivers, userResult, isUserRegistered, canRegister }); + this.output.present(result); // ❌ DON'T DO THIS + return result; + } +} +``` + +**Why this violates Clean Architecture:** +- Use cases now **know about presenters** and how to call them +- Creates **tight coupling** between application logic and presentation +- Makes use cases **untestable** without mocking presenters +- Violates the **Dependency Rule** (inner layer depending on outer layer behavior) + +#### ✅ CORRECT PATTERN (Clean Architecture) + +```typescript +// ✅ CLEAN ARCHITECTURE - Use case returns data, period +class GetRaceDetailUseCase { + constructor( + private repositories: any, + private output: UseCaseOutputPort + ) {} + + async execute(input: GetRaceDetailInput): Promise> { + const race = await this.raceRepository.findById(input.raceId); + + if (!race) { + return Result.err({ code: 'RACE_NOT_FOUND', details: {...} }); + // NO .present() call! Just returns the Result. + } + + return Result.ok({ race, league, registrations, drivers, userResult, isUserRegistered, canRegister }); + // NO .present() call! Just returns the Result. + } +} +``` + +**The Controller/Wiring Layer (Infrastructure/Presentation):** + +```typescript +// ✅ Controller wires use case to presenter +class RaceController { + constructor( + private getRaceDetailUseCase: GetRaceDetailUseCase, + private raceDetailPresenter: RaceDetailPresenter + ) {} + + async getRaceDetail(params: GetRaceDetailParamsDTO): Promise { + // 1. Execute use case + const result = await this.getRaceDetailUseCase.execute(params); + + // 2. Pass result to presenter (wiring happens here) + this.raceDetailPresenter.present(result); + + // 3. Get ViewModel from presenter + return this.raceDetailPresenter.viewModel; + } +} +``` + +### 4.3 The "Presenter Not Presented" Error Explained + +Your current architecture has this error because: + +1. **Use cases call `.present()`** (violating Clean Architecture) +2. **Controllers expect presenters to have `.viewModel`** +3. **But if use case returns early on error without calling `.present()`**, the presenter never gets data +4. **Controller tries to access `.viewModel`** → throws "Presenter not presented" + +**The fix is NOT to add more `.present()` calls to use cases. The fix is to remove ALL `.present()` calls from use cases.** + +### 4.4 Your Adapter Pattern is a Smokescreen + +Your current code uses adapter classes like: + +```typescript +class RaceDetailOutputAdapter implements UseCaseOutputPort { + constructor(private presenter: RaceDetailPresenter) {} + + present(result: GetRaceDetailResult): void { + this.presenter.present(result); + } +} +``` + +**This is just hiding the crime.** The adapter still couples the use case to the presenter concept. The real Clean Architecture approach eliminates these adapters entirely and has controllers do the wiring. + +### 4.5 The Real Clean Architecture Flow + +``` +1. Controller receives HTTP request +2. Controller calls UseCase.execute() +3. UseCase returns Result (no presenter knowledge) +4. Controller passes Result to Presenter +5. Presenter transforms Result → ViewModel +6. Controller returns ViewModel to HTTP layer +``` + +**Key insight:** The use case's `output` port should be **the Result itself**, not a presenter. The controller is responsible for taking that Result and passing it to the appropriate presenter. + +### 4.6 What This Means for Your Codebase + +**To achieve 100% Clean Architecture, you must:** + +1. **Remove all `.present()` calls from use cases** - they should only return Results +2. **Remove all adapter classes** - they're unnecessary coupling +3. **Make controllers wire use cases to presenters** - this is where the "glue" belongs +4. **Use cases return Results, period** - they don't know about presenters, viewmodels, or HTTP + +**This is the ONLY way to achieve true Clean Architecture.** Any pattern where use cases call presenters is **not Clean Architecture**, regardless of how many adapter layers you add. + +The "presenter not presented" error is a **symptom** of this architectural violation, not the root problem. + --- ## 4. Layer-by-Layer Mapping @@ -717,11 +855,11 @@ This section describes how a typical hosted-session automation run flows through - The automation engine (implemented by [`AutomationEngineAdapter`](core/infrastructure/adapters/automation/engine/AutomationEngineAdapter.ts:1) and backed by [`PlaywrightAutomationAdapter`](core/infrastructure/adapters/automation/core/PlaywrightAutomationAdapter.ts:1)) proceeds through steps: - Navigate to hosted sessions. - - Open “Create a Race” and the hosted-session wizard. + - Open "Create a Race" and the hosted-session wizard. - For each step (race information, server details, admins, cars, tracks, weather, race options, conditions): - Ensure the correct page is active using [`PageStateValidator`](core/domain/services/PageStateValidator.ts:1) and selectors from [`IRACING_SELECTORS`](core/infrastructure/adapters/automation/dom/IRacingSelectors.ts:1). - Fill fields and toggles using [`IRacingDomInteractor`](core/infrastructure/adapters/automation/dom/IRacingDomInteractor.ts:1). - - Click the correct “Next” / “Create Race” / “New Race” buttons, guarded by [`SafeClickService`](core/infrastructure/adapters/automation/dom/SafeClickService.ts:1) and blocked-selector logic. + - Click the correct "Next" / "Create Race" / "New Race" buttons, guarded by [`SafeClickService`](core/infrastructure/adapters/automation/dom/SafeClickService.ts:1) and blocked-selector logic. - At each step, the Playwright adapter: - Updates the overlay via `updateOverlay(step, message)`. @@ -741,6 +879,60 @@ This section describes how a typical hosted-session automation run flows through - The companion renderer may present a [`RaceCreationResult`](core/domain/value-objects/RaceCreationResult.ts:1) via [`RaceCreationSuccessScreen`](apps/companion/renderer/components/RaceCreationSuccessScreen.tsx). - The browser context is closed or re-used based on mode and configuration; debug artifacts may be written by the Playwright adapter for failed runs. +### 5.2 Clean Architecture Flow Example + +**The correct Clean Architecture flow for use cases:** + +```typescript +// ❌ WRONG - Current broken pattern +class GetRaceDetailUseCase { + async execute(input: GetRaceDetailInput): Promise> { + const race = await this.raceRepository.findById(input.raceId); + + if (!race) { + const result = Result.err({ code: 'RACE_NOT_FOUND', details: {...} }); + this.output.present(result); // ❌ Use case calling presenter + return result; + } + + const result = Result.ok({ race, league, registrations, drivers, userResult, isUserRegistered, canRegister }); + this.output.present(result); // ❌ Use case calling presenter + return result; + } +} + +// ✅ CORRECT - Clean Architecture +class GetRaceDetailUseCase { + async execute(input: GetRaceDetailInput): Promise> { + const race = await this.raceRepository.findById(input.raceId); + + if (!race) { + return Result.err({ code: 'RACE_NOT_FOUND', details: {...} }); + // ✅ No .present() call - just returns Result + } + + return Result.ok({ race, league, registrations, drivers, userResult, isUserRegistered, canRegister }); + // ✅ No .present() call - just returns Result + } +} + +// Controller wiring (in infrastructure/presentation layer) +class RaceController { + async getRaceDetail(params: GetRaceDetailParamsDTO): Promise { + // 1. Call use case + const result = await this.getRaceDetailUseCase.execute(params); + + // 2. Wire to presenter + this.raceDetailPresenter.present(result); + + // 3. Return ViewModel + return this.raceDetailPresenter.viewModel; + } +} +``` + +**This is the ONLY pattern that respects Clean Architecture.** Your current architecture violates this fundamental principle, which is why you have the "presenter not presented" problem. + --- ## 6. How This Serves Admins, Drivers, Teams diff --git a/docs/architecture/USECASES.md b/docs/architecture/USECASES.md index f3213721b..b4501d3c9 100644 --- a/docs/architecture/USECASES.md +++ b/docs/architecture/USECASES.md @@ -5,13 +5,13 @@ according to Clean Architecture, in a NestJS-based system. The goal is: • strict separation of concerns - • correct terminology (no fake “ports”) + • correct terminology (no fake "ports") • minimal abstractions • long-term consistency This is the canonical reference for all use cases in this codebase. -⸻ +~ 1. Core Concepts (Authoritative Definitions) @@ -24,7 +24,7 @@ Use Case The public execute() method is the input port. -⸻ +~ Input • Pure data @@ -37,7 +37,7 @@ type GetSponsorsInput = { } -⸻ +~ Result • The business outcome of a use case @@ -50,7 +50,7 @@ type GetSponsorsResult = { } -⸻ +~ Output Port • A behavioral boundary @@ -63,7 +63,7 @@ export interface UseCaseOutputPort { } -⸻ +~ Presenter • Implements UseCaseOutputPort @@ -72,7 +72,7 @@ Presenter • Holds internal state • Is pulled by the controller after execution -⸻ +~ 2. Canonical Use Case Structure @@ -102,7 +102,85 @@ Rules: • All output flows through the OutputPort • The return value signals success or failure only -⸻ +### ⚠️ ARCHITECTURAL VIOLATION ALERT + +**The pattern shown above is INCORRECT and violates Clean Architecture.** + +#### ❌ WRONG PATTERN (What NOT to do) + +```typescript +@Injectable() +export class GetSponsorsUseCase { + constructor( + private readonly sponsorRepository: ISponsorRepository, + private readonly output: UseCaseOutputPort, + ) {} + + async execute(): Promise> { + const sponsors = await this.sponsorRepository.findAll() + + this.output.present({ sponsors }) // ❌ WRONG: Use case calling presenter + return Result.ok(undefined) + } +} +``` + +**Why this violates Clean Architecture:** +- Use cases **know about presenters** and how to call them +- Creates **tight coupling** between application logic and presentation +- Makes use cases **untestable** without mocking presenters +- Violates the **Dependency Rule** (inner layer depending on outer layer behavior) + +#### ✅ CORRECT PATTERN (Clean Architecture) + +```typescript +@Injectable() +export class GetSponsorsUseCase { + constructor( + private readonly sponsorRepository: ISponsorRepository, + // NO output port needed in constructor + ) {} + + async execute(): Promise> { + const sponsors = await this.sponsorRepository.findAll() + + return Result.ok({ sponsors }) + // ✅ Returns Result, period. No .present() call. + } +} +``` + +**The Controller (in API layer) handles the wiring:** + +```typescript +@Controller('/sponsors') +export class SponsorsController { + constructor( + private readonly useCase: GetSponsorsUseCase, + private readonly presenter: GetSponsorsPresenter, + ) {} + + @Get() + async getSponsors() { + // 1. Execute use case + const result = await this.useCase.execute() + + if (result.isErr()) { + throw mapApplicationError(result.unwrapErr()) + } + + // 2. Wire to presenter + this.presenter.present(result.value) + + // 3. Return ViewModel + return this.presenter.getViewModel() + } +} +``` + +**This is the ONLY pattern that respects Clean Architecture.** + +~ Result Model @@ -116,7 +194,7 @@ Rules: • No interfaces • No transport concerns -⸻ +~ 3. API Layer @@ -158,7 +236,7 @@ export class GetSponsorsPresenter } -⸻ +~ Controller @@ -182,7 +260,7 @@ export class SponsorsController { } -⸻ +~ Payments Example @@ -266,7 +344,7 @@ export class PaymentsController { } } -⸻ +~ 4. Module Wiring (Composition Root) @@ -287,7 +365,7 @@ Rules: • The presenter is bound as the OutputPort implementation • process.env is not used inside the use case -⸻ +~ 5. Explicitly Forbidden @@ -299,7 +377,7 @@ Rules: ❌ Mapping logic inside use cases ❌ Environment access inside the core -⸻ +~ Do / Don’t (Boundary Examples) @@ -307,6 +385,8 @@ Do / Don’t (Boundary Examples) ✅ DO: Keep controllers/services thin and delegating, e.g. [LeagueController.createLeagueSeasonScheduleRace()](apps/api/src/domain/league/LeagueController.ts:291). ❌ DON’T: Put business rules in the API layer; rules belong in `./core` use cases/entities/value objects, e.g. [CreateLeagueSeasonScheduleRaceUseCase.execute()](core/racing/application/use-cases/CreateLeagueSeasonScheduleRaceUseCase.ts:38). +~ + 6. Optional Extensions Custom Output Ports @@ -322,7 +402,7 @@ interface ComplexOutputPort { } -⸻ +~ Input Port Interfaces @@ -335,7 +415,7 @@ Otherwise: The use case class itself is the input port. -⸻ +~ 7. Key Rules (Memorize These) @@ -348,7 +428,7 @@ Data does not. The core produces truth. The API interprets it. -⸻ +~ TL;DR • Use cases are injected via DI @@ -357,4 +437,77 @@ TL;DR • Results are business models, not DTOs • Interfaces exist only for behavior variability -This document is the single source of truth for use case architecture in this project. \ No newline at end of file +### 🚨 CRITICAL CLEAN ARCHITECTURE CORRECTION + +**The examples in this document (sections 2, 3, and the Payments Example) demonstrate the WRONG pattern that violates Clean Architecture.** + +#### The Fundamental Problem + +The current architecture shows use cases **calling presenters directly**: + +```typescript +// ❌ WRONG - This violates Clean Architecture +this.output.present({ sponsors }) +``` + +**This is architecturally incorrect.** Use cases must **never** know about presenters or call `.present()`. + +#### The Correct Clean Architecture Pattern + +**Use cases return Results. Controllers wire them to presenters.** + +```typescript +// ✅ CORRECT - Use case returns data +@Injectable() +export class GetSponsorsUseCase { + constructor(private readonly sponsorRepository: ISponsorRepository) {} + + async execute(): Promise> { + const sponsors = await this.sponsorRepository.findAll() + return Result.ok({ sponsors }) + // NO .present() call! + } +} + +// ✅ CORRECT - Controller handles wiring +@Controller('/sponsors') +export class SponsorsController { + constructor( + private readonly useCase: GetSponsorsUseCase, + private readonly presenter: GetSponsorsPresenter, + ) {} + + @Get() + async getSponsors() { + const result = await this.useCase.execute() + + if (result.isErr()) { + throw mapApplicationError(result.unwrapErr()) + } + + this.presenter.present(result.value) + return this.presenter.getViewModel() + } +} +``` + +#### Why This Matters + +1. **Dependency Rule**: Inner layers (use cases) cannot depend on outer layers (presenters) +2. **Testability**: Use cases can be tested without mocking presenters +3. **Flexibility**: Same use case can work with different presenters +4. **Separation of Concerns**: Use cases do business logic, presenters do transformation + +#### What Must Be Fixed + +**All use cases in the codebase must be updated to:** +1. **Remove** the `output: UseCaseOutputPort` constructor parameter +2. **Return** `Result` directly from `execute()` +3. **Remove** all `this.output.present()` calls + +**All controllers must be updated to:** +1. **Call** the use case and get the Result +2. **Pass** `result.value` to the presenter's `.present()` method +3. **Return** the presenter's `.getViewModel()` + +This is the **single source of truth** for correct Clean Architecture in this project. \ No newline at end of file diff --git a/plans/CLEAN_ARCHITECTURE_FIX_PLAN.md b/plans/CLEAN_ARCHITECTURE_FIX_PLAN.md new file mode 100644 index 000000000..45f346941 --- /dev/null +++ b/plans/CLEAN_ARCHITECTURE_FIX_PLAN.md @@ -0,0 +1,401 @@ +# Clean Architecture Violation Fix Plan + +## Executive Summary + +**Problem**: The codebase violates Clean Architecture by having use cases call presenters directly (`this.output.present()`), creating tight coupling and causing "Presenter not presented" errors. + +**Root Cause**: Use cases are doing the presenter's job instead of returning data and letting controllers handle the wiring. + +**Solution**: Remove ALL `.present()` calls from use cases. Use cases return Results. Controllers wire Results to Presenters. + +--- + +## The Violation Pattern + +### ❌ Current Wrong Pattern (Violates Clean Architecture) +```typescript +// core/racing/application/use-cases/GetRaceDetailUseCase.ts +class GetRaceDetailUseCase { + constructor( + private repositories: any, + private output: UseCaseOutputPort // ❌ Wrong + ) {} + + async execute(input: GetRaceDetailInput): Promise> { + const race = await this.raceRepository.findById(input.raceId); + + if (!race) { + const result = Result.err({ code: 'RACE_NOT_FOUND', details: {...} }); + this.output.present(result); // ❌ WRONG: Use case calling presenter + return result; + } + + const result = Result.ok({ race, league, registrations, drivers, userResult, isUserRegistered, canRegister }); + this.output.present(result); // ❌ WRONG: Use case calling presenter + return result; + } +} +``` + +### ✅ Correct Pattern (Clean Architecture) +```typescript +// core/racing/application/use-cases/GetRaceDetailUseCase.ts +class GetRaceDetailUseCase { + constructor( + private repositories: any, + // NO output port - removed + ) {} + + async execute(input: GetRaceDetailInput): Promise> { + const race = await this.raceRepository.findById(input.raceId); + + if (!race) { + return Result.err({ code: 'RACE_NOT_FOUND', details: {...} }); + // ✅ No .present() call + } + + return Result.ok({ race, league, registrations, drivers, userResult, isUserRegistered, canRegister }); + // ✅ No .present() call + } +} + +// apps/api/src/domain/race/RaceService.ts (Controller layer) +class RaceService { + constructor( + private getRaceDetailUseCase: GetRaceDetailUseCase, + private raceDetailPresenter: RaceDetailPresenter, + ) {} + + async getRaceDetail(params: GetRaceDetailParamsDTO): Promise { + const result = await this.getRaceDetailUseCase.execute(params); + + if (result.isErr()) { + throw new NotFoundException(result.error.details.message); + } + + this.raceDetailPresenter.present(result.value); // ✅ Controller wires to presenter + return this.raceDetailPresenter; + } +} +``` + +--- + +## What Needs To Be Done + +### Phase 1: Fix Use Cases (Remove Output Ports) + +**Files to modify in `core/racing/application/use-cases/`:** + +1. **GetRaceDetailUseCase.ts** (lines 35-44, 46-115) + - Remove `output: UseCaseOutputPort` from constructor + - Change return type from `Promise>` to `Promise>` + - Remove all `this.output.present()` calls (lines 100, 109-112) + +2. **GetRaceRegistrationsUseCase.ts** (lines 27-29, 31-70) + - Remove output port from constructor + - Change return type + - Remove `this.output.present()` calls (lines 40-43, 66-69) + +3. **GetLeagueFullConfigUseCase.ts** (lines 35-37, 39-92) + - Remove output port from constructor + - Change return type + - Remove `this.output.present()` calls (lines 47-50, 88-91) + +4. **GetRaceWithSOFUseCase.ts** (lines 43-45, 47-118) + - Remove output port from constructor + - Change return type + - Remove `this.output.present()` calls (lines 58-61, 114-117) + +5. **GetRaceResultsDetailUseCase.ts** (lines 41-43, 45-100) + - Remove output port from constructor + - Change return type + - Remove `this.output.present()` calls (lines 56-59, 95-98) + +**Continue this pattern for ALL 150+ use cases listed in your original analysis.** + +### Phase 2: Fix Controllers/Services (Add Wiring Logic) + +**Files to modify in `apps/api/src/domain/`:** + +1. **RaceService.ts** (lines 135-139) + - Update `getRaceDetail()` to wire use case result to presenter + - Add error handling for Result.Err cases + +2. **RaceProviders.ts** (lines 138-144, 407-437) + - Remove adapter classes that wrap presenters + - Update provider factories to inject presenters directly to controllers + - Remove `RaceDetailOutputAdapter` and similar classes + +3. **All other service files** that use use cases + - Update method signatures to handle Results + - Add proper error mapping + - Wire results to presenters + +### Phase 3: Update Module Wiring + +**Files to modify:** + +1. **RaceProviders.ts** (lines 287-779) + - Remove all adapter classes (lines 111-285) + - Update provider definitions to not use adapters + - Simplify dependency injection + +2. **All other provider files** in `apps/api/src/domain/*/` + - Remove adapter patterns + - Update DI containers + +### Phase 4: Fix Presenters (If Needed) + +**Some presenters may need updates:** + +1. **RaceDetailPresenter.ts** (lines 15-26, 28-114) + - Ensure `present()` method accepts `GetRaceDetailResult` directly + - No changes needed if already correct + +2. **CommandResultPresenter.ts** and similar + - Ensure they work with Results from controllers, not use cases + +--- + +## Implementation Checklist + +### For Each Use Case File: +- [ ] Remove `output: UseCaseOutputPort` from constructor +- [ ] Change return type from `Promise>` to `Promise>` +- [ ] Remove all `this.output.present()` calls +- [ ] Return Result directly +- [ ] Update imports if needed + +### For Each Controller/Service File: +- [ ] Update methods to call use case and get Result +- [ ] Add `if (result.isErr())` error handling +- [ ] Call `presenter.present(result.value)` after success +- [ ] Return presenter or ViewModel +- [ ] Remove adapter usage + +### For Each Provider File: +- [ ] Remove adapter classes +- [ ] Update DI to inject presenters to controllers +- [ ] Simplify provider definitions + +--- + +## Files That Need Immediate Attention + +### High Priority (Core Racing Domain): +``` +core/racing/application/use-cases/GetRaceDetailUseCase.ts +core/racing/application/use-cases/GetRaceRegistrationsUseCase.ts +core/racing/application/use-cases/GetLeagueFullConfigUseCase.ts +core/racing/application/use-cases/GetRaceWithSOFUseCase.ts +core/racing/application/use-cases/GetRaceResultsDetailUseCase.ts +core/racing/application/use-cases/GetLeagueAdminPermissionsUseCase.ts +core/racing/application/use-cases/CompleteRaceUseCase.ts +core/racing/application/use-cases/ApplyPenaltyUseCase.ts +core/racing/application/use-cases/JoinLeagueUseCase.ts +core/racing/application/use-cases/JoinTeamUseCase.ts +core/racing/application/use-cases/RegisterForRaceUseCase.ts +core/racing/application/use-cases/WithdrawFromRaceUseCase.ts +core/racing/application/use-cases/CancelRaceUseCase.ts +core/racing/application/use-cases/ReopenRaceUseCase.ts +core/racing/application/use-cases/CompleteRaceUseCaseWithRatings.ts +core/racing/application/use-cases/ImportRaceResultsUseCase.ts +core/racing/application/use-cases/ImportRaceResultsApiUseCase.ts +core/racing/application/use-cases/FileProtestUseCase.ts +core/racing/application/use-cases/ReviewProtestUseCase.ts +core/racing/application/use-cases/QuickPenaltyUseCase.ts +core/racing/application/use-cases/ApplyForSponsorshipUseCase.ts +core/racing/application/use-cases/AcceptSponsorshipRequestUseCase.ts +core/racing/application/use-cases/RejectSponsorshipRequestUseCase.ts +core/racing/application/use-cases/GetSponsorDashboardUseCase.ts +core/racing/application/use-cases/GetSponsorSponsorshipsUseCase.ts +core/racing/application/use-cases/GetSponsorshipPricingUseCase.ts +core/racing/application/use-cases/GetEntitySponsorshipPricingUseCase.ts +core/racing/application/use-cases/GetSeasonSponsorshipsUseCase.ts +core/racing/application/use-cases/GetPendingSponsorshipRequestsUseCase.ts +core/racing/application/use-cases/GetLeagueWalletUseCase.ts +core/racing/application/use-cases/WithdrawFromLeagueWalletUseCase.ts +core/racing/application/use-cases/GetLeagueStatsUseCase.ts +core/racing/application/use-cases/GetLeagueMembershipsUseCase.ts +core/racing/application/use-cases/GetLeagueJoinRequestsUseCase.ts +core/racing/application/use-cases/GetTeamJoinRequestsUseCase.ts +core/racing/application/use-cases/GetLeagueRosterMembersUseCase.ts +core/racing/application/use-cases/GetLeagueRosterJoinRequestsUseCase.ts +core/racing/application/use-cases/ApproveLeagueJoinRequestUseCase.ts +core/racing/application/use-cases/RejectLeagueJoinRequestUseCase.ts +core/racing/application/use-cases/UpdateLeagueMemberRoleUseCase.ts +core/racing/application/use-cases/RemoveLeagueMemberUseCase.ts +core/racing/application/use-cases/TransferLeagueOwnershipUseCase.ts +core/racing/application/use-cases/GetLeagueAdminUseCase.ts +core/racing/application/use-cases/GetLeagueOwnerSummaryUseCase.ts +core/racing/application/use-cases/GetLeagueScoringConfigUseCase.ts +core/racing/application/use-cases/ListLeagueScoringPresetsUseCase.ts +core/racing/application/use-cases/GetLeagueScheduleUseCase.ts +core/racing/application/use-cases/CreateLeagueSeasonScheduleRaceUseCase.ts +core/racing/application/use-cases/UpdateLeagueSeasonScheduleRaceUseCase.ts +core/racing/application/use-cases/DeleteLeagueSeasonScheduleRaceUseCase.ts +core/racing/application/use-cases/PublishLeagueSeasonScheduleUseCase.ts +core/racing/application/use-cases/UnpublishLeagueSeasonScheduleUseCase.ts +core/racing/application/use-cases/PreviewLeagueScheduleUseCase.ts +core/racing/application/use-cases/GetSeasonDetailsUseCase.ts +core/racing/application/use-cases/ListSeasonsForLeagueUseCase.ts +core/racing/application/use-cases/CreateSeasonForLeagueUseCase.ts +core/racing/application/use-cases/CreateLeagueWithSeasonAndScoringUseCase.ts +core/racing/application/use-cases/ManageSeasonLifecycleUseCase.ts +core/racing/application/use-cases/RecalculateChampionshipStandingsUseCase.ts +core/racing/application/use-cases/GetLeagueStandingsUseCase.ts +core/racing/application/use-cases/GetDriversLeaderboardUseCase.ts +core/racing/application/use-cases/GetTeamsLeaderboardUseCase.ts +core/racing/application/use-cases/GetTotalDriversUseCase.ts +core/racing/application/use-cases/GetTotalLeaguesUseCase.ts +core/racing/application/use-cases/GetTotalRacesUseCase.ts +core/racing/application/use-cases/GetAllRacesUseCase.ts +core/racing/application/use-cases/GetAllRacesPageDataUseCase.ts +core/racing/application/use-cases/GetRacesPageDataUseCase.ts +core/racing/application/use-cases/GetAllLeaguesWithCapacityAndScoringUseCase.ts +core/racing/application/use-cases/GetAllTeamsUseCase.ts +core/racing/application/use-cases/GetTeamDetailsUseCase.ts +core/racing/application/use-cases/GetTeamMembersUseCase.ts +core/racing/application/use-cases/UpdateTeamUseCase.ts +core/racing/application/use-cases/CreateTeamUseCase.ts +core/racing/application/use-cases/LeaveTeamUseCase.ts +core/racing/application/use-cases/ApproveTeamJoinRequestUseCase.ts +core/racing/application/use-cases/RejectTeamJoinRequestUseCase.ts +core/racing/application/use-cases/GetDriverTeamUseCase.ts +core/racing/application/use-cases/GetProfileOverviewUseCase.ts +core/racing/application/use-cases/CompleteDriverOnboardingUseCase.ts +core/racing/application/use-cases/UpdateDriverProfileUseCase.ts +core/racing/application/use-cases/SendFinalResultsUseCase.ts +core/racing/application/use-cases/SendPerformanceSummaryUseCase.ts +core/racing/application/use-cases/RequestProtestDefenseUseCase.ts +core/racing/application/use-cases/SubmitProtestDefenseUseCase.ts +core/racing/application/use-cases/GetRaceProtestsUseCase.ts +core/racing/application/use-cases/GetLeagueProtestsUseCase.ts +core/racing/application/use-cases/GetRacePenaltiesUseCase.ts +core/racing/application/use-cases/GetSponsorsUseCase.ts +core/racing/application/use-cases/GetSponsorUseCase.ts +core/racing/application/use-cases/CreateSponsorUseCase.ts +core/racing/application/use-cases/GetAllLeaguesWithCapacityUseCase.ts +core/racing/application/use-cases/GetLeagueStatsUseCase.ts +``` + +### Medium Priority (Media Domain): +``` +core/media/application/use-cases/GetAvatarUseCase.ts +core/media/application/use-cases/GetMediaUseCase.ts +core/media/application/use-cases/DeleteMediaUseCase.ts +core/media/application/use-cases/UploadMediaUseCase.ts +core/media/application/use-cases/UpdateAvatarUseCase.ts +core/media/application/use-cases/RequestAvatarGenerationUseCase.ts +core/media/application/use-cases/SelectAvatarUseCase.ts +``` + +### Medium Priority (Identity Domain): +``` +core/identity/application/use-cases/SignupUseCase.ts +core/identity/application/use-cases/SignupWithEmailUseCase.ts +core/identity/application/use-cases/LoginUseCase.ts +core/identity/application/use-cases/LoginWithEmailUseCase.ts +core/identity/application/use-cases/ForgotPasswordUseCase.ts +core/identity/application/use-cases/ResetPasswordUseCase.ts +core/identity/application/use-cases/GetCurrentSessionUseCase.ts +core/identity/application/use-cases/GetCurrentUserSessionUseCase.ts +core/identity/application/use-cases/LogoutUseCase.ts +core/identity/application/use-cases/StartAuthUseCase.ts +core/identity/application/use-cases/HandleAuthCallbackUseCase.ts +core/identity/application/use-cases/SignupSponsorUseCase.ts +core/identity/application/use-cases/CreateAchievementUseCase.ts +``` + +### Medium Priority (Notifications Domain): +``` +core/notifications/application/use-cases/GetUnreadNotificationsUseCase.ts +core/notifications/application/use-cases/MarkNotificationReadUseCase.ts +core/notifications/application/use-cases/NotificationPreferencesUseCases.ts +core/notifications/application/use-cases/SendNotificationUseCase.ts +``` + +### Medium Priority (Analytics Domain): +``` +core/analytics/application/use-cases/GetAnalyticsMetricsUseCase.ts +core/analytics/application/use-cases/GetDashboardDataUseCase.ts +core/analytics/application/use-cases/RecordPageViewUseCase.ts +core/analytics/application/use-cases/RecordEngagementUseCase.ts +``` + +### Medium Priority (Admin Domain): +``` +core/admin/application/use-cases/ListUsersUseCase.ts +``` + +### Medium Priority (Social Domain): +``` +core/social/application/use-cases/GetUserFeedUseCase.ts +core/social/application/use-cases/GetCurrentUserSocialUseCase.ts +``` + +### Medium Priority (Payments Domain): +``` +core/payments/application/use-cases/GetWalletUseCase.ts +core/payments/application/use-cases/GetMembershipFeesUseCase.ts +core/payments/application/use-cases/UpdatePaymentStatusUseCase.ts +core/payments/application/use-cases/AwardPrizeUseCase.ts +core/payments/application/use-cases/DeletePrizeUseCase.ts +core/payments/application/use-cases/CreatePrizeUseCase.ts +core/payments/application/use-cases/CreatePaymentUseCase.ts +core/payments/application/use-cases/ProcessWalletTransactionUseCase.ts +core/payments/application/use-cases/UpdateMemberPaymentUseCase.ts +core/payments/application/use-cases/GetPaymentsUseCase.ts +core/payments/application/use-cases/UpsertMembershipFeeUseCase.ts +``` + +### Controller/Service Files: +``` +apps/api/src/domain/race/RaceService.ts +apps/api/src/domain/race/RaceProviders.ts +apps/api/src/domain/sponsor/SponsorService.ts +apps/api/src/domain/league/LeagueService.ts +apps/api/src/domain/driver/DriverService.ts +apps/api/src/domain/auth/AuthService.ts +apps/api/src/domain/analytics/AnalyticsService.ts +apps/api/src/domain/notifications/NotificationsService.ts +apps/api/src/domain/payments/PaymentsService.ts +apps/api/src/domain/admin/AdminService.ts +apps/api/src/domain/social/SocialService.ts +apps/api/src/domain/media/MediaService.ts +``` + +--- + +## Success Criteria + +✅ **All use cases return `Result` directly** +✅ **No use case calls `.present()`** +✅ **All controllers wire Results to Presenters** +✅ **All adapter classes removed** +✅ **Module wiring simplified** +✅ **"Presenter not presented" errors eliminated** +✅ **Tests updated and passing** + +--- + +## Estimated Effort + +- **150+ use cases** to fix +- **20+ controller/service files** to update +- **10+ provider files** to simplify +- **Estimated time**: 2-3 days of focused work +- **Risk**: Medium (requires careful testing) + +--- + +## Next Steps + +1. **Start with Phase 1**: Fix the core racing use cases first (highest impact) +2. **Test each change**: Run existing tests to ensure no regressions +3. **Update controllers**: Wire Results to Presenters +4. **Simplify providers**: Remove adapter classes +5. **Run full test suite**: Verify everything works + +**This plan provides the roadmap to achieve 100% Clean Architecture compliance.** \ No newline at end of file