feat: Complete NestJS backend scaffold — 22 modules, 39 entities, WebSocket gateway
All checks were successful
Test Asgard Runner / test (push) Successful in 3s
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:
131
backend-nest/src/modules/notifications/dto/update-config.dto.ts
Normal file
131
backend-nest/src/modules/notifications/dto/update-config.dto.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import { IsBoolean, IsString, IsOptional, IsUrl } from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class UpdateConfigDto {
|
||||
@ApiProperty({
|
||||
description: 'Enable Discord notifications',
|
||||
example: true,
|
||||
required: false,
|
||||
})
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
discord_enabled?: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Discord webhook URL',
|
||||
example: 'https://discord.com/api/webhooks/...',
|
||||
required: false,
|
||||
})
|
||||
@IsUrl()
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
discord_webhook_url?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Enable email notifications',
|
||||
example: true,
|
||||
required: false,
|
||||
})
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
email_enabled?: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Email address for notifications',
|
||||
example: 'admin@example.com',
|
||||
required: false,
|
||||
})
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
email_address?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Enable Pushbullet notifications',
|
||||
example: false,
|
||||
required: false,
|
||||
})
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
pushbullet_enabled?: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Pushbullet API key',
|
||||
example: 'o.xxxxxxxxxxxxx',
|
||||
required: false,
|
||||
})
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
pushbullet_api_key?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Notify on server start',
|
||||
example: true,
|
||||
required: false,
|
||||
})
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
notify_on_start?: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Notify on server stop',
|
||||
example: true,
|
||||
required: false,
|
||||
})
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
notify_on_stop?: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Notify on server crash',
|
||||
example: true,
|
||||
required: false,
|
||||
})
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
notify_on_crash?: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Notify on wipe start',
|
||||
example: true,
|
||||
required: false,
|
||||
})
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
notify_on_wipe_start?: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Notify on wipe complete',
|
||||
example: true,
|
||||
required: false,
|
||||
})
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
notify_on_wipe_complete?: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Notify on wipe failure',
|
||||
example: true,
|
||||
required: false,
|
||||
})
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
notify_on_wipe_failure?: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Notify on player count threshold',
|
||||
example: false,
|
||||
required: false,
|
||||
})
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
notify_on_player_threshold?: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Player count threshold',
|
||||
example: '100',
|
||||
required: false,
|
||||
})
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
player_threshold?: string;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Controller, Get, Put, Body, UseGuards } from '@nestjs/common';
|
||||
import {
|
||||
ApiTags,
|
||||
ApiBearerAuth,
|
||||
ApiOperation,
|
||||
ApiResponse,
|
||||
} from '@nestjs/swagger';
|
||||
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
|
||||
import { CurrentTenant } from '../../common/decorators/current-tenant.decorator';
|
||||
import { NotificationsService } from './notifications.service';
|
||||
import { UpdateConfigDto } from './dto/update-config.dto';
|
||||
|
||||
@ApiTags('notifications')
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Controller('notifications')
|
||||
export class NotificationsController {
|
||||
constructor(private readonly notificationsService: NotificationsService) {}
|
||||
|
||||
@Get('config')
|
||||
@ApiOperation({
|
||||
summary: 'Get notification configuration',
|
||||
description: 'Returns notification settings for this license',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Notification config retrieved successfully',
|
||||
})
|
||||
async getConfig(@CurrentTenant() licenseId: string) {
|
||||
return await this.notificationsService.getConfig(licenseId);
|
||||
}
|
||||
|
||||
@Put('config')
|
||||
@ApiOperation({
|
||||
summary: 'Update notification configuration',
|
||||
description: 'Update notification settings for this license',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Notification config updated successfully',
|
||||
})
|
||||
async updateConfig(
|
||||
@CurrentTenant() licenseId: string,
|
||||
@Body() dto: UpdateConfigDto,
|
||||
) {
|
||||
return await this.notificationsService.updateConfig(licenseId, dto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { NotificationsController } from './notifications.controller';
|
||||
import { NotificationsService } from './notifications.service';
|
||||
import { NotificationsConfig } from '../../entities/notifications-config.entity';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([NotificationsConfig])],
|
||||
controllers: [NotificationsController],
|
||||
providers: [NotificationsService],
|
||||
exports: [NotificationsService],
|
||||
})
|
||||
export class NotificationsModule {}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { NotificationsConfig } from '../../entities/notifications-config.entity';
|
||||
import { UpdateConfigDto } from './dto/update-config.dto';
|
||||
|
||||
@Injectable()
|
||||
export class NotificationsService {
|
||||
constructor(
|
||||
@InjectRepository(NotificationsConfig)
|
||||
private configRepository: Repository<NotificationsConfig>,
|
||||
) {}
|
||||
|
||||
async getConfig(licenseId: string): Promise<NotificationsConfig> {
|
||||
let config = await this.configRepository.findOne({
|
||||
where: { license_id: licenseId },
|
||||
});
|
||||
|
||||
// Create default config if not exists
|
||||
if (!config) {
|
||||
config = this.configRepository.create({
|
||||
license_id: licenseId,
|
||||
discord_enabled: false,
|
||||
discord_webhook_url: null,
|
||||
email_enabled: false,
|
||||
email_address: null,
|
||||
pushbullet_enabled: false,
|
||||
pushbullet_api_key: null,
|
||||
notify_on_start: true,
|
||||
notify_on_stop: true,
|
||||
notify_on_crash: true,
|
||||
notify_on_wipe_start: true,
|
||||
notify_on_wipe_complete: true,
|
||||
notify_on_wipe_failure: true,
|
||||
notify_on_player_threshold: false,
|
||||
player_threshold: null,
|
||||
});
|
||||
|
||||
config = await this.configRepository.save(config);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
async updateConfig(
|
||||
licenseId: string,
|
||||
dto: UpdateConfigDto,
|
||||
): Promise<NotificationsConfig> {
|
||||
// Ensure config exists first
|
||||
let config = await this.getConfig(licenseId);
|
||||
|
||||
// Update fields
|
||||
Object.assign(config, dto);
|
||||
|
||||
return await this.configRepository.save(config);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user