Files
maps-saas/apps/api/dist/usage/usage.service.js
T

135 lines
5.8 KiB
JavaScript

"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var UsageService_1;
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.UsageService = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const usage_entity_1 = require("./usage.entity");
const redis_service_1 = require("../common/redis.service");
let UsageService = UsageService_1 = class UsageService {
usageRepository;
redisService;
logger = new common_1.Logger(UsageService_1.name);
constructor(usageRepository, redisService) {
this.usageRepository = usageRepository;
this.redisService = redisService;
}
async recordRequest(data) {
this.usageRepository.save(data).catch((err) => {
this.logger.error(`Failed to save usage log to DB: ${err.message}`);
});
const monthKey = this.getMonthlyKey(data.tenantId);
try {
await this.redisService.getClient().incr(monthKey);
}
catch (err) {
this.logger.error(`Failed to increment Redis usage counter: ${err.message}`);
}
}
async getMonthlyUsage(tenantId) {
const key = this.getMonthlyKey(tenantId);
const val = await this.redisService.getClient().get(key);
return val ? parseInt(val, 10) : 0;
}
async checkQuota(tenantId, limit) {
const used = await this.getMonthlyUsage(tenantId);
return {
allowed: used < limit,
used,
};
}
async checkRateLimit(tenantId, limit) {
const key = this.getRateLimitKey(tenantId);
try {
const client = this.redisService.getClient();
const usedRaw = await client.get(key);
const used = usedRaw ? parseInt(usedRaw, 10) : 0;
if (used >= limit) {
return { allowed: false, used };
}
const multi = client.multi();
multi.incr(key);
if (!usedRaw) {
multi.expire(key, 60);
}
await multi.exec();
return { allowed: true, used: used + 1 };
}
catch (err) {
this.logger.error(`Rate limit check failed for ${tenantId}: ${err.message}`);
return { allowed: true, used: 0 };
}
}
async getUsageHistory(tenantId, days = 30) {
return this.usageRepository
.createQueryBuilder('usage')
.select("DATE_TRUNC('day', usage.createdAt)", 'date')
.addSelect('COUNT(*)', 'count')
.where('usage.tenantId = :tenantId', { tenantId })
.andWhere("usage.createdAt >= NOW() - (:days || ' days')::INTERVAL", { days: days.toString() })
.groupBy("DATE_TRUNC('day', usage.createdAt)")
.orderBy('date', 'ASC')
.getRawMany();
}
async getUsageSummary(tenantId, plan = 'FREE') {
const monthlyUsage = await this.getMonthlyUsage(tenantId);
const QUOTA_LIMITS = {
'FREE': 8000,
'PRO': 50000,
'ENTERPRISE': 1000000
};
const monthlyLimit = QUOTA_LIMITS[plan] || 8000;
const stats = await this.usageRepository
.createQueryBuilder('usage')
.select('COUNT(*)', 'totalToday')
.addSelect('AVG(usage.responseTimeMs)', 'avgLatency')
.addSelect('COUNT(CASE WHEN usage.statusCode >= 200 AND usage.statusCode < 300 THEN 1 END)', 'successCount')
.where('usage.tenantId = :tenantId', { tenantId })
.andWhere('usage.createdAt >= CURRENT_DATE')
.getRawOne();
const totalToday = parseInt(stats.totalToday || '0', 10);
const avgLatency = Math.round(parseFloat(stats.avgLatency || '0'));
const successRate = totalToday > 0
? Math.round((parseInt(stats.successCount || '0', 10) / totalToday) * 100)
: 100;
return {
monthlyUsage,
monthlyLimit,
totalToday,
avgLatency,
successRate,
lastUpdated: new Date()
};
}
getMonthlyKey(tenantId) {
const now = new Date();
const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
return `usage:${tenantId}:${yearMonth}`;
}
getRateLimitKey(tenantId) {
const now = new Date();
const window = `${now.getFullYear()}${now.getMonth()}${now.getDate()}${now.getHours()}${now.getMinutes()}`;
return `ratelimit:${tenantId}:${window}`;
}
};
exports.UsageService = UsageService;
exports.UsageService = UsageService = UsageService_1 = __decorate([
(0, common_1.Injectable)(),
__param(0, (0, typeorm_1.InjectRepository)(usage_entity_1.UsageLog)),
__metadata("design:paramtypes", [typeof (_a = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _a : Object, redis_service_1.RedisService])
], UsageService);
//# sourceMappingURL=usage.service.js.map