| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227 |
- import { Injectable, Logger } from '@nestjs/common';
- import { ConfigService } from '@nestjs/config';
- import { InjectRepository } from '@nestjs/typeorm';
- import { Repository } from 'typeorm';
- import { User } from '../user/user.entity';
- export interface UserQuota {
- userId: string;
- monthlyCost: number;
- maxCost: number;
- remaining: number;
- lastReset: Date;
- }
- export interface CostEstimate {
- estimatedCost: number;
- estimatedTime: number;
- pageBreakdown: {
- pageIndex: number;
- cost: number;
- }[];
- }
- @Injectable()
- export class CostControlService {
- private readonly logger = new Logger(CostControlService.name);
- private readonly COST_PER_PAGE = 0.01;
- private readonly DEFAULT_MONTHLY_LIMIT = 100;
- constructor(
- private configService: ConfigService,
- @InjectRepository(User)
- private userRepository: Repository<User>,
- ) { }
-
- estimateCost(pageCount: number, quality: 'low' | 'medium' | 'high' = 'medium'): CostEstimate {
-
- const qualityMultiplier = {
- low: 0.5,
- medium: 1.0,
- high: 1.5,
- };
- const baseCost = pageCount * this.COST_PER_PAGE * qualityMultiplier[quality];
- const estimatedTime = pageCount * 3;
- const pageBreakdown = Array.from({ length: pageCount }, (_, i) => ({
- pageIndex: i + 1,
- cost: this.COST_PER_PAGE * qualityMultiplier[quality],
- }));
- return {
- estimatedCost: baseCost,
- estimatedTime,
- pageBreakdown,
- };
- }
-
- async checkQuota(userId: string, estimatedCost: number): Promise<{
- allowed: boolean;
- quota: UserQuota;
- reason?: string;
- }> {
- const quota = await this.getUserQuota(userId);
-
- this.checkAndResetMonthlyQuota(quota);
- if (quota.remaining < estimatedCost) {
- this.logger.warn(
- `Insufficient quota for user ${userId}: Remaining $${quota.remaining.toFixed(2)}, Required $${estimatedCost.toFixed(2)}`,
- );
- return {
- allowed: false,
- quota,
- reason: `Insufficient quota: remaining $${quota.remaining.toFixed(2)}, required $${estimatedCost.toFixed(2)}`,
- };
- }
- return {
- allowed: true,
- quota,
- };
- }
-
- async deductQuota(userId: string, actualCost: number): Promise<void> {
- const quota = await this.getUserQuota(userId);
- quota.monthlyCost += actualCost;
- quota.remaining = quota.maxCost - quota.monthlyCost;
- await this.userRepository.update(userId, {
- monthlyCost: quota.monthlyCost,
- });
- this.logger.log(
- `Deducted $${actualCost.toFixed(2)} from user ${userId}'s quota. Remaining $${quota.remaining.toFixed(2)}`,
- );
- }
-
- async getUserQuota(userId: string): Promise<UserQuota> {
- const user = await this.userRepository.findOne({ where: { id: userId } });
- if (!user) {
- throw new Error(`User ${userId} does not exist`);
- }
-
- const monthlyCost = user.monthlyCost || 0;
- const maxCost = user.maxCost || this.DEFAULT_MONTHLY_LIMIT;
- const lastReset = user.lastQuotaReset || new Date();
- return {
- userId,
- monthlyCost,
- maxCost,
- remaining: maxCost - monthlyCost,
- lastReset,
- };
- }
-
- private checkAndResetMonthlyQuota(quota: UserQuota): void {
- const now = new Date();
- const lastReset = quota.lastReset;
-
- if (
- now.getMonth() !== lastReset.getMonth() ||
- now.getFullYear() !== lastReset.getFullYear()
- ) {
- this.logger.log(`Reset monthly quota for user ${quota.userId}`);
-
- quota.monthlyCost = 0;
- quota.remaining = quota.maxCost;
- quota.lastReset = now;
-
- this.userRepository.update(quota.userId, {
- monthlyCost: 0,
- lastQuotaReset: now,
- });
- }
- }
-
- async setQuotaLimit(userId: string, maxCost: number): Promise<void> {
- await this.userRepository.update(userId, { maxCost });
- this.logger.log(`Set quota limit for user ${userId} to $${maxCost}`);
- }
-
- async getCostReport(userId: string, days: number = 30): Promise<{
- totalCost: number;
- dailyAverage: number;
- pageStats: {
- totalPages: number;
- avgCostPerPage: number;
- };
- quotaUsage: number;
- }> {
- const quota = await this.getUserQuota(userId);
- const usagePercent = (quota.monthlyCost / quota.maxCost) * 100;
-
-
- return {
- totalCost: quota.monthlyCost,
- dailyAverage: quota.monthlyCost / Math.max(days, 1),
- pageStats: {
- totalPages: Math.floor(quota.monthlyCost / this.COST_PER_PAGE),
- avgCostPerPage: this.COST_PER_PAGE,
- },
- quotaUsage: usagePercent,
- };
- }
-
- async checkWarningThreshold(userId: string): Promise<{
- shouldWarn: boolean;
- message: string;
- }> {
- const quota = await this.getUserQuota(userId);
- const usagePercent = (quota.monthlyCost / quota.maxCost) * 100;
- if (usagePercent >= 90) {
- return {
- shouldWarn: true,
- message: `⚠️ Quota usage has reached ${usagePercent.toFixed(1)}%. Remaining $${quota.remaining.toFixed(2)}`,
- };
- }
- if (usagePercent >= 75) {
- return {
- shouldWarn: true,
- message: `💡 Quota usage ${usagePercent.toFixed(1)}%. Be careful with controlling costs`,
- };
- }
- return {
- shouldWarn: false,
- message: '',
- };
- }
-
- formatCost(cost: number): string {
- return `$${cost.toFixed(2)}`;
- }
-
- formatTime(seconds: number): string {
- if (seconds < 60) {
- return `${seconds.toFixed(0)}s`;
- }
- const minutes = Math.floor(seconds / 60);
- const remainingSeconds = seconds % 60;
- return `${minutes}m ${remainingSeconds.toFixed(0)}s`;
- }
- }
|