import { BadRequestException, Controller, Get, Header, NotFoundException, Param, Res, } from '@nestjs/common'; import type { Response } from 'express'; import { createReadStream, existsSync } from 'fs'; import { join } from 'path'; const DATA_ROOT = process.env.DATA_ROOT || join(__dirname, '..', '..', 'data'); // Allow-list of known filenames — guards against path traversal. const ALLOWED = new Set([ 'index.json', 'character-xp.json', 'spec-combat.json', 'spec-crafting.json', 'spec-exploration.json', 'spec-gathering.json', 'spec-sabotage.json', 'faction-atreides.json', 'faction-harkonnen.json', 'skills-benegesserit.json', 'skills-mentat.json', 'skills-planetologist.json', 'skills-swordmaster.json', 'skills-trooper.json', ]); @Controller('data') export class DataController { @Get(':file') @Header('Content-Type', 'application/json; charset=utf-8') // These files change with the data extractor and schema. A 3600s cache // bit us once — a browser held an older shape after a schema migration // and crashed the UI. Force revalidation per request. @Header('Cache-Control', 'no-cache, must-revalidate') one(@Param('file') file: string, @Res() res: Response) { if (!ALLOWED.has(file)) { throw new BadRequestException('unknown data file'); } const path = join(DATA_ROOT, file); if (!existsSync(path)) { throw new NotFoundException(); } createReadStream(path).pipe(res); } }