UNPKG

@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
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