import dotenv from "dotenv"; import { partModel } from "../../schemas/management/part.schema.js"; import log4js from "log4js"; import mongoose from "mongoose"; import multer from "multer"; import fs from "fs"; import path from "path"; import { newAuditLog } from "../../util/index.js"; import { auditLogModel } from "../../schemas/management/auditlog.schema.js"; dotenv.config(); const logger = log4js.getLogger("Parts"); logger.level = process.env.LOG_LEVEL; // Set storage engine const partsStorage = multer.diskStorage({ destination: process.env.PART_STORAGE, filename: async function (req, file, cb) { // Retrieve custom file name from request body const customFileName = req.params.id || "default"; // Default to 'default' if not provided // Create the final filename ensuring it ends with .g const finalFilename = `${customFileName}.stl`; // Call callback with the final filename cb(null, finalFilename); }, }); // Initialise upload const partUpload = multer({ storage: partsStorage, limits: { fileSize: 500000000 }, // 50MB limit fileFilter: function (req, file, cb) { checkFileType(file, cb); }, }).single("partFile"); // The name attribute of the file input in the HTML form // Check file type function checkFileType(file, cb) { // Allowed ext const filetypes = /stl|stl|stl/; // Check ext const extname = filetypes.test(path.extname(file.originalname).toLowerCase()); if (extname) { console.log(file); return cb(null, true); } else { cb("Error: .stl files only!"); } } export const listPartsRouteHandler = async ( req, res, page = 1, limit = 25, property = "", filter = {}, search = "", sort = "", order = "ascend" ) => { try { // Calculate the skip value based on the page number and limit const skip = (page - 1) * limit; let part; let aggregateCommand = []; if (filter != {}) { // use filtering if present aggregateCommand.push({ $match: filter }); } if (property != "") { logger.error(property); aggregateCommand.push({ $group: { _id: `$${property}` } }); // group all same properties aggregateCommand.push({ $project: { _id: 0, [property]: "$_id" } }); // rename _id to the property name } else { aggregateCommand.push({ $lookup: { from: "products", // The collection name (usually lowercase plural) localField: "product", // The field in your current model foreignField: "_id", // The field in the products collection as: "product", // The output field name }, }); aggregateCommand.push({ $unwind: "$product" }); aggregateCommand.push({ $project: { name: 1, _id: 1, createdAt: 1, updatedAt: 1, "product._id": 1, "product.name": 1, }, }); } // Add sorting if sort parameter is provided if (sort) { const sortOrder = order === "descend" ? -1 : 1; aggregateCommand.push({ $sort: { [sort]: sortOrder } }); } aggregateCommand.push({ $skip: skip }); aggregateCommand.push({ $limit: Number(limit) }); console.log(aggregateCommand); part = await partModel.aggregate(aggregateCommand); logger.trace( `List of parts (Page ${page}, Limit ${limit}, Property ${property}, Sort ${sort}, Order ${order}):`, part, ); res.send(part); } catch (error) { logger.error("Error listing parts:", error); res.status(500).send({ error: error }); } }; export const getPartRouteHandler = async (req, res) => { try { // Get ID from params const id = new mongoose.Types.ObjectId(req.params.id); // Fetch the part with the given remote address const part = await partModel .findOne({ _id: id, }) .populate("product"); if (!part) { logger.warn(`Part not found with supplied id.`); return res.status(404).send({ error: "Print job not found." }); } logger.trace(`Part with ID: ${id}:`, part); const auditLogs = await auditLogModel.find({ target: id }).populate('owner'); res.send({...part._doc, auditLogs: auditLogs}); } catch (error) { logger.error("Error fetching Part:", error); res.status(500).send({ error: error.message }); } }; export const editPartRouteHandler = async (req, res) => { try { // Get ID from params const id = new mongoose.Types.ObjectId(req.params.id); // Fetch the part with the given remote address const part = await partModel.findOne({ _id: id }); if (!part) { // Error handling logger.warn(`Part not found with supplied id.`); return res.status(404).send({ error: "Print job not found." }); } logger.trace(`Part with ID: ${id}:`, part); try { const { createdAt, updatedAt, started_at, status, ...updateData } = req.body; // Create audit log before updating await newAuditLog( part.toObject(), updateData, id, 'Part', req.user._id, 'User' ); const result = await partModel.updateOne( { _id: id }, { $set: updateData }, ); if (result.nModified === 0) { logger.error("No Part updated."); res.status(500).send({ error: "No parts updated." }); } } catch (updateError) { logger.error("Error updating part:", updateError); res.status(500).send({ error: updateError.message }); } res.send("OK"); } catch (fetchError) { logger.error("Error fetching part:", fetchError); res.status(500).send({ error: fetchError.message }); } }; export const newPartRouteHandler = async (req, res) => { try { if (Array.isArray(req.body)) { // Handle array of parts const partsToCreate = req.body.map((part) => ({ createdAt: new Date(), updatedAt: new Date(), name: part.name, products: part?.products, fileName: part?.fileName, })); const results = await partModel.insertMany(partsToCreate); if (!results.length) { logger.error("No parts created."); return res.status(500).send({ error: "No parts created." }); } // Create audit logs for each new part for (const result of results) { await newAuditLog( {}, result.toObject(), result._id, 'Part', req.user._id, 'User' ); } return res.status(200).send(results); } else { // Handle single part const newPart = { createdAt: new Date(), updatedAt: new Date(), name: req.body.name, products: req.body?.products, fileName: req.body?.fileName, }; const result = await partModel.create(newPart); // Create audit log for new part await newAuditLog( {}, newPart, result._id, 'Part', req.user._id, 'User' ); return res.status(200).send(result); } } catch (error) { logger.error("Error creating part(s):", error); return res.status(500).send({ error: error.message }); } }; export const uploadPartFileContentRouteHandler = async (req, res) => { try { // Get ID from params const id = new mongoose.Types.ObjectId(req.params.id); // Fetch the part with the given id const part = await partModel.findOne({ _id: id }); if (!part) { // Error handling logger.warn(`Part not found with supplied id.`); return res.status(404).send({ error: "Print job not found." }); } logger.trace(`Part with ID: ${id}`); try { partUpload(req, res, async (err) => { if (err) { res.status(500).send({ error: err, }); } else { if (req.file == undefined) { res.send({ message: "No file selected!", }); } else { res.send({ status: "OK", file: `${req.file.filename}`, }); } } }); } catch (updateError) { logger.error("Error updating part:", updateError); res.status(500).send({ error: updateError.message }); } } catch (fetchError) { logger.error("Error fetching part:", fetchError); res.status(500).send({ error: fetchError.message }); } }; export const getPartFileContentRouteHandler = async (req, res) => { try { // Get ID from params const id = new mongoose.Types.ObjectId(req.params.id); // Fetch the part with the given remote address const part = await partModel.findOne({ _id: id, }); if (!part) { logger.warn(`Part not found with supplied id.`); return res.status(404).send({ error: "Part not found." }); } logger.trace(`Returning part file contents with ID: ${id}:`); const filePath = path.join(process.env.PART_STORAGE, id + ".stl"); // Read the file fs.readFile(filePath, "utf8", (err, data) => { if (err) { if (err.code === "ENOENT") { // File not found return res.status(404).send({ error: "File not found!" }); } else { // Other errors return res.status(500).send({ error: "Error reading file." }); } } // Send the file contents in the response res.send(data); }); } catch (error) { logger.error("Error fetching Part:", error); res.status(500).send({ error: error.message }); } };