diff --git a/src/controllers/file/file.ts b/src/controllers/file/file.ts index 3a2b9f9..822dd1a 100644 --- a/src/controllers/file/file.ts +++ b/src/controllers/file/file.ts @@ -6,11 +6,11 @@ import { ILogObj, Logger } from "tslog"; import { Language } from "../../langs/lang"; import fileUpload from "express-fileupload"; import { v4 as uuidv4 } from 'uuid'; -import config from "config"; import fs from "fs"; import path from "path"; const log: Logger = new Logger({ name: '[FileController]', type: 'pretty' }); +const STORAGE_DIR = 'assets/saude/'; export class FileController { static async upload(req: Request, res: Response, next: NextFunction): Promise { @@ -29,29 +29,14 @@ export class FileController { format: "binary", description: "Image file (max 1MB, jpg/png/webp)" }, - path: { - type: "string", - format: "text", - description: "Path File" - }, }, - required: ["file", "path"] + required: ["file"] } } } } */ try { - const schema = Joi.object().keys({ - path: Joi.string().required().label('File Path'), - }); - - const param: { path: string } = await schema.validateAsync(req.body); - - if (param.path.includes('..')) { - return ReturnHelper.errorResponse(res, 400, 400, Language.lang.failed_insert, "Invalid path"); - } - // Check if file exists if (!req.files || !req.files.file) { return ReturnHelper.errorResponse(res, 400, 404, Language.lang.failed_insert, "File not found"); @@ -76,12 +61,6 @@ export class FileController { if (!allowedMimes.includes(file.mimetype)) { return ReturnHelper.errorResponse(res, 400, 400, Language.lang.failed_insert, "Invalid file type. Only JPG, PNG, and WEBP are allowed"); } - param.path = param.path[param.path.length - 1] !== '/' ? param.path + '/' : param.path; - - if (!param.path.startsWith('uploads/')) { - param.path = 'uploads/' + param.path.replace(/^\/+/, ''); - } - const ext = file.name.split('.'); const name = uuidv4() + '.' + ext[ext.length - 1]; @@ -89,7 +68,7 @@ export class FileController { file: "", }; - const folder_path = param.path; + const folder_path = STORAGE_DIR; const file_path = path.join(folder_path, name); // Create directory if it doesn't exist @@ -133,10 +112,9 @@ export class FileController { schema: { type: "object", properties: { - file_name: { type: "string", description: "File name to delete" }, - path: { type: "string", description: "Folder path (e.g. patient-photos or uploads/patient-photos/)" } + file_name: { type: "string", description: "File name to delete" } }, - required: ["file_name", "path"] + required: ["file_name"] } } } @@ -146,22 +124,15 @@ export class FileController { try { const schema = Joi.object().keys({ file_name: Joi.string().required().label('File Name'), - path: Joi.string().required().label('File Path'), }); - const param: { file_name: string; path: string } = await schema.validateAsync(req.body); + const param: { file_name: string } = await schema.validateAsync(req.body); - if (param.path.includes('..')) { - return ReturnHelper.errorResponse(res, 400, 400, Language.lang.failed_delete, "Invalid path"); + if (param.file_name.includes('..') || param.file_name.includes('/') || param.file_name.includes('\\')) { + return ReturnHelper.errorResponse(res, 400, 400, Language.lang.failed_delete, "Invalid file name"); } - param.path = param.path[param.path.length - 1] !== '/' ? param.path + '/' : param.path; - - if (!param.path.startsWith('uploads/')) { - param.path = 'uploads/' + param.path.replace(/^\/+/, ''); - } - - const file_path = path.join(param.path, param.file_name); + const file_path = path.join(STORAGE_DIR, param.file_name); // Check if file exists if (!fs.existsSync(file_path)) { @@ -179,4 +150,66 @@ export class FileController { return ReturnHelper.errorResponse(res, 500, 402, Language.lang.failed_delete, err.message); } } + + static async download(req: Request, res: Response, next: NextFunction): Promise { + /* + #swagger.tags = ['Handle File'] + #swagger.security = [{ "bearerAuth": [] }] + #swagger.parameters['name'] = { + in: 'query', + required: true, + type: 'string', + description: 'File name' + } + */ + try { + const schema = Joi.object({ + name: Joi.string().required().label("File Name"), + }); + + const param: { name: string } = await schema.validateAsync(req.query); + + if (param.name.includes('..') || param.name.includes('/') || param.name.includes('\\')) { + return ReturnHelper.errorResponse(res, 400, 400, Language.lang.failed_not_found, "Invalid file name"); + } + + const file_path = path.join(STORAGE_DIR, param.name); + + if (!fs.existsSync(file_path)) { + return ReturnHelper.errorResponse(res, 404, 404, Language.lang.failed_not_found, "File not found"); + } + + const buffer = fs.readFileSync(file_path); + const fileName = param.name; + const ext = fileName.split(".").pop()?.toLowerCase(); + + const mimeMap: Record = { + jpg: "image/jpeg", + jpeg: "image/jpeg", + png: "image/png", + webp: "image/webp", + gif: "image/gif", + pdf: "application/pdf", + }; + + const contentType = mimeMap[ext ?? ""] ?? "application/octet-stream"; + + res.setHeader("Content-Type", contentType); + res.setHeader("Content-Length", buffer.length); + + const isInline = contentType.startsWith("image/") || contentType === "application/pdf"; + + if (isInline) { + res.setHeader("Content-Disposition", `inline; filename="${encodeURIComponent(fileName)}"`); + } else { + res.setHeader("Content-Disposition", `attachment; filename="${encodeURIComponent(fileName)}"`); + } + + return res.end(buffer); + } catch (e: unknown) { + log.error(e); + const err = e as Error; + return ReturnHelper.errorResponse(res, 400, 401, Language.lang.failed_not_found, err.message); + } + } } \ No newline at end of file diff --git a/src/routes/private.ts b/src/routes/private.ts index 0c069bc..013fa87 100644 --- a/src/routes/private.ts +++ b/src/routes/private.ts @@ -87,6 +87,7 @@ export class RoutePrivate { app.post('/api/upload', FileController.upload) app.delete('/api/delete', FileController.delete) + app.get('/api/download', FileController.download) app.get("/api/administrativu/list", AdministrativuController.list); app.get("/api/administrativu/export", AdministrativuController.export);