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.

260 lines (259 loc) 10.5 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 { BadRequestException } from "../exceptions/runtime.exceptions.js"; import { AppConstants } from "../server.constants.js"; import { ROLES } from "../constants/authorization.constants.js"; import { MulterService } from "../services/core/multer.service.js"; import { authenticate, authorizeRoles } from "../middleware/authenticate.js"; import { FileStorageService } from "../services/file-storage.service.js"; import { FileAnalysisService } from "../services/file-analysis.service.js"; import { extname } from "node:path"; import { copyFileSync, existsSync, unlinkSync } from "node:fs"; import { DELETE, GET, POST, before, route } from "awilix-express"; //#region src/controllers/file-storage.controller.ts var file_storage_controller_exports = /* @__PURE__ */ __exportAll({ FileStorageController: () => FileStorageController }); var _ref, _ref2, _ref3, _FileStorageController; let FileStorageController = _FileStorageController = class FileStorageController { logger; constructor(loggerFactory, fileStorageService, multerService, fileAnalysisService) { this.fileStorageService = fileStorageService; this.multerService = multerService; this.fileAnalysisService = fileAnalysisService; this.logger = loggerFactory(_FileStorageController.name); } async listFiles(req, res) { try { const files = await this.fileStorageService.listAllFiles(); res.send({ files: files.map((file) => { const thumbnails = (file.metadata?._thumbnails || []).map((thumb) => ({ index: thumb.index, width: thumb.width, height: thumb.height, format: thumb.format, size: thumb.size })); return { fileStorageId: file.fileStorageId, fileName: file.fileName, fileFormat: file.fileFormat, fileSize: file.fileSize, fileHash: file.fileHash, createdAt: file.createdAt, thumbnails, metadata: file.metadata }; }), totalCount: files.length }); } catch (error) { this.logger.error(`Failed to list files: ${error}`); res.status(500).send({ error: "Failed to list files" }); } } /** * Get file metadata * GET /api/file-storage/:fileStorageId */ async getFileMetadata(req, res) { const { fileStorageId } = req.params; try { const file = await this.fileStorageService.getFileInfo(fileStorageId); if (!file) { res.status(404).send({ error: "File not found" }); return; } const thumbnails = (file.metadata?._thumbnails || []).map((thumb) => ({ index: thumb.index, width: thumb.width, height: thumb.height, format: thumb.format, size: thumb.size })); res.send({ fileStorageId: file.fileStorageId, fileName: file.fileName, fileFormat: file.fileFormat, fileSize: file.fileSize, fileHash: file.fileHash, createdAt: file.createdAt, thumbnails, metadata: file.metadata }); } catch (error) { this.logger.error(`Failed to get file metadata for ${fileStorageId}: ${error}`); res.status(500).send({ error: "Failed to get file metadata" }); } } /** * Delete a stored file and its thumbnails * DELETE /api/file-storage/:fileStorageId */ async deleteFile(req, res) { const { fileStorageId } = req.params; try { await this.fileStorageService.deleteFile(fileStorageId); this.logger.log(`Deleted file ${fileStorageId}`); res.send({ message: "File deleted successfully", fileStorageId }); } catch (error) { this.logger.error(`Failed to delete file ${fileStorageId}: ${error}`); res.status(500).send({ error: "Failed to delete file" }); } } async analyzeFile(req, res) { const { fileStorageId } = req.params; try { const filePath = this.fileStorageService.getFilePath(fileStorageId); if (!await this.fileStorageService.fileExists(fileStorageId)) { res.status(404).send({ error: "File not found" }); return; } this.logger.log(`Analyzing file: ${fileStorageId}`); const existingMetadata = await this.fileStorageService.loadMetadata(fileStorageId); const analysisResult = await this.fileAnalysisService.analyzeFile(filePath); const metadata = analysisResult.metadata; const thumbnails = analysisResult.thumbnails; this.logger.log(`Analysis complete for ${fileStorageId}: format=${metadata.fileFormat}, layers=${metadata.totalLayers}, time=${metadata.gcodePrintTimeSeconds}s, thumbnails=${thumbnails.length}`); const fileHash = await this.fileStorageService.calculateFileHash(filePath); const originalFileName = existingMetadata?._originalFileName || fileStorageId; metadata.fileName = originalFileName; let thumbnailMetadata = []; if (thumbnails.length > 0) { thumbnailMetadata = await this.fileStorageService.saveThumbnails(fileStorageId, thumbnails); this.logger.log(`Saved ${thumbnailMetadata.length} thumbnails for ${fileStorageId}`); } await this.fileStorageService.saveMetadata(fileStorageId, metadata, fileHash, originalFileName, thumbnailMetadata); res.send({ message: "File analyzed successfully", fileStorageId, metadata, thumbnailCount: thumbnails.length }); } catch (error) { this.logger.error(`Failed to analyze file ${fileStorageId}: ${error}`); res.status(500).send({ error: `Failed to analyze file: ${error}` }); } } async getThumbnailByIndex(req, res) { const { fileStorageId, index } = req.params; const thumbnailIndex = Number.parseInt(index); if (Number.isNaN(thumbnailIndex)) { res.status(400).send({ error: "Invalid thumbnail index" }); return; } try { const thumbnail = await this.fileStorageService.getThumbnail(fileStorageId, thumbnailIndex); if (!thumbnail) { res.status(404).send({ error: "Thumbnail not found" }); return; } const isJPG = thumbnail[0] === 255 && thumbnail[1] === 216; if (thumbnail[0] === 113 && thumbnail[1] === 111 && thumbnail[2] === 105 && thumbnail[3] === 102) { res.status(404).send({ error: "Thumbnail format not supported (QOI)" }); return; } const mimeType = isJPG ? "image/jpeg" : "image/png"; const base64 = thumbnail.toString("base64"); res.send({ thumbnailBase64: `data:${mimeType};base64,${base64}` }); } catch (error) { this.logger.error(`Failed to get thumbnail ${thumbnailIndex} for ${fileStorageId}: ${error}`); res.status(500).send({ error: "Failed to get thumbnail" }); } } /** * Upload a file to storage and analyze it * POST /api/file-storage/upload */ async uploadFile(req, res) { const files = await this.multerService.multerLoadFileAsync(req, res, [ ".gcode", ".3mf", ".bgcode" ], true); if (!files?.length) throw new BadRequestException("No file uploaded"); if (files.length > 1) throw new BadRequestException("Only 1 file can be uploaded at a time"); const file = files[0]; await this.fileStorageService.validateUniqueFilename(file.originalname); const ext = extname(file.originalname); const tempPathWithExt = file.path + ext; try { copyFileSync(file.path, tempPathWithExt); const fileHash = await this.fileStorageService.calculateFileHash(tempPathWithExt); this.logger.log(`Analyzing ${file.originalname}`); const { metadata, thumbnails } = await this.fileAnalysisService.analyzeFile(tempPathWithExt); const fileStorageId = await this.fileStorageService.saveFile(file, fileHash); this.logger.log(`Saved ${file.originalname} as ${fileStorageId}`); const thumbnailMetadata = thumbnails.length > 0 ? await this.fileStorageService.saveThumbnails(fileStorageId, thumbnails) : []; await this.fileStorageService.saveMetadata(fileStorageId, metadata, fileHash, file.originalname, thumbnailMetadata); res.send({ message: "File uploaded successfully", fileStorageId, fileName: file.originalname, fileSize: file.size, fileHash, metadata, thumbnailCount: thumbnails.length }); } finally { if (existsSync(tempPathWithExt)) unlinkSync(tempPathWithExt); } } }; __decorate([ GET(), __decorateMetadata("design:type", Function), __decorateMetadata("design:paramtypes", [Object, Object]), __decorateMetadata("design:returntype", Promise) ], FileStorageController.prototype, "listFiles", null); __decorate([ GET(), route("/:fileStorageId"), __decorateMetadata("design:type", Function), __decorateMetadata("design:paramtypes", [Object, Object]), __decorateMetadata("design:returntype", Promise) ], FileStorageController.prototype, "getFileMetadata", null); __decorate([ DELETE(), route("/:fileStorageId"), __decorateMetadata("design:type", Function), __decorateMetadata("design:paramtypes", [Object, Object]), __decorateMetadata("design:returntype", Promise) ], FileStorageController.prototype, "deleteFile", null); __decorate([ POST(), route("/:fileStorageId/analyze"), __decorateMetadata("design:type", Function), __decorateMetadata("design:paramtypes", [Object, Object]), __decorateMetadata("design:returntype", Promise) ], FileStorageController.prototype, "analyzeFile", null); __decorate([ GET(), route("/:fileStorageId/thumbnail/:index"), __decorateMetadata("design:type", Function), __decorateMetadata("design:paramtypes", [Object, Object]), __decorateMetadata("design:returntype", Promise) ], FileStorageController.prototype, "getThumbnailByIndex", null); __decorate([ POST(), route("/upload"), __decorateMetadata("design:type", Function), __decorateMetadata("design:paramtypes", [Object, Object]), __decorateMetadata("design:returntype", Promise) ], FileStorageController.prototype, "uploadFile", null); FileStorageController = _FileStorageController = __decorate([ route(AppConstants.apiRoute + "/file-storage"), before([authenticate(), authorizeRoles([ROLES.ADMIN, ROLES.OPERATOR])]), __decorateMetadata("design:paramtypes", [ Object, typeof (_ref = typeof FileStorageService !== "undefined" && FileStorageService) === "function" ? _ref : Object, typeof (_ref2 = typeof MulterService !== "undefined" && MulterService) === "function" ? _ref2 : Object, typeof (_ref3 = typeof FileAnalysisService !== "undefined" && FileAnalysisService) === "function" ? _ref3 : Object ]) ], FileStorageController); //#endregion export { FileStorageController, file_storage_controller_exports }; //# sourceMappingURL=file-storage.controller.js.map