AI Implementation feature(820): Project Scaffold and Platform Foundations (#1)
This commit was merged in pull request #1.
This commit is contained in:
@@ -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,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user