Skip to content
Open
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
8 changes: 8 additions & 0 deletions backend/src/contract/contract.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ import {
xdr,
} from '@stellar/stellar-sdk';

export interface RewardDistribution {
rank1: number;
rank2: number;
rank3: number;
rank4?: number;
rank5?: number;
}

export interface ContractEvent {
eventId: string;
inviteCode: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,17 @@ describe('CreatorEventsService predictions and stats', () => {
maxParticipants: 100,
participantCount: 3,
isActive: true,
prizePool: '5000000000',
entryFee: '100000000',
category: 'Sports',
bannerUrl: 'https://example.com/banner.jpg',
rewardDistribution: {
rank1: 40,
rank2: 30,
rank3: 20,
rank4: 5,
rank5: 5,
},
};

const mockMatches = [
Expand All @@ -42,6 +53,8 @@ describe('CreatorEventsService predictions and stats', () => {
eventId: '1',
homeTeam: 'Alpha',
awayTeam: 'Beta',
homeScore: 1,
awayScore: 0,
startTime: 1_100_000,
resolved: true,
outcome: 'TEAM_A',
Expand All @@ -51,6 +64,8 @@ describe('CreatorEventsService predictions and stats', () => {
eventId: '1',
homeTeam: 'Gamma',
awayTeam: 'Delta',
homeScore: null,
awayScore: null,
startTime: 1_200_000,
resolved: false,
outcome: null,
Expand Down Expand Up @@ -170,6 +185,14 @@ describe('CreatorEventsService predictions and stats', () => {
expect(result.averagePredictionsPerUser).toBe(1.67);
expect(result.completionRate).toBe(67);
expect(result.winnersVerified).toBe(false);
expect(result.prizePool).toBe('5000000000');
expect(result.rewardDistribution).toEqual({
rank1: 40,
rank2: 30,
rank3: 20,
rank4: 5,
rank5: 5,
});
});

it('throws when event is not found', async () => {
Expand All @@ -179,5 +202,35 @@ describe('CreatorEventsService predictions and stats', () => {
NotFoundException,
);
});

it('includes prizePool and rewardDistribution in stats response', async () => {
contractService.getEvent.mockResolvedValue(mockEvent);
contractService.getEventStatistics.mockResolvedValue({
eventId: '1',
participantCount: 1,
matchCount: 1,
totalPredictions: 1,
allMatchesResolved: false,
winnersVerified: false,
winnerCount: 0,
});
contractService.getEventParticipants.mockResolvedValue([]);
contractService.getPredictionDistribution.mockResolvedValue({
teamA: 0,
teamB: 0,
draw: 0,
});

const result = await service.getEventStats('1');

expect(result.prizePool).toBe('5000000000');
expect(result.rewardDistribution).toEqual({
rank1: 40,
rank2: 30,
rank3: 20,
rank4: 5,
rank5: 5,
});
});
});
});
24 changes: 24 additions & 0 deletions backend/src/creator-events/creator-events.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ describe('CreatorEventsController', () => {
eventId: 'event-1',
homeTeam: 'Team A',
awayTeam: 'Team B',
homeScore: 0,
awayScore: 0,
startTime: 1100000,
resolved: false,
outcome: null,
Expand Down Expand Up @@ -218,6 +220,17 @@ describe('PublicCreatorEventsController', () => {
matchPreview: [],
startTime: 1000000,
endTime: 2000000,
prizePool: '5000000000',
entryFee: '100000000',
category: 'Sports',
bannerUrl: 'https://example.com/banner.jpg',
rewardDistribution: {
rank1: 40,
rank2: 30,
rank3: 20,
rank4: 5,
rank5: 5,
},
};

service.getEventByInviteCode.mockResolvedValue(mockEvent);
Expand Down Expand Up @@ -247,6 +260,17 @@ describe('PublicCreatorEventsController', () => {
],
startTime: 1000000,
endTime: 2000000,
prizePool: '5000000000',
entryFee: '100000000',
category: 'Sports',
bannerUrl: 'https://example.com/banner.jpg',
rewardDistribution: {
rank1: 40,
rank2: 30,
rank3: 20,
rank4: 5,
rank5: 5,
},
};

service.getEventByInviteCode.mockResolvedValue(mockEvent);
Expand Down
4 changes: 4 additions & 0 deletions backend/src/creator-events/creator-events.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,14 @@ describe('CreatorEventsService searchEvents', () => {
await service.searchEvents({
q: 'league',
status: CreatorEventSearchStatus.Cancelled,
page: 1,
limit: 20,
});
await service.searchEvents({
q: 'league',
status: CreatorEventSearchStatus.Inactive,
page: 1,
limit: 20,
});

expect(queryBuilder.andWhere).toHaveBeenCalledWith(
Expand Down
10 changes: 9 additions & 1 deletion backend/src/creator-events/creator-events.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ import {
normalizeContractPrediction,
resolveCorrectness,
} from './utils/prediction.util';
import { toRewardDistributionDto } from './utils/reward-distribution.util';

// Type definitions - exported for use in controllers
export interface ParticipantWithStats {
address: string;
joinedAt: number;
Expand Down Expand Up @@ -319,6 +319,11 @@ export class CreatorEventsService {
matchPreview,
startTime: event.startTime,
endTime: event.endTime,
prizePool: event.prizePool,
entryFee: event.entryFee,
category: event.category,
bannerUrl: event.bannerUrl,
rewardDistribution: toRewardDistributionDto(event.rewardDistribution),
};
}

Expand Down Expand Up @@ -470,6 +475,8 @@ export class CreatorEventsService {
winnerCount: statistics?.winnerCount ?? 0,
averagePredictionsPerUser,
completionRate,
prizePool: event.prizePool,
rewardDistribution: toRewardDistributionDto(event.rewardDistribution),
};
}

Expand Down Expand Up @@ -634,6 +641,7 @@ export class CreatorEventsService {
match_count: event.match_count,
rank: Number(rank ?? 0),
highlights: this.buildHighlights(event, searchTerm),
category: (event as CreatorEvent & { category?: string }).category,
};
}

Expand Down
41 changes: 40 additions & 1 deletion backend/src/creator-events/dto/event-by-code-response.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ApiProperty } from '@nestjs/swagger';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';

export class MatchPreviewDto {
@ApiProperty({ description: 'Match ID' })
Expand All @@ -14,6 +14,23 @@ export class MatchPreviewDto {
startTime: number;
}

export class RewardDistributionDto {
@ApiProperty({ description: 'Rank 1 percentage' })
rank1: number;

@ApiProperty({ description: 'Rank 2 percentage' })
rank2: number;

@ApiProperty({ description: 'Rank 3 percentage' })
rank3: number;

@ApiPropertyOptional({ description: 'Rank 4 percentage' })
rank4?: number;

@ApiPropertyOptional({ description: 'Rank 5 percentage' })
rank5?: number;
}

export class EventByCodeResponseDto {
@ApiProperty({ description: 'Event ID' })
eventId: string;
Expand Down Expand Up @@ -53,4 +70,26 @@ export class EventByCodeResponseDto {

@ApiProperty({ description: 'Event end time (Unix timestamp)' })
endTime: number;

@ApiPropertyOptional({
description: 'Prize pool amount in stroops (smallest unit)',
})
prizePool?: string;

@ApiPropertyOptional({
description: 'Entry fee amount in stroops (smallest unit)',
})
entryFee?: string;

@ApiPropertyOptional({ description: 'Event category' })
category?: string;

@ApiPropertyOptional({ description: 'Banner image URL' })
bannerUrl?: string;

@ApiPropertyOptional({
description: 'Reward distribution percentages by rank',
type: RewardDistributionDto,
})
rewardDistribution?: RewardDistributionDto;
}
39 changes: 39 additions & 0 deletions backend/src/creator-events/dto/event-response.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';

export class RewardDistributionDto {
@ApiProperty({ description: 'Rank 1 percentage' })
rank1: number;

@ApiProperty({ description: 'Rank 2 percentage' })
rank2: number;

@ApiProperty({ description: 'Rank 3 percentage' })
rank3: number;

@ApiPropertyOptional({ description: 'Rank 4 percentage' })
rank4?: number;

@ApiPropertyOptional({ description: 'Rank 5 percentage' })
rank5?: number;
}

export class EventResponseDto {
@ApiProperty({ description: 'Event ID' })
eventId: string;
Expand Down Expand Up @@ -34,6 +51,28 @@ export class EventResponseDto {
@ApiProperty({ description: 'Is event active' })
isActive: boolean;

@ApiPropertyOptional({
description: 'Prize pool amount in stroops (smallest unit)',
})
prizePool?: string;

@ApiPropertyOptional({
description: 'Entry fee amount in stroops (smallest unit)',
})
entryFee?: string;

@ApiPropertyOptional({ description: 'Event category' })
category?: string;

@ApiPropertyOptional({ description: 'Banner image URL' })
bannerUrl?: string;

@ApiPropertyOptional({
description: 'Reward distribution percentages by rank',
type: RewardDistributionDto,
})
rewardDistribution?: RewardDistributionDto;

@ApiPropertyOptional({ description: 'Number of winners' })
winnerCount?: number;

Expand Down
30 changes: 29 additions & 1 deletion backend/src/creator-events/dto/event-stats-response.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ApiProperty } from '@nestjs/swagger';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';

export class MatchPredictionDistributionDto {
@ApiProperty({ description: 'Match identifier' })
Expand All @@ -23,6 +23,23 @@ export class MatchPredictionDistributionDto {
total: number;
}

export class RewardDistributionDto {
@ApiProperty({ description: 'Rank 1 percentage' })
rank1: number;

@ApiProperty({ description: 'Rank 2 percentage' })
rank2: number;

@ApiProperty({ description: 'Rank 3 percentage' })
rank3: number;

@ApiPropertyOptional({ description: 'Rank 4 percentage' })
rank4?: number;

@ApiPropertyOptional({ description: 'Rank 5 percentage' })
rank5?: number;
}

export class EventStatsResponseDto {
@ApiProperty({ description: 'Event identifier' })
eventId: string;
Expand Down Expand Up @@ -65,4 +82,15 @@ export class EventStatsResponseDto {
description: 'Percentage of participants who predicted all matches (0-100)',
})
completionRate: number;

@ApiPropertyOptional({
description: 'Prize pool amount in stroops (smallest unit)',
})
prizePool?: string;

@ApiPropertyOptional({
description: 'Reward distribution percentages by rank',
type: RewardDistributionDto,
})
rewardDistribution?: RewardDistributionDto;
}
8 changes: 8 additions & 0 deletions backend/src/creator-events/dto/search-events-query.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,12 @@ export class SearchEventsQueryDto {
@IsOptional()
@IsString()
creator?: string;

@ApiPropertyOptional({
description: 'Filter results by event category.',
example: 'football',
})
@IsOptional()
@IsString()
category?: string;
}
5 changes: 4 additions & 1 deletion backend/src/creator-events/dto/search-events-response.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ApiProperty } from '@nestjs/swagger';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';

export class SearchHighlightsDto {
@ApiProperty({ required: false })
Expand Down Expand Up @@ -44,6 +44,9 @@ export class SearchEventResultDto {

@ApiProperty({ type: SearchHighlightsDto })
highlights: SearchHighlightsDto;

@ApiPropertyOptional({ description: 'Event category' })
category?: string;
}

export class SearchEventsResponseDto {
Expand Down
Loading
Loading