team rating

This commit is contained in:
2025-12-30 12:25:45 +01:00
parent ccaa39c39c
commit 83371ea839
93 changed files with 10324 additions and 490 deletions

View File

@@ -1,6 +1,6 @@
import { Provider } from '@nestjs/common';
import { IMAGE_SERVICE_TOKEN, LOGGER_TOKEN } from './TeamTokens';
import { IMAGE_SERVICE_TOKEN, LOGGER_TOKEN, TEAM_STATS_REPOSITORY_TOKEN, MEDIA_REPOSITORY_TOKEN } from './TeamTokens';
export {
TEAM_REPOSITORY_TOKEN,
@@ -8,16 +8,22 @@ export {
DRIVER_REPOSITORY_TOKEN,
IMAGE_SERVICE_TOKEN,
LOGGER_TOKEN,
TEAM_STATS_REPOSITORY_TOKEN,
MEDIA_REPOSITORY_TOKEN,
} from './TeamTokens';
// Import core interfaces
import type { Logger } from '@core/shared/application/Logger';
import type { ITeamStatsRepository } from '@core/racing/domain/repositories/ITeamStatsRepository';
// Import concrete in-memory implementations
import { InMemoryImageServiceAdapter } from '@adapters/media/ports/InMemoryImageServiceAdapter';
import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
import { InMemoryTeamStatsRepository } from '@adapters/racing/persistence/inmemory/InMemoryTeamStatsRepository';
import { InMemoryMediaRepository } from '@adapters/racing/persistence/media/InMemoryMediaRepository';
// Use cases are imported and used directly in the service
// Import presenters
import { AllTeamsPresenter } from './presenters/AllTeamsPresenter';
export const TeamProviders: Provider[] = [
{
@@ -29,5 +35,19 @@ export const TeamProviders: Provider[] = [
provide: LOGGER_TOKEN,
useClass: ConsoleLogger,
},
// Use cases are created directly in the service
{
provide: TEAM_STATS_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemoryTeamStatsRepository(logger),
inject: [LOGGER_TOKEN],
},
{
provide: MEDIA_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemoryMediaRepository(logger),
inject: [LOGGER_TOKEN],
},
{
provide: AllTeamsPresenter,
useFactory: (teamStatsRepository: ITeamStatsRepository) => new AllTeamsPresenter(teamStatsRepository),
inject: [TEAM_STATS_REPOSITORY_TOKEN],
},
];

View File

@@ -111,7 +111,37 @@ describe('TeamService', () => {
error: vi.fn(),
} as unknown as Logger;
service = new TeamService(teamRepository as unknown as never, membershipRepository as unknown as never, driverRepository as unknown as never, logger);
const teamStatsRepository = {
getTeamStats: vi.fn(),
getTeamStatsSync: vi.fn(),
saveTeamStats: vi.fn(),
getAllStats: vi.fn(),
clear: vi.fn(),
};
const mediaRepository = {
getTeamAvatar: vi.fn(),
saveTeamAvatar: vi.fn(),
getDriverAvatar: vi.fn(),
saveDriverAvatar: vi.fn(),
};
const allTeamsPresenter = {
reset: vi.fn(),
present: vi.fn(),
getResponseModel: vi.fn(() => ({ teams: [], totalCount: 0 })),
responseModel: { teams: [], totalCount: 0 },
};
service = new TeamService(
teamRepository as unknown as never,
membershipRepository as unknown as never,
driverRepository as unknown as never,
logger,
teamStatsRepository as unknown as never,
mediaRepository as unknown as never,
allTeamsPresenter as unknown as never
);
});
it('getAll returns teams and totalCount on success', async () => {

View File

@@ -37,7 +37,9 @@ import { CreateTeamPresenter } from './presenters/CreateTeamPresenter';
import { UpdateTeamPresenter } from './presenters/UpdateTeamPresenter';
// Tokens
import { TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, LOGGER_TOKEN } from './TeamTokens';
import { TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, LOGGER_TOKEN, TEAM_STATS_REPOSITORY_TOKEN, MEDIA_REPOSITORY_TOKEN } from './TeamTokens';
import type { ITeamStatsRepository } from '@core/racing/domain/repositories/ITeamStatsRepository';
import type { IMediaRepository } from '@core/racing/domain/repositories/IMediaRepository';
@Injectable()
export class TeamService {
@@ -46,20 +48,29 @@ export class TeamService {
@Inject(TEAM_MEMBERSHIP_REPOSITORY_TOKEN) private readonly membershipRepository: ITeamMembershipRepository,
@Inject(DRIVER_REPOSITORY_TOKEN) private readonly driverRepository: IDriverRepository,
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
@Inject(TEAM_STATS_REPOSITORY_TOKEN) private readonly teamStatsRepository: ITeamStatsRepository,
@Inject(MEDIA_REPOSITORY_TOKEN) private readonly mediaRepository: IMediaRepository,
private readonly allTeamsPresenter: AllTeamsPresenter,
) {}
async getAll(): Promise<GetAllTeamsOutputDTO> {
this.logger.debug('[TeamService] Fetching all teams.');
const presenter = new AllTeamsPresenter();
const useCase = new GetAllTeamsUseCase(this.teamRepository, this.membershipRepository, this.logger, presenter);
const useCase = new GetAllTeamsUseCase(
this.teamRepository,
this.membershipRepository,
this.teamStatsRepository,
this.mediaRepository,
this.logger,
this.allTeamsPresenter
);
const result = await useCase.execute();
if (result.isErr()) {
this.logger.error('Error fetching all teams', new Error(result.error?.details?.message || 'Unknown error'));
return { teams: [], totalCount: 0 };
}
return presenter.getResponseModel()!;
return this.allTeamsPresenter.getResponseModel()!;
}
async getDetails(teamId: string, userId?: string): Promise<GetTeamDetailsOutputDTO | null> {

View File

@@ -2,4 +2,6 @@ export const TEAM_REPOSITORY_TOKEN = 'ITeamRepository';
export const TEAM_MEMBERSHIP_REPOSITORY_TOKEN = 'ITeamMembershipRepository';
export const DRIVER_REPOSITORY_TOKEN = 'IDriverRepository';
export const IMAGE_SERVICE_TOKEN = 'IImageServicePort';
export const LOGGER_TOKEN = 'Logger';
export const LOGGER_TOKEN = 'Logger';
export const TEAM_STATS_REPOSITORY_TOKEN = 'ITeamStatsRepository';
export const MEDIA_REPOSITORY_TOKEN = 'IMediaRepository';

View File

@@ -1,21 +1,23 @@
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
import type { GetAllTeamsResult } from '@core/racing/application/use-cases/GetAllTeamsUseCase';
import { GetAllTeamsOutputDTO } from '../dtos/GetAllTeamsOutputDTO';
import { TeamStatsStore } from '@adapters/racing/services/TeamStatsStore';
import type { ITeamStatsRepository } from '@core/racing/domain/repositories/ITeamStatsRepository';
export class AllTeamsPresenter implements UseCaseOutputPort<GetAllTeamsResult> {
private model: GetAllTeamsOutputDTO | null = null;
constructor(
private readonly teamStatsRepository: ITeamStatsRepository
) {}
reset(): void {
this.model = null;
}
present(result: GetAllTeamsResult): void {
const statsStore = TeamStatsStore.getInstance();
this.model = {
teams: result.teams.map(team => {
const stats = statsStore.getTeamStats(team.id.toString());
const stats = this.teamStatsRepository.getTeamStatsSync(team.id.toString());
return {
id: team.id,