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,130 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { WipeProfile } from '../../entities/wipe-profile.entity';
import { WipeSchedule } from '../../entities/wipe-schedule.entity';
import { WipeHistory } from '../../entities/wipe-history.entity';
import { CreateProfileDto } from './dto/create-profile.dto';
import { UpdateProfileDto } from './dto/update-profile.dto';
import { CreateScheduleDto } from './dto/create-schedule.dto';
import { TriggerWipeDto } from './dto/trigger-wipe.dto';
@Injectable()
export class WipesService {
constructor(
@InjectRepository(WipeProfile)
private readonly wipeProfileRepo: Repository<WipeProfile>,
@InjectRepository(WipeSchedule)
private readonly wipeScheduleRepo: Repository<WipeSchedule>,
@InjectRepository(WipeHistory)
private readonly wipeHistoryRepo: Repository<WipeHistory>,
) {}
async getProfiles(licenseId: string): Promise<WipeProfile[]> {
return this.wipeProfileRepo.find({
where: { license_id: licenseId },
order: { created_at: 'DESC' },
});
}
async createProfile(licenseId: string, dto: CreateProfileDto): Promise<WipeProfile> {
const profile = this.wipeProfileRepo.create({
license_id: licenseId,
...dto,
});
return this.wipeProfileRepo.save(profile);
}
async updateProfile(
licenseId: string,
profileId: string,
dto: UpdateProfileDto,
): Promise<WipeProfile> {
const profile = await this.wipeProfileRepo.findOne({
where: { id: profileId, license_id: licenseId },
});
if (!profile) {
throw new NotFoundException(`Wipe profile ${profileId} not found`);
}
Object.assign(profile, dto);
profile.updated_at = new Date();
return this.wipeProfileRepo.save(profile);
}
async deleteProfile(licenseId: string, profileId: string): Promise<void> {
const result = await this.wipeProfileRepo.delete({
id: profileId,
license_id: licenseId,
});
if (result.affected === 0) {
throw new NotFoundException(`Wipe profile ${profileId} not found`);
}
}
async getSchedules(licenseId: string): Promise<WipeSchedule[]> {
return this.wipeScheduleRepo.find({
where: { license_id: licenseId },
relations: ['wipe_profile'],
order: { created_at: 'DESC' },
});
}
async createSchedule(licenseId: string, dto: CreateScheduleDto): Promise<WipeSchedule> {
const schedule = this.wipeScheduleRepo.create({
license_id: licenseId,
...dto,
});
return this.wipeScheduleRepo.save(schedule);
}
async getHistory(licenseId: string, limit: number = 50): Promise<WipeHistory[]> {
return this.wipeHistoryRepo.find({
where: { license_id: licenseId },
relations: ['wipe_profile', 'wipe_schedule', 'map'],
order: { created_at: 'DESC' },
take: limit,
});
}
async triggerWipe(
licenseId: string,
dto: TriggerWipeDto,
): Promise<{ wipe_history_id: string }> {
const history = this.wipeHistoryRepo.create({
license_id: licenseId,
wipe_type: dto.wipe_type,
wipe_profile_id: dto.wipe_profile_id,
trigger_type: 'manual',
status: 'pending',
});
const saved = await this.wipeHistoryRepo.save(history);
return { wipe_history_id: saved.id };
}
async triggerDryRun(
licenseId: string,
dto: TriggerWipeDto,
): Promise<{
would_delete: string[];
would_preserve: string[];
estimated_duration_seconds: number;
}> {
// Stub implementation - real logic would analyze wipe profile config
const mockResult = {
would_delete: ['*.sav', '*.db', 'player.deaths.db', 'player.identities.db'],
would_preserve: ['oxide/', 'oxide/plugins/', 'oxide/data/', 'backups/'],
estimated_duration_seconds: 45,
};
if (dto.wipe_type === 'full') {
mockResult.would_delete.push('oxide/data/*');
mockResult.estimated_duration_seconds = 120;
}
return mockResult;
}
}