From 4094c7ef2699240725e35ed3944c2f1abff3bbe4 Mon Sep 17 00:00:00 2001 From: Timrossid Date: Wed, 17 Jun 2026 21:59:10 +0100 Subject: [PATCH 1/5] feat: add scoreline prediction, points_earned (1/3/4 rules), and totalPoints to user score MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add predicted_home_score, predicted_away_score, points_earned to MatchPrediction entity - Create migration for new match_predictions columns - Derive predicted_outcome from scoreline in handlePredictionSubmitted - Grade predictions with 1/3/4-point rules (× points_multiplier) in gradePredictions - Include totalPoints in GET /creator-events/:id/score/:address response - Add Match, MatchPrediction, User repos to CreatorEventsService --- .../creator-events/creator-events.module.ts | 5 +- .../creator-events/creator-events.service.ts | 34 +++++- .../dto/user-score-response.dto.ts | 3 + backend/src/indexer/indexer.service.ts | 112 ++++++++++++++---- .../entities/match-prediction.entity.ts | 9 ++ ...-AddScorelineAndPointsToMatchPrediction.ts | 31 +++++ 6 files changed, 169 insertions(+), 25 deletions(-) create mode 100644 backend/src/migrations/1775900000000-AddScorelineAndPointsToMatchPrediction.ts diff --git a/backend/src/creator-events/creator-events.module.ts b/backend/src/creator-events/creator-events.module.ts index 021ea60b..dfa62252 100644 --- a/backend/src/creator-events/creator-events.module.ts +++ b/backend/src/creator-events/creator-events.module.ts @@ -3,6 +3,9 @@ import { CacheModule } from '@nestjs/cache-manager'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ContractModule } from '../contract/contract.module'; import { CreatorEvent } from '../matches/entities/creator-event.entity'; +import { Match } from '../matches/entities/match.entity'; +import { MatchPrediction } from '../matches/entities/match-prediction.entity'; +import { User } from '../users/entities/user.entity'; import { AdminCreatorEventsController, CreatorEventsController, @@ -13,7 +16,7 @@ import { CreatorEventsService } from './creator-events.service'; @Module({ imports: [ ContractModule, - TypeOrmModule.forFeature([CreatorEvent]), + TypeOrmModule.forFeature([CreatorEvent, Match, MatchPrediction, User]), CacheModule.register(), ], controllers: [ diff --git a/backend/src/creator-events/creator-events.service.ts b/backend/src/creator-events/creator-events.service.ts index dac02f46..e6e6efe4 100644 --- a/backend/src/creator-events/creator-events.service.ts +++ b/backend/src/creator-events/creator-events.service.ts @@ -1,6 +1,6 @@ import { Injectable, Logger, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Brackets, Repository } from 'typeorm'; +import { Brackets, In, Repository } from 'typeorm'; import { ContractService, ContractEvent, @@ -9,6 +9,9 @@ import { ContractMatch, } from '../contract/contract.service'; import { CreatorEvent } from '../matches/entities/creator-event.entity'; +import { Match } from '../matches/entities/match.entity'; +import { MatchPrediction } from '../matches/entities/match-prediction.entity'; +import { User } from '../users/entities/user.entity'; import { EventByCodeResponseDto, MatchPreviewDto, @@ -74,6 +77,12 @@ export class CreatorEventsService { private readonly contractService: ContractService, @InjectRepository(CreatorEvent) private readonly creatorEventRepository: Repository, + @InjectRepository(Match) + private readonly matchRepository: Repository, + @InjectRepository(MatchPrediction) + private readonly matchPredictionRepository: Repository, + @InjectRepository(User) + private readonly userRepository: Repository, ) {} async searchEvents( @@ -519,6 +528,28 @@ export class CreatorEventsService { incorrectPredictions === 0 && pendingPredictions === 0; + let totalPoints = 0; + const userEntity = await this.userRepository.findOne({ + where: { stellar_address: address }, + }); + if (userEntity && matches.length > 0) { + const dbMatches = await this.matchRepository.find({ + where: { event: { on_chain_event_id: Number(eventId) } }, + }); + if (dbMatches.length > 0) { + const dbPredictions = await this.matchPredictionRepository.find({ + where: { + user: { id: userEntity.id }, + match: { id: In(dbMatches.map((m) => m.id)) }, + }, + }); + totalPoints = dbPredictions.reduce( + (sum, p) => sum + p.points_earned, + 0, + ); + } + } + return { address, totalMatches: matches.length, @@ -529,6 +560,7 @@ export class CreatorEventsService { accuracyPercentage, rank, isWinner, + totalPoints, }; } diff --git a/backend/src/creator-events/dto/user-score-response.dto.ts b/backend/src/creator-events/dto/user-score-response.dto.ts index 15173aff..c74b2919 100644 --- a/backend/src/creator-events/dto/user-score-response.dto.ts +++ b/backend/src/creator-events/dto/user-score-response.dto.ts @@ -35,4 +35,7 @@ export class UserScoreResponseDto { description: 'Whether user is a winner (all predictions correct)', }) isWinner: boolean; + + @ApiProperty({ description: 'Total points earned by the user' }) + totalPoints: number; } diff --git a/backend/src/indexer/indexer.service.ts b/backend/src/indexer/indexer.service.ts index f28477db..abe2eac2 100644 --- a/backend/src/indexer/indexer.service.ts +++ b/backend/src/indexer/indexer.service.ts @@ -376,6 +376,8 @@ export class IndexerService implements OnModuleInit { event_id: this.readBigInt(base, 'event_id'), predictor: this.readStr(base, 'predictor'), predicted_outcome: this.readStr(base, 'predicted_outcome'), + predicted_home_score: this.readNum(base, 'predicted_home_score'), + predicted_away_score: this.readNum(base, 'predicted_away_score'), predicted_at: this.readNum(base, 'predicted_at'), }; case 'MatchResultSubmitted': @@ -648,9 +650,8 @@ export class IndexerService implements OnModuleInit { ): Promise { const matchId = Number(data.match_id); const predictorAddress = this.readStr(data, 'predictor'); - const predictedOutcome = this.readStr(data, 'predicted_outcome'); - if (!matchId || !predictorAddress || !predictedOutcome) { + if (!matchId || !predictorAddress) { this.logger.warn('PredictionSubmitted skipped: missing data'); return; } @@ -676,18 +677,6 @@ export class IndexerService implements OnModuleInit { return; } - const normalizedOutcome = predictedOutcome.toUpperCase(); - if ( - ![PredictedOutcome.TEAM_A, PredictedOutcome.TEAM_B, PredictedOutcome.DRAW] - .map((o) => o.toString()) - .includes(normalizedOutcome) - ) { - this.logger.warn( - `PredictionSubmitted skipped: invalid outcome ${predictedOutcome}`, - ); - return; - } - const existing = await this.matchPredictionRepository.findOne({ where: { match: { id: match.id }, @@ -696,10 +685,57 @@ export class IndexerService implements OnModuleInit { }); if (existing) return; + const predictedHomeScore = + data.predicted_home_score !== undefined && data.predicted_home_score !== null + ? Number(data.predicted_home_score) + : null; + const predictedAwayScore = + data.predicted_away_score !== undefined && data.predicted_away_score !== null + ? Number(data.predicted_away_score) + : null; + + let predictedOutcome: PredictedOutcome; + if (predictedHomeScore !== null && predictedAwayScore !== null) { + if (predictedHomeScore > predictedAwayScore) { + predictedOutcome = PredictedOutcome.TEAM_A; + } else if (predictedHomeScore < predictedAwayScore) { + predictedOutcome = PredictedOutcome.TEAM_B; + } else { + predictedOutcome = PredictedOutcome.DRAW; + } + } else { + const rawOutcome = this.readStr(data, 'predicted_outcome'); + if (!rawOutcome) { + this.logger.warn( + 'PredictionSubmitted skipped: no scoreline or predicted_outcome', + ); + return; + } + const normalizedOutcome = rawOutcome.toUpperCase(); + if ( + ![ + PredictedOutcome.TEAM_A, + PredictedOutcome.TEAM_B, + PredictedOutcome.DRAW, + ] + .map((o) => o.toString()) + .includes(normalizedOutcome) + ) { + this.logger.warn( + `PredictionSubmitted skipped: invalid outcome ${rawOutcome}`, + ); + return; + } + predictedOutcome = normalizedOutcome as PredictedOutcome; + } + const prediction = this.matchPredictionRepository.create({ match, user, - predicted_outcome: normalizedOutcome as PredictedOutcome, + predicted_outcome: predictedOutcome, + predicted_home_score: predictedHomeScore, + predicted_away_score: predictedAwayScore, + points_earned: 0, is_correct: null, }); @@ -762,7 +798,7 @@ export class IndexerService implements OnModuleInit { await this.matchRepository.save(match); - await this.gradePredictions(match.id, winningTeam); + await this.gradePredictions(match); this.logger.log( `Indexed MatchResultSubmitted: match=${matchId} winner=${winningTeam}`, ); @@ -772,17 +808,47 @@ export class IndexerService implements OnModuleInit { this.broadcasterService.broadcastMatchResolved(data); } - private async gradePredictions( - matchId: string, - winningTeam: WinningTeam, - ): Promise { + private async gradePredictions(match: Match): Promise { const predictions = await this.matchPredictionRepository.find({ - where: { match: { id: matchId } }, + where: { match: { id: match.id } }, }); + const { home_score, away_score, winning_team, points_multiplier } = match; + for (const prediction of predictions) { - prediction.is_correct = - String(prediction.predicted_outcome) === String(winningTeam); + const outcomeCorrect = + String(prediction.predicted_outcome) === String(winning_team); + prediction.is_correct = outcomeCorrect; + + if (!outcomeCorrect) { + prediction.points_earned = 0; + continue; + } + + if ( + home_score !== null && + away_score !== null && + prediction.predicted_home_score !== null && + prediction.predicted_away_score !== null + ) { + const exactScore = + prediction.predicted_home_score === home_score && + prediction.predicted_away_score === away_score; + + const goalDiffCorrect = + prediction.predicted_home_score - prediction.predicted_away_score === + home_score - away_score; + + if (exactScore) { + prediction.points_earned = 4 * points_multiplier; + } else if (goalDiffCorrect) { + prediction.points_earned = 3 * points_multiplier; + } else { + prediction.points_earned = 1 * points_multiplier; + } + } else { + prediction.points_earned = 1 * points_multiplier; + } } if (predictions.length > 0) { diff --git a/backend/src/matches/entities/match-prediction.entity.ts b/backend/src/matches/entities/match-prediction.entity.ts index bbe9626a..f8c1028f 100644 --- a/backend/src/matches/entities/match-prediction.entity.ts +++ b/backend/src/matches/entities/match-prediction.entity.ts @@ -41,6 +41,15 @@ export class MatchPrediction { }) predicted_outcome: PredictedOutcome; + @Column({ type: 'int', nullable: true }) + predicted_home_score: number | null; + + @Column({ type: 'int', nullable: true }) + predicted_away_score: number | null; + + @Column({ type: 'int', default: 0 }) + points_earned: number; + @Column({ type: 'boolean', nullable: true }) is_correct: boolean | null; diff --git a/backend/src/migrations/1775900000000-AddScorelineAndPointsToMatchPrediction.ts b/backend/src/migrations/1775900000000-AddScorelineAndPointsToMatchPrediction.ts new file mode 100644 index 00000000..dc1b5d7c --- /dev/null +++ b/backend/src/migrations/1775900000000-AddScorelineAndPointsToMatchPrediction.ts @@ -0,0 +1,31 @@ +import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm'; + +export class AddScorelineAndPointsToMatchPrediction1775900000000 + implements MigrationInterface +{ + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.addColumns('match_predictions', [ + new TableColumn({ + name: 'predicted_home_score', + type: 'int', + isNullable: true, + }), + new TableColumn({ + name: 'predicted_away_score', + type: 'int', + isNullable: true, + }), + new TableColumn({ + name: 'points_earned', + type: 'int', + default: 0, + }), + ]); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropColumn('match_predictions', 'points_earned'); + await queryRunner.dropColumn('match_predictions', 'predicted_away_score'); + await queryRunner.dropColumn('match_predictions', 'predicted_home_score'); + } +} From 03487dee4d11ca38b33edce1e66a62e7378aac7a Mon Sep 17 00:00:00 2001 From: Timrossid Date: Thu, 18 Jun 2026 08:06:37 +0100 Subject: [PATCH 2/5] fix: add missing Match, MatchPrediction, User repository mocks to test specs The CreatorEventsService now depends on Match, MatchPrediction, and User repositories. Both test files were missing their mock providers, causing NestJS module compilation failures. --- .../creator-events-predictions-stats.spec.ts | 15 +++++++++++++++ .../creator-events/creator-events.service.spec.ts | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/backend/src/creator-events/creator-events-predictions-stats.spec.ts b/backend/src/creator-events/creator-events-predictions-stats.spec.ts index b3639565..77406364 100644 --- a/backend/src/creator-events/creator-events-predictions-stats.spec.ts +++ b/backend/src/creator-events/creator-events-predictions-stats.spec.ts @@ -6,6 +6,9 @@ import { ContractService, } from '../contract/contract.service'; import { CreatorEvent } from '../matches/entities/creator-event.entity'; +import { Match } from '../matches/entities/match.entity'; +import { MatchPrediction } from '../matches/entities/match-prediction.entity'; +import { User } from '../users/entities/user.entity'; import { CreatorEventsService } from './creator-events.service'; describe('CreatorEventsService predictions and stats', () => { @@ -74,6 +77,18 @@ describe('CreatorEventsService predictions and stats', () => { provide: getRepositoryToken(CreatorEvent), useValue: { createQueryBuilder: jest.fn() }, }, + { + provide: getRepositoryToken(Match), + useValue: {}, + }, + { + provide: getRepositoryToken(MatchPrediction), + useValue: {}, + }, + { + provide: getRepositoryToken(User), + useValue: {}, + }, ], }).compile(); diff --git a/backend/src/creator-events/creator-events.service.spec.ts b/backend/src/creator-events/creator-events.service.spec.ts index bf76778d..0ca792bc 100644 --- a/backend/src/creator-events/creator-events.service.spec.ts +++ b/backend/src/creator-events/creator-events.service.spec.ts @@ -3,6 +3,9 @@ import { getRepositoryToken } from '@nestjs/typeorm'; import { Repository, SelectQueryBuilder } from 'typeorm'; import { ContractService } from '../contract/contract.service'; import { CreatorEvent } from '../matches/entities/creator-event.entity'; +import { Match } from '../matches/entities/match.entity'; +import { MatchPrediction } from '../matches/entities/match-prediction.entity'; +import { User } from '../users/entities/user.entity'; import { CreatorEventsService } from './creator-events.service'; import { CreatorEventSearchStatus } from './dto/search-events-query.dto'; @@ -89,6 +92,18 @@ describe('CreatorEventsService searchEvents', () => { provide: getRepositoryToken(CreatorEvent), useValue: creatorEventRepository, }, + { + provide: getRepositoryToken(Match), + useValue: {}, + }, + { + provide: getRepositoryToken(MatchPrediction), + useValue: {}, + }, + { + provide: getRepositoryToken(User), + useValue: {}, + }, ], }).compile(); From f9ba134e5b79b996d15e41cf813b91bc0e277a9c Mon Sep 17 00:00:00 2001 From: Timrossid Date: Thu, 18 Jun 2026 10:45:13 +0100 Subject: [PATCH 3/5] fix: inject missing LeaderboardEntry repository into CreatorEventsService --- .../creator-events/creator-events-predictions-stats.spec.ts | 5 +++++ backend/src/creator-events/creator-events.module.ts | 3 ++- backend/src/creator-events/creator-events.service.spec.ts | 5 +++++ backend/src/creator-events/creator-events.service.ts | 3 +++ 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/backend/src/creator-events/creator-events-predictions-stats.spec.ts b/backend/src/creator-events/creator-events-predictions-stats.spec.ts index 14b0e522..7f979292 100644 --- a/backend/src/creator-events/creator-events-predictions-stats.spec.ts +++ b/backend/src/creator-events/creator-events-predictions-stats.spec.ts @@ -10,6 +10,7 @@ import { CreatorEventLeaderboardEntry } from '../matches/entities/creator-event- import { Match } from '../matches/entities/match.entity'; import { MatchPrediction } from '../matches/entities/match-prediction.entity'; import { User } from '../users/entities/user.entity'; +import { LeaderboardEntry } from '../leaderboard/entities/leaderboard-entry.entity'; import { CreatorEventsService } from './creator-events.service'; describe('CreatorEventsService predictions and stats', () => { @@ -94,6 +95,10 @@ describe('CreatorEventsService predictions and stats', () => { provide: getRepositoryToken(User), useValue: {}, }, + { + provide: getRepositoryToken(LeaderboardEntry), + useValue: {}, + }, ], }).compile(); diff --git a/backend/src/creator-events/creator-events.module.ts b/backend/src/creator-events/creator-events.module.ts index 442b872e..4f1df53a 100644 --- a/backend/src/creator-events/creator-events.module.ts +++ b/backend/src/creator-events/creator-events.module.ts @@ -6,6 +6,7 @@ import { CreatorEvent } from '../matches/entities/creator-event.entity'; import { Match } from '../matches/entities/match.entity'; import { MatchPrediction } from '../matches/entities/match-prediction.entity'; import { User } from '../users/entities/user.entity'; +import { LeaderboardEntry } from '../leaderboard/entities/leaderboard-entry.entity'; import { AdminCreatorEventsController, CreatorEventsController, @@ -16,7 +17,7 @@ import { CreatorEventsService } from './creator-events.service'; @Module({ imports: [ ContractModule, - TypeOrmModule.forFeature([CreatorEvent, Match, MatchPrediction, User]), + TypeOrmModule.forFeature([CreatorEvent, Match, MatchPrediction, User, LeaderboardEntry]), CacheModule.register(), ], controllers: [ diff --git a/backend/src/creator-events/creator-events.service.spec.ts b/backend/src/creator-events/creator-events.service.spec.ts index d2858f21..d359579b 100644 --- a/backend/src/creator-events/creator-events.service.spec.ts +++ b/backend/src/creator-events/creator-events.service.spec.ts @@ -7,6 +7,7 @@ import { CreatorEventLeaderboardEntry } from '../matches/entities/creator-event- import { Match } from '../matches/entities/match.entity'; import { MatchPrediction } from '../matches/entities/match-prediction.entity'; import { User } from '../users/entities/user.entity'; +import { LeaderboardEntry } from '../leaderboard/entities/leaderboard-entry.entity'; import { CreatorEventsService } from './creator-events.service'; import { CreatorEventSearchStatus } from './dto/search-events-query.dto'; @@ -109,6 +110,10 @@ describe('CreatorEventsService searchEvents', () => { provide: getRepositoryToken(User), useValue: {}, }, + { + provide: getRepositoryToken(LeaderboardEntry), + useValue: {}, + }, ], }).compile(); diff --git a/backend/src/creator-events/creator-events.service.ts b/backend/src/creator-events/creator-events.service.ts index e59d200a..b9300ba7 100644 --- a/backend/src/creator-events/creator-events.service.ts +++ b/backend/src/creator-events/creator-events.service.ts @@ -12,6 +12,7 @@ import { CreatorEvent } from '../matches/entities/creator-event.entity'; import { Match } from '../matches/entities/match.entity'; import { MatchPrediction } from '../matches/entities/match-prediction.entity'; import { User } from '../users/entities/user.entity'; +import { LeaderboardEntry } from '../leaderboard/entities/leaderboard-entry.entity'; import { EventByCodeResponseDto, MatchPreviewDto, @@ -88,6 +89,8 @@ export class CreatorEventsService { private readonly matchPredictionRepository: Repository, @InjectRepository(User) private readonly userRepository: Repository, + @InjectRepository(LeaderboardEntry) + private readonly leaderboardEntryRepository: Repository, ) {} async searchEvents( From 614743e071a4737a74f61cde653ffe3e9b3d9130 Mon Sep 17 00:00:00 2001 From: Timrossid Date: Thu, 18 Jun 2026 16:44:36 +0100 Subject: [PATCH 4/5] feat: add CreatorEventPayout entity, PayoutsQueryDto, and repository injection --- .../creator-events-predictions-stats.spec.ts | 4 +- .../creator-events/creator-events.module.ts | 5 ++- .../creator-events.service.spec.ts | 4 +- .../creator-events/creator-events.service.ts | 14 +++++-- .../creator-events/dto/payouts-query.dto.ts | 40 +++++++++++++++++++ 5 files changed, 58 insertions(+), 9 deletions(-) create mode 100644 backend/src/creator-events/dto/payouts-query.dto.ts diff --git a/backend/src/creator-events/creator-events-predictions-stats.spec.ts b/backend/src/creator-events/creator-events-predictions-stats.spec.ts index 3946ec7c..7f8c2cfa 100644 --- a/backend/src/creator-events/creator-events-predictions-stats.spec.ts +++ b/backend/src/creator-events/creator-events-predictions-stats.spec.ts @@ -7,10 +7,10 @@ import { } from '../contract/contract.service'; import { CreatorEvent } from '../matches/entities/creator-event.entity'; import { CreatorEventLeaderboardEntry } from '../matches/entities/creator-event-leaderboard-entry.entity'; +import { CreatorEventPayout } from '../matches/entities/creator-event-payout.entity'; import { Match } from '../matches/entities/match.entity'; import { MatchPrediction } from '../matches/entities/match-prediction.entity'; import { User } from '../users/entities/user.entity'; -import { LeaderboardEntry } from '../leaderboard/entities/leaderboard-entry.entity'; import { CreatorEventsService } from './creator-events.service'; describe('CreatorEventsService predictions and stats', () => { @@ -102,7 +102,7 @@ describe('CreatorEventsService predictions and stats', () => { useValue: {}, }, { - provide: getRepositoryToken(LeaderboardEntry), + provide: getRepositoryToken(CreatorEventPayout), useValue: {}, }, ], diff --git a/backend/src/creator-events/creator-events.module.ts b/backend/src/creator-events/creator-events.module.ts index 4f1df53a..035107d4 100644 --- a/backend/src/creator-events/creator-events.module.ts +++ b/backend/src/creator-events/creator-events.module.ts @@ -6,7 +6,8 @@ import { CreatorEvent } from '../matches/entities/creator-event.entity'; import { Match } from '../matches/entities/match.entity'; import { MatchPrediction } from '../matches/entities/match-prediction.entity'; import { User } from '../users/entities/user.entity'; -import { LeaderboardEntry } from '../leaderboard/entities/leaderboard-entry.entity'; +import { CreatorEventLeaderboardEntry } from '../matches/entities/creator-event-leaderboard-entry.entity'; +import { CreatorEventPayout } from '../matches/entities/creator-event-payout.entity'; import { AdminCreatorEventsController, CreatorEventsController, @@ -17,7 +18,7 @@ import { CreatorEventsService } from './creator-events.service'; @Module({ imports: [ ContractModule, - TypeOrmModule.forFeature([CreatorEvent, Match, MatchPrediction, User, LeaderboardEntry]), + TypeOrmModule.forFeature([CreatorEvent, Match, MatchPrediction, User, CreatorEventLeaderboardEntry, CreatorEventPayout]), CacheModule.register(), ], controllers: [ diff --git a/backend/src/creator-events/creator-events.service.spec.ts b/backend/src/creator-events/creator-events.service.spec.ts index d359579b..ff8f4387 100644 --- a/backend/src/creator-events/creator-events.service.spec.ts +++ b/backend/src/creator-events/creator-events.service.spec.ts @@ -4,10 +4,10 @@ import { Repository, SelectQueryBuilder } from 'typeorm'; import { ContractService } from '../contract/contract.service'; import { CreatorEvent } from '../matches/entities/creator-event.entity'; import { CreatorEventLeaderboardEntry } from '../matches/entities/creator-event-leaderboard-entry.entity'; +import { CreatorEventPayout } from '../matches/entities/creator-event-payout.entity'; import { Match } from '../matches/entities/match.entity'; import { MatchPrediction } from '../matches/entities/match-prediction.entity'; import { User } from '../users/entities/user.entity'; -import { LeaderboardEntry } from '../leaderboard/entities/leaderboard-entry.entity'; import { CreatorEventsService } from './creator-events.service'; import { CreatorEventSearchStatus } from './dto/search-events-query.dto'; @@ -111,7 +111,7 @@ describe('CreatorEventsService searchEvents', () => { useValue: {}, }, { - provide: getRepositoryToken(LeaderboardEntry), + provide: getRepositoryToken(CreatorEventPayout), useValue: {}, }, ], diff --git a/backend/src/creator-events/creator-events.service.ts b/backend/src/creator-events/creator-events.service.ts index 69065816..6600669b 100644 --- a/backend/src/creator-events/creator-events.service.ts +++ b/backend/src/creator-events/creator-events.service.ts @@ -12,7 +12,8 @@ import { CreatorEvent } from '../matches/entities/creator-event.entity'; import { Match } from '../matches/entities/match.entity'; import { MatchPrediction } from '../matches/entities/match-prediction.entity'; import { User } from '../users/entities/user.entity'; -import { LeaderboardEntry } from '../leaderboard/entities/leaderboard-entry.entity'; +import { CreatorEventLeaderboardEntry } from '../matches/entities/creator-event-leaderboard-entry.entity'; +import { CreatorEventPayout } from '../matches/entities/creator-event-payout.entity'; import { EventByCodeResponseDto, MatchPreviewDto, @@ -45,6 +46,11 @@ import { LeaderboardEntryResponse, PaginatedLeaderboardResponse, } from './dto/leaderboard-query.dto'; +import { + PayoutsQueryDto, + PaginatedPayoutsDto, + PayoutEntryDto, +} from './dto/payouts-query.dto'; import { normalizeContractPrediction, resolveCorrectness, @@ -89,8 +95,10 @@ export class CreatorEventsService { private readonly matchPredictionRepository: Repository, @InjectRepository(User) private readonly userRepository: Repository, - @InjectRepository(LeaderboardEntry) - private readonly leaderboardEntryRepository: Repository, + @InjectRepository(CreatorEventLeaderboardEntry) + private readonly leaderboardEntryRepository: Repository, + @InjectRepository(CreatorEventPayout) + private readonly creatorEventPayoutRepository: Repository, ) {} async searchEvents( diff --git a/backend/src/creator-events/dto/payouts-query.dto.ts b/backend/src/creator-events/dto/payouts-query.dto.ts new file mode 100644 index 00000000..fae071a9 --- /dev/null +++ b/backend/src/creator-events/dto/payouts-query.dto.ts @@ -0,0 +1,40 @@ +import { IsOptional, IsInt, Min, Max } from 'class-validator'; +import { Type } from 'class-transformer'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; + +export class PayoutsQueryDto { + @ApiPropertyOptional({ description: 'Page number', default: 1, minimum: 1 }) + @IsOptional() + @Type(() => Number) + @IsInt() + @Min(1) + page: number = 1; + + @ApiPropertyOptional({ + description: 'Results per page (max 100)', + default: 20, + maximum: 100, + }) + @IsOptional() + @Type(() => Number) + @IsInt() + @Min(1) + @Max(100) + limit: number = 20; +} + +export interface PayoutEntryDto { + address: string; + amount: string; + transaction_hash: string | null; + paid_at: string | null; + rank: number; +} + +export interface PaginatedPayoutsDto { + data: PayoutEntryDto[]; + total: number; + page: number; + limit: number; + totalPages: number; +} From 5d63b0e9fdae2a2651e06236914e3b1646b21923 Mon Sep 17 00:00:00 2001 From: Timrossid Date: Thu, 18 Jun 2026 16:48:21 +0100 Subject: [PATCH 5/5] fix: use existing payouts.dto, align CreatorEventPayout injection --- .../creator-events/creator-events.service.ts | 2 +- .../creator-events/dto/payouts-query.dto.ts | 40 ------------------- 2 files changed, 1 insertion(+), 41 deletions(-) delete mode 100644 backend/src/creator-events/dto/payouts-query.dto.ts diff --git a/backend/src/creator-events/creator-events.service.ts b/backend/src/creator-events/creator-events.service.ts index 6600669b..4b0def7b 100644 --- a/backend/src/creator-events/creator-events.service.ts +++ b/backend/src/creator-events/creator-events.service.ts @@ -50,7 +50,7 @@ import { PayoutsQueryDto, PaginatedPayoutsDto, PayoutEntryDto, -} from './dto/payouts-query.dto'; +} from './dto/payouts.dto'; import { normalizeContractPrediction, resolveCorrectness, diff --git a/backend/src/creator-events/dto/payouts-query.dto.ts b/backend/src/creator-events/dto/payouts-query.dto.ts deleted file mode 100644 index fae071a9..00000000 --- a/backend/src/creator-events/dto/payouts-query.dto.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { IsOptional, IsInt, Min, Max } from 'class-validator'; -import { Type } from 'class-transformer'; -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; - -export class PayoutsQueryDto { - @ApiPropertyOptional({ description: 'Page number', default: 1, minimum: 1 }) - @IsOptional() - @Type(() => Number) - @IsInt() - @Min(1) - page: number = 1; - - @ApiPropertyOptional({ - description: 'Results per page (max 100)', - default: 20, - maximum: 100, - }) - @IsOptional() - @Type(() => Number) - @IsInt() - @Min(1) - @Max(100) - limit: number = 20; -} - -export interface PayoutEntryDto { - address: string; - amount: string; - transaction_hash: string | null; - paid_at: string | null; - rank: number; -} - -export interface PaginatedPayoutsDto { - data: PayoutEntryDto[]; - total: number; - page: number; - limit: number; - totalPages: number; -}