445 lines
12 KiB
JavaScript

import dotenv from "dotenv";
import { gcodeFileModel } from "../../schemas/gcodefile.schema.js";
import { filamentModel } from "../../schemas/filament.schema.js";
import jwt from "jsonwebtoken";
import log4js from "log4js";
import multer from "multer";
import crypto from "crypto";
import path from "path";
import fs from "fs";
import mongoose from "mongoose";
import { newAuditLog } from "../../util/index.js";
import { auditLogModel } from "../../schemas/auditlog.schema.js";
import { extractConfigBlock } from "../../util/index.js";
dotenv.config();
const logger = log4js.getLogger("GCodeFiles");
logger.level = process.env.LOG_LEVEL;
// Set storage engine
const gcodeStorage = multer.diskStorage({
destination: process.env.GCODE_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 .gcode
const finalFilename = `${customFileName}.gcode`;
// Call callback with the final filename
cb(null, finalFilename);
},
});
// Initialise upload
const gcodeUpload = multer({
storage: gcodeStorage,
limits: { fileSize: 500000000 }, // 50MB limit
fileFilter: function (req, file, cb) {
checkFileType(file, cb);
},
}).single("gcodeFile"); // The name attribute of the file input in the HTML form
// Check file type
function checkFileType(file, cb) {
// Allowed ext
const filetypes = /g|gco|gcode/;
// Check ext
const extname = filetypes.test(path.extname(file.originalname).toLowerCase());
if (extname) {
console.log(file);
return cb(null, true);
} else {
cb("Error: .g, .gco, and .gcode files only!");
}
}
export const listGCodeFilesRouteHandler = 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 gcodeFile;
let aggregateCommand = [];
if (search) {
// Add a text search match stage for name and brand fields
aggregateCommand.push({
$match: {
$text: {
$search: search,
},
},
});
}
aggregateCommand.push({
$lookup: {
from: "filaments", // The name of the Filament collection
localField: "filament",
foreignField: "_id",
as: "filament",
},
});
aggregateCommand.push({
$unwind: {
path: "$filament",
preserveNullAndEmptyArrays: true, // Keep documents without a matching filament
},
});
aggregateCommand.push({
$addFields: {
filament: "$filament",
},
});
aggregateCommand.push({
$lookup: {
from: "vendors", // The collection name (usually lowercase plural)
localField: "filament.vendor", // The field in your current model
foreignField: "_id", // The field in the products collection
as: "filament.vendor", // The output field name
},
});
aggregateCommand.push({ $unwind: "$filament.vendor" });
if (filter != {}) {
// use filtering if present
aggregateCommand.push({ $match: filter });
}
if (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({
$project: {
"filament.gcodeFileInfo.estimatedPrintingTimeNormalMode": 0,
url: 0,
"filament.image": 0,
"filament.createdAt": 0,
"filament.updatedAt": 0,
},
});
}
// 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);
gcodeFile = await gcodeFileModel.aggregate(aggregateCommand);
logger.trace(
`List of gcode files (Page ${page}, Limit ${limit}, Property ${property}, Sort ${sort}, Order ${order}):`,
gcodeFile,
);
res.send(gcodeFile);
} catch (error) {
logger.error("Error listing gcode files:", error);
res.status(500).send({ error: error });
}
};
export const getGCodeFileContentRouteHandler = async (req, res) => {
try {
// Get ID from params
const id = new mongoose.Types.ObjectId(req.params.id);
// Fetch the gcodeFile with the given remote address
const gcodeFile = await gcodeFileModel.findOne({
_id: id,
});
if (!gcodeFile) {
logger.warn(`GCodeFile not found with supplied id.`);
return res.status(404).send({ error: "Print job not found." });
}
logger.trace(`Returning GCode File contents with ID: ${id}:`);
const filePath = path.join(
process.env.GCODE_STORAGE,
gcodeFile.gcodeFileName,
);
// 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 GCodeFile:", error);
res.status(500).send({ error: error.message });
}
};
export const editGCodeFileRouteHandler = async (req, res) => {
try {
// Get ID from params
const id = new mongoose.Types.ObjectId(req.params.id);
// Fetch the gcodeFile with the given remote address
const gcodeFile = await gcodeFileModel.findOne({ _id: id });
if (!gcodeFile) {
// Error handling
logger.warn(`GCodeFile not found with supplied id.`);
return res.status(404).send({ error: "Print job not found." });
}
logger.trace(`GCodeFile with ID: ${id}:`, gcodeFile);
try {
const updateData = {
updatedAt: new Date(),
name: req.body.name,
filament: req.body?.filament?._id,
};
// Create audit log before updating
await newAuditLog(
gcodeFile.toObject(),
updateData,
id,
'GCodeFile',
req.user._id,
'User'
);
const result = await gcodeFileModel.updateOne(
{ _id: id },
{ $set: updateData },
);
if (result.nModified === 0) {
logger.error("No gcodeFile updated.");
res.status(500).send({ error: "No gcodeFiles updated." });
}
} catch (updateError) {
logger.error("Error updating gcodeFile:", updateError);
res.status(500).send({ error: updateError.message });
}
res.send("OK");
} catch (fetchError) {
logger.error("Error fetching gcodeFile:", fetchError);
//res.status(500).send({ error: fetchError.message });
}
};
export const newGCodeFileRouteHandler = async (req, res) => {
var filament = null;
try {
// Get ID from params
const id = new mongoose.Types.ObjectId(req.body.filament._id);
// Fetch the filament with the given remote address
filament = await filamentModel.findOne({
_id: id,
});
if (!filament) {
logger.warn(`Filament not found with supplied id.`);
return res.status(404).send({ error: "Filament not found." });
}
logger.trace(`Filament with ID: ${id}:`, filament);
} catch (error) {
logger.error("Error fetching filament:", error);
return res.status(500).send({ error: error.message });
}
try {
const newGCodeFile = {
createdAt: new Date(),
updatedAt: new Date(),
gcodeFileInfo: req.body.gcodeFileInfo,
filament: req.body.filament._id,
name: req.body.name,
cost: (filament.cost / 1000) * req.body.gcodeFileInfo.filamentUsedG,
};
const result = await gcodeFileModel.create(newGCodeFile);
if (result.nCreated === 0) {
logger.error("No gcode file created.");
res.status(500).send({ error: "No gcode file created." });
}
// Create audit log for new gcodefile
await newAuditLog(
{},
newGCodeFile,
result._id,
'GCodeFile',
req.user._id,
'User'
);
res.status(200).send({ status: "ok" });
} catch (updateError) {
logger.error("Error creating gcode file:", updateError);
res.status(500).send({ error: updateError.message });
}
};
export const parseGCodeFileHandler = async (req, res) => {
try {
// Use the same upload middleware as the uploadGCodeFileContentRouteHandler
gcodeUpload(req, res, async (err) => {
if (err) {
return res.status(500).send({
error: err,
});
}
if (req.file == undefined) {
return res.send({
message: "No file selected!",
});
}
try {
// Get the path to the uploaded file
const filePath = path.join(req.file.destination, req.file.filename);
// Read the file content
const fileContent = fs.readFileSync(filePath, "utf8");
// Extract the config block
const configInfo = extractConfigBlock(fileContent);
// Return the config as JSON
res.json(configInfo);
// Optionally clean up the file after processing if it's not needed
fs.unlinkSync(filePath);
} catch (parseError) {
logger.error("Error parsing GCode file:", parseError);
res.status(500).send({ error: parseError.message });
}
});
} catch (error) {
logger.error("Error in parseGCodeFileHandler:", error);
res.status(500).send({ error: error.message });
}
};
export const uploadGCodeFileContentRouteHandler = async (req, res) => {
try {
// Get ID from params
const id = new mongoose.Types.ObjectId(req.params.id);
// Fetch the gcodeFile with the given remote address
const gcodeFile = await gcodeFileModel.findOne({ _id: id });
if (!gcodeFile) {
// Error handling
logger.warn(`GCodeFile not found with supplied id.`);
return res.status(404).send({ error: "Print job not found." });
}
logger.trace(`GCodeFile with ID: ${id}`);
try {
gcodeUpload(req, res, async (err) => {
if (err) {
res.status(500).send({
error: err,
});
} else {
if (req.file == undefined) {
res.send({
message: "No file selected!",
});
} else {
// Get the path to the uploaded file
const filePath = path.join(req.file.destination, req.file.filename);
// Read the file content
const fileContent = fs.readFileSync(filePath, "utf8");
// Update the gcodeFile document with the filename and the extracted config
const result = await gcodeFileModel.updateOne(
{ _id: id },
{
$set: {
gcodeFileName: req.file.filename,
},
},
);
if (result.nModified === 0) {
logger.error("No gcodeFile updated.");
res.status(500).send({ error: "No gcodeFiles updated." });
}
res.send({
status: "OK",
file: `${req.file.filename}`,
});
}
}
});
} catch (updateError) {
logger.error("Error updating gcodeFile:", updateError);
res.status(500).send({ error: updateError.message });
}
} catch (fetchError) {
logger.error("Error fetching gcodeFile:", fetchError);
res.status(500).send({ error: fetchError.message });
}
};
export const getGCodeFileRouteHandler = async (req, res) => {
try {
// Get ID from params
const id = new mongoose.Types.ObjectId(req.params.id);
// Fetch the gcodeFile with the given remote address
const gcodeFile = await gcodeFileModel
.findOne({
_id: id,
})
.populate("filament");
if (!gcodeFile) {
logger.warn(`GCodeFile not found with supplied id.`);
return res.status(404).send({ error: "Print job not found." });
}
logger.trace(`GCodeFile with ID: ${id}:`, gcodeFile);
const auditLogs = await auditLogModel.find({
target: id
}).populate('owner');
res.send({...gcodeFile._doc, auditLogs: auditLogs});
} catch (error) {
logger.error("Error fetching GCodeFile:", error);
res.status(500).send({ error: error.message });
}
};