diff --git a/app/controllers/upload.controller.js b/app/controllers/upload.controller.js index 6968cf8..2f70570 100644 --- a/app/controllers/upload.controller.js +++ b/app/controllers/upload.controller.js @@ -1,17 +1,13 @@ -const upload = require("../middlewares/upload"); -// const uploadPhysical = require("../middlewares/upload_physical"); -const dbConfig = require("../config/db.config"); -const MongoClient = require("mongodb").MongoClient; -const GridFSBucket = require("mongodb").GridFSBucket; const util = require("util"); var fs = require('fs'); var multer = require('multer'); const path = require("path"); -const url = dbConfig.url; -const baseUrl = dbConfig.fileUrl; -const mongoClient = new MongoClient(url); +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 } @@ -66,71 +62,169 @@ const findDriverSignatureFilePath = async (fileBaseName = '') => { 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." }); } - // Driver signature upload now stores to shared server files. - if (isDriverSignatureFileName(fileName)) { - await uploadToMemoryAsync(req, res); - if (!req.file) { - return res.status(400).send({ message: "You must select a file." }); - } - const year = getDriverSignatureYear(req); - const targetDir = path.join(DRIVER_SIGNATURE_DIR, year); - 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 - }); + if (!isDriverSignatureFileName(fileName)) { + return res.status(400).send({ message: "This endpoint is only for driver signature upload." }); } - - await upload(req, res); - if (req.file == undefined) { - return res.send({ - message: "You must select a file.", - }); + await uploadToMemoryAsync(req, res); + if (!req.file) { + return res.status(400).send({ message: "You must select a file." }); } + const year = getDriverSignatureYear(req); + const targetDir = path.join(DRIVER_SIGNATURE_DIR, year); + 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: "File has been uploaded.", + message: "Driver signature has been uploaded.", + path: targetFilePath }); } catch (error) { console.log(error); - return res.send({ - message: "Error when trying upload image: ${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 signaturePath = await findDriverSignatureFilePath(fileName); - if (signaturePath) { - const fileBuffer = await fs.promises.readFile(signaturePath); - return res.status(200).send(fileBuffer.toString('base64')); + const legacyData = await getLegacySignatureFromMongo(fileName); + if (legacyData) { + return res.status(200).send(legacyData); } } - 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: fileName}).toArray()); - if (cursor.length === 0) { + if (!targetPath) { return res.status(500).send({ message: "No files found!", }); } - const chunkCursor = await(chunks.find({files_id: cursor[cursor.length-1]._id}).toArray()); - return res.status(200).send(chunkCursor[0].data); } catch (error) { return res.status(500).send({ message: error.message, @@ -141,20 +235,16 @@ const getFile = async (req, res) => { const deleteFile = async (req, res) => { try { const targetName = `${req?.body?.name || ''}`.trim(); - if (isDriverSignatureFileName(targetName)) { - const signaturePath = await findDriverSignatureFilePath(targetName); - if (signaturePath && fs.existsSync(signaturePath)) { - await fs.promises.unlink(signaturePath); - } + if (!targetName) { + return res.status(400).send({ message: 'Invalid file name' }); } - 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: targetName}).toArray()); - if (cursor.length > 0) { - await chunks.deleteMany({files_id: cursor[cursor.length-1]._id}); - await images.deleteMany({filename: targetName}); + 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) { @@ -270,6 +360,7 @@ const getFilesByType = async (req, res) => { module.exports = { uploadFiles, + uploadGeneralFile, getFile, deleteFile, uploadPhysicalFile, diff --git a/app/routes/upload.routes.js b/app/routes/upload.routes.js index 7c9cffe..747387c 100644 --- a/app/routes/upload.routes.js +++ b/app/routes/upload.routes.js @@ -15,6 +15,7 @@ module.exports = app => { var router = require("express").Router(); router.get("/:name", upload.getFile); router.post("/upload/:filename", upload.uploadFiles); + router.post("/upload-general/:filename", upload.uploadGeneralFile); router.post("/upload-physical", handleUploadMiddleware, upload.uploadPhysicalFile); router.post("/delete", upload.deleteFile); router.post("/delete-physical", upload.deletePhysicalFile); diff --git a/client/src/components/signature/DriverSignature.js b/client/src/components/signature/DriverSignature.js index 38180f3..0d40714 100644 --- a/client/src/components/signature/DriverSignature.js +++ b/client/src/components/signature/DriverSignature.js @@ -48,7 +48,7 @@ const DriverSignature = () => { const formData = new FormData(); formData.append('file', blob, `${fileName}.jpg`); - CustomerService.uploadAvatar(fileName, formData, { + CustomerService.uploadDriverSignature(fileName, formData, { year: moment(signatureRequest?.route_date).format('YYYY') }).then(() => { SignatureRequestService.updateSignatureRequest(signatureRequest?.id, { status: 'done'}).then(() => { diff --git a/client/src/services/CustomerService.js b/client/src/services/CustomerService.js index 5608851..aeb00c4 100644 --- a/client/src/services/CustomerService.js +++ b/client/src/services/CustomerService.js @@ -36,7 +36,7 @@ const deleteClient = (id, data) => { return http.put(`/clients/${id}`, data); } -const uploadAvatar = (filename, data, options = {}) => { +const uploadDriverSignature = (filename, data, options = {}) => { const safeFilename = `${filename || ''}`.trim(); if (!safeFilename || safeFilename.includes('undefined') || safeFilename.includes('null')) { throw new Error('Invalid upload filename for avatar/signature.'); @@ -46,6 +46,14 @@ const uploadAvatar = (filename, data, options = {}) => { }) } +const uploadAvatar = (filename, data) => { + const safeFilename = `${filename || ''}`.trim(); + if (!safeFilename || safeFilename.includes('undefined') || safeFilename.includes('null')) { + throw new Error('Invalid upload filename for avatar/signature.'); + } + return http.post(`/files/upload-general/${encodeURIComponent(safeFilename)}`, data); +} + const getAvatar = (filename) => { return http.get(`/files/${filename}`); } @@ -94,6 +102,7 @@ const getFileDownloadUrl = (fileUrl) => { export const CustomerService = { getAllActiveCustomers, uploadAvatar, + uploadDriverSignature, getAvatar, getAvatarAsBlob, deleteFile,