fix: Wire real data sources across players, analytics, status, and maps services
All checks were successful
Test Asgard Runner / test (push) Successful in 3s
All checks were successful
Test Asgard Runner / test (push) Successful in 3s
- players: Primary data from player_sessions (online status, playtime aggregates);
ban/unban status overlaid from player_actions latest action per steam_id.
Register PlayerSession entity in PlayersModule. Extend NATS forwarding to
include 'unban' alongside kick and ban.
- analytics: Fix retention period boundary bug — sessions were queried with only
a lower-bound filter (MoreThan), causing all future cycles to bleed into earlier
wipe windows. Replaced with Between(wipeDate, endDate) for correct isolation.
- status: Replace hardcoded player_count=0/max_players=0 with live data from
most-recent server_stats row per license. Register ServerStats entity in
StatusModule. Falls back to 0 gracefully when no stats exist yet.
- maps: File buffer was computed and discarded — never written to disk.
Now writes to /app/map_data/{licenseId}/{timestamp}_{filename} (tenant-isolated,
docker volume map_data). Creates directories with mkdirSync(recursive:true).
Logs success/failure via NestJS Logger. Throws 500 on disk write failure
instead of silently losing data.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,21 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { Injectable, NotFoundException, InternalServerErrorException, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { createHash } from 'crypto';
|
||||
import { mkdirSync, writeFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { MapLibrary } from '../../entities/map-library.entity';
|
||||
import { MapRotation } from '../../entities/map-rotation.entity';
|
||||
import { UpdateRotationDto } from './dto/update-rotation.dto';
|
||||
import { UploadMapDto } from './dto/upload-map.dto';
|
||||
|
||||
// Docker volume mount point for map storage. Tenant-scoped subdirectory enforces isolation.
|
||||
const MAP_DATA_ROOT = process.env.MAP_DATA_PATH || '/app/map_data';
|
||||
|
||||
@Injectable()
|
||||
export class MapsService {
|
||||
private readonly logger = new Logger(MapsService.name);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(MapLibrary)
|
||||
private readonly mapLibraryRepo: Repository<MapLibrary>,
|
||||
@@ -22,7 +29,23 @@ export class MapsService {
|
||||
file: Express.Multer.File,
|
||||
): Promise<MapLibrary> {
|
||||
const checksum = createHash('sha256').update(file.buffer).digest('hex');
|
||||
const storagePath = `/maps/${licenseId}/${Date.now()}_${file.originalname}`;
|
||||
|
||||
// Build tenant-scoped storage path: /app/map_data/{licenseId}/{timestamp}_{filename}
|
||||
const filename = `${Date.now()}_${file.originalname}`;
|
||||
const tenantDir = join(MAP_DATA_ROOT, licenseId);
|
||||
const absolutePath = join(tenantDir, filename);
|
||||
|
||||
// Relative storage path stored in DB — avoids coupling to the absolute mount point
|
||||
const storagePath = `/map_data/${licenseId}/${filename}`;
|
||||
|
||||
try {
|
||||
mkdirSync(tenantDir, { recursive: true });
|
||||
writeFileSync(absolutePath, file.buffer);
|
||||
this.logger.log(`Map uploaded: ${absolutePath} (${file.size} bytes)`);
|
||||
} catch (err) {
|
||||
this.logger.error(`Failed to write map file to disk: ${absolutePath}`, err);
|
||||
throw new InternalServerErrorException('Failed to save map file to storage');
|
||||
}
|
||||
|
||||
const map = this.mapLibraryRepo.create({
|
||||
license_id: licenseId,
|
||||
|
||||
Reference in New Issue
Block a user