AI Implementation feature(820): Project Scaffold and Platform Foundations (#1)

This commit was merged in pull request #1.
This commit is contained in:
2026-07-21 14:23:53 +00:00
parent e55c9cda56
commit 03bcb6b156
131 changed files with 23657 additions and 0 deletions
+75
View File
@@ -0,0 +1,75 @@
import { ConfigService } from '@nestjs/config';
import * as path from 'path';
import * as fs from 'fs';
import * as crypto from 'crypto';
import multer from 'multer';
export interface UploadLimits {
fileSize: number;
}
/**
* Build a multer-style `limits` object from the UPLOAD_SIZE_LIMIT env value.
* Examples: '50mb', '500kb', '2gb'. Defaults to '50mb'.
*/
export function parseUploadSizeLimit(value: string | undefined): number {
const v = (value ?? '50mb').trim().toLowerCase();
const m = /^(\d+(?:\.\d+)?)(b|kb|mb|gb)$/.exec(v);
if (!m) return 50 * 1024 * 1024;
const n = parseFloat(m[1]);
const unit = m[2];
const mult = unit === 'b' ? 1 : unit === 'kb' ? 1024 : unit === 'mb' ? 1024 * 1024 : 1024 * 1024 * 1024;
return Math.floor(n * mult);
}
export function buildUploadLimits(config: ConfigService): UploadLimits {
const v = config.get<string>('UPLOAD_SIZE_LIMIT', '50mb');
return { fileSize: parseUploadSizeLimit(v) };
}
/**
* Produce a safe, collision-resistant filename from the original upload
* filename. Strips directory traversal, lowercases, restricts to a safe
* charset, caps the stem length, and appends a short random suffix so
* identical names from different uploads never overwrite each other.
*/
export function safeFilename(original: string): string {
const base = path.basename(original || 'file').toLowerCase();
const cleaned = base
.replace(/[^a-z0-9._-]+/g, '-')
.replace(/^-+|-+$/g, '')
.slice(0, 120);
const stem = cleaned || 'file';
const dot = stem.lastIndexOf('.');
const head = dot > 0 ? stem.slice(0, dot) : stem;
const ext = dot > 0 ? stem.slice(dot) : '';
const suffix = crypto.randomBytes(4).toString('hex');
const safeHead = (head || 'file').slice(0, 100);
return `${safeHead}-${suffix}${ext}`;
}
/**
* Build a configured Multer instance with a per-route `fileSize` limit
* (taken from `UPLOAD_SIZE_LIMIT`) and safe-filename storage at the
* supplied destination directory.
*/
export interface MulterOptions {
destination?: string;
fieldName?: string;
fileSize?: number;
}
export function buildMulter(config: ConfigService, opts: MulterOptions = {}): multer.Multer {
const limits = opts.fileSize !== undefined
? { fileSize: opts.fileSize }
: buildUploadLimits(config);
const dest = opts.destination ?? path.resolve(config.get<string>('UPLOAD_DIR', '/data/hipctf/uploads'));
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
return multer({
storage: multer.diskStorage({
destination: (_req, _file, cb) => cb(null, dest),
filename: (_req, file, cb) => cb(null, safeFilename(file.originalname)),
}),
limits,
});
}