This commit is contained in:
2025-12-11 13:50:38 +01:00
parent e4c1be628d
commit c7e5de40d6
212 changed files with 2965 additions and 763 deletions

View File

@@ -1,4 +1,4 @@
import type { ParticipantRef } from '@gridpilot/racing/domain/value-objects/ParticipantRef';
import type { ParticipantRef } from '@gridpilot/racing/domain/types/ParticipantRef';
export interface ChampionshipStandingsRowDTO {
participant: ParticipantRef;

View File

@@ -53,9 +53,9 @@ export interface LeagueTimingsFormDTO {
timezoneId?: string; // IANA ID, e.g. "Europe/Berlin", or "track" for track local time
recurrenceStrategy?: 'weekly' | 'everyNWeeks' | 'monthlyNthWeekday';
intervalWeeks?: number;
weekdays?: import('../../domain/value-objects/Weekday').Weekday[];
weekdays?: import('../../domain/types/Weekday').Weekday[];
monthlyOrdinal?: 1 | 2 | 3 | 4;
monthlyWeekday?: import('../../domain/value-objects/Weekday').Weekday;
monthlyWeekday?: import('../../domain/types/Weekday').Weekday;
}
/**

View File

@@ -1,11 +1,11 @@
import type { LeagueTimingsFormDTO } from './LeagueConfigFormDTO';
import type { Weekday } from '../../domain/value-objects/Weekday';
import type { Weekday } from '../../domain/types/Weekday';
import { RaceTimeOfDay } from '../../domain/value-objects/RaceTimeOfDay';
import { LeagueTimezone } from '../../domain/value-objects/LeagueTimezone';
import { WeekdaySet } from '../../domain/value-objects/WeekdaySet';
import { MonthlyRecurrencePattern } from '../../domain/value-objects/MonthlyRecurrencePattern';
import type { RecurrenceStrategy } from '../../domain/value-objects/RecurrenceStrategy';
import { RecurrenceStrategyFactory } from '../../domain/value-objects/RecurrenceStrategy';
import type { RecurrenceStrategy } from '../../domain/types/RecurrenceStrategy';
import { RecurrenceStrategyFactory } from '../../domain/types/RecurrenceStrategy';
import { SeasonSchedule } from '../../domain/value-objects/SeasonSchedule';
import { BusinessRuleViolationError } from '../errors/RacingApplicationError';

View File

@@ -1,4 +1,8 @@
import type { Team, TeamJoinRequest, TeamMembership } from '../../domain/entities/Team';
import type { Team } from '../../domain/entities/Team';
import type {
TeamJoinRequest,
TeamMembership,
} from '../../domain/types/TeamMembership';
export interface JoinTeamCommandDTO {
teamId: string;

View File

@@ -1,5 +1,12 @@
export abstract class RacingApplicationError extends Error {
import type { IApplicationError, CommonApplicationErrorKind } from '@gridpilot/shared/errors';
export abstract class RacingApplicationError
extends Error
implements IApplicationError<CommonApplicationErrorKind | string, unknown>
{
readonly type = 'application' as const;
readonly context = 'racing-application';
abstract readonly kind: CommonApplicationErrorKind | string;
constructor(message: string) {
super(message);
@@ -22,11 +29,16 @@ export interface EntityNotFoundDetails {
id: string;
}
export class EntityNotFoundError extends RacingApplicationError {
export class EntityNotFoundError
extends RacingApplicationError
implements IApplicationError<'not_found', EntityNotFoundDetails>
{
readonly kind = 'not_found' as const;
readonly details: EntityNotFoundDetails;
constructor(public readonly details: EntityNotFoundDetails) {
constructor(details: EntityNotFoundDetails) {
super(`${details.entity} not found for id: ${details.id}`);
this.details = details;
}
}
@@ -39,15 +51,25 @@ export type PermissionDeniedReason =
| 'TEAM_OWNER_CANNOT_LEAVE'
| 'UNAUTHORIZED';
export class PermissionDeniedError extends RacingApplicationError {
export class PermissionDeniedError
extends RacingApplicationError
implements IApplicationError<'forbidden', PermissionDeniedReason>
{
readonly kind = 'forbidden' as const;
constructor(public readonly reason: PermissionDeniedReason, message?: string) {
super(message ?? `Permission denied: ${reason}`);
}
get details(): PermissionDeniedReason {
return this.reason;
}
}
export class BusinessRuleViolationError extends RacingApplicationError {
export class BusinessRuleViolationError
extends RacingApplicationError
implements IApplicationError<'conflict', undefined>
{
readonly kind = 'conflict' as const;
constructor(message: string) {

View File

@@ -54,13 +54,13 @@ export * from './ports/DriverRatingProvider';
export type { RaceRegistration } from '../domain/entities/RaceRegistration';
export type { Team } from '../domain/entities/Team';
export type {
Team,
TeamMembership,
TeamJoinRequest,
TeamRole,
TeamMembershipStatus,
} from '../domain/entities/Team';
} from '../domain/types/TeamMembership';
export type { DriverDTO } from './dto/DriverDTO';
export type { LeagueDTO } from './dto/LeagueDTO';

View File

@@ -0,0 +1,12 @@
/**
* Application Port: IImageServicePort
*
* Abstraction used by racing application use cases to obtain image URLs
* for drivers, teams and leagues without depending on UI/media layers.
*/
export interface IImageServicePort {
getDriverAvatar(driverId: string): string;
getTeamLogo(teamId: string): string;
getLeagueCover(leagueId: string): string;
getLeagueLogo(leagueId: string): string;
}

View File

@@ -1,4 +1,5 @@
import type { Team, TeamMembership } from '../../domain/entities/Team';
import type { Team } from '../../domain/entities/Team';
import type { TeamMembership } from '../../domain/types/TeamMembership';
export interface DriverTeamViewModel {
team: {

View File

@@ -1,4 +1,5 @@
import type { Team, TeamMembership } from '../../domain/entities/Team';
import type { Team } from '../../domain/entities/Team';
import type { TeamMembership } from '../../domain/types/TeamMembership';
export interface TeamDetailsViewModel {
team: {

View File

@@ -1,4 +1,4 @@
import type { TeamJoinRequest } from '../../domain/entities/Team';
import type { TeamJoinRequest } from '../../domain/types/TeamMembership';
export interface TeamJoinRequestViewModel {
requestId: string;

View File

@@ -1,4 +1,4 @@
import type { TeamMembership } from '../../domain/entities/Team';
import type { TeamMembership } from '../../domain/types/TeamMembership';
export interface TeamMemberViewModel {
driverId: string;

View File

@@ -8,6 +8,7 @@
import type { ISponsorshipRequestRepository } from '../../domain/repositories/ISponsorshipRequestRepository';
import type { ISeasonSponsorshipRepository } from '../../domain/repositories/ISeasonSponsorshipRepository';
import { SeasonSponsorship } from '../../domain/entities/SeasonSponsorship';
import type { AsyncUseCase } from '@gridpilot/shared/application';
export interface AcceptSponsorshipRequestDTO {
requestId: string;
@@ -23,7 +24,8 @@ export interface AcceptSponsorshipRequestResultDTO {
netAmount: number;
}
export class AcceptSponsorshipRequestUseCase {
export class AcceptSponsorshipRequestUseCase
implements AsyncUseCase<AcceptSponsorshipRequestDTO, AcceptSponsorshipRequestResultDTO> {
constructor(
private readonly sponsorshipRequestRepo: ISponsorshipRequestRepository,
private readonly seasonSponsorshipRepo: ISeasonSponsorshipRepository,

View File

@@ -11,6 +11,7 @@ import type { ISponsorshipRequestRepository } from '../../domain/repositories/IS
import type { ISponsorshipPricingRepository } from '../../domain/repositories/ISponsorshipPricingRepository';
import type { ISponsorRepository } from '../../domain/repositories/ISponsorRepository';
import { Money, type Currency } from '../../domain/value-objects/Money';
import type { AsyncUseCase } from '@gridpilot/shared/application';
import {
EntityNotFoundError,
BusinessRuleViolationError,
@@ -31,8 +32,10 @@ export interface ApplyForSponsorshipResultDTO {
status: 'pending';
createdAt: Date;
}
export class ApplyForSponsorshipUseCase {
export class ApplyForSponsorshipUseCase
implements AsyncUseCase<ApplyForSponsorshipDTO, ApplyForSponsorshipResultDTO>
{
constructor(
private readonly sponsorshipRequestRepo: ISponsorshipRequestRepository,
private readonly sponsorshipPricingRepo: ISponsorshipPricingRepository,

View File

@@ -11,6 +11,7 @@ import type { IProtestRepository } from '../../domain/repositories/IProtestRepos
import type { IRaceRepository } from '../../domain/repositories/IRaceRepository';
import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository';
import { randomUUID } from 'crypto';
import type { AsyncUseCase } from '@gridpilot/shared/application';
export interface ApplyPenaltyCommand {
raceId: string;
@@ -23,7 +24,8 @@ export interface ApplyPenaltyCommand {
notes?: string;
}
export class ApplyPenaltyUseCase {
export class ApplyPenaltyUseCase
implements AsyncUseCase<ApplyPenaltyCommand, { penaltyId: string }> {
constructor(
private readonly penaltyRepository: IPenaltyRepository,
private readonly protestRepository: IProtestRepository,

View File

@@ -4,10 +4,12 @@ import type {
TeamMembershipStatus,
TeamRole,
TeamJoinRequest,
} from '../../domain/entities/Team';
} from '../../domain/types/TeamMembership';
import type { ApproveTeamJoinRequestCommandDTO } from '../dto/TeamCommandAndQueryDTO';
import type { AsyncUseCase } from '@gridpilot/shared/application';
export class ApproveTeamJoinRequestUseCase {
export class ApproveTeamJoinRequestUseCase
implements AsyncUseCase<ApproveTeamJoinRequestCommandDTO, void> {
constructor(
private readonly membershipRepository: ITeamMembershipRepository,
) {}

View File

@@ -1,4 +1,5 @@
import type { IRaceRepository } from '../../domain/repositories/IRaceRepository';
import type { AsyncUseCase } from '@gridpilot/shared/application';
/**
* Use Case: CancelRaceUseCase
@@ -13,7 +14,8 @@ export interface CancelRaceCommandDTO {
raceId: string;
}
export class CancelRaceUseCase {
export class CancelRaceUseCase
implements AsyncUseCase<CancelRaceCommandDTO, void> {
constructor(
private readonly raceRepository: IRaceRepository,
) {}

View File

@@ -4,6 +4,7 @@ import type { ILeagueRepository } from '../../domain/repositories/ILeagueReposit
import type { ISeasonRepository } from '../../domain/repositories/ISeasonRepository';
import type { ILeagueScoringConfigRepository } from '../../domain/repositories/ILeagueScoringConfigRepository';
import type { LeagueScoringConfig } from '../../domain/entities/LeagueScoringConfig';
import type { AsyncUseCase } from '@gridpilot/shared/application';
import type {
LeagueScoringPresetProvider,
LeagueScoringPresetDTO,
@@ -47,7 +48,8 @@ export interface CreateLeagueWithSeasonAndScoringResultDTO {
scoringPresetName?: string;
}
export class CreateLeagueWithSeasonAndScoringUseCase {
export class CreateLeagueWithSeasonAndScoringUseCase
implements AsyncUseCase<CreateLeagueWithSeasonAndScoringCommand, CreateLeagueWithSeasonAndScoringResultDTO> {
constructor(
private readonly leagueRepository: ILeagueRepository,
private readonly seasonRepository: ISeasonRepository,

View File

@@ -1,11 +1,11 @@
import type { ITeamRepository } from '../../domain/repositories/ITeamRepository';
import type { ITeamMembershipRepository } from '../../domain/repositories/ITeamMembershipRepository';
import { Team } from '../../domain/entities/Team';
import type {
Team,
TeamMembership,
TeamMembershipStatus,
TeamRole,
} from '../../domain/entities/Team';
} from '../../domain/types/TeamMembership';
import type {
CreateTeamCommandDTO,
CreateTeamResultDTO,

View File

@@ -5,12 +5,15 @@ import type { ILeagueScoringConfigRepository } from '../../domain/repositories/I
import type { IGameRepository } from '../../domain/repositories/IGameRepository';
import type { LeagueScoringPresetProvider } from '../ports/LeagueScoringPresetProvider';
import type { IAllLeaguesWithCapacityAndScoringPresenter, LeagueEnrichedData } from '../presenters/IAllLeaguesWithCapacityAndScoringPresenter';
import type { AsyncUseCase } from '@gridpilot/shared/application';
/**
* Use Case for retrieving all leagues with capacity and scoring information.
* Orchestrates domain logic and delegates presentation to the presenter.
*/
export class GetAllLeaguesWithCapacityAndScoringUseCase {
export class GetAllLeaguesWithCapacityAndScoringUseCase
implements AsyncUseCase<void, void>
{
constructor(
private readonly leagueRepository: ILeagueRepository,
private readonly leagueMembershipRepository: ILeagueMembershipRepository,

View File

@@ -1,12 +1,15 @@
import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository';
import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository';
import type { IAllLeaguesWithCapacityPresenter } from '../presenters/IAllLeaguesWithCapacityPresenter';
import type { AsyncUseCase } from '@gridpilot/shared/application';
/**
* Use Case for retrieving all leagues with capacity information.
* Orchestrates domain logic and delegates presentation to the presenter.
*/
export class GetAllLeaguesWithCapacityUseCase {
export class GetAllLeaguesWithCapacityUseCase
implements AsyncUseCase<void, void>
{
constructor(
private readonly leagueRepository: ILeagueRepository,
private readonly leagueMembershipRepository: ILeagueMembershipRepository,

View File

@@ -6,8 +6,10 @@ import type {
AllRacesListItemViewModel,
AllRacesFilterOptionsViewModel,
} from '../presenters/IAllRacesPagePresenter';
import type { AsyncUseCase } from '@gridpilot/shared/application';
export class GetAllRacesPageDataUseCase {
export class GetAllRacesPageDataUseCase
implements AsyncUseCase<void, void> {
constructor(
private readonly raceRepository: IRaceRepository,
private readonly leagueRepository: ILeagueRepository,

View File

@@ -1,12 +1,14 @@
import type { ITeamRepository } from '../../domain/repositories/ITeamRepository';
import type { ITeamMembershipRepository } from '../../domain/repositories/ITeamMembershipRepository';
import type { IAllTeamsPresenter } from '../presenters/IAllTeamsPresenter';
import type { AsyncUseCase } from '@gridpilot/shared/application';
/**
* Use Case for retrieving all teams.
* Orchestrates domain logic and delegates presentation to the presenter.
*/
export class GetAllTeamsUseCase {
export class GetAllTeamsUseCase
implements AsyncUseCase<void, void> {
constructor(
private readonly teamRepository: ITeamRepository,
private readonly teamMembershipRepository: ITeamMembershipRepository,

View File

@@ -5,9 +5,10 @@ import type { ILeagueRepository } from '../../domain/repositories/ILeagueReposit
import type { IStandingRepository } from '../../domain/repositories/IStandingRepository';
import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository';
import type { IRaceRegistrationRepository } from '../../domain/repositories/IRaceRegistrationRepository';
import type { IImageService } from '../../domain/services/IImageService';
import type { IImageServicePort } from '../ports/IImageServicePort';
import type { IFeedRepository } from '@gridpilot/social/domain/repositories/IFeedRepository';
import type { ISocialGraphRepository } from '@gridpilot/social/domain/repositories/ISocialGraphRepository';
import type { AsyncUseCase } from '@gridpilot/shared/application';
import type {
IDashboardOverviewPresenter,
DashboardOverviewViewModel,
@@ -33,7 +34,8 @@ export interface GetDashboardOverviewParams {
driverId: string;
}
export class GetDashboardOverviewUseCase {
export class GetDashboardOverviewUseCase
implements AsyncUseCase<GetDashboardOverviewParams, void> {
constructor(
private readonly driverRepository: IDriverRepository,
private readonly raceRepository: IRaceRepository,
@@ -44,7 +46,7 @@ export class GetDashboardOverviewUseCase {
private readonly raceRegistrationRepository: IRaceRegistrationRepository,
private readonly feedRepository: IFeedRepository,
private readonly socialRepository: ISocialGraphRepository,
private readonly imageService: IImageService,
private readonly imageService: IImageServicePort,
private readonly getDriverStats: (driverId: string) => DashboardDriverStatsAdapter | null,
public readonly presenter: IDashboardOverviewPresenter,
) {}

View File

@@ -1,12 +1,14 @@
import type { ITeamRepository } from '../../domain/repositories/ITeamRepository';
import type { ITeamMembershipRepository } from '../../domain/repositories/ITeamMembershipRepository';
import type { IDriverTeamPresenter } from '../presenters/IDriverTeamPresenter';
import type { AsyncUseCase } from '@gridpilot/shared/application';
/**
* Use Case for retrieving a driver's team.
* Orchestrates domain logic and delegates presentation to the presenter.
*/
export class GetDriverTeamUseCase {
export class GetDriverTeamUseCase
implements AsyncUseCase<string, boolean> {
constructor(
private readonly teamRepository: ITeamRepository,
private readonly membershipRepository: ITeamMembershipRepository,

View File

@@ -1,19 +1,21 @@
import type { IDriverRepository } from '../../domain/repositories/IDriverRepository';
import type { IRankingService } from '../../domain/services/IRankingService';
import type { IDriverStatsService } from '../../domain/services/IDriverStatsService';
import type { IImageService } from '../../domain/services/IImageService';
import type { IImageServicePort } from '../ports/IImageServicePort';
import type { IDriversLeaderboardPresenter } from '../presenters/IDriversLeaderboardPresenter';
import type { AsyncUseCase } from '@gridpilot/shared/application';
/**
* Use Case for retrieving driver leaderboard data.
* Orchestrates domain logic and delegates presentation to the presenter.
*/
export class GetDriversLeaderboardUseCase {
export class GetDriversLeaderboardUseCase
implements AsyncUseCase<void, void> {
constructor(
private readonly driverRepository: IDriverRepository,
private readonly rankingService: IRankingService,
private readonly driverStatsService: IDriverStatsService,
private readonly imageService: IImageService,
private readonly imageService: IImageServicePort,
public readonly presenter: IDriversLeaderboardPresenter,
) {}

View File

@@ -11,6 +11,7 @@ import type { ISeasonSponsorshipRepository } from '../../domain/repositories/ISe
import type { SponsorableEntityType } from '../../domain/entities/SponsorshipRequest';
import type { SponsorshipTier } from '../../domain/entities/SeasonSponsorship';
import type { IEntitySponsorshipPricingPresenter } from '../presenters/IEntitySponsorshipPricingPresenter';
import type { AsyncUseCase } from '@gridpilot/shared/application';
export interface GetEntitySponsorshipPricingDTO {
entityType: SponsorableEntityType;
@@ -38,7 +39,8 @@ export interface GetEntitySponsorshipPricingResultDTO {
secondarySlot?: SponsorshipSlotDTO;
}
export class GetEntitySponsorshipPricingUseCase {
export class GetEntitySponsorshipPricingUseCase
implements AsyncUseCase<GetEntitySponsorshipPricingDTO, void> {
constructor(
private readonly sponsorshipPricingRepo: ISponsorshipPricingRepository,
private readonly sponsorshipRequestRepo: ISponsorshipRequestRepository,

View File

@@ -3,6 +3,7 @@ import type { IResultRepository } from '../../domain/repositories/IResultReposit
import type { IPenaltyRepository } from '../../domain/repositories/IPenaltyRepository';
import type { IRaceRepository } from '../../domain/repositories/IRaceRepository';
import type { ILeagueDriverSeasonStatsPresenter } from '../presenters/ILeagueDriverSeasonStatsPresenter';
import type { AsyncUseCase } from '@gridpilot/shared/application';
export interface DriverRatingPort {
getRating(driverId: string): { rating: number | null; ratingChange: number | null };
@@ -16,7 +17,8 @@ export interface GetLeagueDriverSeasonStatsUseCaseParams {
* Use Case for retrieving league driver season statistics.
* Orchestrates domain logic and delegates presentation to the presenter.
*/
export class GetLeagueDriverSeasonStatsUseCase {
export class GetLeagueDriverSeasonStatsUseCase
implements AsyncUseCase<GetLeagueDriverSeasonStatsUseCaseParams, void> {
constructor(
private readonly standingRepository: IStandingRepository,
private readonly resultRepository: IResultRepository,

View File

@@ -3,13 +3,16 @@ import type { ISeasonRepository } from '../../domain/repositories/ISeasonReposit
import type { ILeagueScoringConfigRepository } from '../../domain/repositories/ILeagueScoringConfigRepository';
import type { IGameRepository } from '../../domain/repositories/IGameRepository';
import type { ILeagueFullConfigPresenter, LeagueFullConfigData } from '../presenters/ILeagueFullConfigPresenter';
import type { AsyncUseCase } from '@gridpilot/shared/application';
import { EntityNotFoundError } from '../errors/RacingApplicationError';
/**
* Use Case for retrieving a league's full configuration.
* Orchestrates domain logic and delegates presentation to the presenter.
*/
export class GetLeagueFullConfigUseCase {
export class GetLeagueFullConfigUseCase
implements AsyncUseCase<{ leagueId: string }, void>
{
constructor(
private readonly leagueRepository: ILeagueRepository,
private readonly seasonRepository: ISeasonRepository,

View File

@@ -4,12 +4,14 @@ import type { ILeagueScoringConfigRepository } from '../../domain/repositories/I
import type { IGameRepository } from '../../domain/repositories/IGameRepository';
import type { LeagueScoringPresetProvider } from '../ports/LeagueScoringPresetProvider';
import type { ILeagueScoringConfigPresenter, LeagueScoringConfigData } from '../presenters/ILeagueScoringConfigPresenter';
import type { AsyncUseCase } from '@gridpilot/shared/application';
/**
* Use Case for retrieving a league's scoring configuration for its active season.
* Orchestrates domain logic and delegates presentation to the presenter.
*/
export class GetLeagueScoringConfigUseCase {
export class GetLeagueScoringConfigUseCase
implements AsyncUseCase<{ leagueId: string }, void> {
constructor(
private readonly leagueRepository: ILeagueRepository,
private readonly seasonRepository: ISeasonRepository,

View File

@@ -1,5 +1,6 @@
import type { IStandingRepository } from '../../domain/repositories/IStandingRepository';
import type { ILeagueStandingsPresenter } from '../presenters/ILeagueStandingsPresenter';
import type { AsyncUseCase } from '@gridpilot/shared/application';
export interface GetLeagueStandingsUseCaseParams {
leagueId: string;
@@ -9,7 +10,8 @@ export interface GetLeagueStandingsUseCaseParams {
* Use Case for retrieving league standings.
* Orchestrates domain logic and delegates presentation to the presenter.
*/
export class GetLeagueStandingsUseCase {
export class GetLeagueStandingsUseCase
implements AsyncUseCase<GetLeagueStandingsUseCaseParams, void> {
constructor(
private readonly standingRepository: IStandingRepository,
public readonly presenter: ILeagueStandingsPresenter,

View File

@@ -8,6 +8,7 @@ import type { IRaceRepository } from '../../domain/repositories/IRaceRepository'
import type { IResultRepository } from '../../domain/repositories/IResultRepository';
import type { DriverRatingProvider } from '../ports/DriverRatingProvider';
import type { ILeagueStatsPresenter } from '../presenters/ILeagueStatsPresenter';
import type { AsyncUseCase } from '@gridpilot/shared/application';
import {
AverageStrengthOfFieldCalculator,
type StrengthOfFieldCalculator,
@@ -20,7 +21,8 @@ export interface GetLeagueStatsUseCaseParams {
/**
* Use Case for retrieving league statistics including average SOF across completed races.
*/
export class GetLeagueStatsUseCase {
export class GetLeagueStatsUseCase
implements AsyncUseCase<GetLeagueStatsUseCaseParams, void> {
private readonly sofCalculator: StrengthOfFieldCalculator;
constructor(

View File

@@ -1,7 +1,7 @@
import type { IDriverRepository } from '../../domain/repositories/IDriverRepository';
import type { ITeamRepository } from '../../domain/repositories/ITeamRepository';
import type { ITeamMembershipRepository } from '../../domain/repositories/ITeamMembershipRepository';
import type { IImageService } from '../../domain/services/IImageService';
import type { IImageServicePort } from '../ports/IImageServicePort';
import type { ISocialGraphRepository } from '@gridpilot/social/domain/repositories/ISocialGraphRepository';
import type {
IProfileOverviewPresenter,
@@ -44,7 +44,7 @@ export class GetProfileOverviewUseCase {
private readonly teamRepository: ITeamRepository,
private readonly teamMembershipRepository: ITeamMembershipRepository,
private readonly socialRepository: ISocialGraphRepository,
private readonly imageService: IImageService,
private readonly imageService: IImageServicePort,
private readonly getDriverStats: (driverId: string) => ProfileDriverStatsAdapter | null,
private readonly getAllDriverRankings: () => DriverRankingEntry[],
public readonly presenter: IProfileOverviewPresenter,

View File

@@ -5,7 +5,7 @@ import type { IRaceRegistrationRepository } from '../../domain/repositories/IRac
import type { IResultRepository } from '../../domain/repositories/IResultRepository';
import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository';
import type { DriverRatingProvider } from '../ports/DriverRatingProvider';
import type { IImageService } from '../../domain/services/IImageService';
import type { IImageServicePort } from '../ports/IImageServicePort';
import type {
IRaceDetailPresenter,
RaceDetailViewModel,
@@ -39,7 +39,7 @@ export class GetRaceDetailUseCase {
private readonly resultRepository: IResultRepository,
private readonly leagueMembershipRepository: ILeagueMembershipRepository,
private readonly driverRatingProvider: DriverRatingProvider,
private readonly imageService: IImageService,
private readonly imageService: IImageServicePort,
public readonly presenter: IRaceDetailPresenter,
) {}

View File

@@ -1,6 +1,6 @@
import type { ITeamMembershipRepository } from '../../domain/repositories/ITeamMembershipRepository';
import type { IDriverRepository } from '../../domain/repositories/IDriverRepository';
import type { IImageService } from '../../domain/services/IImageService';
import type { IImageServicePort } from '../ports/IImageServicePort';
import type { ITeamJoinRequestsPresenter } from '../presenters/ITeamJoinRequestsPresenter';
/**
@@ -11,7 +11,7 @@ export class GetTeamJoinRequestsUseCase {
constructor(
private readonly membershipRepository: ITeamMembershipRepository,
private readonly driverRepository: IDriverRepository,
private readonly imageService: IImageService,
private readonly imageService: IImageServicePort,
public readonly presenter: ITeamJoinRequestsPresenter,
) {}

View File

@@ -1,6 +1,6 @@
import type { ITeamMembershipRepository } from '../../domain/repositories/ITeamMembershipRepository';
import type { IDriverRepository } from '../../domain/repositories/IDriverRepository';
import type { IImageService } from '../../domain/services/IImageService';
import type { IImageServicePort } from '../ports/IImageServicePort';
import type { ITeamMembersPresenter } from '../presenters/ITeamMembersPresenter';
/**
@@ -11,7 +11,7 @@ export class GetTeamMembersUseCase {
constructor(
private readonly membershipRepository: ITeamMembershipRepository,
private readonly driverRepository: IDriverRepository,
private readonly imageService: IImageService,
private readonly imageService: IImageServicePort,
public readonly presenter: ITeamMembersPresenter,
) {}

View File

@@ -3,6 +3,7 @@ import type { ILeagueRepository } from '../../domain/repositories/ILeagueReposit
import type { IResultRepository } from '../../domain/repositories/IResultRepository';
import type { IStandingRepository } from '../../domain/repositories/IStandingRepository';
import { Result } from '../../domain/entities/Result';
import type { AsyncUseCase } from '@gridpilot/shared/application';
import {
BusinessRuleViolationError,
EntityNotFoundError,
@@ -26,8 +27,10 @@ export interface ImportRaceResultsParams {
raceId: string;
results: ImportRaceResultDTO[];
}
export class ImportRaceResultsUseCase {
export class ImportRaceResultsUseCase
implements AsyncUseCase<ImportRaceResultsParams, void>
{
constructor(
private readonly raceRepository: IRaceRepository,
private readonly leagueRepository: ILeagueRepository,

View File

@@ -1,15 +1,16 @@
import type {
ILeagueMembershipRepository,
} from '@gridpilot/racing/domain/repositories/ILeagueMembershipRepository';
import type {
import type { AsyncUseCase } from '@gridpilot/shared/application';
import {
LeagueMembership,
MembershipRole,
MembershipStatus,
type MembershipRole,
type MembershipStatus,
} from '@gridpilot/racing/domain/entities/LeagueMembership';
import type { JoinLeagueCommandDTO } from '../dto/JoinLeagueCommandDTO';
import { BusinessRuleViolationError } from '../errors/RacingApplicationError';
export class JoinLeagueUseCase {
export class JoinLeagueUseCase implements AsyncUseCase<JoinLeagueCommandDTO, LeagueMembership> {
constructor(private readonly membershipRepository: ILeagueMembershipRepository) {}
/**
@@ -27,13 +28,12 @@ export class JoinLeagueUseCase {
throw new BusinessRuleViolationError('Already a member or have a pending request');
}
const membership: LeagueMembership = {
const membership = LeagueMembership.create({
leagueId,
driverId,
role: 'member' as MembershipRole,
status: 'active' as MembershipStatus,
joinedAt: new Date(),
};
});
return this.membershipRepository.saveMembership(membership);
}

View File

@@ -4,14 +4,15 @@ import type {
TeamMembership,
TeamMembershipStatus,
TeamRole,
} from '../../domain/entities/Team';
} from '../../domain/types/TeamMembership';
import type { JoinTeamCommandDTO } from '../dto/TeamCommandAndQueryDTO';
import type { AsyncUseCase } from '@gridpilot/shared/application';
import {
BusinessRuleViolationError,
EntityNotFoundError,
} from '../errors/RacingApplicationError';
export class JoinTeamUseCase {
export class JoinTeamUseCase implements AsyncUseCase<JoinTeamCommandDTO, void> {
constructor(
private readonly teamRepository: ITeamRepository,
private readonly membershipRepository: ITeamMembershipRepository,

View File

@@ -5,8 +5,8 @@ import type { IResultRepository } from '@gridpilot/racing/domain/repositories/IR
import type { IPenaltyRepository } from '@gridpilot/racing/domain/repositories/IPenaltyRepository';
import type { IChampionshipStandingRepository } from '@gridpilot/racing/domain/repositories/IChampionshipStandingRepository';
import type { ChampionshipConfig } from '@gridpilot/racing/domain/value-objects/ChampionshipConfig';
import type { SessionType } from '@gridpilot/racing/domain/value-objects/SessionType';
import type { ChampionshipConfig } from '@gridpilot/racing/domain/types/ChampionshipConfig';
import type { SessionType } from '@gridpilot/racing/domain/types/SessionType';
import type { ChampionshipStanding } from '@gridpilot/racing/domain/entities/ChampionshipStanding';
import { EventScoringService } from '@gridpilot/racing/domain/services/EventScoringService';
import { ChampionshipAggregator } from '@gridpilot/racing/domain/services/ChampionshipAggregator';

View File

@@ -1,13 +1,16 @@
import type { IRaceRegistrationRepository } from '@gridpilot/racing/domain/repositories/IRaceRegistrationRepository';
import type { ILeagueMembershipRepository } from '@gridpilot/racing/domain/repositories/ILeagueMembershipRepository';
import type { RaceRegistration } from '@gridpilot/racing/domain/entities/RaceRegistration';
import { RaceRegistration } from '@gridpilot/racing/domain/entities/RaceRegistration';
import type { RegisterForRaceCommandDTO } from '../dto/RegisterForRaceCommandDTO';
import type { AsyncUseCase } from '@gridpilot/shared/application';
import {
BusinessRuleViolationError,
PermissionDeniedError,
} from '../errors/RacingApplicationError';
export class RegisterForRaceUseCase {
export class RegisterForRaceUseCase
implements AsyncUseCase<RegisterForRaceCommandDTO, void>
{
constructor(
private readonly registrationRepository: IRaceRegistrationRepository,
private readonly membershipRepository: ILeagueMembershipRepository,
@@ -32,11 +35,10 @@ export class RegisterForRaceUseCase {
throw new PermissionDeniedError('NOT_ACTIVE_MEMBER', 'Must be an active league member to register for races');
}
const registration: RaceRegistration = {
const registration = RaceRegistration.create({
raceId,
driverId,
registeredAt: new Date(),
};
});
await this.registrationRepository.register(registration);
}

View File

@@ -8,7 +8,7 @@
import type { IProtestRepository } from '../../domain/repositories/IProtestRepository';
import type { IRaceRepository } from '../../domain/repositories/IRaceRepository';
import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository';
import { isLeagueStewardOrHigherRole } from '../../domain/value-objects/LeagueRoles';
import { isLeagueStewardOrHigherRole } from '../../domain/types/LeagueRoles';
export interface RequestProtestDefenseCommand {
protestId: string;

View File

@@ -1,6 +1,6 @@
import type { ITeamRepository } from '../../domain/repositories/ITeamRepository';
import type { ITeamMembershipRepository } from '../../domain/repositories/ITeamMembershipRepository';
import type { Team } from '../../domain/entities/Team';
import { Team } from '../../domain/entities/Team';
import type { UpdateTeamCommandDTO } from '../dto/TeamCommandAndQueryDTO';
export class UpdateTeamUseCase {