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:
98
backend-nest/src/modules/players/players.service.ts
Normal file
98
backend-nest/src/modules/players/players.service.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { PlayerAction } from '../../entities/player-action.entity';
|
||||
import { NatsService } from '../../services/nats.service';
|
||||
import { PlayerActionDto } from './dto/player-action.dto';
|
||||
|
||||
export interface Player {
|
||||
steam_id: string;
|
||||
player_name: string;
|
||||
status: 'online' | 'offline' | 'banned';
|
||||
last_seen?: Date;
|
||||
ban_expires?: Date | null;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class PlayersService {
|
||||
constructor(
|
||||
@InjectRepository(PlayerAction)
|
||||
private readonly actionRepo: Repository<PlayerAction>,
|
||||
private readonly natsService: NatsService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get recent players for a license
|
||||
*
|
||||
* TODO: This needs a player_sessions table to track online/offline status.
|
||||
* For now, we query player_actions to get a list of players who have had actions.
|
||||
*/
|
||||
async getPlayers(licenseId: string): Promise<{ players: Player[] }> {
|
||||
const actions = await this.actionRepo
|
||||
.createQueryBuilder('action')
|
||||
.where('action.license_id = :licenseId', { licenseId })
|
||||
.orderBy('action.created_at', 'DESC')
|
||||
.take(100)
|
||||
.getMany();
|
||||
|
||||
// Group by steam_id to get unique players
|
||||
const playerMap = new Map<string, Player>();
|
||||
|
||||
for (const action of actions) {
|
||||
if (!playerMap.has(action.steam_id)) {
|
||||
// Determine status based on latest action
|
||||
let status: 'online' | 'offline' | 'banned' = 'offline';
|
||||
if (action.action_type === 'ban') {
|
||||
status = 'banned';
|
||||
}
|
||||
|
||||
playerMap.set(action.steam_id, {
|
||||
steam_id: action.steam_id,
|
||||
player_name: action.player_name,
|
||||
status,
|
||||
last_seen: action.created_at,
|
||||
ban_expires: action.duration_minutes
|
||||
? new Date(action.created_at.getTime() + action.duration_minutes * 60000)
|
||||
: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const players = Array.from(playerMap.values());
|
||||
|
||||
return { players };
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a moderation action on a player
|
||||
*/
|
||||
async performAction(
|
||||
licenseId: string,
|
||||
userId: string,
|
||||
dto: PlayerActionDto,
|
||||
): Promise<{ success: boolean }> {
|
||||
// Insert action record
|
||||
const action = this.actionRepo.create({
|
||||
license_id: licenseId,
|
||||
steam_id: dto.steam_id,
|
||||
player_name: dto.player_name,
|
||||
action_type: dto.action_type,
|
||||
reason: dto.reason || null,
|
||||
duration_minutes: dto.duration_minutes || null,
|
||||
performed_by: userId,
|
||||
});
|
||||
|
||||
await this.actionRepo.save(action);
|
||||
|
||||
// For kick/ban, send NATS command to the server
|
||||
if (dto.action_type === 'kick' || dto.action_type === 'ban') {
|
||||
await this.natsService.sendServerCommand(licenseId, dto.action_type, {
|
||||
steam_id: dto.steam_id,
|
||||
reason: dto.reason,
|
||||
duration_minutes: dto.duration_minutes,
|
||||
});
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user