first (part 3: src directory)
Deploy Iddaai Backend / build-and-deploy (push) Successful in 33s

This commit is contained in:
2026-04-16 15:12:27 +03:00
parent 2f0b85a0c7
commit 182f4aae16
125 changed files with 22552 additions and 0 deletions
+238
View File
@@ -0,0 +1,238 @@
import {
Controller,
Post,
Get,
Body,
Query,
HttpCode,
HttpStatus,
UseGuards,
Req,
Logger,
} from '@nestjs/common';
import {
ApiTags,
ApiBearerAuth,
ApiOperation,
ApiResponse,
} from '@nestjs/swagger';
import { CouponsService } from './coupons.service';
import { MatchesService } from '../matches/matches.service';
import { SmartCouponService } from './services/smart-coupon.service';
import {
UserCouponService,
CreateCouponDto,
} from './services/user-coupon.service';
import {
AnalyzeMatchDto,
DailyBankoDto,
SuggestCouponDto,
} from './dto/coupons-request.dto';
import { Public } from '../../common/decorators';
import { JwtAuthGuard } from '../auth/guards/auth.guards'; // Assuming standard guard
import { Sport } from '../matches/dto';
@ApiTags('Coupon')
@Controller('coupon')
export class CouponsController {
private readonly logger = new Logger(CouponsController.name);
constructor(
private readonly couponsService: CouponsService,
private readonly smartCouponService: SmartCouponService,
private readonly userCouponService: UserCouponService,
private readonly matchesService: MatchesService,
) {}
/**
* POST /coupon/analyze-match
* Analyze a single match with V20+ single-match package
*/
@Post('analyze-match')
@Public()
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Analyze single match with V20 model' })
@ApiResponse({ status: 200, description: 'Match analysis' })
async analyzeMatch(@Body() dto: AnalyzeMatchDto) {
const analysis = await this.smartCouponService.analyzeMatch(dto.matchId);
if (!analysis) {
return { success: false, message: 'Analiz yapılamadı.' };
}
return { success: true, data: analysis };
}
/**
* POST /coupon/analyze (alias for /analyze-match - frontend compatibility)
*/
@Post('analyze')
@Public()
@HttpCode(HttpStatus.OK)
@ApiOperation({
summary: 'Analyze single match with V20 model (alias)',
deprecated: true,
})
async analyzeMatchAlias(@Body() dto: AnalyzeMatchDto) {
return this.analyzeMatch(dto);
}
/**
* POST /coupon
* Alias for /coupon/create - frontend compatibility
*/
@Post()
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@HttpCode(HttpStatus.CREATED)
@ApiOperation({ summary: 'Create and save a user coupon (alias)' })
async createCouponAlias(@Body() dto: CreateCouponDto, @Req() req: any) {
return this.createCoupon(dto, req);
}
/**
* POST /coupon/daily-banko
* Generate a high-confidence banko combo (2 matches)
*/
@Post('daily-banko')
@Public()
@HttpCode(HttpStatus.OK)
@ApiOperation({
summary: 'Generate a high-confidence banko combo (2 matches)',
})
async getDailyBanko(@Body() dto: DailyBankoDto) {
// If no match IDs provided, fetch from system (top 50 upcoming)
let candidateMatches = dto.matchIds || [];
if (candidateMatches.length === 0) {
candidateMatches = await this.matchesService.findUpcomingMatches(
Sport.FOOTBALL,
20,
);
this.logger.debug(
`Auto-fetched ${candidateMatches.length} matches for daily-banko`,
);
} else {
candidateMatches = await this.matchesService.filterUpcomingMatchIds(
candidateMatches,
Sport.FOOTBALL,
);
this.logger.debug(
`Sanitized candidate matches for daily-banko: ${candidateMatches.length}`,
);
}
if (candidateMatches.length === 0) {
return {
success: false,
message: 'Kupon için uygun, henüz başlamamış maç bulunamadı.',
};
}
const coupon =
await this.smartCouponService.generateDailyBankoCoupon(candidateMatches);
if (!coupon) {
return {
success: false,
message: 'Kriterlere uygun (80%+ güvenli) yeterli maç bulunamadı.',
};
}
return { success: true, data: coupon };
}
/**
* POST /coupon/suggest
* Generate Smart Coupon
*/
@Post('suggest')
@Public()
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Suggest Smart Coupon' })
@ApiResponse({ status: 200, description: 'Smart Coupon generated' })
async suggestCoupon(@Body() dto: SuggestCouponDto) {
// If no match IDs provided, fetch from system (top 50 upcoming)
let candidateMatches = dto.matchIds || [];
if (candidateMatches.length === 0) {
candidateMatches = await this.matchesService.findUpcomingMatches(
Sport.FOOTBALL,
20,
);
this.logger.debug(
`Auto-fetched ${candidateMatches.length} matches for suggest`,
);
} else {
candidateMatches = await this.matchesService.filterUpcomingMatchIds(
candidateMatches,
Sport.FOOTBALL,
);
this.logger.debug(
`Sanitized candidate matches for suggest: ${candidateMatches.length}`,
);
}
if (candidateMatches.length === 0) {
return {
success: false,
message: 'Tahmin için uygun, henüz başlamamış maç bulunamadı.',
};
}
const coupon = await this.smartCouponService.getSmartCoupon(
candidateMatches,
dto.strategy,
{
maxMatches: dto.maxMatches,
minConfidence: dto.minConfidence,
},
);
if (!coupon) {
return { success: false, message: 'Kupon oluşturulamadı.' };
}
return { success: true, data: coupon };
}
// ============================================
// USER COUPON ENDPOINTS (NEW)
// ============================================
/**
* POST /coupon/create
* Save a user generated coupon
*/
@Post('create')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@HttpCode(HttpStatus.CREATED)
@ApiOperation({ summary: 'Create and save a user coupon' })
async createCoupon(@Body() dto: CreateCouponDto, @Req() req: any) {
// req.user is populated by JwtAuthGuard
const coupon = await this.userCouponService.createCoupon(req.user, dto);
return { success: true, data: coupon };
}
/**
* GET /coupon/my-stats
* Get user betting statistics (ROI, Win Rate)
*/
@Get('my-stats')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOperation({ summary: 'Get user betting statistics' })
async getUserStats(@Req() req: any) {
const stats = await this.userCouponService.getUserStatistics(req.user.id);
return { success: true, data: stats };
}
/**
* GET /coupon/history
* Get coupon history (Public/System coupons)
*/
@Get('history')
@ApiBearerAuth()
@ApiOperation({ summary: 'Get coupon history' })
@ApiResponse({ status: 200, description: 'History retrieved' })
async getHistory(@Query('limit') limit?: string) {
// eslint-disable-next-line @typescript-eslint/await-thenable
const results = await this.couponsService.getCouponHistory(
Number(limit) || 10,
);
return { success: true, data: results };
}
}