Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions backend/src/disputes/disputes.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,40 @@ export class DisputesController {
return this.disputesService.create(createDisputeDto, user);
}

@Get('my')
@ApiOperation({ summary: 'Get disputes filed by the current user' })
@ApiQuery({
name: 'page',
required: false,
type: Number,
description: 'Page number',
})
@ApiQuery({
name: 'limit',
required: false,
type: Number,
description: 'Items per page',
})
@ApiResponse({
status: HttpStatus.OK,
description: 'Disputes retrieved successfully',
})
async findMyDisputes(
@CurrentUser() user: User,
@Query('page') page?: string,
@Query('limit') limit?: string,
): Promise<{
disputes: Dispute[];
total: number;
page: number;
limit: number;
}> {
const pageNum = page ? parseInt(page, 10) : 1;
const limitNum = limit ? parseInt(limit, 10) : 20;

return this.disputesService.findMyDisputes(user.id, pageNum, limitNum);
}

@Get(':id')
@ApiOperation({ summary: 'Get a dispute by ID' })
@ApiParam({ name: 'id', description: 'Dispute ID' })
Expand Down
34 changes: 32 additions & 2 deletions backend/src/disputes/disputes.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,13 +162,19 @@ describe('DisputesService', () => {
);
});

it('should throw ConflictException if dispute already exists', async () => {
it('should throw ConflictException if dispute already exists regardless of status', async () => {
jest.spyOn(marketsRepository, 'findOne').mockResolvedValue(mockMarket);
jest.spyOn(disputesRepository, 'findOne').mockResolvedValue(mockDispute);

const resolvedDispute = { ...mockDispute, status: DisputeStatus.RESOLVED };
jest.spyOn(disputesRepository, 'findOne').mockResolvedValue(resolvedDispute);

await expect(service.create(createDisputeDto, mockUser)).rejects.toThrow(
ConflictException,
);

expect(disputesRepository.findOne).toHaveBeenCalledWith({
where: { marketId: 'market-123' },
});
});
});

Expand Down Expand Up @@ -275,6 +281,30 @@ describe('DisputesService', () => {
});
});

describe('findMyDisputes', () => {
it('should return paginated disputes for a user', async () => {
const disputes = [mockDispute];
const mockFindAndCount: [Dispute[], number] = [disputes, 1];
jest.spyOn(disputesRepository, 'findAndCount').mockResolvedValue(mockFindAndCount);

const result = await service.findMyDisputes('user-123', 1, 20);

expect(result).toEqual({
disputes,
total: 1,
page: 1,
limit: 20,
});
expect(disputesRepository.findAndCount).toHaveBeenCalledWith({
where: { disputantId: 'user-123' },
relations: ['market', 'resolvedBy'],
order: { createdAt: 'DESC' },
skip: 0,
take: 20,
});
});
});

describe('findAll', () => {
it('should return paginated disputes', async () => {
const disputes = [mockDispute];
Expand Down
31 changes: 30 additions & 1 deletion backend/src/disputes/disputes.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export class DisputesService {

// Check if dispute already exists for this market
const existingDispute = await this.disputesRepository.findOne({
where: { marketId, status: DisputeStatus.PENDING },
where: { marketId },
});

if (existingDispute) {
Expand Down Expand Up @@ -163,6 +163,35 @@ export class DisputesService {
});
}

/**
* Find disputes filed by a specific user with pagination
*/
async findMyDisputes(
userId: string,
page = 1,
limit = 20,
): Promise<{
disputes: Dispute[];
total: number;
page: number;
limit: number;
}> {
const [disputes, total] = await this.disputesRepository.findAndCount({
where: { disputantId: userId },
relations: ['market', 'resolvedBy'],
order: { createdAt: 'DESC' },
skip: (page - 1) * limit,
take: limit,
});

return {
disputes,
total,
page,
limit,
};
}

/**
* Find all disputes with pagination
*/
Expand Down
3 changes: 3 additions & 0 deletions backend/src/search/dto/global-search.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ export class GlobalSearchResponseDto {
competitions: CompetitionSearchResult[];

@ApiProperty() total: number;
@ApiPropertyOptional() total_markets?: number;
@ApiPropertyOptional() total_users?: number;
@ApiPropertyOptional() total_competitions?: number;
@ApiProperty() page: number;
@ApiProperty() limit: number;
}
4 changes: 3 additions & 1 deletion backend/src/search/search.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type MockQb<T> = jest.Mocked<
| 'skip'
| 'take'
| 'getMany'
| 'getManyAndCount'
>
>;

Expand All @@ -37,7 +38,8 @@ function makeQb<T>(results: T[]): MockQb<T> {
skip: jest.fn().mockReturnThis(),
take: jest.fn().mockReturnThis(),
getMany: jest.fn().mockResolvedValue(results),
} as MockQb<T>;
getManyAndCount: jest.fn().mockResolvedValue([results, results.length]),
} as unknown as MockQb<T>;
return qb;
}

Expand Down
38 changes: 26 additions & 12 deletions backend/src/search/search.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,28 +31,42 @@ export class SearchService {
const searchType = dto.type ?? SearchType.All;
const query = dto.query;

const [markets, users, competitions] = await Promise.all([
const [
[markets, total_markets],
[users, total_users],
[competitions, total_competitions],
] = await Promise.all([
searchType === SearchType.All || searchType === SearchType.Markets
? this.searchMarkets(query, skip, limit)
: Promise.resolve([]),
: Promise.resolve([[], 0] as [Market[], number]),
searchType === SearchType.All || searchType === SearchType.Users
? this.searchUsers(query, skip, limit)
: Promise.resolve([]),
: Promise.resolve([[], 0] as [User[], number]),
searchType === SearchType.All || searchType === SearchType.Competitions
? this.searchCompetitions(query, skip, limit)
: Promise.resolve([]),
: Promise.resolve([[], 0] as [Competition[], number]),
]);

const total = markets.length + users.length + competitions.length;
const total = total_markets + total_users + total_competitions;

return { markets, users, competitions, total, page, limit };
return {
markets,
users,
competitions,
total,
total_markets,
total_users,
total_competitions,
page,
limit,
};
}

private async searchMarkets(
query: string,
skip: number,
limit: number,
): Promise<Market[]> {
): Promise<[Market[], number]> {
return this.marketsRepository
.createQueryBuilder('market')
.select([
Expand All @@ -75,14 +89,14 @@ export class SearchService {
)
.skip(skip)
.take(limit)
.getMany();
.getManyAndCount();
}

private async searchUsers(
query: string,
skip: number,
limit: number,
): Promise<User[]> {
): Promise<[User[], number]> {
return this.usersRepository
.createQueryBuilder('user')
.select([
Expand All @@ -103,14 +117,14 @@ export class SearchService {
)
.skip(skip)
.take(limit)
.getMany();
.getManyAndCount();
}

private async searchCompetitions(
query: string,
skip: number,
limit: number,
): Promise<Competition[]> {
): Promise<[Competition[], number]> {
return this.competitionsRepository
.createQueryBuilder('competition')
.select([
Expand All @@ -135,6 +149,6 @@ export class SearchService {
)
.skip(skip)
.take(limit)
.getMany();
.getManyAndCount();
}
}
Loading