import { Injectable, BadRequestException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, Like } from 'typeorm'; import { User } from '../../entities/user.entity'; import { License } from '../../entities/license.entity'; import { ServerConnection } from '../../entities/server-connection.entity'; import { WebstoreSubscription } from '../../entities/webstore-subscription.entity'; import * as crypto from 'crypto'; import * as argon2 from 'argon2'; @Injectable() export class AdminService { constructor( @InjectRepository(User) private readonly userRepo: Repository, @InjectRepository(License) private readonly licenseRepo: Repository, @InjectRepository(ServerConnection) private readonly serverConnectionRepo: Repository, @InjectRepository(WebstoreSubscription) private readonly webstoreSubRepo: Repository, ) {} async getStats() { const [totalUsers, totalLicenses, activeServers] = await Promise.all([ this.userRepo.count(), this.licenseRepo.count(), this.serverConnectionRepo.count({ where: { connection_status: 'connected' }, }), ]); return { total_users: totalUsers, total_licenses: totalLicenses, active_servers: activeServers, }; } async getLicenses(page: number = 1, limit: number = 25, search?: string) { const skip = (page - 1) * limit; const queryBuilder = this.licenseRepo .createQueryBuilder('license') .leftJoinAndSelect('license.owner', 'owner') .orderBy('license.created_at', 'DESC') .skip(skip) .take(limit); if (search) { queryBuilder.where( '(license.license_key ILIKE :search OR license.server_name ILIKE :search OR license.subdomain ILIKE :search OR owner.email ILIKE :search)', { search: `%${search}%` }, ); } const [licenses, total] = await queryBuilder.getManyAndCount(); return { data: licenses.map(l => ({ id: l.id, license_key: l.license_key, owner_email: l.owner?.email ?? '', server_name: l.server_name, status: l.status, created_at: l.created_at, expires_at: l.expires_at, })), total, }; } async getLicenseById(id: string) { return this.licenseRepo.findOne({ where: { id }, relations: ['owner'], }); } async createLicense(email: string) { // Find or create user let user = await this.userRepo.findOne({ where: { email } }); if (!user) { // Create new user with random password const randomPassword = crypto.randomBytes(16).toString('hex'); const passwordHash = await argon2.hash(randomPassword); const username = email.split('@')[0] + '_' + Math.random().toString(36).substr(2, 5); user = this.userRepo.create({ email, username, password_hash: passwordHash, }); await this.userRepo.save(user); } // Create license (branded CORR-XXXX-XXXX-XXXX format) const part1 = crypto.randomBytes(2).toString('hex').toUpperCase(); const part2 = crypto.randomBytes(2).toString('hex').toUpperCase(); const part3 = crypto.randomBytes(2).toString('hex').toUpperCase(); const licenseKey = `CORR-${part1}-${part2}-${part3}`; const license = this.licenseRepo.create({ license_key: licenseKey, owner_user_id: user.id, status: 'active', }); return this.licenseRepo.save(license); } async getUsers(page: number = 1, limit: number = 25) { const skip = (page - 1) * limit; const [users, total] = await this.userRepo.findAndCount({ order: { created_at: 'DESC' }, skip, take: limit, }); return { data: users.map(u => ({ id: u.id, email: u.email, username: u.username, is_super_admin: u.is_super_admin, email_verified: u.email_verified, totp_enabled: u.totp_enabled, created_at: u.created_at, last_login_at: u.last_login_at, })), pagination: { page, limit, total, total_pages: Math.ceil(total / limit), }, }; } async updateUser(userId: string, data: Partial) { const user = await this.userRepo.findOne({ where: { id: userId } }); if (!user) { throw new BadRequestException('User not found'); } // Only allow updating specific fields if (typeof data.is_super_admin !== 'undefined') { user.is_super_admin = data.is_super_admin; } if (typeof data.email_verified !== 'undefined') { user.email_verified = data.email_verified; } return this.userRepo.save(user); } async getSubscriptions() { const subs = await this.webstoreSubRepo.find({ relations: ['license', 'license.owner'], order: { created_at: 'DESC' }, }); return { subscriptions: subs.map(sub => ({ owner_email: sub.license?.owner?.email ?? 'Unknown', module_name: sub.plan_id, license_id: sub.license_id, })), }; } async getServers() { return this.serverConnectionRepo .createQueryBuilder('conn') .leftJoinAndSelect('conn.license', 'license') .leftJoinAndSelect('license.owner', 'owner') .orderBy('conn.created_at', 'DESC') .getMany(); } }