File: /var/www/api-storage/src/modules/storage/api/controllers/file.controller.ts
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import {
Body,
Controller,
Delete,
Get,
HttpCode,
HttpStatus,
Param,
Post,
Res,
UploadedFile,
UploadedFiles,
UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor, FilesInterceptor } from '@nestjs/platform-express';
import { Response } from 'express';
import { UploadFileUseCase } from '../../application/use-cases/upload-file.usecase';
import { UploadFilesUseCase } from '../../application/use-cases/upload-files.usecase';
import { ResponseEntity } from 'src/shared/core/entities/reponse.entity';
import { FileEntity } from '../../core/entities/file.entity';
import { GetFileUseCase } from '../../application/use-cases/get-file.usecase';
import * as mime from 'mime-types';
import { DeleteFileUseCase } from '../../application/use-cases/delete-file.usecase';
@Controller('files')
export class FileController {
constructor(
private readonly uploadFileUseCase: UploadFileUseCase,
private readonly uploadFilesUseCase: UploadFilesUseCase,
private readonly getFileUseCase: GetFileUseCase,
private readonly deleteFileUseCase: DeleteFileUseCase,
) {}
@Post('upload')
@UseInterceptors(FileInterceptor('file'))
async upload(
@UploadedFile() file: Express.Multer.File,
@Body() body: { bucketPath: string },
): Promise<ResponseEntity<FileEntity>> {
return this.uploadFileUseCase.execute(file, body.bucketPath);
}
@Post('upload-multiple')
@UseInterceptors(FilesInterceptor('files'))
async uploadMultiple(
@UploadedFiles() files: Express.Multer.File[],
@Body() body: { bucketPath: string },
): Promise<ResponseEntity<FileEntity[]>> {
return this.uploadFilesUseCase.execute(files, body.bucketPath);
}
@Get(':bucket/*path')
async getFile(
@Param('bucket') bucket: string,
@Param('path') fullPath: string,
@Res() res: Response,
) {
const decodedPath = decodeURIComponent(fullPath);
const fileStream = await this.getFileUseCase.execute(bucket, decodedPath);
// Obtener el tipo MIME basado en la extensión del archivo
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
const mimeType = mime.lookup(decodedPath) ?? 'application/octet-stream';
// Configurar headers según el tipo de archivo
res.setHeader('Content-Type', mimeType);
// Decidir si mostrar inline o forzar descarga
const disposition = this.shouldDisplayInline(mimeType)
? 'inline'
: 'attachment';
res.setHeader(
'Content-Disposition',
`${disposition}; filename="${decodedPath}"`,
);
fileStream.pipe(res);
}
private shouldDisplayInline(mimeType: string): boolean {
// Lista de tipos que pueden mostrarse directamente en el navegador
const inlineTypes = [
'image/',
'application/pdf',
'text/',
'application/json',
'video/',
'audio/',
];
return inlineTypes.some((type) => mimeType.includes(type));
}
@Delete(':bucket/*path')
@HttpCode(HttpStatus.NO_CONTENT)
async deleteFile(
@Param('bucket') bucket: string,
@Param('path') fullPath: string,
): Promise<void> {
const decodedPath = decodeURIComponent(fullPath);
await this.deleteFileUseCase.execute(bucket, decodedPath);
}
}