feat: Complete NestJS backend scaffold — 22 modules, 39 entities, WebSocket gateway
All checks were successful
Test Asgard Runner / test (push) Successful in 3s

Full backend rewrite from Rust/Axum to NestJS/TypeScript.
- 22 feature modules (auth, servers, wipes, maps, plugins, players, console,
  chat, team, notifications, settings, schedules, analytics, alerts, status,
  store, webstore, admin, setup, migration, users, licenses)
- 39 TypeORM entities matching PostgreSQL schema (12 migrations)
- Common infrastructure: JWT/RBAC guards, decorators, exception filter
- NATS service with pub/sub/request-reply
- Socket.IO WebSocket gateway with NATS bridge
- Docker: NestJS Dockerfile + updated docker-compose.yml
- Zero compile errors (npx tsc --noEmit clean)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Vantz Stockwell
2026-02-15 21:29:25 -05:00
parent 0f8d0dd14f
commit d20493d533
141 changed files with 13552 additions and 4 deletions

View File

@@ -0,0 +1,81 @@
import { Controller, Get, Post, Patch, Body, Param, Query, UseGuards } from '@nestjs/common';
import { ApiTags, ApiBearerAuth, ApiOperation, ApiQuery, ApiParam } from '@nestjs/swagger';
import { AdminService } from './admin.service';
import { SuperAdminGuard } from '../../common/guards/super-admin.guard';
@ApiTags('admin')
@ApiBearerAuth()
@UseGuards(SuperAdminGuard)
@Controller('admin')
export class AdminController {
constructor(private readonly adminService: AdminService) {}
@Get('stats')
@ApiOperation({ summary: 'Get platform statistics' })
async getStats() {
return this.adminService.getStats();
}
@Get('licenses')
@ApiOperation({ summary: 'Get paginated list of licenses' })
@ApiQuery({ name: 'page', required: false, type: Number })
@ApiQuery({ name: 'limit', required: false, type: Number })
@ApiQuery({ name: 'search', required: false, type: String })
async getLicenses(
@Query('page') page?: string,
@Query('limit') limit?: string,
@Query('search') search?: string,
) {
const p = page ? parseInt(page, 10) : 1;
const l = limit ? parseInt(limit, 10) : 25;
return this.adminService.getLicenses(p, l, search);
}
@Get('licenses/:id')
@ApiOperation({ summary: 'Get license details by ID' })
@ApiParam({ name: 'id', description: 'License ID' })
async getLicenseById(@Param('id') id: string) {
return this.adminService.getLicenseById(id);
}
@Post('licenses')
@ApiOperation({ summary: 'Create a new license' })
async createLicense(@Body('email') email: string) {
return this.adminService.createLicense(email);
}
@Get('users')
@ApiOperation({ summary: 'Get paginated list of users' })
@ApiQuery({ name: 'page', required: false, type: Number })
@ApiQuery({ name: 'limit', required: false, type: Number })
async getUsers(
@Query('page') page?: string,
@Query('limit') limit?: string,
) {
const p = page ? parseInt(page, 10) : 1;
const l = limit ? parseInt(limit, 10) : 25;
return this.adminService.getUsers(p, l);
}
@Patch('users/:id')
@ApiOperation({ summary: 'Update user (admin only)' })
@ApiParam({ name: 'id', description: 'User ID' })
async updateUser(
@Param('id') userId: string,
@Body() data: { is_super_admin?: boolean; email_verified?: boolean },
) {
return this.adminService.updateUser(userId, data);
}
@Get('subscriptions')
@ApiOperation({ summary: 'Get all webstore subscriptions' })
async getSubscriptions() {
return this.adminService.getSubscriptions();
}
@Get('servers')
@ApiOperation({ summary: 'Get all server connections' })
async getServers() {
return this.adminService.getServers();
}
}