@fdm-monster/server
Version:
FDM Monster is a bulk OctoPrint, Klipper, PrusaLink and BambuLab manager to set up, configure and monitor 3D printers. Our aim is to provide neat overview over your farm.
378 lines (377 loc) • 14 kB
JavaScript
import { __exportAll } from "../_virtual/_rolldown/runtime.js";
import { __decorateMetadata } from "../_virtual/_@oxc-project_runtime@0.129.0/helpers/decorateMetadata.js";
import { __decorate } from "../_virtual/_@oxc-project_runtime@0.129.0/helpers/decorate.js";
import { NotFoundException } from "../exceptions/runtime.exceptions.js";
import { validateInput } from "../handlers/validators.js";
import { AppConstants } from "../server.constants.js";
import { ROLES } from "../constants/authorization.constants.js";
import { authenticate, authorizeRoles } from "../middleware/authenticate.js";
import { ParamId } from "../middleware/param-converter.middleware.js";
import { FileStorageService } from "../services/file-storage.service.js";
import { FileAnalysisService } from "../services/file-analysis.service.js";
import { searchJobsPagedSchema, searchJobsSchema } from "../services/validators/print-job.validation.js";
import { PrintJobService } from "../services/orm/print-job.service.js";
import { extractThumbnailsFromMetadata } from "../utils/thumbnail.util.js";
import { DELETE, GET, POST, before, route } from "awilix-express";
//#region src/controllers/print-job.controller.ts
var print_job_controller_exports = /* @__PURE__ */ __exportAll({ PrintJobController: () => PrintJobController });
var _ref, _ref2, _ref3, _PrintJobController;
let PrintJobController = _PrintJobController = class PrintJobController {
logger;
constructor(loggerFactory, printJobService, fileAnalysisService, fileStorageService) {
this.printJobService = printJobService;
this.fileAnalysisService = fileAnalysisService;
this.fileStorageService = fileStorageService;
this.logger = loggerFactory(_PrintJobController.name);
}
async searchJobs(req, res) {
const { searchPrinter, searchFile, startDate, endDate } = await validateInput(req.query, searchJobsSchema);
const endDateObj = endDate ? this.toEndOfDay(new Date(endDate)) : void 0;
const result = await this.printJobService.searchPrintJobs(searchPrinter, searchFile, startDate ? new Date(startDate) : void 0, endDateObj);
res.send(result);
}
async searchJobsPaged(req, res) {
const { page, pageSize, searchPrinter, searchFile, startDate, endDate } = await validateInput(req.query, searchJobsPagedSchema);
const endDateObj = endDate ? this.toEndOfDay(new Date(endDate)) : void 0;
const [items, count] = await this.printJobService.searchPrintJobsPaged(searchPrinter, searchFile, startDate ? new Date(startDate) : void 0, endDateObj, page, pageSize);
const itemsWithThumbnails = await Promise.all(items.map(async (job) => {
let thumbnails = [];
if (job.fileStorageId) try {
thumbnails = extractThumbnailsFromMetadata(await this.fileStorageService.loadMetadata(job.fileStorageId));
} catch {}
return {
...job,
thumbnails
};
}));
res.send({
items: itemsWithThumbnails,
count,
pages: Math.ceil(count / pageSize)
});
}
async getJob(req, res) {
const jobId = req.local.id;
const job = await this.printJobService.getJobByIdOrFail(jobId, ["printer"]);
try {
let thumbnails = [];
if (job.fileStorageId) thumbnails = extractThumbnailsFromMetadata(await this.fileStorageService.loadMetadata(job.fileStorageId));
res.send({
...job,
thumbnails
});
} catch (error) {
this.logger.error(`Failed to get job ${jobId}: ${error}`);
res.status(500).send({ error: "Failed to get job" });
}
}
async setCompleted(req, res) {
const jobId = req.local.id;
const job = await this.printJobService.getJobByIdOrFail(jobId);
try {
if (["PENDING", "QUEUED"].includes(job.status)) {
res.status(400).send({
error: "Can only mark jobs which are not \"PENDING\" | \"QUEUED\" as completed",
currentStatus: job.status,
suggestion: "This endpoint is for resolving jobs with unknown state"
});
return;
}
const previousStatus = job.status;
await this.printJobService.markAsCompleted(jobId);
res.send({
message: "Job marked as completed",
jobId,
previousStatus,
newStatus: "COMPLETED"
});
} catch (error) {
this.logger.error(`Failed to mark job ${jobId} as completed: ${error}`);
res.status(500).send({
error: "Failed to update job status",
message: error instanceof Error ? error.message : "Unknown error"
});
}
}
async setFailed(req, res) {
const jobId = req.local.id;
const job = await this.printJobService.getJobByIdOrFail(jobId);
const previousStatus = job.status;
if (![
"UNKNOWN",
"PRINTING",
"CANCELLED",
"COMPLETED"
].includes(job.status)) {
res.status(400).send({
error: "Can only mark UNKNOWN, PRINTING, CANCELLED, or COMPLETED jobs as failed",
currentStatus: job.status
});
return;
}
try {
await this.printJobService.markAsFailed(jobId, "Manually marked as failed by user");
res.send({
message: "Job marked as failed",
jobId,
previousStatus,
newStatus: "FAILED"
});
} catch (error) {
this.logger.error(`Failed to mark job ${jobId} as failed: ${error}`);
res.status(500).send({
error: "Failed to update job status",
message: error instanceof Error ? error.message : "Unknown error"
});
}
}
async setCancelled(req, res) {
const jobId = req.local.id;
const job = await this.printJobService.getJobByIdOrFail(jobId);
const previousStatus = job.status;
if (![
"UNKNOWN",
"PRINTING",
"PAUSED"
].includes(job.status)) {
res.status(400).send({
error: "Can only mark UNKNOWN, PRINTING, or PAUSED jobs as cancelled",
currentStatus: job.status
});
return;
}
try {
await this.printJobService.markAsCancelled(jobId, "Manually marked as cancelled by user");
res.send({
message: "Job marked as cancelled",
jobId,
previousStatus,
newStatus: "CANCELLED"
});
} catch (error) {
this.logger.error(`Failed to mark job ${jobId} as cancelled: ${error}`);
res.status(500).send({
error: "Failed to update job status",
message: error instanceof Error ? error.message : "Unknown error"
});
}
}
async setUnknown(req, res) {
const jobId = req.local.id;
const job = await this.printJobService.getJobByIdOrFail(jobId);
const previousStatus = job.status;
if (!["PRINTING", "PAUSED"].includes(job.status)) {
res.status(400).send({
error: "Can only mark PRINTING or PAUSED jobs as unknown",
currentStatus: job.status,
suggestion: "This endpoint is for marking jobs with uncertain state (e.g., after connection loss)"
});
return;
}
try {
await this.printJobService.markAsUnknown(jobId);
res.send({
message: "Job marked as unknown",
jobId,
previousStatus,
newStatus: "UNKNOWN"
});
} catch (error) {
this.logger.error(`Failed to mark job ${jobId} as unknown: ${error}`);
res.status(500).send({
error: "Failed to update job status",
message: error instanceof Error ? error.message : "Unknown error"
});
}
}
async reAnalyzeJob(req, res) {
const jobId = req.local.id;
const job = await this.printJobService.getJobByIdOrFail(jobId);
this.logger.log(`Re-analyzing job ${jobId}`);
try {
if (!job.fileStorageId) {
this.logger.log(`Job ${jobId} has no fileStorageId - triggering download and analysis`);
await this.printJobService.triggerFileAnalysis(jobId);
res.send({
message: "File download and analysis triggered (async)",
jobId,
status: "pending"
});
return;
}
const filePath = this.fileStorageService.getFilePath(job.fileStorageId);
if (!await this.fileAnalysisService.needsAnalysis(filePath)) throw new NotFoundException(`File not found in storage: ${job.fileStorageId}`);
this.logger.log(`Re-analyzing file for job ${jobId}: ${filePath}`);
job.analysisState = "ANALYZING";
await this.printJobService.updateJob(job);
const { metadata, thumbnails } = await this.fileAnalysisService.analyzeFile(filePath);
let thumbnailMetadata = [];
if (thumbnails && thumbnails.length > 0) {
thumbnailMetadata = await this.fileStorageService.saveThumbnails(job.fileStorageId, thumbnails);
this.logger.log(`Saved ${thumbnailMetadata.length} thumbnail(s) for job ${jobId}`);
}
await this.printJobService.handleFileAnalyzed(jobId, metadata, thumbnails);
const fileHash = job.fileHash || void 0;
await this.fileStorageService.saveMetadata(job.fileStorageId, metadata, fileHash, job.fileName, thumbnailMetadata);
this.logger.log(`Successfully re-analyzed job ${jobId}`);
res.send({
message: "File re-analyzed successfully",
jobId,
status: "analyzed",
metadata,
thumbnailCount: thumbnails?.length || 0
});
} catch (error) {
this.logger.error(`Failed to re-analyze job ${jobId}: ${error}`);
res.status(500).send({
error: "Re-analysis failed",
message: error instanceof Error ? error.message : "Unknown error"
});
}
}
async deleteJob(req, res) {
const jobId = req.local.id;
const job = await this.printJobService.getJobByIdOrFail(jobId);
const deleteFileParam = req.query.deleteFile === "true";
if (job.status === "PRINTING" || job.status === "PAUSED") {
res.status(400).send({
error: "Cannot delete active print job",
status: job.status,
suggestion: "Wait for print to complete or cancel it first"
});
return;
}
try {
const fileStorageId = job.fileStorageId;
const fileName = job.fileName;
await this.printJobService.deleteJob(job);
this.logger.log(`Deleted job ${jobId}: ${fileName}`);
if (fileStorageId && deleteFileParam) {
const otherJobs = await this.printJobService.countJobsReferencingFile(fileStorageId);
if (otherJobs === 0) try {
await this.fileStorageService.deleteFile(fileStorageId);
this.logger.log(`Deleted file as requested: ${fileStorageId}`);
res.send({
message: "Job and associated file deleted",
jobId,
fileDeleted: true
});
} catch (error) {
this.logger.warn(`Failed to delete file ${fileStorageId}: ${error}`);
res.send({
message: "Job deleted, but file deletion failed",
jobId,
fileDeleted: false
});
}
else {
this.logger.log(`File ${fileStorageId} still referenced by ${otherJobs} other job(s) - keeping file`);
res.send({
message: "Job deleted (file kept - still used by other jobs)",
jobId,
fileDeleted: false,
remainingReferences: otherJobs
});
}
} else res.send({
message: "Job deleted",
jobId,
fileDeleted: false
});
} catch (error) {
this.logger.error(`Failed to delete job ${jobId}: ${error}`);
res.status(500).send({
error: "Delete failed",
message: error instanceof Error ? error.message : "Unknown error"
});
}
}
toEndOfDay(date) {
const endOfDay = new Date(date);
endOfDay.setHours(23, 59, 59, 999);
return endOfDay;
}
};
__decorate([
GET(),
route("/search"),
__decorateMetadata("design:type", Function),
__decorateMetadata("design:paramtypes", [Object, Object]),
__decorateMetadata("design:returntype", Promise)
], PrintJobController.prototype, "searchJobs", null);
__decorate([
GET(),
route("/search-paged"),
__decorateMetadata("design:type", Function),
__decorateMetadata("design:paramtypes", [Object, Object]),
__decorateMetadata("design:returntype", Promise)
], PrintJobController.prototype, "searchJobsPaged", null);
__decorate([
GET(),
route("/:id"),
before([ParamId("id")]),
__decorateMetadata("design:type", Function),
__decorateMetadata("design:paramtypes", [Object, Object]),
__decorateMetadata("design:returntype", Promise)
], PrintJobController.prototype, "getJob", null);
__decorate([
POST(),
route("/:id/set-completed"),
before([ParamId("id")]),
__decorateMetadata("design:type", Function),
__decorateMetadata("design:paramtypes", [Object, Object]),
__decorateMetadata("design:returntype", Promise)
], PrintJobController.prototype, "setCompleted", null);
__decorate([
POST(),
route("/:id/set-failed"),
before([ParamId("id")]),
__decorateMetadata("design:type", Function),
__decorateMetadata("design:paramtypes", [Object, Object]),
__decorateMetadata("design:returntype", Promise)
], PrintJobController.prototype, "setFailed", null);
__decorate([
POST(),
route("/:id/set-cancelled"),
before([ParamId("id")]),
__decorateMetadata("design:type", Function),
__decorateMetadata("design:paramtypes", [Object, Object]),
__decorateMetadata("design:returntype", Promise)
], PrintJobController.prototype, "setCancelled", null);
__decorate([
POST(),
route("/:id/set-unknown"),
before([ParamId("id")]),
__decorateMetadata("design:type", Function),
__decorateMetadata("design:paramtypes", [Object, Object]),
__decorateMetadata("design:returntype", Promise)
], PrintJobController.prototype, "setUnknown", null);
__decorate([
POST(),
route("/:id/re-analyze"),
before([ParamId("id")]),
__decorateMetadata("design:type", Function),
__decorateMetadata("design:paramtypes", [Object, Object]),
__decorateMetadata("design:returntype", Promise)
], PrintJobController.prototype, "reAnalyzeJob", null);
__decorate([
DELETE(),
route("/:id"),
before([ParamId("id")]),
__decorateMetadata("design:type", Function),
__decorateMetadata("design:paramtypes", [Object, Object]),
__decorateMetadata("design:returntype", Promise)
], PrintJobController.prototype, "deleteJob", null);
PrintJobController = _PrintJobController = __decorate([
route(AppConstants.apiRoute + "/print-jobs"),
before([authenticate(), authorizeRoles([ROLES.ADMIN])]),
__decorateMetadata("design:paramtypes", [
Object,
typeof (_ref = typeof PrintJobService !== "undefined" && PrintJobService) === "function" ? _ref : Object,
typeof (_ref2 = typeof FileAnalysisService !== "undefined" && FileAnalysisService) === "function" ? _ref2 : Object,
typeof (_ref3 = typeof FileStorageService !== "undefined" && FileStorageService) === "function" ? _ref3 : Object
])
], PrintJobController);
//#endregion
export { PrintJobController, print_job_controller_exports };
//# sourceMappingURL=print-job.controller.js.map