import { Controller, Get, Post, Put, Body, Param, UseGuards, Req, ForbiddenException, BadRequestException } from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from './guards/jwt-auth.guard'; import { AuthService } from './auth.service'; import { UsersService } from '../users/users.service'; import { OrganizationsService } from '../organizations/organizations.service'; import { AdminAnalyticsService } from './admin-analytics.service'; import * as bcrypt from 'bcryptjs'; @ApiTags('admin') @Controller('admin') @ApiBearerAuth() @UseGuards(JwtAuthGuard) export class AdminController { constructor( private authService: AuthService, private usersService: UsersService, private orgService: OrganizationsService, private analyticsService: AdminAnalyticsService, ) {} private async requireSuperadmin(req: any) { const user = await this.usersService.findById(req.user.userId || req.user.sub); if (!user?.isSuperadmin) { throw new ForbiddenException('SuperUser Admin access required'); } } // ── Platform Metrics ── @Get('metrics') async getPlatformMetrics(@Req() req: any) { await this.requireSuperadmin(req); return this.analyticsService.getPlatformMetrics(); } // ── Users ── @Get('users') async listUsers(@Req() req: any) { await this.requireSuperadmin(req); const users = await this.usersService.findAllUsers(); return users.map(u => ({ id: u.id, email: u.email, firstName: u.firstName, lastName: u.lastName, isSuperadmin: u.isSuperadmin, isPlatformOwner: u.isPlatformOwner || false, lastLoginAt: u.lastLoginAt, createdAt: u.createdAt, organizations: u.userOrganizations?.map(uo => ({ id: uo.organizationId, name: uo.organization?.name, role: uo.role, })) || [], })); } // ── Organizations ── @Get('organizations') async listOrganizations(@Req() req: any) { await this.requireSuperadmin(req); return this.usersService.findAllOrganizations(); } @Get('organizations/:id/detail') async getTenantDetail(@Req() req: any, @Param('id') id: string) { await this.requireSuperadmin(req); const detail = await this.analyticsService.getTenantDetail(id); if (!detail) { throw new BadRequestException('Organization not found'); } return detail; } @Put('organizations/:id/subscription') async updateSubscription( @Req() req: any, @Param('id') id: string, @Body() body: { paymentDate?: string; confirmationNumber?: string; renewalDate?: string }, ) { await this.requireSuperadmin(req); const org = await this.orgService.updateSubscription(id, body); return { success: true, organization: org }; } @Put('organizations/:id/status') async updateOrgStatus( @Req() req: any, @Param('id') id: string, @Body() body: { status: string }, ) { await this.requireSuperadmin(req); const validStatuses = ['active', 'suspended', 'trial', 'archived']; if (!validStatuses.includes(body.status)) { throw new BadRequestException(`Invalid status. Must be one of: ${validStatuses.join(', ')}`); } const org = await this.orgService.updateStatus(id, body.status); return { success: true, organization: org }; } // ── Plan Level ── @Put('organizations/:id/plan') async updateOrgPlan( @Req() req: any, @Param('id') id: string, @Body() body: { planLevel: string }, ) { await this.requireSuperadmin(req); const validPlans = ['standard', 'premium', 'enterprise']; if (!validPlans.includes(body.planLevel)) { throw new BadRequestException(`Invalid plan. Must be one of: ${validPlans.join(', ')}`); } const org = await this.orgService.updatePlanLevel(id, body.planLevel); return { success: true, organization: org }; } // ── Superadmin Toggle ── @Post('users/:id/superadmin') async toggleSuperadmin(@Req() req: any, @Param('id') id: string, @Body() body: { isSuperadmin: boolean }) { await this.requireSuperadmin(req); await this.usersService.setSuperadmin(id, body.isSuperadmin); return { success: true }; } // ── User Impersonation ── @Post('impersonate/:userId') async impersonateUser(@Req() req: any, @Param('userId') userId: string) { await this.requireSuperadmin(req); const adminUserId = req.user.userId || req.user.sub; return this.authService.impersonateUser(adminUserId, userId); } // ── Tenant Health ── @Get('tenants-health') async getTenantsHealth(@Req() req: any) { await this.requireSuperadmin(req); return this.analyticsService.getAllTenantsHealth(); } // ── Create Tenant ── @Post('tenants') async createTenant(@Req() req: any, @Body() body: { orgName: string; email?: string; phone?: string; addressLine1?: string; city?: string; state?: string; zipCode?: string; contractNumber?: string; planLevel?: string; fiscalYearStartMonth?: number; adminEmail: string; adminPassword: string; adminFirstName: string; adminLastName: string; }) { await this.requireSuperadmin(req); if (!body.orgName || !body.adminEmail || !body.adminPassword) { throw new BadRequestException('Organization name, admin email and password are required'); } // Check if admin email already exists const existingUser = await this.usersService.findByEmail(body.adminEmail); let userId: string; if (existingUser) { userId = existingUser.id; } else { // Create the first user for this tenant const passwordHash = await bcrypt.hash(body.adminPassword, 12); const newUser = await this.usersService.create({ email: body.adminEmail, passwordHash, firstName: body.adminFirstName, lastName: body.adminLastName, }); userId = newUser.id; } // Create the organization + tenant schema + membership const org = await this.orgService.create({ name: body.orgName, email: body.email, phone: body.phone, addressLine1: body.addressLine1, city: body.city, state: body.state, zipCode: body.zipCode, contractNumber: body.contractNumber, planLevel: body.planLevel || 'standard', fiscalYearStartMonth: body.fiscalYearStartMonth || 1, }, userId); return { success: true, organization: org }; } }