AI Implementation feature(885): Admin Area General Settings and Categories 1.03 (#25)
This commit was merged in pull request #25.
This commit is contained in:
@@ -4,7 +4,7 @@ import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import sharp from 'sharp';
|
||||
import sharp, { FormatEnum } from 'sharp';
|
||||
import { AdminGuard } from '../../common/guards/admin.guard';
|
||||
import { Roles } from '../../common/decorators/roles.decorator';
|
||||
import { parseUploadSizeLimit, safeFilename } from '../../common/utils/upload';
|
||||
@@ -50,6 +50,21 @@ export class UploadsController {
|
||||
return this.handleLogoUpload(req);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allowed image formats for the site logo. Detection is performed by
|
||||
* decoding the full payload with sharp — never by trusting the
|
||||
* multipart `mimetype` header, the original filename extension, or
|
||||
* the browser's `accept="image/*"` filter.
|
||||
*/
|
||||
private static readonly ALLOWED_LOGO_FORMATS: ReadonlySet<keyof FormatEnum> = new Set<keyof FormatEnum>([
|
||||
'png',
|
||||
'jpeg',
|
||||
'gif',
|
||||
'webp',
|
||||
]);
|
||||
private static readonly LOGO_ERROR_MESSAGE =
|
||||
'Logo must be a valid PNG, JPEG, GIF, or WebP image.';
|
||||
|
||||
private async handleCategoryIcon(req: any): Promise<{ id: string; publicUrl: string; width: number; height: number; mimeType: string; storedPath: string; size: number; originalFilename: string }> {
|
||||
const file = req.file as any;
|
||||
if (!file) throw new BadRequestException('No file uploaded under field "file"');
|
||||
@@ -120,12 +135,22 @@ export class UploadsController {
|
||||
};
|
||||
}
|
||||
|
||||
private handleLogoUpload(req: any): { publicUrl: string; originalFilename: string } {
|
||||
private async handleLogoUpload(req: any): Promise<{ publicUrl: string; originalFilename: string }> {
|
||||
const file = req.file as any;
|
||||
if (!file) throw new BadRequestException('No file uploaded under field "file"');
|
||||
if (file.size > this.globalLimit) {
|
||||
throw new BadRequestException(`File exceeds UPLOAD_SIZE_LIMIT (${file.size} > ${this.globalLimit})`);
|
||||
}
|
||||
let metadata: { format?: keyof FormatEnum; width?: number; height?: number };
|
||||
try {
|
||||
metadata = await sharp(file.buffer).metadata();
|
||||
} catch {
|
||||
throw new BadRequestException(UploadsController.LOGO_ERROR_MESSAGE);
|
||||
}
|
||||
const fmt = metadata.format;
|
||||
if (!fmt || !UploadsController.ALLOWED_LOGO_FORMATS.has(fmt)) {
|
||||
throw new BadRequestException(UploadsController.LOGO_ERROR_MESSAGE);
|
||||
}
|
||||
const safeName = safeFilename(file.originalname || 'logo');
|
||||
// Files live at the top level of UPLOAD_DIR (served directly at /uploads/...).
|
||||
const finalPath = path.join(this.uploadDir, safeName);
|
||||
|
||||
Reference in New Issue
Block a user