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