import { HospitalInformation } from "entity"; import { NextFunction, Response } from "express"; import { Request } from "express-jwt"; import Joi from "joi"; import { ILogObj, Logger } from "tslog"; import CommonHelper from "../helpers/common"; import { ReturnHelper } from "../helpers/express/return"; import { Language } from "../langs/lang"; import { OrmHelper } from "../helpers/orm"; import moment from "moment"; import dayjs from 'dayjs'; import fileUpload from "express-fileupload"; import { v4 as uuidv4 } from 'uuid'; import fs from "fs"; import path from "path"; import config from '../../config/stag.json'; const log: Logger = new Logger({ name: "[HospitalInformationController]", type: "pretty", }); const hospitalCreateUpdateSchema = { name: Joi.string().required().label("Hospital Name"), address: Joi.string().required().label("Hospital Address"), phone: Joi.string().required().label("Hospital Phone"), logo: Joi.string().allow(null, "").label("Hospital Logo"), // province: Joi.string().allow("").optional().label("Province Code"), // city: Joi.string().allow("").optional().label("City Code"), // subdistrict: Joi.string().allow("").optional().label("Subdistrict Code"), // ward: Joi.string().allow("").optional().label("Ward Code"), munisipo: Joi.string().allow(null, "").label("Munisipo ID"), administrativu: Joi.string().allow(null, "").label("Administrativu ID"), aldeia: Joi.string().allow(null, "").label("Aldeia ID"), suco: Joi.string().allow(null, "").label("Suco ID"), }; interface FileUploadRequest extends Request { files?: fileUpload.FileArray | null; body: any; } export class HospitalInformationController { static async view(req: Request, res: Response, next: NextFunction): Promise { /* #swagger.tags = ['Hospital Information'] #swagger.security = [{ "bearerAuth": [] }] */ try { const hospital = await OrmHelper.DB.getRepository(HospitalInformation) .createQueryBuilder("HospitalInformation") .leftJoinAndSelect("HospitalInformation.munisipo", "munisipo") .leftJoinAndSelect("HospitalInformation.administrativu", "administrativu") .leftJoinAndSelect("HospitalInformation.aldeia", "aldeia") .leftJoinAndSelect("HospitalInformation.suco", "suco") // .leftJoinAndSelect("HospitalInformation.province", "province") // .leftJoinAndSelect("HospitalInformation.city", "city") // .leftJoinAndSelect("HospitalInformation.subdistrict", "subdistrict") // .leftJoinAndSelect("HospitalInformation.ward", "ward") .getOne(); if (!hospital) return ReturnHelper.errorResponse(res, 404, 404, Language.lang.failed_view, "Hospital not found"); let hospitallogo = ''; if (hospital.logo != null && hospital.logo != undefined && hospital.logo != '') { hospitallogo = `${config.server.host_swagger}uploads/hospital-logo/${hospital.logo}`; } const result = { id: hospital.id, name: hospital.name, address: hospital.address, phone: hospital.phone, logo: hospitallogo, file: hospital.logo, munisipo: hospital.munisipo, administrativu: hospital.administrativu, aldeia: hospital.aldeia, suco: hospital.suco }; return ReturnHelper.successResponseAny(res, 200, Language.lang.success_view, result); } catch (e: unknown) { log.error(e); const err = e as Error; return ReturnHelper.errorResponse(res, 400, 401, Language.lang.failed_view, err.message); } } static async create(req: Request, res: Response, next: NextFunction): Promise { /* #swagger.tags = ['Hospital Information'] #swagger.security = [{ "bearerAuth": [] }] #swagger.requestBody = { required: true, content: { "application/json": { schema: { $ref: "#/components/schemas/hospitalinformation" } } } } */ const queryRunner = OrmHelper.DB.createQueryRunner(); await queryRunner.connect(); await queryRunner.startTransaction(); try { const schema = Joi.object().keys({ ...hospitalCreateUpdateSchema }); let param: any = await schema.validateAsync(req.body); const existing = await OrmHelper.DB.getRepository(HospitalInformation).findOne({ where: {} }); let hospitalinformation: HospitalInformation; if (existing) { // ── UPDATE ───────────────────────────────────────────── hospitalinformation = existing; hospitalinformation.name = param.name; hospitalinformation.address = param.address; hospitalinformation.phone = param.phone; hospitalinformation.logo = param.logo || existing.logo; // ✅ pertahankan logo lama kalau kosong // hospitalinformation.province = param.province; // hospitalinformation.city = param.city; // hospitalinformation.subdistrict = param.subdistrict; // hospitalinformation.ward = param.ward; hospitalinformation.suco = param.suco; hospitalinformation.administrativu = param.administrativu; hospitalinformation.aldeia = param.aldeia; hospitalinformation.munisipo = param.munisipo; hospitalinformation.updated_at = dayjs().toDate(); hospitalinformation.updated_by = req.auth?.data.name; } else { // ── INSERT ───────────────────────────────────────────── hospitalinformation = new HospitalInformation(); hospitalinformation.name = param.name; hospitalinformation.address = param.address; hospitalinformation.phone = param.phone; hospitalinformation.logo = param.logo; // hospitalinformation.province = param.province; // hospitalinformation.city = param.city; // hospitalinformation.subdistrict = param.subdistrict; // hospitalinformation.ward = param.ward; hospitalinformation.suco = param.suco; hospitalinformation.administrativu = param.administrativu; hospitalinformation.aldeia = param.aldeia; hospitalinformation.munisipo = param.munisipo; hospitalinformation.created_at = dayjs().toDate(); hospitalinformation.updated_at = dayjs().toDate(); hospitalinformation.created_by = req.auth?.data.name; hospitalinformation.updated_by = req.auth?.data.name; } await queryRunner.manager.save(hospitalinformation); await queryRunner.commitTransaction(); return ReturnHelper.successResponseAny(res, 200, Language.lang.success_insert, hospitalinformation); } catch (e: unknown) { await queryRunner.rollbackTransaction(); log.error(e); const err = e as Error; return ReturnHelper.errorResponse(res, 400, 401, Language.lang.failed_view, err.message); } finally { await queryRunner.release(); } } static async uploadlogo(req: FileUploadRequest, res: Response, next: NextFunction): Promise { /* #swagger.tags = ['Hospital Information'] #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)" }, }, required: ["file"] } } } } */ try { // Validation schema // const schema = Joi.object().keys({ // path: Joi.string().label('File Path'), // application: Joi.string().label('Application'), // }); log.info("body", req.body); // const param: { path: string, application: string } = await schema.validateAsync(req.body); // 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"); } // Normalize path // param.path = param.path[param.path.length - 1] !== '/' ? param.path + '/' : param.path; // if (param.path == undefined || param.path == null) param.path = 'uploads/hospital-logo/'; // Generate unique filename const ext = file.name.split('.'); const name = uuidv4() + '.' + ext[ext.length - 1]; const result: { file: string } = { // url: "", // path: "", file: "", // mime: file.mimetype, // size: file.size, // md5: file.md5 }; const folder_path = 'uploads/hospital-logo/'; 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.path = param.path[param.path.length - 1] === '/' ? param.path.substring(0, param.path.length - 1) : param.path; result.file = name; // result.url = `${result.path}/${name}`; // Generate URL 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 deletelogo(req: Request, res: Response, next: NextFunction): Promise { /* #swagger.tags = ['Hospital Information'] #swagger.security = [{ "bearerAuth": [] }] #swagger.requestBody = { required: true, content: { "application/json": { schema: { $ref: "#/components/schemas/files" } } } } */ try { const schema = Joi.object().keys({ file_name: Joi.string().required().label('File Name'), }); const param: { file_name: string } = await schema.validateAsync(req.body); // const env = process.env.NODE_ENV + '/' const fixedPath = 'uploads/hospital-logo/'; const file_path = fixedPath + 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); } } }