Refactor file handling to improve URL safety and enhance file viewing capabilities across components

This commit is contained in:
Marshal
2026-06-28 08:19:21 +00:00
parent b424de8436
commit da533a488d
11 changed files with 100 additions and 28 deletions

View File

@@ -12,6 +12,20 @@ export interface CreateFileInput {
file: Express.Multer.File;
}
/**
* Make a filename safe to use as a MinIO object-key segment: collapse runs of
* spaces/unsafe characters to a single underscore while keeping the dot before
* the extension. Prevents percent-encoding mismatches between the stored URL
* and the actual object key.
*/
function sanitizeObjectName(name: string): string {
return name
.normalize("NFKD")
.replace(/[^\w.\-]+/g, "_")
.replace(/_{2,}/g, "_")
.replace(/^_+|_+$/g, "");
}
@Injectable()
export class FilesService {
constructor(
@@ -21,7 +35,12 @@ export class FilesService {
async upload(input: CreateFileInput): Promise<FileRecord> {
const { resourceId, resource, code, file } = input;
const objectName = `${resource}/${resourceId}/${Date.now()}_${file.originalname}`;
// Keep the object key URL-safe so it survives the round-trip through the
// stored URL (spaces/unicode in the original name would otherwise be
// percent-encoded in the URL and no longer match the MinIO key). The
// human-readable name is preserved separately on the record below.
const safeName = sanitizeObjectName(file.originalname);
const objectName = `${resource}/${resourceId}/${Date.now()}_${safeName}`;
const url = await this.minioService.uploadFile(objectName, file.buffer, file.mimetype);
return this.filesRepository.create({

View File

@@ -65,7 +65,14 @@ export class MinioService {
}
const url = new URL(trimmed);
const parts = url.pathname.split("/").filter(Boolean);
// `url.pathname` percent-encodes the object key (e.g. a space becomes
// "%20"), but MinIO stores the key with its literal characters. Decode each
// segment so the recovered key matches what was uploaded — otherwise a file
// whose name had spaces/unicode 404s with "specified key does not exist".
const parts = url.pathname
.split("/")
.filter(Boolean)
.map((segment) => decodeURIComponent(segment));
if (parts[0] === this.bucket) {
parts.shift();
}