This commit is contained in:
Shibly Teknologi Solusi
2026-05-18 15:33:08 +07:00
parent 03022576bd
commit 1c084ecca6
2 changed files with 72 additions and 38 deletions

View File

@ -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<ILogObj> = new Logger({ name: '[FileController]', type: 'pretty' });
const STORAGE_DIR = 'assets/saude/';
export class FileController {
static async upload(req: Request, res: Response, next: NextFunction): Promise<Response> {
@ -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<void | Response> {
/*
#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<string, string> = {
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);
}
}
}

View File

@ -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);