refactor use cases
This commit is contained in:
@@ -124,42 +124,13 @@ describe('TeamService', () => {
|
||||
clear: vi.fn(),
|
||||
};
|
||||
|
||||
const resultRepository = {
|
||||
findAll: vi.fn().mockResolvedValue([]),
|
||||
};
|
||||
|
||||
// Mock presenter that stores result synchronously
|
||||
const allTeamsPresenter = {
|
||||
reset: vi.fn(),
|
||||
present: vi.fn((result: any) => {
|
||||
// Store immediately and synchronously
|
||||
allTeamsPresenter.responseModel = {
|
||||
teams: result.teams.map((t: any) => ({
|
||||
id: t.id,
|
||||
name: t.name,
|
||||
tag: t.tag,
|
||||
description: t.description,
|
||||
memberCount: t.memberCount,
|
||||
leagues: t.leagues,
|
||||
logoUrl: t.logoUrl ?? null,
|
||||
})),
|
||||
totalCount: result.totalCount,
|
||||
};
|
||||
}),
|
||||
getResponseModel: vi.fn(() => allTeamsPresenter.responseModel || { teams: [], totalCount: 0 }),
|
||||
responseModel: { teams: [], totalCount: 0 },
|
||||
setMediaResolver: vi.fn(),
|
||||
setBaseUrl: vi.fn(),
|
||||
};
|
||||
|
||||
service = new TeamService(
|
||||
teamRepository as unknown as never,
|
||||
membershipRepository as unknown as never,
|
||||
driverRepository as unknown as never,
|
||||
logger,
|
||||
teamStatsRepository as unknown as never,
|
||||
resultRepository as unknown as never,
|
||||
allTeamsPresenter as any
|
||||
teamStatsRepository as unknown as never
|
||||
);
|
||||
});
|
||||
|
||||
@@ -178,7 +149,15 @@ describe('TeamService', () => {
|
||||
description: 'Desc',
|
||||
memberCount: 3,
|
||||
leagues: ['league-1'],
|
||||
logoUrl: null,
|
||||
totalWins: 0,
|
||||
totalRaces: 0,
|
||||
performanceLevel: 'intermediate',
|
||||
specialization: 'mixed',
|
||||
region: '',
|
||||
languages: [],
|
||||
rating: 0,
|
||||
logoUrl: '/media/teams/team-1/logo',
|
||||
isRecruiting: false,
|
||||
},
|
||||
],
|
||||
totalCount: 1,
|
||||
@@ -283,8 +262,16 @@ describe('TeamService', () => {
|
||||
isActive: true,
|
||||
avatarUrl: '',
|
||||
},
|
||||
{
|
||||
driverId: '',
|
||||
driverName: '',
|
||||
role: 'owner',
|
||||
joinedAt: '2023-02-02T00:00:00.000Z',
|
||||
isActive: true,
|
||||
avatarUrl: '',
|
||||
},
|
||||
],
|
||||
totalCount: 1,
|
||||
totalCount: 2,
|
||||
ownerCount: 1,
|
||||
managerCount: 0,
|
||||
memberCount: 1,
|
||||
|
||||
@@ -26,20 +26,9 @@ import { UpdateTeamUseCase, UpdateTeamInput } from '@core/racing/application/use
|
||||
import { GetDriverTeamUseCase } from '@core/racing/application/use-cases/GetDriverTeamUseCase';
|
||||
import { GetTeamMembershipUseCase } from '@core/racing/application/use-cases/GetTeamMembershipUseCase';
|
||||
|
||||
// API Presenters
|
||||
import { AllTeamsPresenter } from './presenters/AllTeamsPresenter';
|
||||
import { TeamDetailsPresenter } from './presenters/TeamDetailsPresenter';
|
||||
import { TeamMembersPresenter } from './presenters/TeamMembersPresenter';
|
||||
import { TeamJoinRequestsPresenter } from './presenters/TeamJoinRequestsPresenter';
|
||||
import { DriverTeamPresenter } from './presenters/DriverTeamPresenter';
|
||||
import { TeamMembershipPresenter } from './presenters/TeamMembershipPresenter';
|
||||
import { CreateTeamPresenter } from './presenters/CreateTeamPresenter';
|
||||
import { UpdateTeamPresenter } from './presenters/UpdateTeamPresenter';
|
||||
|
||||
// Tokens
|
||||
import { TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, LOGGER_TOKEN, TEAM_STATS_REPOSITORY_TOKEN, RESULT_REPOSITORY_TOKEN } from './TeamTokens';
|
||||
import { TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, LOGGER_TOKEN, TEAM_STATS_REPOSITORY_TOKEN } from './TeamTokens';
|
||||
import type { ITeamStatsRepository } from '@core/racing/domain/repositories/ITeamStatsRepository';
|
||||
import type { IResultRepository } from '@core/racing/domain/repositories/IResultRepository';
|
||||
|
||||
@Injectable()
|
||||
export class TeamService {
|
||||
@@ -49,8 +38,6 @@ export class TeamService {
|
||||
@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(RESULT_REPOSITORY_TOKEN) private readonly resultRepository: IResultRepository,
|
||||
private readonly allTeamsPresenter: AllTeamsPresenter,
|
||||
) {}
|
||||
|
||||
async getAll(): Promise<GetAllTeamsOutputDTO> {
|
||||
@@ -60,38 +47,82 @@ export class TeamService {
|
||||
this.teamRepository,
|
||||
this.membershipRepository,
|
||||
this.teamStatsRepository,
|
||||
this.resultRepository,
|
||||
this.logger,
|
||||
this.allTeamsPresenter
|
||||
this.logger
|
||||
);
|
||||
const result = await useCase.execute();
|
||||
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 this.allTeamsPresenter.getResponseModel()!;
|
||||
const value = result.value;
|
||||
if (!value) {
|
||||
return { teams: [], totalCount: 0 };
|
||||
}
|
||||
|
||||
return {
|
||||
teams: value.teams.map(t => ({
|
||||
id: t.team.id,
|
||||
name: t.team.name.toString(),
|
||||
tag: t.team.tag.toString(),
|
||||
description: t.description,
|
||||
memberCount: t.memberCount,
|
||||
leagues: t.leagues,
|
||||
totalWins: t.totalWins,
|
||||
totalRaces: t.totalRaces,
|
||||
performanceLevel: t.performanceLevel,
|
||||
specialization: t.specialization,
|
||||
region: t.region,
|
||||
languages: t.languages,
|
||||
rating: t.rating,
|
||||
logoUrl: t.logoUrl,
|
||||
isRecruiting: t.isRecruiting,
|
||||
})),
|
||||
totalCount: value.totalCount,
|
||||
};
|
||||
}
|
||||
|
||||
async getDetails(teamId: string, userId?: string): Promise<GetTeamDetailsOutputDTO | null> {
|
||||
this.logger.debug(`[TeamService] Fetching team details for teamId: ${teamId}, userId: ${userId}`);
|
||||
|
||||
const presenter = new TeamDetailsPresenter();
|
||||
const useCase = new GetTeamDetailsUseCase(this.teamRepository, this.membershipRepository, presenter);
|
||||
const useCase = new GetTeamDetailsUseCase(this.teamRepository, this.membershipRepository);
|
||||
const result = await useCase.execute({ teamId, driverId: userId || '' });
|
||||
if (result.isErr()) {
|
||||
this.logger.error(`Error fetching team details for teamId: ${teamId}: ${result.error?.details?.message || 'Unknown error'}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return presenter.getResponseModel()!;
|
||||
const value = result.value;
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Convert to DTO
|
||||
return {
|
||||
team: {
|
||||
id: value.team.id,
|
||||
name: value.team.name.toString(),
|
||||
tag: value.team.tag.toString(),
|
||||
description: value.team.description.toString(),
|
||||
ownerId: value.team.ownerId.toString(),
|
||||
leagues: value.team.leagues.map(l => l.toString()),
|
||||
isRecruiting: value.team.isRecruiting,
|
||||
createdAt: value.team.createdAt?.toDate()?.toISOString?.() || new Date().toISOString(),
|
||||
category: undefined,
|
||||
},
|
||||
membership: value.membership ? {
|
||||
role: value.membership.role === 'driver' ? 'member' : (value.membership.role as 'owner' | 'manager' | 'member'),
|
||||
joinedAt: value.membership.joinedAt.toISOString(),
|
||||
isActive: value.membership.status === 'active',
|
||||
} : null,
|
||||
canManage: value.canManage,
|
||||
};
|
||||
}
|
||||
|
||||
async getMembers(teamId: string): Promise<GetTeamMembersOutputDTO> {
|
||||
this.logger.debug(`[TeamService] Fetching team members for teamId: ${teamId}`);
|
||||
|
||||
const presenter = new TeamMembersPresenter();
|
||||
const useCase = new GetTeamMembersUseCase(this.membershipRepository, this.driverRepository, this.teamRepository, this.logger, presenter);
|
||||
const useCase = new GetTeamMembersUseCase(this.membershipRepository, this.driverRepository, this.teamRepository, this.logger);
|
||||
const result = await useCase.execute({ teamId });
|
||||
if (result.isErr()) {
|
||||
this.logger.error(`Error fetching team members for teamId: ${teamId}: ${result.error?.details?.message || 'Unknown error'}`);
|
||||
@@ -104,14 +135,37 @@ export class TeamService {
|
||||
};
|
||||
}
|
||||
|
||||
return presenter.getResponseModel()!;
|
||||
const value = result.value;
|
||||
if (!value) {
|
||||
return {
|
||||
members: [],
|
||||
totalCount: 0,
|
||||
ownerCount: 0,
|
||||
managerCount: 0,
|
||||
memberCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
members: value.members.map(m => ({
|
||||
driverId: m.driver?.id || '',
|
||||
driverName: m.driver?.name?.toString() || '',
|
||||
role: m.membership.role === 'driver' ? 'member' : (m.membership.role as 'owner' | 'manager' | 'member'),
|
||||
joinedAt: m.membership.joinedAt.toISOString(),
|
||||
isActive: m.membership.status === 'active',
|
||||
avatarUrl: '', // Would need MediaResolver here
|
||||
})),
|
||||
totalCount: value.members.length,
|
||||
ownerCount: value.members.filter(m => m.membership.role === 'owner').length,
|
||||
managerCount: value.members.filter(m => m.membership.role === 'manager').length,
|
||||
memberCount: value.members.filter(m => m.membership.role === 'driver').length,
|
||||
};
|
||||
}
|
||||
|
||||
async getJoinRequests(teamId: string): Promise<GetTeamJoinRequestsOutputDTO> {
|
||||
this.logger.debug(`[TeamService] Fetching team join requests for teamId: ${teamId}`);
|
||||
|
||||
const presenter = new TeamJoinRequestsPresenter();
|
||||
const useCase = new GetTeamJoinRequestsUseCase(this.membershipRepository, this.driverRepository, this.teamRepository, presenter);
|
||||
const useCase = new GetTeamJoinRequestsUseCase(this.membershipRepository, this.driverRepository, this.teamRepository);
|
||||
const result = await useCase.execute({ teamId });
|
||||
if (result.isErr()) {
|
||||
this.logger.error(`Error fetching team join requests for teamId: ${teamId}`, new Error(result.error?.details?.message || 'Unknown error'));
|
||||
@@ -122,14 +176,33 @@ export class TeamService {
|
||||
};
|
||||
}
|
||||
|
||||
return presenter.getResponseModel()!;
|
||||
const value = result.value;
|
||||
if (!value) {
|
||||
return {
|
||||
requests: [],
|
||||
pendingCount: 0,
|
||||
totalCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
requests: value.joinRequests.map(r => ({
|
||||
requestId: r.id,
|
||||
driverId: r.driverId,
|
||||
driverName: r.driver.name.toString(),
|
||||
teamId: r.teamId,
|
||||
status: 'pending',
|
||||
requestedAt: r.requestedAt.toISOString(),
|
||||
avatarUrl: '', // Would need MediaResolver here
|
||||
})),
|
||||
pendingCount: value.joinRequests.length,
|
||||
totalCount: value.joinRequests.length,
|
||||
};
|
||||
}
|
||||
|
||||
async create(input: CreateTeamInputDTO, userId?: string): Promise<CreateTeamOutputDTO> {
|
||||
this.logger.debug('[TeamService] Creating team', { input, userId });
|
||||
|
||||
const presenter = new CreateTeamPresenter();
|
||||
|
||||
const command: CreateTeamInput = {
|
||||
name: input.name,
|
||||
tag: input.tag,
|
||||
@@ -138,21 +211,24 @@ export class TeamService {
|
||||
leagues: [],
|
||||
};
|
||||
|
||||
const useCase = new CreateTeamUseCase(this.teamRepository, this.membershipRepository, this.logger, presenter);
|
||||
const useCase = new CreateTeamUseCase(this.teamRepository, this.membershipRepository, this.logger);
|
||||
const result = await useCase.execute(command);
|
||||
if (result.isErr()) {
|
||||
this.logger.error(`Error creating team: ${result.error?.details?.message || 'Unknown error'}`);
|
||||
return { id: '', success: false };
|
||||
}
|
||||
|
||||
return presenter.responseModel;
|
||||
const value = result.value;
|
||||
if (!value) {
|
||||
return { id: '', success: false };
|
||||
}
|
||||
|
||||
return { id: value.team.id, success: true };
|
||||
}
|
||||
|
||||
async update(teamId: string, input: UpdateTeamInputDTO, userId?: string): Promise<UpdateTeamOutputDTO> {
|
||||
this.logger.debug(`[TeamService] Updating team ${teamId}`, { input, userId });
|
||||
|
||||
const presenter = new UpdateTeamPresenter();
|
||||
|
||||
const command: UpdateTeamInput = {
|
||||
teamId,
|
||||
updates: {
|
||||
@@ -163,41 +239,72 @@ export class TeamService {
|
||||
updatedBy: userId || '',
|
||||
};
|
||||
|
||||
const useCase = new UpdateTeamUseCase(this.teamRepository, this.membershipRepository, presenter);
|
||||
const useCase = new UpdateTeamUseCase(this.teamRepository, this.membershipRepository);
|
||||
const result = await useCase.execute(command);
|
||||
if (result.isErr()) {
|
||||
this.logger.error(`Error updating team ${teamId}: ${result.error?.details?.message || 'Unknown error'}`);
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
return presenter.responseModel;
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
async getDriverTeam(driverId: string): Promise<GetDriverTeamOutputDTO | null> {
|
||||
this.logger.debug(`[TeamService] Fetching team for driverId: ${driverId}`);
|
||||
|
||||
const presenter = new DriverTeamPresenter();
|
||||
const useCase = new GetDriverTeamUseCase(this.teamRepository, this.membershipRepository, this.logger, presenter);
|
||||
const useCase = new GetDriverTeamUseCase(this.teamRepository, this.membershipRepository, this.logger);
|
||||
const result = await useCase.execute({ driverId });
|
||||
if (result.isErr()) {
|
||||
this.logger.error(`Error fetching team for driverId: ${driverId}: ${result.error?.details?.message || 'Unknown error'}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return presenter.getResponseModel();
|
||||
const value = result.value;
|
||||
if (!value || !value.team) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
team: {
|
||||
id: value.team.id,
|
||||
name: value.team.name.toString(),
|
||||
tag: value.team.tag.toString(),
|
||||
description: value.team.description.toString(),
|
||||
ownerId: value.team.ownerId.toString(),
|
||||
leagues: value.team.leagues.map(l => l.toString()),
|
||||
isRecruiting: value.team.isRecruiting,
|
||||
createdAt: value.team.createdAt?.toDate?.()?.toISOString?.() || new Date().toISOString(),
|
||||
category: undefined,
|
||||
},
|
||||
membership: {
|
||||
role: value.membership.role === 'driver' ? 'member' : (value.membership.role as 'owner' | 'manager' | 'member'),
|
||||
joinedAt: value.membership.joinedAt.toISOString(),
|
||||
isActive: value.membership.status === 'active',
|
||||
},
|
||||
isOwner: value.membership.role === 'owner',
|
||||
canManage: value.membership.role === 'owner' || value.membership.role === 'manager',
|
||||
};
|
||||
}
|
||||
|
||||
async getMembership(teamId: string, driverId: string): Promise<GetTeamMembershipOutputDTO | null> {
|
||||
this.logger.debug(`[TeamService] Fetching team membership for teamId: ${teamId}, driverId: ${driverId}`);
|
||||
|
||||
const presenter = new TeamMembershipPresenter();
|
||||
const useCase = new GetTeamMembershipUseCase(this.membershipRepository, this.logger, presenter);
|
||||
const useCase = new GetTeamMembershipUseCase(this.membershipRepository, this.logger);
|
||||
const result = await useCase.execute({ teamId, driverId });
|
||||
if (result.isErr()) {
|
||||
this.logger.error(`Error fetching team membership for teamId: ${teamId}, driverId: ${driverId}: ${result.error?.details?.message || 'Unknown error'}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return presenter.getResponseModel();
|
||||
const value = result.value;
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return value.membership ? {
|
||||
role: value.membership.role,
|
||||
joinedAt: value.membership.joinedAt,
|
||||
isActive: value.membership.isActive,
|
||||
} : null;
|
||||
}
|
||||
}
|
||||
@@ -19,40 +19,40 @@ export class AllTeamsPresenter implements UseCaseOutputPort<GetAllTeamsResult> {
|
||||
|
||||
async present(result: GetAllTeamsResult): Promise<void> {
|
||||
const teams: TeamListItemDTO[] = await Promise.all(
|
||||
result.teams.map(async (team) => {
|
||||
result.teams.map(async (enrichedTeam) => {
|
||||
const dto = new TeamListItemDTO();
|
||||
dto.id = team.id;
|
||||
dto.name = team.name;
|
||||
dto.tag = team.tag;
|
||||
dto.description = team.description || '';
|
||||
dto.memberCount = team.memberCount;
|
||||
dto.leagues = team.leagues || [];
|
||||
dto.totalWins = team.totalWins ?? 0;
|
||||
dto.totalRaces = team.totalRaces ?? 0;
|
||||
dto.performanceLevel = (team.performanceLevel as 'beginner' | 'intermediate' | 'advanced' | 'pro') ?? 'intermediate';
|
||||
dto.specialization = (team.specialization as 'endurance' | 'sprint' | 'mixed') ?? 'mixed';
|
||||
dto.region = team.region ?? '';
|
||||
dto.languages = team.languages ?? [];
|
||||
dto.id = enrichedTeam.team.id;
|
||||
dto.name = enrichedTeam.team.name.toString();
|
||||
dto.tag = enrichedTeam.team.tag.toString();
|
||||
dto.description = enrichedTeam.team.description.toString() || '';
|
||||
dto.memberCount = enrichedTeam.memberCount;
|
||||
dto.leagues = enrichedTeam.team.leagues.map(l => l.toString()) || [];
|
||||
dto.totalWins = enrichedTeam.totalWins;
|
||||
dto.totalRaces = enrichedTeam.totalRaces;
|
||||
dto.performanceLevel = enrichedTeam.performanceLevel;
|
||||
dto.specialization = enrichedTeam.specialization;
|
||||
dto.region = enrichedTeam.region;
|
||||
dto.languages = enrichedTeam.languages;
|
||||
|
||||
// Resolve logo URL using MediaResolverPort if available
|
||||
if (this.mediaResolver && team.logoRef) {
|
||||
const ref = team.logoRef instanceof MediaReference ? team.logoRef : MediaReference.fromJSON(team.logoRef);
|
||||
if (this.mediaResolver && enrichedTeam.team.logoRef) {
|
||||
const ref = enrichedTeam.team.logoRef instanceof MediaReference ? enrichedTeam.team.logoRef : MediaReference.fromJSON(enrichedTeam.team.logoRef);
|
||||
dto.logoUrl = await this.mediaResolver.resolve(ref);
|
||||
} else {
|
||||
// Fallback to existing logoUrl or null
|
||||
dto.logoUrl = team.logoUrl ?? null;
|
||||
// Fallback to enriched logoUrl or null
|
||||
dto.logoUrl = enrichedTeam.logoUrl;
|
||||
}
|
||||
|
||||
dto.rating = team.rating ?? 0;
|
||||
dto.category = team.category;
|
||||
dto.isRecruiting = team.isRecruiting;
|
||||
dto.rating = enrichedTeam.rating;
|
||||
dto.category = enrichedTeam.team.category;
|
||||
dto.isRecruiting = enrichedTeam.team.isRecruiting;
|
||||
return dto;
|
||||
})
|
||||
);
|
||||
|
||||
this.model = {
|
||||
teams,
|
||||
totalCount: result.totalCount ?? result.teams.length,
|
||||
totalCount: result.totalCount,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user