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.

254 lines (253 loc) 13.1 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 "../services/printer-api.interface.js"; import { NotFoundException, ValidationException } from "../exceptions/runtime.exceptions.js"; import { validateInput } from "../handlers/validators.js"; import { AppConstants } from "../server.constants.js"; import { errorSummary } from "../utils/error.utils.js"; import { PERMS, ROLES } from "../constants/authorization.constants.js"; import { MulterService } from "../services/core/multer.service.js"; import { authenticate, authorizeRoles, permission } from "../middleware/authenticate.js"; import { FileStorageService } from "../services/file-storage.service.js"; import { FileAnalysisService } from "../services/file-analysis.service.js"; import { PrintJobService } from "../services/orm/print-job.service.js"; import { downloadFileSchema, getFileSchema, getFilesSchema, startPrintFileSchema, uploadFileSchema } from "./validation/printer-files-controller.validation.js"; import { printerResolveMiddleware } from "../middleware/printer.js"; import { PrinterThumbnailCache } from "../state/printer-thumbnail.cache.js"; import { getScopedPrinter } from "../middleware/printer-resolver.js"; import { captureException } from "@sentry/node"; import { extname } from "node:path"; import { copyFileSync, createReadStream, existsSync, unlinkSync } from "node:fs"; import { DELETE, GET, POST, before, route } from "awilix-express"; //#region src/controllers/printer-files.controller.ts var printer_files_controller_exports = /* @__PURE__ */ __exportAll({ PrinterFilesController: () => PrinterFilesController }); var _ref, _ref2, _ref3, _ref4, _ref5, _PrinterFilesController; let PrinterFilesController = _PrinterFilesController = class PrinterFilesController { logger; constructor(loggerFactory, printerApi, printJobService, fileAnalysisService, fileStorageService, multerService, printerThumbnailCache) { this.printerApi = printerApi; this.printJobService = printJobService; this.fileAnalysisService = fileAnalysisService; this.fileStorageService = fileStorageService; this.multerService = multerService; this.printerThumbnailCache = printerThumbnailCache; this.logger = loggerFactory(_PrinterFilesController.name); } async getThumbnails(req, res) { const thumbnails = await this.printerThumbnailCache.getAllValues(); res.send(thumbnails); } async getFiles(req, res) { const { printerApi } = getScopedPrinter(req); const { recursive: recursiveStr, startDir } = await validateInput(req.query, getFilesSchema); const recursive = recursiveStr === "true"; const files = await printerApi.getFiles(recursive, startDir); res.send(files); } async startPrintFile(req, res) { const { currentPrinterId } = getScopedPrinter(req); const { filePath } = await validateInput(req.body, startPrintFileSchema); const encodedFilePath = filePath.split("/").map(encodeURIComponent).join("/"); await this.printerApi.startPrint(encodedFilePath); this.logger.log(`Started print for printer ${currentPrinterId}`); res.send(); } async downloadFile(req, res) { this.logger.log(`Downloading file ${req.params.path}`); const { path } = await validateInput(req.params, downloadFileSchema); const encodedFilePath = path.split("/").map(encodeURIComponent).join("/"); const response = await this.printerApi.downloadFile(encodedFilePath); res.setHeader("Content-Type", response.headers["content-type"]); res.setHeader("Content-Length", response.headers["content-length"]); res.setHeader("Content-Disposition", response.headers["content-disposition"]); if (response.headers["etag"]?.length) res.setHeader("ETag", response.headers["etag"]); response.data.pipe(res); } async deleteFileOrFolder(req, res) { const { path } = await validateInput(req.query, getFileSchema); const encodedFilePath = path.split("/").map(encodeURIComponent).join("/"); const result = await this.printerApi.deleteFile(encodedFilePath); res.send(result); } async getPrinterThumbnail(req, res) { const { currentPrinterId } = getScopedPrinter(req); const printerThumbnail = await this.printerThumbnailCache.getValue(currentPrinterId); res.send(printerThumbnail); } async uploadPrinterFile(req, res) { const { currentPrinterId, currentPrinter } = getScopedPrinter(req); const acceptedExtensions = this.getAcceptedFileExtensions(currentPrinter.printerType); const files = await this.multerService.multerLoadFileAsync(req, res, acceptedExtensions, true); const { startPrint: startPrintString } = await validateInput(req.body, uploadFileSchema); const startPrint = startPrintString === "true"; if (!files?.length) throw new ValidationException({ error: `No file was available for upload. Did you upload files with one of these extensions: ${acceptedExtensions.join(", ")}?` }); if (files.length > 1) throw new ValidationException({ error: "Only 1 file can be uploaded at a time" }); const uploadedFile = files[0]; const token = this.multerService.startTrackingSession(uploadedFile, currentPrinterId); await this.printerApi.uploadFile({ stream: createReadStream(uploadedFile.path), fileName: uploadedFile.originalname, contentLength: uploadedFile.size, startPrint, uploadToken: token }).catch((e) => { try { this.multerService.clearUploadedFile(uploadedFile); } catch (e) { this.logger.error(`Could not remove uploaded file from temporary storage ${errorSummary(e)}`); } throw e; }); const ext = extname(uploadedFile.originalname); const tempPathWithExt = uploadedFile.path + ext; try { this.logger.log(`Processing uploaded file: ${uploadedFile.originalname} (ext: ${ext})`); if (!existsSync(uploadedFile.path)) throw new NotFoundException(`Upload file does not exist: ${uploadedFile.path}`); copyFileSync(uploadedFile.path, tempPathWithExt); const fileHash = await this.fileStorageService.calculateFileHash(tempPathWithExt); this.logger.log(`File hash: ${fileHash.substring(0, 12)}...`); const existingJob = await this.fileStorageService.findDuplicateByHash(fileHash); let metadata; let fileStorageId; if (existingJob && existingJob.fileStorageId) { const cachedMetadata = await this.fileStorageService.loadMetadata(existingJob.fileStorageId); if (cachedMetadata) { this.logger.log(`Duplicate file detected (job ${existingJob.id}, hash match) - reusing storage ${existingJob.fileStorageId}`); metadata = { ...cachedMetadata, fileName: uploadedFile.originalname }; fileStorageId = existingJob.fileStorageId; } else if (existingJob.analysisState === "ANALYZED" && existingJob.metadata) { this.logger.log(`Duplicate file with DB metadata (job ${existingJob.id}) - reusing storage ${existingJob.fileStorageId}`); metadata = { ...existingJob.metadata, fileName: uploadedFile.originalname }; fileStorageId = existingJob.fileStorageId; await this.fileStorageService.saveMetadata(fileStorageId, metadata, fileHash, uploadedFile.originalname); } else { this.logger.log(`Duplicate file not analyzed - reusing storage ${existingJob.fileStorageId}, analyzing now`); const existingFilePath = this.fileStorageService.getFilePath(existingJob.fileStorageId); metadata = (await this.fileAnalysisService.analyzeFile(existingFilePath)).metadata; fileStorageId = existingJob.fileStorageId; await this.fileStorageService.saveMetadata(fileStorageId, metadata, fileHash, uploadedFile.originalname); this.logger.log(`Analysis complete and cached: ${fileStorageId}`); } } else { this.logger.log(`Analyzing new file: ${uploadedFile.originalname}`); const analysisResult = await this.fileAnalysisService.analyzeFile(tempPathWithExt); metadata = analysisResult.metadata; const thumbnails = analysisResult.thumbnails; this.logger.log(`Analysis complete: format=${metadata.fileFormat}, layers=${metadata.totalLayers}, time=${metadata.gcodePrintTimeSeconds}s, filament=${metadata.filamentUsedGrams}g, thumbnails=${thumbnails.length}`); fileStorageId = await this.fileStorageService.saveFile(uploadedFile, fileHash); this.logger.log(`Saved file to storage: ${fileStorageId} (deterministic from hash+name)`); let thumbnailMetadata = []; if (thumbnails.length > 0) { thumbnailMetadata = await this.fileStorageService.saveThumbnails(fileStorageId, thumbnails); this.logger.log(`Saved ${thumbnailMetadata.length} thumbnail(s) for ${fileStorageId}`); } await this.fileStorageService.saveMetadata(fileStorageId, metadata, fileHash, uploadedFile.originalname, thumbnailMetadata); this.logger.log(`Saved metadata JSON for ${fileStorageId}`); } const job = await this.printJobService.createPendingJob(currentPrinterId, uploadedFile.originalname, metadata, currentPrinter.name); job.fileStorageId = fileStorageId; job.fileHash = fileHash; await this.printJobService.updateJob(job); this.logger.log(`Created job ${job.id}: format=${job.fileFormat}, state=${job.analysisState}, storageId=${fileStorageId}, hash=${fileHash.substring(0, 8)}...`); if (existsSync(tempPathWithExt)) unlinkSync(tempPathWithExt); } catch (error) { this.logger.error(`File processing failed: ${errorSummary(error)}`); captureException(error); } finally { try { this.multerService.clearUploadedFile(uploadedFile); } catch (e) { this.logger.error(`Could not remove uploaded file from temporary storage ${errorSummary(e)}`); } } res.send(); } getAcceptedFileExtensions(printerType) { if (printerType === 3) return AppConstants.defaultAcceptedBambuExtensions; return AppConstants.defaultAcceptedGcodeExtensions; } }; __decorate([ GET(), route("/thumbnails"), before(permission(PERMS.PrinterFiles.Get)), __decorateMetadata("design:type", Function), __decorateMetadata("design:paramtypes", [Object, Object]), __decorateMetadata("design:returntype", Promise) ], PrinterFilesController.prototype, "getThumbnails", null); __decorate([ GET(), route("/:id"), before(permission(PERMS.PrinterFiles.Get)), __decorateMetadata("design:type", Function), __decorateMetadata("design:paramtypes", [Object, Object]), __decorateMetadata("design:returntype", Promise) ], PrinterFilesController.prototype, "getFiles", null); __decorate([ POST(), route("/:id/select"), route("/:id/print"), before(permission(PERMS.PrinterFiles.Actions)), __decorateMetadata("design:type", Function), __decorateMetadata("design:paramtypes", [Object, Object]), __decorateMetadata("design:returntype", Promise) ], PrinterFilesController.prototype, "startPrintFile", null); __decorate([ GET(), route("/:id/download/:path"), before(permission(PERMS.PrinterFiles.Get)), __decorateMetadata("design:type", Function), __decorateMetadata("design:paramtypes", [Object, Object]), __decorateMetadata("design:returntype", Promise) ], PrinterFilesController.prototype, "downloadFile", null); __decorate([ DELETE(), route("/:id"), before(permission(PERMS.PrinterFiles.Delete)), __decorateMetadata("design:type", Function), __decorateMetadata("design:paramtypes", [Object, Object]), __decorateMetadata("design:returntype", Promise) ], PrinterFilesController.prototype, "deleteFileOrFolder", null); __decorate([ GET(), route("/:id/thumbnail"), before(permission(PERMS.PrinterFiles.Get)), __decorateMetadata("design:type", Function), __decorateMetadata("design:paramtypes", [Object, Object]), __decorateMetadata("design:returntype", Promise) ], PrinterFilesController.prototype, "getPrinterThumbnail", null); __decorate([ POST(), route("/:id/upload"), before(permission(PERMS.PrinterFiles.Upload)), __decorateMetadata("design:type", Function), __decorateMetadata("design:paramtypes", [Object, Object]), __decorateMetadata("design:returntype", Promise) ], PrinterFilesController.prototype, "uploadPrinterFile", null); PrinterFilesController = _PrinterFilesController = __decorate([ route(AppConstants.apiRoute + "/printer-files"), before([ authenticate(), authorizeRoles([ROLES.ADMIN, ROLES.OPERATOR]), printerResolveMiddleware() ]), __decorateMetadata("design:paramtypes", [ Object, 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, typeof (_ref4 = typeof MulterService !== "undefined" && MulterService) === "function" ? _ref4 : Object, typeof (_ref5 = typeof PrinterThumbnailCache !== "undefined" && PrinterThumbnailCache) === "function" ? _ref5 : Object ]) ], PrinterFilesController); //#endregion export { PrinterFilesController, printer_files_controller_exports }; //# sourceMappingURL=printer-files.controller.js.map