update
This commit is contained in:
182
src/controllers/file/file.ts
Normal file
182
src/controllers/file/file.ts
Normal file
@ -0,0 +1,182 @@
|
||||
import { Response, NextFunction } from "express";
|
||||
import { Request } from "express-jwt";
|
||||
import { ReturnHelper } from "../../helpers/express/return";
|
||||
import Joi from "joi";
|
||||
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' });
|
||||
|
||||
export class FileController {
|
||||
static async upload(req: Request, res: Response, next: NextFunction): Promise<Response> {
|
||||
/* #swagger.tags = ['Handle File']
|
||||
#swagger.security = [{ "bearerAuth": [] }]
|
||||
#swagger.consumes = ['multipart/form-data']
|
||||
#swagger.requestBody = {
|
||||
required: true,
|
||||
content: {
|
||||
"multipart/form-data": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
file: {
|
||||
type: "string",
|
||||
format: "binary",
|
||||
description: "Image file (max 1MB, jpg/png/webp)"
|
||||
},
|
||||
path: {
|
||||
type: "string",
|
||||
format: "text",
|
||||
description: "Path File"
|
||||
},
|
||||
},
|
||||
required: ["file", "path"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
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");
|
||||
}
|
||||
|
||||
const file = req.files.file as fileUpload.UploadedFile;
|
||||
|
||||
// Validate file size (max 1MB)
|
||||
const maxSize = 10 * 1024 * 1024; // 10MB
|
||||
if (file.size > maxSize) {
|
||||
return ReturnHelper.errorResponse(
|
||||
res,
|
||||
400,
|
||||
400,
|
||||
Language.lang.failed_insert,
|
||||
"File size exceeds 10MB limit"
|
||||
);
|
||||
}
|
||||
|
||||
// Validate file type
|
||||
const allowedMimes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp'];
|
||||
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];
|
||||
|
||||
const result: { file: string } = {
|
||||
file: "",
|
||||
};
|
||||
|
||||
const folder_path = param.path;
|
||||
const file_path = path.join(folder_path, name);
|
||||
|
||||
// Create directory if it doesn't exist
|
||||
if (!fs.existsSync(folder_path)) {
|
||||
fs.mkdirSync(folder_path, { recursive: true });
|
||||
}
|
||||
|
||||
// Move file to destination
|
||||
await file.mv(file_path);
|
||||
|
||||
result.file = name;
|
||||
|
||||
log.info(`File uploaded successfully: ${file_path}`);
|
||||
|
||||
return ReturnHelper.successResponseAny(res, 200, Language.lang.success_insert, result);
|
||||
|
||||
} catch (e: unknown) {
|
||||
// Proper error logging to avoid tslog serialization issues
|
||||
if (e instanceof Error) {
|
||||
log.error("File upload failed:", {
|
||||
message: e.message,
|
||||
stack: e.stack,
|
||||
name: e.name
|
||||
});
|
||||
return ReturnHelper.errorResponse(res, 500, 500, Language.lang.failed_insert, e.message);
|
||||
} else {
|
||||
log.error("File upload failed with unknown error:", String(e));
|
||||
return ReturnHelper.errorResponse(res, 500, 500, Language.lang.failed_insert, "Unknown error occurred");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static async delete(req: Request, res: Response, next: NextFunction): Promise<Response> {
|
||||
/*
|
||||
#swagger.tags = ['Handle File']
|
||||
#swagger.security = [{ "bearerAuth": [] }]
|
||||
#swagger.requestBody = {
|
||||
required: true,
|
||||
content: {
|
||||
"application/json": {
|
||||
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/)" }
|
||||
},
|
||||
required: ["file_name", "path"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
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);
|
||||
|
||||
if (param.path.includes('..')) {
|
||||
return ReturnHelper.errorResponse(res, 400, 400, Language.lang.failed_delete, "Invalid path");
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Check if file exists
|
||||
if (!fs.existsSync(file_path)) {
|
||||
return ReturnHelper.errorResponse(res, 404, 404, Language.lang.failed_not_found, "File not found");
|
||||
}
|
||||
|
||||
// Delete file
|
||||
fs.unlinkSync(file_path);
|
||||
|
||||
return ReturnHelper.successResponseAny(res, 200, Language.lang.success_delete, {});
|
||||
} catch (e: unknown) {
|
||||
log.error(e);
|
||||
const err = e as Error;
|
||||
|
||||
return ReturnHelper.errorResponse(res, 500, 402, Language.lang.failed_delete, err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -70,6 +70,7 @@ import { InpatientRoomController } from "../controllers/inpatient_room";
|
||||
import { SurgeryRoomController } from "../controllers/surgery_room";
|
||||
import { SurgeryTypeController } from "../controllers/surgery_type";
|
||||
import { FosterCareHistoryController } from "../controllers/fostercarehistory";
|
||||
import { FileController } from "../controllers/file/file";
|
||||
|
||||
export class RoutePrivate {
|
||||
static setup(app: express.Application) {
|
||||
@ -84,6 +85,9 @@ export class RoutePrivate {
|
||||
app.delete("/api/menu/delete/:id/:hard", MenuController.delete);
|
||||
app.put("/api/menu/restore/:id", MenuController.restore);
|
||||
|
||||
app.post('/api/upload', FileController.upload)
|
||||
app.delete('/api/delete', FileController.delete)
|
||||
|
||||
app.get("/api/administrativu/list", AdministrativuController.list);
|
||||
app.get("/api/administrativu/export", AdministrativuController.export);
|
||||
app.post("/api/administrativu/create", AdministrativuController.create);
|
||||
|
||||
@ -140,29 +140,6 @@ const doc = {
|
||||
$department_id: "uuid-string",
|
||||
$status: "Y",
|
||||
},
|
||||
inpatient_room_bed: {
|
||||
$inpatient_room_id: "34fdda87-9b42-44d3-8cab-2f2032481d42",
|
||||
$number: 1,
|
||||
$status: {
|
||||
"@enum": ["booked", "empty", "progress", "occupied"],
|
||||
},
|
||||
$bed_detail: {
|
||||
$photo_before: "base64 or url string",
|
||||
$photo_after: "base64 or url string",
|
||||
$electrical: true,
|
||||
$telephone: false,
|
||||
$lighting: true,
|
||||
$wall: false,
|
||||
$ceiling: false,
|
||||
$floor: false,
|
||||
$leakage: false,
|
||||
$temperature: false,
|
||||
$electromedical_equipment: false,
|
||||
$available: true,
|
||||
$online_inpatient_referral: false,
|
||||
$cohort: false,
|
||||
},
|
||||
},
|
||||
// pharmacy: {
|
||||
// $room: "Room name",
|
||||
// $code: "R001",
|
||||
|
||||
Reference in New Issue
Block a user