league service
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TeamService } from './TeamService';
|
||||
import { TeamController } from './TeamController';
|
||||
import { TeamProviders } from './TeamProviders';
|
||||
|
||||
@Module({
|
||||
controllers: [TeamController],
|
||||
providers: [TeamService],
|
||||
providers: TeamProviders,
|
||||
exports: [TeamService],
|
||||
})
|
||||
export class TeamModule {}
|
||||
|
||||
@@ -1,5 +1,54 @@
|
||||
import { Provider } from '@nestjs/common';
|
||||
import { TeamService } from './TeamService';
|
||||
|
||||
export const TeamProviders = [
|
||||
TeamService,
|
||||
// Import core interfaces
|
||||
import { ITeamRepository } from '@gridpilot/racing/domain/repositories/ITeamRepository';
|
||||
import { ITeamMembershipRepository } from '@gridpilot/racing/domain/repositories/ITeamMembershipRepository';
|
||||
import { Logger } from '@gridpilot/shared/application/Logger';
|
||||
|
||||
// Import concrete in-memory implementations
|
||||
import { InMemoryTeamRepository } from 'adapters/racing/persistence/inmemory/InMemoryTeamRepository';
|
||||
import { InMemoryTeamMembershipRepository } from 'adapters/racing/persistence/inmemory/InMemoryTeamMembershipRepository';
|
||||
import { ConsoleLogger } from 'adapters/logging/ConsoleLogger';
|
||||
|
||||
// Import use cases
|
||||
import { GetAllTeamsUseCase } from '@gridpilot/racing/application/use-cases/GetAllTeamsUseCase';
|
||||
import { GetDriverTeamUseCase } from '@gridpilot/racing/application/use-cases/GetDriverTeamUseCase';
|
||||
|
||||
// Tokens
|
||||
export const TEAM_REPOSITORY_TOKEN = 'ITeamRepository';
|
||||
export const TEAM_MEMBERSHIP_REPOSITORY_TOKEN = 'ITeamMembershipRepository';
|
||||
export const TEAM_GET_ALL_USE_CASE_TOKEN = 'GetAllTeamsUseCase';
|
||||
export const TEAM_GET_DRIVER_TEAM_USE_CASE_TOKEN = 'GetDriverTeamUseCase';
|
||||
export const TEAM_LOGGER_TOKEN = 'Logger';
|
||||
|
||||
export const TeamProviders: Provider[] = [
|
||||
TeamService, // Provide the service itself
|
||||
{
|
||||
provide: TEAM_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger) => new InMemoryTeamRepository(logger),
|
||||
inject: [TEAM_LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: TEAM_MEMBERSHIP_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger) => new InMemoryTeamMembershipRepository(logger),
|
||||
inject: [TEAM_LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: TEAM_LOGGER_TOKEN,
|
||||
useClass: ConsoleLogger,
|
||||
},
|
||||
// Use cases
|
||||
{
|
||||
provide: TEAM_GET_ALL_USE_CASE_TOKEN,
|
||||
useFactory: (teamRepo: ITeamRepository, membershipRepo: ITeamMembershipRepository, logger: Logger) =>
|
||||
new GetAllTeamsUseCase(teamRepo, membershipRepo, logger),
|
||||
inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, TEAM_LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: TEAM_GET_DRIVER_TEAM_USE_CASE_TOKEN,
|
||||
useFactory: (teamRepo: ITeamRepository, membershipRepo: ITeamMembershipRepository, logger: Logger) =>
|
||||
new GetDriverTeamUseCase(teamRepo, membershipRepo, logger),
|
||||
inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, TEAM_LOGGER_TOKEN],
|
||||
},
|
||||
];
|
||||
|
||||
168
apps/api/src/modules/team/TeamService.spec.ts
Normal file
168
apps/api/src/modules/team/TeamService.spec.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { TeamService } from './TeamService';
|
||||
import { GetAllTeamsUseCase } from '@gridpilot/racing/application/use-cases/GetAllTeamsUseCase';
|
||||
import { GetDriverTeamUseCase } from '@gridpilot/racing/application/use-cases/GetDriverTeamUseCase';
|
||||
import { Logger } from '@gridpilot/shared/application/Logger';
|
||||
import { AllTeamsPresenter } from './presenters/AllTeamsPresenter';
|
||||
import { DriverTeamPresenter } from './presenters/DriverTeamPresenter';
|
||||
import { AllTeamsViewModel, DriverTeamViewModel, GetDriverTeamQuery } from './dto/TeamDto';
|
||||
import { TEAM_GET_ALL_USE_CASE_TOKEN, TEAM_GET_DRIVER_TEAM_USE_CASE_TOKEN, TEAM_LOGGER_TOKEN } from './TeamProviders';
|
||||
|
||||
describe('TeamService', () => {
|
||||
let service: TeamService;
|
||||
let getAllTeamsUseCase: jest.Mocked<GetAllTeamsUseCase>;
|
||||
let getDriverTeamUseCase: jest.Mocked<GetDriverTeamUseCase>;
|
||||
let logger: jest.Mocked<Logger>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const mockGetAllTeamsUseCase = {
|
||||
execute: jest.fn(),
|
||||
};
|
||||
const mockGetDriverTeamUseCase = {
|
||||
execute: jest.fn(),
|
||||
};
|
||||
const mockLogger = {
|
||||
debug: jest.fn(),
|
||||
info: jest.fn(),
|
||||
error: jest.fn(),
|
||||
};
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
TeamService,
|
||||
{
|
||||
provide: TEAM_GET_ALL_USE_CASE_TOKEN,
|
||||
useValue: mockGetAllTeamsUseCase,
|
||||
},
|
||||
{
|
||||
provide: TEAM_GET_DRIVER_TEAM_USE_CASE_TOKEN,
|
||||
useValue: mockGetDriverTeamUseCase,
|
||||
},
|
||||
{
|
||||
provide: TEAM_LOGGER_TOKEN,
|
||||
useValue: mockLogger,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<TeamService>(TeamService);
|
||||
getAllTeamsUseCase = module.get(TEAM_GET_ALL_USE_CASE_TOKEN);
|
||||
getDriverTeamUseCase = module.get(TEAM_GET_DRIVER_TEAM_USE_CASE_TOKEN);
|
||||
logger = module.get(TEAM_LOGGER_TOKEN);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
|
||||
describe('getAllTeams', () => {
|
||||
it('should create presenter, call use case, and return view model', async () => {
|
||||
const mockViewModel: AllTeamsViewModel = {
|
||||
teams: [],
|
||||
totalCount: 0,
|
||||
};
|
||||
|
||||
const mockPresenter = {
|
||||
reset: jest.fn(),
|
||||
present: jest.fn(),
|
||||
get viewModel(): AllTeamsViewModel {
|
||||
return mockViewModel;
|
||||
},
|
||||
};
|
||||
|
||||
// Mock the presenter constructor
|
||||
const originalConstructor = AllTeamsPresenter;
|
||||
(AllTeamsPresenter as any) = jest.fn().mockImplementation(() => mockPresenter);
|
||||
|
||||
// Mock the use case to call the presenter
|
||||
getAllTeamsUseCase.execute.mockImplementation(async (input, presenter) => {
|
||||
presenter.present({ teams: [] });
|
||||
});
|
||||
|
||||
const result = await service.getAllTeams();
|
||||
|
||||
expect(AllTeamsPresenter).toHaveBeenCalled();
|
||||
expect(getAllTeamsUseCase.execute).toHaveBeenCalledWith(undefined, mockPresenter);
|
||||
expect(result).toBe(mockViewModel);
|
||||
|
||||
// Restore
|
||||
AllTeamsPresenter = originalConstructor;
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDriverTeam', () => {
|
||||
it('should create presenter, call use case, and return view model', async () => {
|
||||
const query: GetDriverTeamQuery = { teamId: 'team1', driverId: 'driver1' };
|
||||
const mockViewModel: DriverTeamViewModel = {
|
||||
team: {
|
||||
id: 'team1',
|
||||
name: 'Team 1',
|
||||
tag: 'T1',
|
||||
description: 'Description',
|
||||
ownerId: 'driver1',
|
||||
leagues: [],
|
||||
},
|
||||
membership: {
|
||||
role: 'owner' as any,
|
||||
joinedAt: new Date(),
|
||||
isActive: true,
|
||||
},
|
||||
isOwner: true,
|
||||
canManage: true,
|
||||
};
|
||||
|
||||
const mockPresenter = {
|
||||
reset: jest.fn(),
|
||||
present: jest.fn(),
|
||||
get viewModel(): DriverTeamViewModel {
|
||||
return mockViewModel;
|
||||
},
|
||||
};
|
||||
|
||||
// Mock the presenter constructor
|
||||
const originalConstructor = DriverTeamPresenter;
|
||||
(DriverTeamPresenter as any) = jest.fn().mockImplementation(() => mockPresenter);
|
||||
|
||||
// Mock the use case to call the presenter
|
||||
getDriverTeamUseCase.execute.mockImplementation(async (input, presenter) => {
|
||||
presenter.present({
|
||||
team: {
|
||||
id: 'team1',
|
||||
name: 'Team 1',
|
||||
tag: 'T1',
|
||||
description: 'Description',
|
||||
ownerId: 'driver1',
|
||||
leagues: [],
|
||||
},
|
||||
membership: {
|
||||
role: 'owner',
|
||||
status: 'active',
|
||||
joinedAt: new Date(),
|
||||
},
|
||||
driverId: 'driver1',
|
||||
});
|
||||
});
|
||||
|
||||
const result = await service.getDriverTeam(query);
|
||||
|
||||
expect(DriverTeamPresenter).toHaveBeenCalled();
|
||||
expect(getDriverTeamUseCase.execute).toHaveBeenCalledWith({ driverId: 'driver1' }, mockPresenter);
|
||||
expect(result).toBe(mockViewModel);
|
||||
|
||||
// Restore
|
||||
DriverTeamPresenter = originalConstructor;
|
||||
});
|
||||
|
||||
it('should return null on error', async () => {
|
||||
const query: GetDriverTeamQuery = { teamId: 'team1', driverId: 'driver1' };
|
||||
|
||||
// Mock the use case to throw an error
|
||||
getDriverTeamUseCase.execute.mockRejectedValue(new Error('Team not found'));
|
||||
|
||||
const result = await service.getDriverTeam(query);
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(logger.error).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,43 +1,46 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { AllTeamsViewModel, GetDriverTeamQuery, DriverTeamViewModel, TeamDto, MembershipDto, TeamLeagueDto, MembershipRole } from './dto/TeamDto';
|
||||
import { Injectable, Inject } from '@nestjs/common';
|
||||
import { AllTeamsViewModel, GetDriverTeamQuery, DriverTeamViewModel } from './dto/TeamDto';
|
||||
|
||||
// Use cases
|
||||
import { GetAllTeamsUseCase } from '@gridpilot/racing/application/use-cases/GetAllTeamsUseCase';
|
||||
import { GetDriverTeamUseCase } from '@gridpilot/racing/application/use-cases/GetDriverTeamUseCase';
|
||||
|
||||
// Presenters
|
||||
import { AllTeamsPresenter } from './presenters/AllTeamsPresenter';
|
||||
import { DriverTeamPresenter } from './presenters/DriverTeamPresenter';
|
||||
|
||||
// Logger
|
||||
import { Logger } from '@gridpilot/shared/application/Logger';
|
||||
|
||||
// Tokens
|
||||
import { TEAM_GET_ALL_USE_CASE_TOKEN, TEAM_GET_DRIVER_TEAM_USE_CASE_TOKEN, TEAM_LOGGER_TOKEN } from './TeamProviders';
|
||||
|
||||
@Injectable()
|
||||
export class TeamService {
|
||||
getAllTeams(): Promise<AllTeamsViewModel> {
|
||||
// TODO: Implement actual logic to fetch all teams
|
||||
return Promise.resolve({
|
||||
teams: [],
|
||||
totalCount: 0,
|
||||
});
|
||||
}
|
||||
constructor(
|
||||
@Inject(TEAM_GET_ALL_USE_CASE_TOKEN) private readonly getAllTeamsUseCase: GetAllTeamsUseCase,
|
||||
@Inject(TEAM_GET_DRIVER_TEAM_USE_CASE_TOKEN) private readonly getDriverTeamUseCase: GetDriverTeamUseCase,
|
||||
@Inject(TEAM_LOGGER_TOKEN) private readonly logger: Logger,
|
||||
) {}
|
||||
|
||||
private teams: Map<string, TeamDto> = new Map(); // In-memory store for teams
|
||||
async getAllTeams(): Promise<AllTeamsViewModel> {
|
||||
this.logger.debug('[TeamService] Fetching all teams.');
|
||||
|
||||
const presenter = new AllTeamsPresenter();
|
||||
await this.getAllTeamsUseCase.execute(undefined, presenter);
|
||||
return presenter.viewModel;
|
||||
}
|
||||
|
||||
async getDriverTeam(query: GetDriverTeamQuery): Promise<DriverTeamViewModel | null> {
|
||||
const { teamId, driverId } = query;
|
||||
this.logger.debug(`[TeamService] Fetching driver team for driverId: ${query.driverId}`);
|
||||
|
||||
const team = this.teams.get(teamId);
|
||||
if (!team) {
|
||||
const presenter = new DriverTeamPresenter();
|
||||
try {
|
||||
await this.getDriverTeamUseCase.execute({ driverId: query.driverId }, presenter);
|
||||
return presenter.viewModel;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error fetching driver team: ${error}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Mock membership and roles
|
||||
const membership: MembershipDto = {
|
||||
role: driverId === team.ownerId ? MembershipRole.OWNER : MembershipRole.MEMBER,
|
||||
joinedAt: new Date(Date.now() - 86400000 * 30), // Joined 30 days ago
|
||||
isActive: true, // Always active for mock
|
||||
};
|
||||
|
||||
const isOwner = team.ownerId === driverId;
|
||||
const canManage = isOwner || membership.role === MembershipRole.MANAGER;
|
||||
|
||||
return {
|
||||
team: team,
|
||||
membership,
|
||||
isOwner,
|
||||
canManage,
|
||||
};
|
||||
}
|
||||
|
||||
// Add other methods related to Team logic here based on other presenters
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsString, IsNotEmpty, IsEnum, IsBoolean, IsDate, IsOptional } from 'class-validator';
|
||||
|
||||
export class TeamLeagueDto {
|
||||
@ApiProperty()
|
||||
@@ -37,7 +38,7 @@ export class AllTeamsViewModel {
|
||||
|
||||
@ApiProperty()
|
||||
totalCount: number;
|
||||
import { IsString, IsNotEmpty, IsEnum, IsBoolean, IsDate } from 'class-validator';
|
||||
}
|
||||
|
||||
export class TeamDto {
|
||||
@ApiProperty()
|
||||
|
||||
31
apps/api/src/modules/team/presenters/AllTeamsPresenter.ts
Normal file
31
apps/api/src/modules/team/presenters/AllTeamsPresenter.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { IAllTeamsPresenter, AllTeamsResultDTO, AllTeamsViewModel } from '@gridpilot/racing/application/presenters/IAllTeamsPresenter';
|
||||
import { TeamListItemViewModel } from '../dto/TeamDto';
|
||||
|
||||
export class AllTeamsPresenter implements IAllTeamsPresenter {
|
||||
private result: AllTeamsViewModel | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(dto: AllTeamsResultDTO) {
|
||||
const teams: TeamListItemViewModel[] = dto.teams.map(team => ({
|
||||
id: team.id,
|
||||
name: team.name,
|
||||
tag: team.tag,
|
||||
description: team.description,
|
||||
memberCount: team.memberCount,
|
||||
leagues: team.leagues || [],
|
||||
}));
|
||||
|
||||
this.result = {
|
||||
teams,
|
||||
totalCount: teams.length,
|
||||
};
|
||||
}
|
||||
|
||||
get viewModel(): AllTeamsViewModel {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
42
apps/api/src/modules/team/presenters/DriverTeamPresenter.ts
Normal file
42
apps/api/src/modules/team/presenters/DriverTeamPresenter.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { IDriverTeamPresenter, DriverTeamResultDTO, DriverTeamViewModel } from '@gridpilot/racing/application/presenters/IDriverTeamPresenter';
|
||||
import { TeamDto, MembershipDto, MembershipRole } from '../dto/TeamDto';
|
||||
|
||||
export class DriverTeamPresenter implements IDriverTeamPresenter {
|
||||
private result: DriverTeamViewModel | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(dto: DriverTeamResultDTO) {
|
||||
const team: TeamDto = {
|
||||
id: dto.team.id,
|
||||
name: dto.team.name,
|
||||
tag: dto.team.tag,
|
||||
description: dto.team.description,
|
||||
ownerId: dto.team.ownerId,
|
||||
leagues: dto.team.leagues || [],
|
||||
};
|
||||
|
||||
const membership: MembershipDto = {
|
||||
role: dto.membership.role as MembershipRole,
|
||||
joinedAt: dto.membership.joinedAt,
|
||||
isActive: dto.membership.status === 'active',
|
||||
};
|
||||
|
||||
const isOwner = dto.team.ownerId === dto.driverId;
|
||||
const canManage = isOwner || membership.role === MembershipRole.MANAGER;
|
||||
|
||||
this.result = {
|
||||
team,
|
||||
membership,
|
||||
isOwner,
|
||||
canManage,
|
||||
};
|
||||
}
|
||||
|
||||
get viewModel(): DriverTeamViewModel {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user