const util = require("util"); var fs = require('fs'); var multer = require('multer'); const path = require("path"); const { splitSite } = require("../middlewares"); const dbConfig = require("../config/db.config"); const MongoClient = require("mongodb").MongoClient; const mongoClient = new MongoClient(dbConfig.url); const BASE_UPLOAD_DIR = `/www/wwwroot/upload/`; const DRIVER_SIGNATURE_DIR = path.join(BASE_UPLOAD_DIR, 'driver', 'sig'); const GENERAL_UPLOAD_DIR = path.join(BASE_UPLOAD_DIR, 'app-files'); const uploadToMemory = multer({ storage: multer.memoryStorage(), limits: { fileSize: 10 * 1024 * 1024 } }).single("file"); const uploadToMemoryAsync = util.promisify(uploadToMemory); const readdir = util.promisify(fs.readdir); const stat = util.promisify(fs.stat); const isDriverSignatureFileName = (name = '') => { const safeName = `${name || ''}`.trim(); return /^.+_.+_\d{2}_\d{2}$/.test(safeName); }; const getDriverSignatureYear = (req) => { const queryYear = `${req?.query?.year || ''}`.trim(); if (/^\d{4}$/.test(queryYear)) { return queryYear; } return `${new Date().getFullYear()}`; }; const getDriverSignatureSiteYearFolder = (req) => { const year = getDriverSignatureYear(req); const site = splitSite.findSiteNumber(req); return `site${site}_${year}`; }; const resolveSignatureExt = (mimetype = '', originalName = '') => { const safeMimetype = `${mimetype || ''}`.toLowerCase(); if (safeMimetype.includes('png')) return '.png'; if (safeMimetype.includes('jpeg') || safeMimetype.includes('jpg')) return '.jpg'; const ext = path.extname(`${originalName || ''}`.toLowerCase()); if (ext === '.png' || ext === '.jpg' || ext === '.jpeg') return ext; return '.jpg'; }; const findDriverSignatureFilePath = async (fileBaseName = '') => { const safeName = `${fileBaseName || ''}`.trim(); if (!safeName) return null; if (!fs.existsSync(DRIVER_SIGNATURE_DIR)) return null; const yearFolders = await readdir(DRIVER_SIGNATURE_DIR, { withFileTypes: true }); const siteYearDirs = yearFolders .filter((entry) => entry.isDirectory() && /^site\d+_\d{4}$/.test(entry.name)) .map((entry) => path.join(DRIVER_SIGNATURE_DIR, entry.name)); for (const siteYearDir of siteYearDirs) { const candidates = [ path.join(siteYearDir, safeName), path.join(siteYearDir, `${safeName}.jpg`), path.join(siteYearDir, `${safeName}.jpeg`), path.join(siteYearDir, `${safeName}.png`) ]; for (const candidate of candidates) { if (fs.existsSync(candidate)) { return candidate; } } } return null; }; const getGeneralUploadSubDir = (name = '') => { const safeName = `${name || ''}`.trim(); if (safeName.startsWith('center_director_signature_site_')) { return path.join('center', 'signature'); } if (/^[a-f0-9]{24}$/i.test(safeName)) { return path.join('customer', 'avatar'); } return 'misc'; }; const findFileByBaseNameInDir = async (targetDir, fileBaseName = '') => { const safeName = `${fileBaseName || ''}`.trim(); if (!safeName || !fs.existsSync(targetDir)) return null; const candidates = [ path.join(targetDir, safeName), path.join(targetDir, `${safeName}.jpg`), path.join(targetDir, `${safeName}.jpeg`), path.join(targetDir, `${safeName}.png`) ]; for (const candidate of candidates) { if (fs.existsSync(candidate)) { return candidate; } } return null; }; const findGeneralFilePath = async (fileBaseName = '') => { const safeName = `${fileBaseName || ''}`.trim(); if (!safeName) return null; const preferredDir = path.join(GENERAL_UPLOAD_DIR, getGeneralUploadSubDir(safeName)); const preferred = await findFileByBaseNameInDir(preferredDir, safeName); if (preferred) return preferred; if (!fs.existsSync(GENERAL_UPLOAD_DIR)) return null; const firstLevel = await readdir(GENERAL_UPLOAD_DIR, { withFileTypes: true }); for (const entry of firstLevel) { if (!entry.isDirectory()) continue; const firstDir = path.join(GENERAL_UPLOAD_DIR, entry.name); const directMatch = await findFileByBaseNameInDir(firstDir, safeName); if (directMatch) return directMatch; const secondLevel = await readdir(firstDir, { withFileTypes: true }); for (const entry2 of secondLevel) { if (!entry2.isDirectory()) continue; const secondDir = path.join(firstDir, entry2.name); const secondMatch = await findFileByBaseNameInDir(secondDir, safeName); if (secondMatch) return secondMatch; } } return null; }; const getLegacySignatureFromMongo = async (fileName = '') => { const safeName = `${fileName || ''}`.trim(); if (!safeName) return null; try { await mongoClient.connect(); const database = mongoClient.db(dbConfig.database); const images = database.collection(`${dbConfig.imgBucket}.files`); const chunks = database.collection(`${dbConfig.imgBucket}.chunks`); const cursor = await images.find({ filename: safeName }).toArray(); if (!cursor.length) return null; const latestFile = cursor[cursor.length - 1]; const chunkCursor = await chunks.find({ files_id: latestFile._id }).toArray(); if (!chunkCursor.length) return null; return chunkCursor[0].data; } catch (_error) { return null; } }; const uploadFiles = async (req, res) => { try { const fileName = `${req?.params?.filename || ''}`.trim(); if (!fileName) { return res.status(400).send({ message: "Invalid filename." }); } if (!isDriverSignatureFileName(fileName)) { return res.status(400).send({ message: "This endpoint is only for driver signature upload." }); } await uploadToMemoryAsync(req, res); if (!req.file) { return res.status(400).send({ message: "You must select a file." }); } const siteYearFolder = getDriverSignatureSiteYearFolder(req); const targetDir = path.join(DRIVER_SIGNATURE_DIR, siteYearFolder); if (!fs.existsSync(targetDir)) { fs.mkdirSync(targetDir, { recursive: true }); } const ext = resolveSignatureExt(req.file?.mimetype, req.file?.originalname); const targetFilePath = path.join(targetDir, `${fileName}${ext}`); fs.writeFileSync(targetFilePath, req.file.buffer); return res.send({ message: "Driver signature has been uploaded.", path: targetFilePath }); } catch (error) { console.log(error); return res.status(500).send({ message: error.message || "Error when trying upload signature." }); } }; const uploadGeneralFile = async (req, res) => { try { const fileName = `${req?.params?.filename || ''}`.trim(); if (!fileName) { return res.status(400).send({ message: "Invalid filename." }); } await uploadToMemoryAsync(req, res); if (!req.file) { return res.status(400).send({ message: "You must select a file." }); } const targetDir = path.join(GENERAL_UPLOAD_DIR, getGeneralUploadSubDir(fileName)); if (!fs.existsSync(targetDir)) { fs.mkdirSync(targetDir, { recursive: true }); } const ext = resolveSignatureExt(req.file?.mimetype, req.file?.originalname); const targetFilePath = path.join(targetDir, `${fileName}${ext}`); fs.writeFileSync(targetFilePath, req.file.buffer); return res.status(200).send({ message: "File has been uploaded.", path: targetFilePath }); } catch (error) { return res.status(500).send({ message: error.message || "Error when trying upload file." }); } }; const getFile = async (req, res) => { try { const fileName = decodeURIComponent(`${req?.params?.name || ''}`.trim()); if (!fileName) { return res.status(400).send({ message: "Invalid file name", }); } const signaturePath = isDriverSignatureFileName(fileName) ? await findDriverSignatureFilePath(fileName) : null; const generalPath = signaturePath ? null : await findGeneralFilePath(fileName); const targetPath = signaturePath || generalPath; if (targetPath) { const fileBuffer = await fs.promises.readFile(targetPath); return res.status(200).send(fileBuffer.toString('base64')); } // Legacy fallback for old driver signatures kept in Mongo/GridFS. if (isDriverSignatureFileName(fileName)) { const legacyData = await getLegacySignatureFromMongo(fileName); if (legacyData) { return res.status(200).send(legacyData); } } if (!targetPath) { return res.status(500).send({ message: "No files found!", }); } } catch (error) { return res.status(500).send({ message: error.message, }); } }; const deleteFile = async (req, res) => { try { const targetName = `${req?.body?.name || ''}`.trim(); if (!targetName) { return res.status(400).send({ message: 'Invalid file name' }); } const signaturePath = isDriverSignatureFileName(targetName) ? await findDriverSignatureFilePath(targetName) : null; const generalPath = signaturePath ? null : await findGeneralFilePath(targetName); const targetPath = signaturePath || generalPath; if (targetPath && fs.existsSync(targetPath)) { await fs.promises.unlink(targetPath); } return res.status(200).send({ message: 'Delete Image Succeed'}); } catch (error) { return res.status(500).send({ message: error.message, }); } }; const uploadPhysicalFile = async (req, res) => { try { // Destination and file name would be adjusted to be the same with original file system const {objectId, name, fileType, model} = req.query; const uploadedFile = req.file; if (!uploadedFile) { return res.status(400).send({ message: 'No file uploaded.' }) } return res.status(200).send({ message: `File ${fileType} Uploaded Successfully for ${model} ${objectId}-${name}` }) } catch (error) { return res.status(500).send({ message: error.message, }); } } const deletePhysicalFile = async (req, res) => { try { const { objectId, fileType, model, fileName } = req.body || {}; if (!objectId || !fileType || !model || !fileName) { return res.status(400).send({ message: 'Required fields missed' }); } const BASE_UPLOAD_DIR = `/www/wwwroot/upload/`; const targetFilePath = path.join(BASE_UPLOAD_DIR, model, objectId, fileType, fileName); if (!fs.existsSync(targetFilePath)) { return res.status(200).send({ message: 'File already removed.' }); } await fs.promises.unlink(targetFilePath); return res.status(200).send({ message: 'File deleted successfully.' }); } catch (error) { return res.status(500).send({ message: error.message, }); } } const getFilesByType = async (req, res) => { try { const {objectId, fileType, name, model} = req.params; if (!objectId || !name || !fileType || !model) { return res.status(400).send({message: 'Required fields missed'}); } const BASE_UPLOAD_DIR = `/www/wwwroot/upload/`; const filesDir = `${BASE_UPLOAD_DIR}${model}/${objectId}/${fileType}/`; if (!fs.existsSync(filesDir)) { return res.status(200).send({ data: { objectId, model, fileType, name, files: [], count: 0 } }) } const fileEntries = await readdir(filesDir, { withFileTypes: true}); const filePromises = fileEntries.filter(entry => entry.isFile()).map(async (entry) => { const filePath = path.join(filesDir, entry.name); const stats = await stat(filePath); const fileUrl = `/files/${model}/${objectId}/${fileType}/${entry.name}`; return { name: entry.name, url: fileUrl, size: stats.size, extension: path.extname(entry.name), createdAt: stats.birthtime, modifiedAt: stats.mtime } }); const files = await Promise.all(filePromises); return res.status(200).json({ data: { objectId, fileType, name, files, count: files.length } }) } catch(err) { return res.status(500).send({ message: err.message }) } } module.exports = { uploadFiles, uploadGeneralFile, getFile, deleteFile, uploadPhysicalFile, deletePhysicalFile, getFilesByType };