Files
iddaai-be/src/modules/coupons/coupons.controller.ts
T
fahricansecer a338d02244
Deploy Iddaai Backend / build-and-deploy (push) Successful in 2m42s
main
2026-04-26 03:07:18 +03:00

344 lines
9.2 KiB
TypeScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
FrequencyCouponDto,
} 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",
schema: {
type: "object",
properties: {
success: { type: "boolean" },
data: { type: "object" },
message: { type: "string" },
},
},
})
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)",
})
@ApiResponse({
status: 200,
description: "Daily banko coupon",
schema: {
type: "object",
properties: {
success: { type: "boolean" },
data: { type: "object" },
message: { type: "string" },
},
},
})
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",
schema: {
type: "object",
properties: {
success: { type: "boolean" },
data: { type: "object" },
message: { type: "string" },
},
},
})
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 };
}
/**
* POST /coupon/frequency-coupon
* Generate a frequency-based parlay coupon (Conditional Frequency Engine)
*/
@Post("frequency-coupon")
@Public()
@HttpCode(HttpStatus.OK)
@ApiOperation({
summary: "Generate frequency-based parlay coupon",
description:
"Scans upcoming matches, applies conditional frequency analysis " +
"(team odds-band performance), and builds 2-5 match combos with +EV calculation.",
})
@ApiResponse({ status: 200, description: "Frequency coupon generated" })
async getFrequencyCoupon(@Body() dto: FrequencyCouponDto) {
const coupon = await this.smartCouponService.generateFrequencyBasedCoupon({
matchIds: dto.matchIds,
maxMatches: dto.maxMatches,
minSignal: dto.minSignal,
markets: dto.markets,
});
if (!coupon || coupon.bets.length === 0) {
return {
success: false,
message:
"Frekans analizine uygun yeterli maç bulunamadı. " +
"minSignal değerini düşürmeyi veya daha fazla maç beklemeyi deneyin.",
data: coupon,
};
}
return { success: true, data: coupon };
}
// ============================================
// USER COUPON ENDPOINTS
// ============================================
/**
* POST /coupon/create
* Save a user generated coupon
*/
@Post("create")
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@HttpCode(HttpStatus.CREATED)
@ApiOperation({ summary: "Create and save a user coupon" })
@ApiResponse({
status: 201,
description: "Coupon created",
schema: {
type: "object",
properties: {
success: { type: "boolean" },
data: { type: "object" },
message: { type: "string" },
},
},
})
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" })
@ApiResponse({
status: 200,
description: "User statistics",
schema: {
type: "object",
properties: {
success: { type: "boolean" },
data: { type: "object" },
message: { type: "string" },
},
},
})
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",
schema: {
type: "object",
properties: {
success: { type: "boolean" },
data: { type: "array", items: { type: "object" } },
message: { type: "string" },
},
},
})
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 };
}
}