"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); } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SuperadminService = void 0; const common_1 = require("@nestjs/common"); const typeorm_1 = require("@nestjs/typeorm"); const typeorm_2 = require("typeorm"); const tenant_entity_1 = require("../tenants/entities/tenant.entity"); const user_entity_1 = require("../auth/entities/user.entity"); const subscription_entity_1 = require("../billing/entities/subscription.entity"); let SuperadminService = class SuperadminService { constructor(tenantRepository, userRepository, subscriptionRepository) { this.tenantRepository = tenantRepository; this.userRepository = userRepository; this.subscriptionRepository = subscriptionRepository; } async listTenants(query) { const { page = 1, limit = 10, search, status, sortBy = 'created_at', sortOrder = 'DESC' } = query; const where = {}; if (search) { where.name = (0, typeorm_2.ILike)(`%${search}%`); } if (status) { where.status = status; } const [tenants, total] = await this.tenantRepository.findAndCount({ where, order: { [sortBy]: sortOrder }, skip: (page - 1) * limit, take: limit, }); const tenantsWithStats = await Promise.all(tenants.map(async (tenant) => { const userCount = await this.userRepository.count({ where: { tenant_id: tenant.id }, }); const subscription = await this.subscriptionRepository.findOne({ where: { tenant_id: tenant.id }, relations: ['plan'], }); return { ...tenant, userCount, subscription, }; })); return { data: tenantsWithStats, total, page, limit, totalPages: Math.ceil(total / limit), }; } async getTenant(id) { const tenant = await this.tenantRepository.findOne({ where: { id }, }); if (!tenant) { throw new common_1.NotFoundException('Tenant not found'); } const userCount = await this.userRepository.count({ where: { tenant_id: id }, }); const subscription = await this.subscriptionRepository.findOne({ where: { tenant_id: id }, relations: ['plan'], }); return { ...tenant, userCount, subscription, }; } async createTenant(dto) { const existingTenant = await this.tenantRepository.findOne({ where: { slug: dto.slug }, }); if (existingTenant) { throw new common_1.ConflictException('A tenant with this slug already exists'); } const tenant = this.tenantRepository.create({ name: dto.name, slug: dto.slug, domain: dto.domain, logo_url: dto.logo_url, plan_id: dto.plan_id, status: dto.status || 'trial', trial_ends_at: dto.status === 'trial' ? new Date(Date.now() + 14 * 24 * 60 * 60 * 1000) : null, }); return this.tenantRepository.save(tenant); } async updateTenant(id, dto) { const tenant = await this.tenantRepository.findOne({ where: { id }, }); if (!tenant) { throw new common_1.NotFoundException('Tenant not found'); } Object.assign(tenant, dto); return this.tenantRepository.save(tenant); } async updateTenantStatus(id, dto) { const tenant = await this.tenantRepository.findOne({ where: { id }, }); if (!tenant) { throw new common_1.NotFoundException('Tenant not found'); } tenant.status = dto.status; if (dto.reason) { tenant.metadata = { ...tenant.metadata, statusChangeReason: dto.reason, statusChangedAt: new Date().toISOString(), }; } return this.tenantRepository.save(tenant); } async deleteTenant(id) { const tenant = await this.tenantRepository.findOne({ where: { id }, }); if (!tenant) { throw new common_1.NotFoundException('Tenant not found'); } const userCount = await this.userRepository.count({ where: { tenant_id: id }, }); if (userCount > 0) { throw new common_1.BadRequestException('Cannot delete tenant with active users. Please remove all users first or suspend the tenant.'); } await this.tenantRepository.remove(tenant); } async getTenantUsers(tenantId, page = 1, limit = 10) { const tenant = await this.tenantRepository.findOne({ where: { id: tenantId }, }); if (!tenant) { throw new common_1.NotFoundException('Tenant not found'); } const [users, total] = await this.userRepository.findAndCount({ where: { tenant_id: tenantId }, order: { created_at: 'DESC' }, skip: (page - 1) * limit, take: limit, }); return { data: users, total, page, limit, totalPages: Math.ceil(total / limit), }; } async getDashboardStats() { const [totalTenants, activeTenants, trialTenants, suspendedTenants, totalUsers, newTenantsThisMonth,] = await Promise.all([ this.tenantRepository.count(), this.tenantRepository.count({ where: { status: 'active' } }), this.tenantRepository.count({ where: { status: 'trial' } }), this.tenantRepository.count({ where: { status: 'suspended' } }), this.userRepository.count(), this.tenantRepository .createQueryBuilder('tenant') .where('tenant.created_at >= :startOfMonth', { startOfMonth: new Date(new Date().getFullYear(), new Date().getMonth(), 1), }) .getCount(), ]); return { totalTenants, activeTenants, trialTenants, suspendedTenants, totalUsers, newTenantsThisMonth, }; } async getTenantGrowth(months = 12) { const result = []; const now = new Date(); for (let i = months - 1; i >= 0; i--) { const startDate = new Date(now.getFullYear(), now.getMonth() - i, 1); const endDate = new Date(now.getFullYear(), now.getMonth() - i + 1, 0, 23, 59, 59); const count = await this.tenantRepository .createQueryBuilder('tenant') .where('tenant.created_at >= :startDate', { startDate }) .andWhere('tenant.created_at <= :endDate', { endDate }) .getCount(); result.push({ month: startDate.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }), count, }); } return result; } async getUserGrowth(months = 12) { const result = []; const now = new Date(); for (let i = months - 1; i >= 0; i--) { const startDate = new Date(now.getFullYear(), now.getMonth() - i, 1); const endDate = new Date(now.getFullYear(), now.getMonth() - i + 1, 0, 23, 59, 59); const count = await this.userRepository .createQueryBuilder('user') .where('user.created_at >= :startDate', { startDate }) .andWhere('user.created_at <= :endDate', { endDate }) .getCount(); result.push({ month: startDate.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }), count, }); } return result; } async getPlanDistribution() { const subscriptions = await this.subscriptionRepository .createQueryBuilder('sub') .leftJoinAndSelect('sub.plan', 'plan') .where('sub.status = :status', { status: 'active' }) .getMany(); const planCounts = {}; let total = 0; for (const sub of subscriptions) { const planName = sub.plan?.display_name || sub.plan?.name || 'Unknown'; planCounts[planName] = (planCounts[planName] || 0) + 1; total++; } const tenantsWithSubscription = subscriptions.map(s => s.tenant_id); const freeCount = await this.tenantRepository .createQueryBuilder('tenant') .where('tenant.id NOT IN (:...ids)', { ids: tenantsWithSubscription.length > 0 ? tenantsWithSubscription : ['00000000-0000-0000-0000-000000000000'] }) .getCount(); if (freeCount > 0) { planCounts['Free'] = freeCount; total += freeCount; } return Object.entries(planCounts).map(([plan, count]) => ({ plan, count, percentage: total > 0 ? Math.round((count / total) * 100) : 0, })); } async getStatusDistribution() { const statuses = ['active', 'trial', 'suspended', 'canceled']; const total = await this.tenantRepository.count(); const result = await Promise.all(statuses.map(async (status) => { const count = await this.tenantRepository.count({ where: { status } }); return { status: status.charAt(0).toUpperCase() + status.slice(1), count, percentage: total > 0 ? Math.round((count / total) * 100) : 0, }; })); return result; } async getTopTenants(limit = 10) { const tenants = await this.tenantRepository.find({ order: { created_at: 'ASC' }, take: 100, }); const tenantsWithCounts = await Promise.all(tenants.map(async (tenant) => { const userCount = await this.userRepository.count({ where: { tenant_id: tenant.id }, }); const subscription = await this.subscriptionRepository.findOne({ where: { tenant_id: tenant.id }, relations: ['plan'], }); return { id: tenant.id, name: tenant.name, slug: tenant.slug, userCount, status: tenant.status, planName: subscription?.plan?.display_name || 'Free', }; })); return tenantsWithCounts .sort((a, b) => b.userCount - a.userCount) .slice(0, limit); } async getMetricsSummary() { const [tenantGrowth, userGrowth, planDistribution, statusDistribution, topTenants] = await Promise.all([ this.getTenantGrowth(12), this.getUserGrowth(12), this.getPlanDistribution(), this.getStatusDistribution(), this.getTopTenants(10), ]); return { tenantGrowth, userGrowth, planDistribution, statusDistribution, topTenants, }; } }; exports.SuperadminService = SuperadminService; exports.SuperadminService = SuperadminService = __decorate([ (0, common_1.Injectable)(), __param(0, (0, typeorm_1.InjectRepository)(tenant_entity_1.Tenant)), __param(1, (0, typeorm_1.InjectRepository)(user_entity_1.User)), __param(2, (0, typeorm_1.InjectRepository)(subscription_entity_1.Subscription)), __metadata("design:paramtypes", [typeorm_2.Repository, typeorm_2.Repository, typeorm_2.Repository]) ], SuperadminService); //# sourceMappingURL=superadmin.service.js.map