445 lines
12 KiB
JavaScript
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 });
|
|
}
|
|
};
|