All checks were successful
Build And Deploy Main / build-and-deploy (push) Successful in 29s
390 lines
12 KiB
JavaScript
390 lines
12 KiB
JavaScript
const util = require("util");
|
|
var fs = require('fs');
|
|
var multer = require('multer');
|
|
const path = require("path");
|
|
const sharp = require("sharp");
|
|
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 resizeDriverSignatureBuffer = async (fileBuffer, ext = '.jpg') => {
|
|
const pipeline = sharp(fileBuffer).resize(400, 200, {
|
|
fit: 'fill'
|
|
});
|
|
if (ext === '.png') {
|
|
return pipeline.png().toBuffer();
|
|
}
|
|
return pipeline.jpeg({ quality: 90 }).toBuffer();
|
|
};
|
|
|
|
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}`);
|
|
const resizedSignature = await resizeDriverSignatureBuffer(req.file.buffer, ext);
|
|
fs.writeFileSync(targetFilePath, resizedSignature);
|
|
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
|
|
}; |