This commit is contained in:
Executable
+238
@@ -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 };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user