@fdm-monster/server 2.1.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.yarn/install-state.gz +0 -0
- package/.yarn/releases/{yarn-4.13.0.cjs → yarn-4.14.1.cjs} +288 -288
- package/.yarnrc.yml +5 -1
- package/RELEASE_NOTES.MD +14 -0
- package/dist/_virtual/{_@oxc-project_runtime@0.127.0 → _@oxc-project_runtime@0.129.0}/helpers/decorate.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.127.0 → _@oxc-project_runtime@0.129.0}/helpers/decorateMetadata.js +1 -1
- package/dist/consoles/typeorm-create.js.map +1 -1
- package/dist/consoles/typeorm-generate.js.map +1 -1
- package/dist/consoles/typeorm-migrate.js.map +1 -1
- package/dist/constants/authorization.constants.js.map +1 -1
- package/dist/container.js.map +1 -1
- package/dist/controllers/api-key.controller.js +2 -2
- package/dist/controllers/api-key.controller.js.map +1 -1
- package/dist/controllers/auth.controller.js +5 -3
- package/dist/controllers/auth.controller.js.map +1 -1
- package/dist/controllers/batch-call.controller.js +2 -2
- package/dist/controllers/batch-call.controller.js.map +1 -1
- package/dist/controllers/camera-stream.controller.js +2 -2
- package/dist/controllers/camera-stream.controller.js.map +1 -1
- package/dist/controllers/file-storage.controller.js +2 -2
- package/dist/controllers/file-storage.controller.js.map +1 -1
- package/dist/controllers/first-time-setup.controller.js +2 -2
- package/dist/controllers/first-time-setup.controller.js.map +1 -1
- package/dist/controllers/floor.controller.js +2 -2
- package/dist/controllers/floor.controller.js.map +1 -1
- package/dist/controllers/metrics.controller.js +2 -2
- package/dist/controllers/metrics.controller.js.map +1 -1
- package/dist/controllers/print-job.controller.js +2 -2
- package/dist/controllers/print-job.controller.js.map +1 -1
- package/dist/controllers/print-queue.controller.js +2 -2
- package/dist/controllers/print-queue.controller.js.map +1 -1
- package/dist/controllers/printer-files.controller.js +2 -2
- package/dist/controllers/printer-files.controller.js.map +1 -1
- package/dist/controllers/printer-maintenance-log.controller.js +2 -2
- package/dist/controllers/printer-maintenance-log.controller.js.map +1 -1
- package/dist/controllers/printer-settings.controller.js +2 -2
- package/dist/controllers/printer-settings.controller.js.map +1 -1
- package/dist/controllers/printer-tag.controller.js +2 -2
- package/dist/controllers/printer-tag.controller.js.map +1 -1
- package/dist/controllers/printer.controller.js +2 -2
- package/dist/controllers/printer.controller.js.map +1 -1
- package/dist/controllers/server-private.controller.js +2 -2
- package/dist/controllers/server-private.controller.js.map +1 -1
- package/dist/controllers/server-public.controller.js +2 -2
- package/dist/controllers/server-public.controller.js.map +1 -1
- package/dist/controllers/settings.controller.js +2 -2
- package/dist/controllers/settings.controller.js.map +1 -1
- package/dist/controllers/slicer-compat.controller.js +2 -2
- package/dist/controllers/slicer-compat.controller.js.map +1 -1
- package/dist/controllers/user.controller.js +2 -2
- package/dist/controllers/user.controller.js.map +1 -1
- package/dist/entities/api-key.entity.js +2 -2
- package/dist/entities/camera-stream.entity.js +2 -2
- package/dist/entities/floor-position.entity.js +2 -2
- package/dist/entities/floor.entity.js +2 -2
- package/dist/entities/print-job.entity.js +2 -2
- package/dist/entities/printer-maintenance-log.entity.js +2 -2
- package/dist/entities/printer-tag.entity.js +2 -2
- package/dist/entities/printer.entity.js +2 -2
- package/dist/entities/refresh-token.entity.js +2 -2
- package/dist/entities/role.entity.js +2 -2
- package/dist/entities/settings.entity.js +2 -2
- package/dist/entities/tag.entity.js +2 -2
- package/dist/entities/user-role.entity.js +2 -2
- package/dist/entities/user.entity.js +2 -2
- package/dist/exceptions/failed-dependency.exception.js.map +1 -1
- package/dist/exceptions/job.exceptions.js.map +1 -1
- package/dist/exceptions/runtime.exceptions.js.map +1 -1
- package/dist/handlers/event-emitter.js.map +1 -1
- package/dist/handlers/logger-factory.js.map +1 -1
- package/dist/handlers/logger.js.map +1 -1
- package/dist/handlers/logging/file-logging.transport.js +1 -2
- package/dist/handlers/logging/file-logging.transport.js.map +1 -1
- package/dist/handlers/logging/loki-logging.transport.js.map +1 -1
- package/dist/handlers/logging/static.logger.js.map +1 -1
- package/dist/handlers/validators.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/middleware/api-key.strategy.js.map +1 -1
- package/dist/middleware/authenticate.js.map +1 -1
- package/dist/middleware/database.js.map +1 -1
- package/dist/middleware/demo.middleware.js.map +1 -1
- package/dist/middleware/exception.filter.js.map +1 -1
- package/dist/middleware/global.middleware.js.map +1 -1
- package/dist/middleware/param-converter.middleware.js.map +1 -1
- package/dist/middleware/passport.js.map +1 -1
- package/dist/middleware/printer-resolver.js.map +1 -1
- package/dist/middleware/printer.js.map +1 -1
- package/dist/middleware/slicer-api-key.middleware.js.map +1 -1
- package/dist/middleware/socketio.middleware.js.map +1 -1
- package/dist/migrations/1706829146617-InitSqlite.js.map +1 -1
- package/dist/migrations/1707494762198-PrinterGroup.js.map +1 -1
- package/dist/migrations/1708465930665-ChangePrintCompletionDeletePrinterCascade.js.map +1 -1
- package/dist/migrations/1713300747465-ChangeRoleNameUnique.js.map +1 -1
- package/dist/migrations/1713897879622-AddPrinterType.js.map +1 -1
- package/dist/migrations/1720338804844-RemovePrinterFile.js.map +1 -1
- package/dist/migrations/1745141688926-AddPrinterUsernamePassword.js.map +1 -1
- package/dist/migrations/1766576698569-DropPermissions.js.map +1 -1
- package/dist/migrations/1767278216516-ChangeCameraPrinterOnDeleteSetNull.js.map +1 -1
- package/dist/migrations/1767279607392-DropCustomGcode.js.map +1 -1
- package/dist/migrations/1767291804417-DropPrintCompletions.js.map +1 -1
- package/dist/migrations/1767352862576-DropSettingsFileClean.js.map +1 -1
- package/dist/migrations/1767355639023-ChangeFloorLevelToOrder.js.map +1 -1
- package/dist/migrations/1767370191762-ChangeFloorNonUniqueOrder.js.map +1 -1
- package/dist/migrations/1767432108916-RenameGroupToTag.js.map +1 -1
- package/dist/migrations/1767451444137-AddPrintJob.js.map +1 -1
- package/dist/migrations/1767909428129-AddPrinterMaintenanceLog.js.map +1 -1
- package/dist/migrations/1778446203015-AddApiKey.js.map +1 -1
- package/dist/plugins/controllers-plugin.js.map +1 -1
- package/dist/server.constants.js +2 -1
- package/dist/server.constants.js.map +1 -1
- package/dist/server.core.js.map +1 -1
- package/dist/server.env.js.map +1 -1
- package/dist/server.host.js.map +1 -1
- package/dist/services/authentication/auth.service.js.map +1 -1
- package/dist/services/authentication/jwt.service.js.map +1 -1
- package/dist/services/bambu/bambu-ftp.adapter.js.map +1 -1
- package/dist/services/bambu/bambu-mqtt.adapter.js.map +1 -1
- package/dist/services/bambu/bambu.client.js.map +1 -1
- package/dist/services/bambu.api.js.map +1 -1
- package/dist/services/core/batch-call.service.js.map +1 -1
- package/dist/services/core/client-bundle.service.js.map +1 -1
- package/dist/services/core/config.service.js +4 -0
- package/dist/services/core/config.service.js.map +1 -1
- package/dist/services/core/cradle.service.js.map +1 -1
- package/dist/services/core/github.service.js.map +1 -1
- package/dist/services/core/http-client.factory.js.map +1 -1
- package/dist/services/core/logs-manager.service.js.map +1 -1
- package/dist/services/core/monsterpi.service.js.map +1 -1
- package/dist/services/core/multer.service.js.map +1 -1
- package/dist/services/core/server-release.service.js.map +1 -1
- package/dist/services/core/yaml.service.js.map +1 -1
- package/dist/services/file-analysis.service.js.map +1 -1
- package/dist/services/file-storage.service.js.map +1 -1
- package/dist/services/moonraker/moonraker-websocket.adapter.js.map +1 -1
- package/dist/services/moonraker/moonraker.client.js.map +1 -1
- package/dist/services/moonraker.api.js.map +1 -1
- package/dist/services/octoprint/octoprint-api.routes.js.map +1 -1
- package/dist/services/octoprint/octoprint-websocket.adapter.js.map +1 -1
- package/dist/services/octoprint/octoprint.client.js.map +1 -1
- package/dist/services/octoprint/utils/api.utils.js.map +1 -1
- package/dist/services/octoprint/utils/file.utils.js.map +1 -1
- package/dist/services/octoprint/utils/octoprint-http-client.builder.js.map +1 -1
- package/dist/services/octoprint.api.js.map +1 -1
- package/dist/services/orm/api-key.service.js.map +1 -1
- package/dist/services/orm/base.service.js.map +1 -1
- package/dist/services/orm/camera-stream.service.js.map +1 -1
- package/dist/services/orm/floor-position.service.js.map +1 -1
- package/dist/services/orm/floor.service.js.map +1 -1
- package/dist/services/orm/permission.service.js.map +1 -1
- package/dist/services/orm/print-job.service.js.map +1 -1
- package/dist/services/orm/printer-maintenance-log.service.js.map +1 -1
- package/dist/services/orm/printer-tag.service.js.map +1 -1
- package/dist/services/orm/printer.service.js.map +1 -1
- package/dist/services/orm/refresh-token.service.js.map +1 -1
- package/dist/services/orm/role.service.js.map +1 -1
- package/dist/services/orm/settings.service.js.map +1 -1
- package/dist/services/orm/user-role.service.js.map +1 -1
- package/dist/services/orm/user.service.js.map +1 -1
- package/dist/services/print-file-downloader.service.js.map +1 -1
- package/dist/services/print-queue.service.js.map +1 -1
- package/dist/services/printer-api.factory.js.map +1 -1
- package/dist/services/printer-api.interface.js.map +1 -1
- package/dist/services/prusa-link/prusa-link-http-polling.adapter.js.map +1 -1
- package/dist/services/prusa-link/prusa-link.api.js.map +1 -1
- package/dist/services/prusa-link/utils/digest-auth.util.js +19 -12
- package/dist/services/prusa-link/utils/digest-auth.util.js.map +1 -1
- package/dist/services/prusa-link/utils/prusa-link-http-client.builder.js +45 -11
- package/dist/services/prusa-link/utils/prusa-link-http-client.builder.js.map +1 -1
- package/dist/services/socket.factory.js.map +1 -1
- package/dist/services/task-manager.service.js.map +1 -1
- package/dist/services/typeorm/typeorm.service.js.map +1 -1
- package/dist/services/validators/printer-service.validation.js.map +1 -1
- package/dist/shared/default-http-client.builder.js.map +1 -1
- package/dist/shared/load-controllers.js.map +1 -1
- package/dist/shared/runtime-settings.migration.js.map +1 -1
- package/dist/shared/websocket-rpc-extended.adapter.js.map +1 -1
- package/dist/shared/websocket.adapter.js.map +1 -1
- package/dist/state/file-upload-tracker.cache.js.map +1 -1
- package/dist/state/floor.store.js.map +1 -1
- package/dist/state/printer-events.cache.js.map +1 -1
- package/dist/state/printer-socket.store.js.map +1 -1
- package/dist/state/printer-thumbnail.cache.js.map +1 -1
- package/dist/state/printer.cache.js.map +1 -1
- package/dist/state/settings.store.js.map +1 -1
- package/dist/state/socket-io.gateway.js.map +1 -1
- package/dist/state/test-printer-socket.store.js.map +1 -1
- package/dist/tasks/boot.task.js.map +1 -1
- package/dist/tasks/client-bundle.task.js.map +1 -1
- package/dist/tasks/print-job-analysis.task.js.map +1 -1
- package/dist/tasks/printer-websocket-restore.task.js.map +1 -1
- package/dist/tasks/printer-websocket.task.js.map +1 -1
- package/dist/tasks/socketio.task.js.map +1 -1
- package/dist/tasks/software-update.task.js.map +1 -1
- package/dist/tasks.js.map +1 -1
- package/dist/utils/array.util.js.map +1 -1
- package/dist/utils/bgcode/bgcode-thumbnail.parser.js.map +1 -1
- package/dist/utils/bgcode/bgcode.utils.js.map +1 -1
- package/dist/utils/bgcode/heatshrink-decoder.js.map +1 -1
- package/dist/utils/bgcode/png-encoder.js.map +1 -1
- package/dist/utils/bgcode/qoi-decoder.js.map +1 -1
- package/dist/utils/cache/key-diff.cache.js.map +1 -1
- package/dist/utils/correlation-token.util.js.map +1 -1
- package/dist/utils/crypto.utils.js.map +1 -1
- package/dist/utils/env.utils.js.map +1 -1
- package/dist/utils/error.utils.js.map +1 -1
- package/dist/utils/fs.utils.js.map +1 -1
- package/dist/utils/gcode.utils.js.map +1 -1
- package/dist/utils/image-dimensions.js.map +1 -1
- package/dist/utils/job-stats.util.js.map +1 -1
- package/dist/utils/normalize-url.js.map +1 -1
- package/dist/utils/parsers/3mf.parser.js.map +1 -1
- package/dist/utils/parsers/bgcode.parser.js.map +1 -1
- package/dist/utils/parsers/gcode.parser.js.map +1 -1
- package/dist/utils/pretty-print.utils.js.map +1 -1
- package/dist/utils/semver.utils.js.map +1 -1
- package/dist/utils/swagger/decorators.js.map +1 -1
- package/dist/utils/swagger/generator.js.map +1 -1
- package/dist/utils/swagger/swagger.js.map +1 -1
- package/dist/utils/thumbnail.util.js.map +1 -1
- package/dist/utils/time.utils.js.map +1 -1
- package/dist/utils/url.utils.js.map +1 -1
- package/package.json +10 -7
- package/packages/consoles/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"octoprint.api.js","names":[],"sources":["../../src/services/octoprint.api.ts"],"sourcesContent":["import {\n OctoprintType,\n PrinterType,\n ReprintState,\n UploadFileInput,\n uploadFileInputSchema,\n} from \"@/services/printer-api.interface\";\nimport type { IPrinterApi } from \"@/services/printer-api.interface\";\nimport type { LoginDto } from \"@/services/interfaces/login.dto\";\nimport { OctoprintClient } from \"@/services/octoprint/octoprint.client\";\nimport { NotImplementedException } from \"@/exceptions/runtime.exceptions\";\nimport { AxiosPromise } from \"axios\";\n\nexport class OctoprintApi implements IPrinterApi {\n private readonly client: OctoprintClient;\n\n constructor(\n octoprintClient: OctoprintClient,\n private printerLogin: LoginDto,\n ) {\n this.client = octoprintClient;\n }\n\n get type(): PrinterType {\n return OctoprintType;\n }\n\n get login() {\n return this.printerLogin;\n }\n\n set login(login: LoginDto) {\n this.printerLogin = login;\n }\n\n async getVersion() {\n const result = await this.client.getApiVersion(this.login);\n return result.data?.server;\n }\n\n async validateConnection(): Promise<void> {\n await this.getVersion();\n }\n\n async connect() {\n await this.client.sendConnectionCommand(this.login, this.client.connectCommand);\n }\n\n async disconnect() {\n await this.client.sendConnectionCommand(this.login, this.client.disconnectCommand);\n }\n\n async restartServer() {\n await this.client.postServerRestartCommand(this.login);\n }\n\n async restartHost() {\n // TODO Needs investigation\n throw new NotImplementedException();\n }\n\n async restartPrinterFirmware() {\n // TODO Needs investigation\n throw new NotImplementedException();\n }\n\n async startPrint(filePath: string) {\n await this.client.postSelectPrintFile(this.login, filePath, true);\n }\n\n async pausePrint(): Promise<void> {\n await this.client.sendJobCommand(this.login, this.client.pauseJobCommand);\n }\n\n async resumePrint(): Promise<void> {\n await this.client.sendJobCommand(this.login, this.client.resumeJobCommand);\n }\n\n async cancelPrint(): Promise<void> {\n await this.client.sendJobCommand(this.login, this.client.cancelJobCommand);\n }\n\n async sendGcode(script: string): Promise<void> {\n await this.client.sendCustomGCodeCommand(this.login, script);\n }\n\n async quickStop(): Promise<void> {\n await this.client.sendCustomGCodeCommand(this.login, \"M112\");\n }\n\n async movePrintHead(amounts: { x?: number; y?: number; z?: number; speed?: number }) {\n await this.client.sendPrintHeadJogCommand(this.login, amounts);\n }\n\n async homeAxes(axes: { x?: boolean; y?: boolean; z?: boolean }) {\n await this.client.sendPrintHeadHomeCommand(this.login, axes);\n }\n\n async getFile(path: string) {\n return this.client.getFile(this.login, path);\n }\n\n async getFiles(recursive = false, startDir = \"\") {\n const items = await this.client.getLocalFiles(this.login, recursive, startDir);\n return {\n dirs: items.filter((f) => f.dir),\n files: items.filter((f) => !f.dir),\n };\n }\n\n async downloadFile(path: string): AxiosPromise<NodeJS.ReadableStream> {\n return await this.client.downloadFile(this.login, path);\n }\n\n async getFileChunk(path: string, startBytes: number, endBytes: number): AxiosPromise<string> {\n return await this.client.getFileChunk(this.login, path, startBytes, endBytes);\n }\n\n async uploadFile(input: UploadFileInput) {\n const validated = uploadFileInputSchema.parse(input);\n await this.client.uploadFileAsMultiPart(\n this.login,\n validated.stream,\n validated.fileName,\n validated.contentLength,\n validated.startPrint,\n validated.uploadToken,\n );\n }\n\n async deleteFile(path: string) {\n await this.client.deleteFileOrFolder(this.login, path);\n }\n\n async deleteFolder(path: string) {\n await this.client.deleteFileOrFolder(this.login, path);\n }\n\n async getSettings() {\n const response = await this.client.getSettings(this.login);\n return response.data;\n }\n\n async getReprintState() {\n const connectedResponse = await this.client.getConnection(this.login);\n const connectionState = connectedResponse.data.current?.state;\n\n const selectedJobResponse = await this.client.getJob(this.login);\n const selectedJob = selectedJobResponse.data;\n\n const currentJobFile = selectedJob?.job?.file;\n if (!currentJobFile?.path) {\n return { connectionState, reprintState: ReprintState.NoLastPrint };\n }\n\n return {\n connectionState,\n file: {\n path: currentJobFile.path,\n size: currentJobFile.size,\n date: currentJobFile.date,\n dir: false,\n },\n reprintState: ReprintState.LastPrintReady,\n };\n }\n}\n"],"mappings":";;;AAaA,IAAa,eAAb,MAAiD;CAC/C;CAEA,YACE,iBACA,cACA;
|
|
1
|
+
{"version":3,"file":"octoprint.api.js","names":[],"sources":["../../src/services/octoprint.api.ts"],"sourcesContent":["import {\n OctoprintType,\n PrinterType,\n ReprintState,\n UploadFileInput,\n uploadFileInputSchema,\n} from \"@/services/printer-api.interface\";\nimport type { IPrinterApi } from \"@/services/printer-api.interface\";\nimport type { LoginDto } from \"@/services/interfaces/login.dto\";\nimport { OctoprintClient } from \"@/services/octoprint/octoprint.client\";\nimport { NotImplementedException } from \"@/exceptions/runtime.exceptions\";\nimport { AxiosPromise } from \"axios\";\n\nexport class OctoprintApi implements IPrinterApi {\n private readonly client: OctoprintClient;\n\n constructor(\n octoprintClient: OctoprintClient,\n private printerLogin: LoginDto,\n ) {\n this.client = octoprintClient;\n }\n\n get type(): PrinterType {\n return OctoprintType;\n }\n\n get login() {\n return this.printerLogin;\n }\n\n set login(login: LoginDto) {\n this.printerLogin = login;\n }\n\n async getVersion() {\n const result = await this.client.getApiVersion(this.login);\n return result.data?.server;\n }\n\n async validateConnection(): Promise<void> {\n await this.getVersion();\n }\n\n async connect() {\n await this.client.sendConnectionCommand(this.login, this.client.connectCommand);\n }\n\n async disconnect() {\n await this.client.sendConnectionCommand(this.login, this.client.disconnectCommand);\n }\n\n async restartServer() {\n await this.client.postServerRestartCommand(this.login);\n }\n\n async restartHost() {\n // TODO Needs investigation\n throw new NotImplementedException();\n }\n\n async restartPrinterFirmware() {\n // TODO Needs investigation\n throw new NotImplementedException();\n }\n\n async startPrint(filePath: string) {\n await this.client.postSelectPrintFile(this.login, filePath, true);\n }\n\n async pausePrint(): Promise<void> {\n await this.client.sendJobCommand(this.login, this.client.pauseJobCommand);\n }\n\n async resumePrint(): Promise<void> {\n await this.client.sendJobCommand(this.login, this.client.resumeJobCommand);\n }\n\n async cancelPrint(): Promise<void> {\n await this.client.sendJobCommand(this.login, this.client.cancelJobCommand);\n }\n\n async sendGcode(script: string): Promise<void> {\n await this.client.sendCustomGCodeCommand(this.login, script);\n }\n\n async quickStop(): Promise<void> {\n await this.client.sendCustomGCodeCommand(this.login, \"M112\");\n }\n\n async movePrintHead(amounts: { x?: number; y?: number; z?: number; speed?: number }) {\n await this.client.sendPrintHeadJogCommand(this.login, amounts);\n }\n\n async homeAxes(axes: { x?: boolean; y?: boolean; z?: boolean }) {\n await this.client.sendPrintHeadHomeCommand(this.login, axes);\n }\n\n async getFile(path: string) {\n return this.client.getFile(this.login, path);\n }\n\n async getFiles(recursive = false, startDir = \"\") {\n const items = await this.client.getLocalFiles(this.login, recursive, startDir);\n return {\n dirs: items.filter((f) => f.dir),\n files: items.filter((f) => !f.dir),\n };\n }\n\n async downloadFile(path: string): AxiosPromise<NodeJS.ReadableStream> {\n return await this.client.downloadFile(this.login, path);\n }\n\n async getFileChunk(path: string, startBytes: number, endBytes: number): AxiosPromise<string> {\n return await this.client.getFileChunk(this.login, path, startBytes, endBytes);\n }\n\n async uploadFile(input: UploadFileInput) {\n const validated = uploadFileInputSchema.parse(input);\n await this.client.uploadFileAsMultiPart(\n this.login,\n validated.stream,\n validated.fileName,\n validated.contentLength,\n validated.startPrint,\n validated.uploadToken,\n );\n }\n\n async deleteFile(path: string) {\n await this.client.deleteFileOrFolder(this.login, path);\n }\n\n async deleteFolder(path: string) {\n await this.client.deleteFileOrFolder(this.login, path);\n }\n\n async getSettings() {\n const response = await this.client.getSettings(this.login);\n return response.data;\n }\n\n async getReprintState() {\n const connectedResponse = await this.client.getConnection(this.login);\n const connectionState = connectedResponse.data.current?.state;\n\n const selectedJobResponse = await this.client.getJob(this.login);\n const selectedJob = selectedJobResponse.data;\n\n const currentJobFile = selectedJob?.job?.file;\n if (!currentJobFile?.path) {\n return { connectionState, reprintState: ReprintState.NoLastPrint };\n }\n\n return {\n connectionState,\n file: {\n path: currentJobFile.path,\n size: currentJobFile.size,\n date: currentJobFile.date,\n dir: false,\n },\n reprintState: ReprintState.LastPrintReady,\n };\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-key.service.js","names":[],"sources":["../../../src/services/orm/api-key.service.ts"],"sourcesContent":["import { BaseService } from \"@/services/orm/base.service\";\nimport { ApiKey, Role } from \"@/entities\";\nimport { TypeormService } from \"@/services/typeorm/typeorm.service\";\nimport type { IApiKeyService } from \"@/services/interfaces/api-key.service.interface\";\nimport { ApiKeyDto, CreatedApiKeyDto } from \"@/services/interfaces/api-key.dto\";\nimport { NotFoundException } from \"@/exceptions/runtime.exceptions\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport { createHash, randomBytes, timingSafeEqual } from \"node:crypto\";\nimport { In } from \"typeorm\";\n\nconst TOKEN_PREFIX = \"fdmm_api_\";\nconst SECRET_BYTES = 32;\nconst PREFIX_LEN = 16;\n\nfunction generateToken(): { token: string; prefix: string; hashedSecret: string } {\n const secret = randomBytes(SECRET_BYTES).toString(\"base64url\");\n const token = `${TOKEN_PREFIX}${secret}`;\n const prefix = secret.slice(0, PREFIX_LEN);\n const hashedSecret = createHash(\"sha256\").update(token).digest(\"hex\");\n return { token, prefix, hashedSecret };\n}\n\nexport class ApiKeyService extends BaseService(ApiKey, ApiKeyDto) implements IApiKeyService {\n private readonly logger: LoggerService;\n\n constructor(loggerFactory: ILoggerFactory, typeormService: TypeormService) {\n super(typeormService);\n this.logger = loggerFactory(ApiKeyService.name);\n }\n\n private get roleRepo() {\n return this.typeormService.getDataSource().getRepository(Role);\n }\n\n toDto(entity: ApiKey): ApiKeyDto {\n return {\n id: entity.id,\n createdByUserId: entity.createdByUserId,\n label: entity.label,\n prefix: entity.prefix,\n createdAt: entity.createdAt,\n lastUsedAt: entity.lastUsedAt,\n roles: (entity.roles ?? []).map((r) => r.name),\n };\n }\n\n looksLikeApiKey(token: string): boolean {\n return (\n typeof token === \"string\" && token.startsWith(TOKEN_PREFIX) && token.length > TOKEN_PREFIX.length + PREFIX_LEN\n );\n }\n\n async create(createdByUserId: number, label: string, roleIds: number[]): Promise<CreatedApiKeyDto> {\n const trimmed = label?.trim();\n if (!trimmed?.length) {\n throw new Error(\"API key label is required\");\n }\n if (!roleIds?.length) {\n throw new Error(\"At least one role must be assigned to an API key\");\n }\n\n const roles = await this.roleRepo.find({ where: { id: In(roleIds) } });\n if (roles.length !== roleIds.length) {\n throw new NotFoundException(\"One or more roleIds do not exist\");\n }\n\n const { token, prefix, hashedSecret } = generateToken();\n const entity = await this.repository.save(\n this.repository.create({\n createdByUserId,\n label: trimmed,\n prefix,\n hashedSecret,\n lastUsedAt: null,\n roles,\n }),\n );\n\n this.logger.log(\n `Created API key id=${entity.id} prefix=${prefix} by user ${createdByUserId} with roles ${roleIds}`,\n );\n return {\n ...this.toDto(entity),\n token,\n };\n }\n\n async list(): Promise<ApiKeyDto[]> {\n const rows = await this.repository.find({ order: { createdAt: \"DESC\" } });\n return rows.map((r) => this.toDto(r));\n }\n\n async delete(id: number): Promise<void> {\n const row = await this.repository.findOneBy({ id });\n if (!row) {\n throw new NotFoundException(`API key ${id} not found`);\n }\n await this.repository.delete(id);\n this.logger.log(`Deleted API key id=${id} prefix=${row.prefix}`);\n }\n\n async verify(token: string): Promise<ApiKey | null> {\n if (!this.looksLikeApiKey(token)) return null;\n\n const secret = token.slice(TOKEN_PREFIX.length);\n const prefix = secret.slice(0, PREFIX_LEN);\n\n const candidate = await this.repository.findOne({ where: { prefix } });\n if (!candidate) return null;\n\n const presented = createHash(\"sha256\").update(token).digest();\n const stored = Buffer.from(candidate.hashedSecret, \"hex\");\n if (presented.length !== stored.length || !timingSafeEqual(presented, stored)) {\n return null;\n }\n\n this.repository\n .update({ id: candidate.id }, { lastUsedAt: new Date() })\n .catch((err) => this.logger.warn(`Failed to bump lastUsedAt for api key ${candidate.id}: ${err}`));\n\n return candidate;\n }\n}\n"],"mappings":";;;;;;;;;AAWA,MAAM,eAAe;AACrB,MAAM,eAAe;AACrB,MAAM,aAAa;AAEnB,SAAS,gBAAyE;CAChF,MAAM,SAAS,YAAY,aAAa,CAAC,SAAS,YAAY;CAC9D,MAAM,QAAQ,GAAG,eAAe;
|
|
1
|
+
{"version":3,"file":"api-key.service.js","names":[],"sources":["../../../src/services/orm/api-key.service.ts"],"sourcesContent":["import { BaseService } from \"@/services/orm/base.service\";\nimport { ApiKey, Role } from \"@/entities\";\nimport { TypeormService } from \"@/services/typeorm/typeorm.service\";\nimport type { IApiKeyService } from \"@/services/interfaces/api-key.service.interface\";\nimport { ApiKeyDto, CreatedApiKeyDto } from \"@/services/interfaces/api-key.dto\";\nimport { NotFoundException } from \"@/exceptions/runtime.exceptions\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport { createHash, randomBytes, timingSafeEqual } from \"node:crypto\";\nimport { In } from \"typeorm\";\n\nconst TOKEN_PREFIX = \"fdmm_api_\";\nconst SECRET_BYTES = 32;\nconst PREFIX_LEN = 16;\n\nfunction generateToken(): { token: string; prefix: string; hashedSecret: string } {\n const secret = randomBytes(SECRET_BYTES).toString(\"base64url\");\n const token = `${TOKEN_PREFIX}${secret}`;\n const prefix = secret.slice(0, PREFIX_LEN);\n const hashedSecret = createHash(\"sha256\").update(token).digest(\"hex\");\n return { token, prefix, hashedSecret };\n}\n\nexport class ApiKeyService extends BaseService(ApiKey, ApiKeyDto) implements IApiKeyService {\n private readonly logger: LoggerService;\n\n constructor(loggerFactory: ILoggerFactory, typeormService: TypeormService) {\n super(typeormService);\n this.logger = loggerFactory(ApiKeyService.name);\n }\n\n private get roleRepo() {\n return this.typeormService.getDataSource().getRepository(Role);\n }\n\n toDto(entity: ApiKey): ApiKeyDto {\n return {\n id: entity.id,\n createdByUserId: entity.createdByUserId,\n label: entity.label,\n prefix: entity.prefix,\n createdAt: entity.createdAt,\n lastUsedAt: entity.lastUsedAt,\n roles: (entity.roles ?? []).map((r) => r.name),\n };\n }\n\n looksLikeApiKey(token: string): boolean {\n return (\n typeof token === \"string\" && token.startsWith(TOKEN_PREFIX) && token.length > TOKEN_PREFIX.length + PREFIX_LEN\n );\n }\n\n async create(createdByUserId: number, label: string, roleIds: number[]): Promise<CreatedApiKeyDto> {\n const trimmed = label?.trim();\n if (!trimmed?.length) {\n throw new Error(\"API key label is required\");\n }\n if (!roleIds?.length) {\n throw new Error(\"At least one role must be assigned to an API key\");\n }\n\n const roles = await this.roleRepo.find({ where: { id: In(roleIds) } });\n if (roles.length !== roleIds.length) {\n throw new NotFoundException(\"One or more roleIds do not exist\");\n }\n\n const { token, prefix, hashedSecret } = generateToken();\n const entity = await this.repository.save(\n this.repository.create({\n createdByUserId,\n label: trimmed,\n prefix,\n hashedSecret,\n lastUsedAt: null,\n roles,\n }),\n );\n\n this.logger.log(\n `Created API key id=${entity.id} prefix=${prefix} by user ${createdByUserId} with roles ${roleIds}`,\n );\n return {\n ...this.toDto(entity),\n token,\n };\n }\n\n async list(): Promise<ApiKeyDto[]> {\n const rows = await this.repository.find({ order: { createdAt: \"DESC\" } });\n return rows.map((r) => this.toDto(r));\n }\n\n async delete(id: number): Promise<void> {\n const row = await this.repository.findOneBy({ id });\n if (!row) {\n throw new NotFoundException(`API key ${id} not found`);\n }\n await this.repository.delete(id);\n this.logger.log(`Deleted API key id=${id} prefix=${row.prefix}`);\n }\n\n async verify(token: string): Promise<ApiKey | null> {\n if (!this.looksLikeApiKey(token)) return null;\n\n const secret = token.slice(TOKEN_PREFIX.length);\n const prefix = secret.slice(0, PREFIX_LEN);\n\n const candidate = await this.repository.findOne({ where: { prefix } });\n if (!candidate) return null;\n\n const presented = createHash(\"sha256\").update(token).digest();\n const stored = Buffer.from(candidate.hashedSecret, \"hex\");\n if (presented.length !== stored.length || !timingSafeEqual(presented, stored)) {\n return null;\n }\n\n this.repository\n .update({ id: candidate.id }, { lastUsedAt: new Date() })\n .catch((err) => this.logger.warn(`Failed to bump lastUsedAt for api key ${candidate.id}: ${err}`));\n\n return candidate;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.service.js","names":[],"sources":["../../../src/services/orm/base.service.ts"],"sourcesContent":["import { Type, type IBaseService } from \"@/services/orm/base.interface\";\nimport { TypeormService } from \"@/services/typeorm/typeorm.service\";\nimport { DeepPartial, EntityNotFoundError, EntityTarget, FindManyOptions, FindOneOptions, Repository } from \"typeorm\";\nimport { validate } from \"class-validator\";\nimport { QueryDeepPartialEntity } from \"typeorm/query-builder/QueryPartialEntity\";\nimport { NotFoundException } from \"@/exceptions/runtime.exceptions\";\nimport { DEFAULT_PAGE, type IPagination } from \"@/services/interfaces/page.interface\";\n\nexport function BaseService<\n T extends { id: number },\n DTO extends object,\n CreateDTO extends DeepPartial<T> = DeepPartial<T>,\n UpdateDTO extends object = QueryDeepPartialEntity<T>,\n>(entity: EntityTarget<T>, dto: Type<DTO>, createDTO?: Type<CreateDTO>, updateDto?: Type<UpdateDTO>) {\n abstract class BaseServiceHost implements IBaseService<T, DTO, CreateDTO, UpdateDTO> {\n repository: Repository<T>;\n\n constructor(protected readonly typeormService: TypeormService) {\n this.repository = typeormService.getDataSource().getRepository(entity);\n }\n\n abstract toDto(entity: T): DTO;\n\n async get(id: number, options?: FindOneOptions<T>) {\n try {\n if (id === null || id === undefined) {\n throw new EntityNotFoundError(entity, \"Id was not provided\");\n }\n return this.repository.findOneOrFail({ ...options, where: { id } } as FindOneOptions<T>);\n } catch (e) {\n if (e instanceof EntityNotFoundError) {\n throw new NotFoundException(`The entity ${entity} with the provided id was not found`);\n }\n throw e;\n }\n }\n\n async list(options?: FindManyOptions<T>) {\n return this.repository.find(options);\n }\n\n async listPaged(page: IPagination = DEFAULT_PAGE, options?: FindManyOptions<T>) {\n return this.repository.find({ take: page.pageSize, skip: page.pageSize * page.page, ...options });\n }\n\n async update(id: number, updateDto: UpdateDTO) {\n const entity = await this.get(id);\n await validate(updateDto);\n await validate(Object.assign(entity, updateDto));\n await this.repository.update(entity.id, updateDto);\n return await this.get(id);\n }\n\n async create(dto: CreateDTO) {\n // Explicit runtime check with a meaningful error\n if (\"id\" in dto && dto.id !== undefined && dto.id !== null) {\n throw new Error(\"Cannot create entity with an existing ID. Use update method instead.\");\n }\n\n const entity = this.repository.create(dto) as T;\n await validate(entity);\n\n return await this.repository.save(entity);\n }\n\n async delete(id: number) {\n const entity = await this.get(id);\n await this.repository.delete(entity.id);\n }\n\n async deleteMany(ids: number[]) {\n await this.repository.delete(ids);\n }\n }\n\n return BaseServiceHost;\n}\n"],"mappings":";;;;;AAQA,SAAgB,YAKd,QAAyB,KAAgB,WAA6B,WAA6B;CACnG,MAAe,gBAAsE;EACnF;EAEA,YAAY,gBAAmD;
|
|
1
|
+
{"version":3,"file":"base.service.js","names":[],"sources":["../../../src/services/orm/base.service.ts"],"sourcesContent":["import { Type, type IBaseService } from \"@/services/orm/base.interface\";\nimport { TypeormService } from \"@/services/typeorm/typeorm.service\";\nimport { DeepPartial, EntityNotFoundError, EntityTarget, FindManyOptions, FindOneOptions, Repository } from \"typeorm\";\nimport { validate } from \"class-validator\";\nimport { QueryDeepPartialEntity } from \"typeorm/query-builder/QueryPartialEntity\";\nimport { NotFoundException } from \"@/exceptions/runtime.exceptions\";\nimport { DEFAULT_PAGE, type IPagination } from \"@/services/interfaces/page.interface\";\n\nexport function BaseService<\n T extends { id: number },\n DTO extends object,\n CreateDTO extends DeepPartial<T> = DeepPartial<T>,\n UpdateDTO extends object = QueryDeepPartialEntity<T>,\n>(entity: EntityTarget<T>, dto: Type<DTO>, createDTO?: Type<CreateDTO>, updateDto?: Type<UpdateDTO>) {\n abstract class BaseServiceHost implements IBaseService<T, DTO, CreateDTO, UpdateDTO> {\n repository: Repository<T>;\n\n constructor(protected readonly typeormService: TypeormService) {\n this.repository = typeormService.getDataSource().getRepository(entity);\n }\n\n abstract toDto(entity: T): DTO;\n\n async get(id: number, options?: FindOneOptions<T>) {\n try {\n if (id === null || id === undefined) {\n throw new EntityNotFoundError(entity, \"Id was not provided\");\n }\n return this.repository.findOneOrFail({ ...options, where: { id } } as FindOneOptions<T>);\n } catch (e) {\n if (e instanceof EntityNotFoundError) {\n throw new NotFoundException(`The entity ${entity} with the provided id was not found`);\n }\n throw e;\n }\n }\n\n async list(options?: FindManyOptions<T>) {\n return this.repository.find(options);\n }\n\n async listPaged(page: IPagination = DEFAULT_PAGE, options?: FindManyOptions<T>) {\n return this.repository.find({ take: page.pageSize, skip: page.pageSize * page.page, ...options });\n }\n\n async update(id: number, updateDto: UpdateDTO) {\n const entity = await this.get(id);\n await validate(updateDto);\n await validate(Object.assign(entity, updateDto));\n await this.repository.update(entity.id, updateDto);\n return await this.get(id);\n }\n\n async create(dto: CreateDTO) {\n // Explicit runtime check with a meaningful error\n if (\"id\" in dto && dto.id !== undefined && dto.id !== null) {\n throw new Error(\"Cannot create entity with an existing ID. Use update method instead.\");\n }\n\n const entity = this.repository.create(dto) as T;\n await validate(entity);\n\n return await this.repository.save(entity);\n }\n\n async delete(id: number) {\n const entity = await this.get(id);\n await this.repository.delete(entity.id);\n }\n\n async deleteMany(ids: number[]) {\n await this.repository.delete(ids);\n }\n }\n\n return BaseServiceHost;\n}\n"],"mappings":";;;;;AAQA,SAAgB,YAKd,QAAyB,KAAgB,WAA6B,WAA6B;CACnG,MAAe,gBAAsE;EACnF;EAEA,YAAY,gBAAmD;GAAhC,KAAA,iBAAA;GAC7B,KAAK,aAAa,eAAe,eAAe,CAAC,cAAc,OAAO;;EAKxE,MAAM,IAAI,IAAY,SAA6B;GACjD,IAAI;IACF,IAAI,OAAO,QAAQ,OAAO,KAAA,GACxB,MAAM,IAAI,oBAAoB,QAAQ,sBAAsB;IAE9D,OAAO,KAAK,WAAW,cAAc;KAAE,GAAG;KAAS,OAAO,EAAE,IAAI;KAAE,CAAsB;YACjF,GAAG;IACV,IAAI,aAAa,qBACf,MAAM,IAAI,kBAAkB,cAAc,OAAO,qCAAqC;IAExF,MAAM;;;EAIV,MAAM,KAAK,SAA8B;GACvC,OAAO,KAAK,WAAW,KAAK,QAAQ;;EAGtC,MAAM,UAAU,OAAoB,cAAc,SAA8B;GAC9E,OAAO,KAAK,WAAW,KAAK;IAAE,MAAM,KAAK;IAAU,MAAM,KAAK,WAAW,KAAK;IAAM,GAAG;IAAS,CAAC;;EAGnG,MAAM,OAAO,IAAY,WAAsB;GAC7C,MAAM,SAAS,MAAM,KAAK,IAAI,GAAG;GACjC,MAAM,SAAS,UAAU;GACzB,MAAM,SAAS,OAAO,OAAO,QAAQ,UAAU,CAAC;GAChD,MAAM,KAAK,WAAW,OAAO,OAAO,IAAI,UAAU;GAClD,OAAO,MAAM,KAAK,IAAI,GAAG;;EAG3B,MAAM,OAAO,KAAgB;GAE3B,IAAI,QAAQ,OAAO,IAAI,OAAO,KAAA,KAAa,IAAI,OAAO,MACpD,MAAM,IAAI,MAAM,uEAAuE;GAGzF,MAAM,SAAS,KAAK,WAAW,OAAO,IAAI;GAC1C,MAAM,SAAS,OAAO;GAEtB,OAAO,MAAM,KAAK,WAAW,KAAK,OAAO;;EAG3C,MAAM,OAAO,IAAY;GACvB,MAAM,SAAS,MAAM,KAAK,IAAI,GAAG;GACjC,MAAM,KAAK,WAAW,OAAO,OAAO,GAAG;;EAGzC,MAAM,WAAW,KAAe;GAC9B,MAAM,KAAK,WAAW,OAAO,IAAI;;;CAIrC,OAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"camera-stream.service.js","names":[],"sources":["../../../src/services/orm/camera-stream.service.ts"],"sourcesContent":["import { BaseService } from \"@/services/orm/base.service\";\nimport { CameraStream } from \"@/entities\";\nimport { CameraStreamDto, CreateCameraStreamDto } from \"@/services/interfaces/camera-stream.dto\";\nimport type { ICameraStreamService } from \"@/services/interfaces/camera-stream.service.interface\";\n\nexport class CameraStreamService\n extends BaseService(CameraStream, CameraStreamDto, CreateCameraStreamDto)\n implements ICameraStreamService\n{\n toDto(entity: CameraStream): CameraStreamDto {\n // Maps it to original format\n return {\n id: entity.id,\n streamURL: entity.streamURL,\n name: entity.name,\n printerId: entity.printerId,\n aspectRatio: entity.aspectRatio,\n rotationClockwise: entity.rotationClockwise,\n flipHorizontal: entity.flipHorizontal,\n flipVertical: entity.flipVertical,\n };\n }\n}\n"],"mappings":";;;;;AAKA,IAAa,sBAAb,cACU,YAAY,cAAc,iBAAiB,sBAAsB,CAE3E;CACE,MAAM,QAAuC;
|
|
1
|
+
{"version":3,"file":"camera-stream.service.js","names":[],"sources":["../../../src/services/orm/camera-stream.service.ts"],"sourcesContent":["import { BaseService } from \"@/services/orm/base.service\";\nimport { CameraStream } from \"@/entities\";\nimport { CameraStreamDto, CreateCameraStreamDto } from \"@/services/interfaces/camera-stream.dto\";\nimport type { ICameraStreamService } from \"@/services/interfaces/camera-stream.service.interface\";\n\nexport class CameraStreamService\n extends BaseService(CameraStream, CameraStreamDto, CreateCameraStreamDto)\n implements ICameraStreamService\n{\n toDto(entity: CameraStream): CameraStreamDto {\n // Maps it to original format\n return {\n id: entity.id,\n streamURL: entity.streamURL,\n name: entity.name,\n printerId: entity.printerId,\n aspectRatio: entity.aspectRatio,\n rotationClockwise: entity.rotationClockwise,\n flipHorizontal: entity.flipHorizontal,\n flipVertical: entity.flipVertical,\n };\n }\n}\n"],"mappings":";;;;;AAKA,IAAa,sBAAb,cACU,YAAY,cAAc,iBAAiB,sBAAsB,CAE3E;CACE,MAAM,QAAuC;EAE3C,OAAO;GACL,IAAI,OAAO;GACX,WAAW,OAAO;GAClB,MAAM,OAAO;GACb,WAAW,OAAO;GAClB,aAAa,OAAO;GACpB,mBAAmB,OAAO;GAC1B,gBAAgB,OAAO;GACvB,cAAc,OAAO;GACtB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"floor-position.service.js","names":[],"sources":["../../../src/services/orm/floor-position.service.ts"],"sourcesContent":["import { FloorPosition } from \"@/entities/floor-position.entity\";\nimport { BaseService } from \"@/services/orm/base.service\";\nimport { PositionDto } from \"@/services/interfaces/floor.dto\";\n\nexport class FloorPositionService extends BaseService(FloorPosition, PositionDto) {\n async create(dto: PositionDto): Promise<FloorPosition> {\n return super.create(dto);\n }\n\n findPosition(floorId: number, x: number, y: number) {\n return this.repository.findOneBy({ floorId, x, y });\n }\n\n /**\n * Find the printer across any floor, usually to see if it has been moved elsewhere.\n * @param printerId The printer which position to be looked up.\n */\n findPrinterPosition(printerId: number) {\n return this.repository.findOneBy({ printerId });\n }\n\n deletePrinterPositionsByPrinterId(printerId: number) {\n return this.repository.delete({ printerId });\n }\n\n findPrinterPositionOnFloor(floorId: number, printerId: number) {\n return this.repository.findOneBy({ floorId, printerId });\n }\n\n toDto(entity: FloorPosition): PositionDto {\n return {\n x: entity.x,\n y: entity.y,\n printerId: entity.printerId,\n floorId: entity.floorId,\n };\n }\n}\n"],"mappings":";;;;AAIA,IAAa,uBAAb,cAA0C,YAAY,eAAe,YAAY,CAAC;CAChF,MAAM,OAAO,KAA0C;
|
|
1
|
+
{"version":3,"file":"floor-position.service.js","names":[],"sources":["../../../src/services/orm/floor-position.service.ts"],"sourcesContent":["import { FloorPosition } from \"@/entities/floor-position.entity\";\nimport { BaseService } from \"@/services/orm/base.service\";\nimport { PositionDto } from \"@/services/interfaces/floor.dto\";\n\nexport class FloorPositionService extends BaseService(FloorPosition, PositionDto) {\n async create(dto: PositionDto): Promise<FloorPosition> {\n return super.create(dto);\n }\n\n findPosition(floorId: number, x: number, y: number) {\n return this.repository.findOneBy({ floorId, x, y });\n }\n\n /**\n * Find the printer across any floor, usually to see if it has been moved elsewhere.\n * @param printerId The printer which position to be looked up.\n */\n findPrinterPosition(printerId: number) {\n return this.repository.findOneBy({ printerId });\n }\n\n deletePrinterPositionsByPrinterId(printerId: number) {\n return this.repository.delete({ printerId });\n }\n\n findPrinterPositionOnFloor(floorId: number, printerId: number) {\n return this.repository.findOneBy({ floorId, printerId });\n }\n\n toDto(entity: FloorPosition): PositionDto {\n return {\n x: entity.x,\n y: entity.y,\n printerId: entity.printerId,\n floorId: entity.floorId,\n };\n }\n}\n"],"mappings":";;;;AAIA,IAAa,uBAAb,cAA0C,YAAY,eAAe,YAAY,CAAC;CAChF,MAAM,OAAO,KAA0C;EACrD,OAAO,MAAM,OAAO,IAAI;;CAG1B,aAAa,SAAiB,GAAW,GAAW;EAClD,OAAO,KAAK,WAAW,UAAU;GAAE;GAAS;GAAG;GAAG,CAAC;;;;;;CAOrD,oBAAoB,WAAmB;EACrC,OAAO,KAAK,WAAW,UAAU,EAAE,WAAW,CAAC;;CAGjD,kCAAkC,WAAmB;EACnD,OAAO,KAAK,WAAW,OAAO,EAAE,WAAW,CAAC;;CAG9C,2BAA2B,SAAiB,WAAmB;EAC7D,OAAO,KAAK,WAAW,UAAU;GAAE;GAAS;GAAW,CAAC;;CAG1D,MAAM,QAAoC;EACxC,OAAO;GACL,GAAG,OAAO;GACV,GAAG,OAAO;GACV,WAAW,OAAO;GAClB,SAAS,OAAO;GACjB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"floor.service.js","names":[],"sources":["../../../src/services/orm/floor.service.ts"],"sourcesContent":["import { BaseService } from \"@/services/orm/base.service\";\nimport { Floor, FloorPosition } from \"@/entities\";\nimport type { IFloorService } from \"@/services/interfaces/floor.service.interface\";\nimport { FloorPositionService } from \"./floor-position.service\";\nimport { TypeormService } from \"@/services/typeorm/typeorm.service\";\nimport { CreateFloorDto, FloorDto, PositionDto, UpdateFloorDto } from \"@/services/interfaces/floor.dto\";\nimport { validateInput } from \"@/handlers/validators\";\nimport {\n createOrUpdateFloorSchema,\n printerInFloorSchema,\n updateFloorOrderSchema,\n updateFloorNameSchema,\n} from \"@/services/validators/floor-service.validation\";\nimport { NotFoundException } from \"@/exceptions/runtime.exceptions\";\nimport { FindManyOptions, FindOneOptions } from \"typeorm\";\n\nexport class FloorService\n extends BaseService(Floor, FloorDto, CreateFloorDto, UpdateFloorDto)\n implements IFloorService\n{\n constructor(\n protected readonly typeormService: TypeormService,\n private readonly floorPositionService: FloorPositionService,\n ) {\n super(typeormService);\n }\n\n override async list(options?: FindManyOptions<Floor>): Promise<Floor[]> {\n return super.list(\n Object.assign(options || {}, {\n relations: [\"printers\"],\n }),\n );\n }\n\n override async get(id: number, options?: FindOneOptions<Floor>): Promise<Floor> {\n return super.get(\n id,\n Object.assign(options || {}, {\n relations: [\"printers\"],\n }),\n );\n }\n\n async create(dto: CreateFloorDto): Promise<Floor> {\n const outcome = await validateInput(dto, createOrUpdateFloorSchema);\n\n const floor = await super.create({\n name: outcome.name,\n order: outcome.order,\n printers: [],\n });\n\n if (outcome.printers?.length) {\n for (const position of outcome.printers) {\n await this.addOrUpdatePrinter(floor.id, position as PositionDto);\n }\n }\n\n return this.get(floor.id);\n }\n\n toDto(floor: Floor): FloorDto {\n return {\n id: floor.id,\n name: floor.name,\n order: floor.order,\n printers: floor.printers.map((p) => ({\n printerId: p.printerId,\n floorId: p.floorId,\n x: p.x,\n y: p.y,\n })),\n };\n }\n\n /**\n * This is an overwriting method. Any missing data will be deleted, and can cause sql errors if causing wrong constraints.\n * Merge data before calling this function.\n * @param floorId\n * @param update\n */\n async update(floorId: number, update: UpdateFloorDto) {\n const existingFloor = await this.get(floorId);\n const floorUpdate = {\n ...existingFloor,\n name: update.name,\n printers: update.printers,\n order: update.order,\n };\n const validatedFloor = await validateInput(floorUpdate, createOrUpdateFloorSchema);\n\n // Add new printer positions\n const desiredPositions = validatedFloor.printers;\n if (desiredPositions?.length) {\n for (const printer of desiredPositions) {\n await this.addOrUpdatePrinter(existingFloor.id, printer as PositionDto);\n }\n\n // Remove any printers that should not exist on floor\n const undesiredPositions = existingFloor.printers.filter(\n (pos) => !desiredPositions.find((dp) => dp.printerId === pos.printerId),\n );\n if (undesiredPositions?.length) {\n await this.floorPositionService.deleteMany(undesiredPositions.map((pos) => pos.id));\n }\n }\n delete floorUpdate.printers;\n\n // Persist the floor entity changes itself\n return super.update(floorId, floorUpdate);\n }\n\n async updateName(floorId: number, floorName: string) {\n const { name } = await validateInput({ name: floorName }, updateFloorNameSchema);\n\n const floor = await this.get(floorId);\n floor.name = name;\n return this.update(floorId, floor);\n }\n\n async updateOrder(floorId: number, order: number): Promise<Floor> {\n const { order: validOrder } = await validateInput({ order }, updateFloorOrderSchema);\n\n const floor = await this.get(floorId);\n floor.order = validOrder;\n return await this.update(floorId, floor);\n }\n\n async addOrUpdatePrinter(floorId: number, positionDto: PositionDto): Promise<Floor> {\n // Validation only\n await this.get(floorId);\n positionDto.floorId = floorId;\n const validInput = await validateInput(positionDto, printerInFloorSchema);\n\n const position = await this.floorPositionService.findPrinterPosition(validInput.printerId);\n // Optimization if position is in desired state already\n if (\n position?.floorId === floorId &&\n position.x === validInput.x &&\n position.y === validInput.y &&\n position.printerId === validInput.printerId\n ) {\n return this.get(floorId);\n }\n\n // Clean up the printer's position\n if (position) {\n await this.floorPositionService.delete(position.id);\n }\n\n const xyPosition = await this.floorPositionService.findPosition(floorId, validInput.x, validInput.y);\n if (xyPosition) {\n await this.floorPositionService.delete(xyPosition.id);\n }\n\n const newPosition = new FloorPosition();\n Object.assign(newPosition, {\n x: validInput.x,\n y: validInput.y,\n printerId: validInput.printerId,\n floorId,\n });\n\n await this.floorPositionService.create(newPosition);\n return this.get(floorId);\n }\n\n async removePrinter(floorId: number, printerId: number): Promise<Floor> {\n const position = await this.floorPositionService.findPrinterPositionOnFloor(floorId, printerId);\n if (!position) {\n throw new NotFoundException(\"This printer was not found on this floor\");\n }\n await this.floorPositionService.delete(position.id);\n return await this.get(floorId);\n }\n\n async deletePrinterFromAnyFloor(printerId: number): Promise<void> {\n await this.floorPositionService.deletePrinterPositionsByPrinterId(printerId);\n }\n}\n"],"mappings":";;;;;;;;;AAgBA,IAAa,eAAb,cACU,YAAY,OAAO,UAAU,gBAAgB,eAAe,CAEtE;CACE,YACE,gBACA,sBACA;
|
|
1
|
+
{"version":3,"file":"floor.service.js","names":[],"sources":["../../../src/services/orm/floor.service.ts"],"sourcesContent":["import { BaseService } from \"@/services/orm/base.service\";\nimport { Floor, FloorPosition } from \"@/entities\";\nimport type { IFloorService } from \"@/services/interfaces/floor.service.interface\";\nimport { FloorPositionService } from \"./floor-position.service\";\nimport { TypeormService } from \"@/services/typeorm/typeorm.service\";\nimport { CreateFloorDto, FloorDto, PositionDto, UpdateFloorDto } from \"@/services/interfaces/floor.dto\";\nimport { validateInput } from \"@/handlers/validators\";\nimport {\n createOrUpdateFloorSchema,\n printerInFloorSchema,\n updateFloorOrderSchema,\n updateFloorNameSchema,\n} from \"@/services/validators/floor-service.validation\";\nimport { NotFoundException } from \"@/exceptions/runtime.exceptions\";\nimport { FindManyOptions, FindOneOptions } from \"typeorm\";\n\nexport class FloorService\n extends BaseService(Floor, FloorDto, CreateFloorDto, UpdateFloorDto)\n implements IFloorService\n{\n constructor(\n protected readonly typeormService: TypeormService,\n private readonly floorPositionService: FloorPositionService,\n ) {\n super(typeormService);\n }\n\n override async list(options?: FindManyOptions<Floor>): Promise<Floor[]> {\n return super.list(\n Object.assign(options || {}, {\n relations: [\"printers\"],\n }),\n );\n }\n\n override async get(id: number, options?: FindOneOptions<Floor>): Promise<Floor> {\n return super.get(\n id,\n Object.assign(options || {}, {\n relations: [\"printers\"],\n }),\n );\n }\n\n async create(dto: CreateFloorDto): Promise<Floor> {\n const outcome = await validateInput(dto, createOrUpdateFloorSchema);\n\n const floor = await super.create({\n name: outcome.name,\n order: outcome.order,\n printers: [],\n });\n\n if (outcome.printers?.length) {\n for (const position of outcome.printers) {\n await this.addOrUpdatePrinter(floor.id, position as PositionDto);\n }\n }\n\n return this.get(floor.id);\n }\n\n toDto(floor: Floor): FloorDto {\n return {\n id: floor.id,\n name: floor.name,\n order: floor.order,\n printers: floor.printers.map((p) => ({\n printerId: p.printerId,\n floorId: p.floorId,\n x: p.x,\n y: p.y,\n })),\n };\n }\n\n /**\n * This is an overwriting method. Any missing data will be deleted, and can cause sql errors if causing wrong constraints.\n * Merge data before calling this function.\n * @param floorId\n * @param update\n */\n async update(floorId: number, update: UpdateFloorDto) {\n const existingFloor = await this.get(floorId);\n const floorUpdate = {\n ...existingFloor,\n name: update.name,\n printers: update.printers,\n order: update.order,\n };\n const validatedFloor = await validateInput(floorUpdate, createOrUpdateFloorSchema);\n\n // Add new printer positions\n const desiredPositions = validatedFloor.printers;\n if (desiredPositions?.length) {\n for (const printer of desiredPositions) {\n await this.addOrUpdatePrinter(existingFloor.id, printer as PositionDto);\n }\n\n // Remove any printers that should not exist on floor\n const undesiredPositions = existingFloor.printers.filter(\n (pos) => !desiredPositions.find((dp) => dp.printerId === pos.printerId),\n );\n if (undesiredPositions?.length) {\n await this.floorPositionService.deleteMany(undesiredPositions.map((pos) => pos.id));\n }\n }\n delete floorUpdate.printers;\n\n // Persist the floor entity changes itself\n return super.update(floorId, floorUpdate);\n }\n\n async updateName(floorId: number, floorName: string) {\n const { name } = await validateInput({ name: floorName }, updateFloorNameSchema);\n\n const floor = await this.get(floorId);\n floor.name = name;\n return this.update(floorId, floor);\n }\n\n async updateOrder(floorId: number, order: number): Promise<Floor> {\n const { order: validOrder } = await validateInput({ order }, updateFloorOrderSchema);\n\n const floor = await this.get(floorId);\n floor.order = validOrder;\n return await this.update(floorId, floor);\n }\n\n async addOrUpdatePrinter(floorId: number, positionDto: PositionDto): Promise<Floor> {\n // Validation only\n await this.get(floorId);\n positionDto.floorId = floorId;\n const validInput = await validateInput(positionDto, printerInFloorSchema);\n\n const position = await this.floorPositionService.findPrinterPosition(validInput.printerId);\n // Optimization if position is in desired state already\n if (\n position?.floorId === floorId &&\n position.x === validInput.x &&\n position.y === validInput.y &&\n position.printerId === validInput.printerId\n ) {\n return this.get(floorId);\n }\n\n // Clean up the printer's position\n if (position) {\n await this.floorPositionService.delete(position.id);\n }\n\n const xyPosition = await this.floorPositionService.findPosition(floorId, validInput.x, validInput.y);\n if (xyPosition) {\n await this.floorPositionService.delete(xyPosition.id);\n }\n\n const newPosition = new FloorPosition();\n Object.assign(newPosition, {\n x: validInput.x,\n y: validInput.y,\n printerId: validInput.printerId,\n floorId,\n });\n\n await this.floorPositionService.create(newPosition);\n return this.get(floorId);\n }\n\n async removePrinter(floorId: number, printerId: number): Promise<Floor> {\n const position = await this.floorPositionService.findPrinterPositionOnFloor(floorId, printerId);\n if (!position) {\n throw new NotFoundException(\"This printer was not found on this floor\");\n }\n await this.floorPositionService.delete(position.id);\n return await this.get(floorId);\n }\n\n async deletePrinterFromAnyFloor(printerId: number): Promise<void> {\n await this.floorPositionService.deletePrinterPositionsByPrinterId(printerId);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permission.service.js","names":[],"sources":["../../../src/services/orm/permission.service.ts"],"sourcesContent":["import type { IPermissionService } from \"@/services/interfaces/permission.service.interface\";\nimport { flattenPermissionDefinition, type PermissionName } from \"@/constants/authorization.constants\";\n\nexport class PermissionService implements IPermissionService {\n private _permissions: PermissionName[] = [];\n\n get permissions(): PermissionName[] {\n return this._permissions;\n }\n\n authorizePermission(requiredPermission: PermissionName, assignedPermissions: PermissionName[]): boolean {\n return assignedPermissions.includes(requiredPermission);\n }\n\n async syncPermissions(): Promise<void> {\n this._permissions = flattenPermissionDefinition();\n }\n}\n"],"mappings":";;AAGA,IAAa,oBAAb,MAA6D;CAC3D,eAAyC,EAAE;CAE3C,IAAI,cAAgC;
|
|
1
|
+
{"version":3,"file":"permission.service.js","names":[],"sources":["../../../src/services/orm/permission.service.ts"],"sourcesContent":["import type { IPermissionService } from \"@/services/interfaces/permission.service.interface\";\nimport { flattenPermissionDefinition, type PermissionName } from \"@/constants/authorization.constants\";\n\nexport class PermissionService implements IPermissionService {\n private _permissions: PermissionName[] = [];\n\n get permissions(): PermissionName[] {\n return this._permissions;\n }\n\n authorizePermission(requiredPermission: PermissionName, assignedPermissions: PermissionName[]): boolean {\n return assignedPermissions.includes(requiredPermission);\n }\n\n async syncPermissions(): Promise<void> {\n this._permissions = flattenPermissionDefinition();\n }\n}\n"],"mappings":";;AAGA,IAAa,oBAAb,MAA6D;CAC3D,eAAyC,EAAE;CAE3C,IAAI,cAAgC;EAClC,OAAO,KAAK;;CAGd,oBAAoB,oBAAoC,qBAAgD;EACtG,OAAO,oBAAoB,SAAS,mBAAmB;;CAGzD,MAAM,kBAAiC;EACrC,KAAK,eAAe,6BAA6B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"print-job.service.js","names":[],"sources":["../../../src/services/orm/print-job.service.ts"],"sourcesContent":["import { Repository } from \"typeorm\";\nimport { PrintJob, PrintJobMetadata } from \"@/entities/print-job.entity\";\nimport EventEmitter2 from \"eventemitter2\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { TypeormService } from \"@/services/typeorm/typeorm.service\";\nimport { NotFoundException } from \"@/exceptions/runtime.exceptions\";\nimport {\n updateStatisticsForCompletion,\n updateStatisticsForFailure,\n updateStatisticsForCancellation,\n calculateJobDuration,\n} from \"@/utils/job-stats.util\";\n\n// Events emitted by this service\nexport interface PrintJobAnalyzedEvent {\n jobId: number;\n printerId: number;\n metadata: PrintJobMetadata;\n}\n\nexport interface PrintJobStartedEvent {\n jobId: number;\n printerId: number;\n fileName: string;\n startedAt: Date;\n}\n\nexport interface PrintJobProgressEvent {\n jobId: number;\n printerId: number;\n progress: number;\n currentLayer?: number;\n totalLayers?: number;\n}\n\nexport interface PrintJobCompletedEvent {\n jobId: number;\n printerId: number;\n fileName: string;\n actualTimeSeconds: number;\n estimatedTimeSeconds?: number;\n}\n\nexport interface PrintJobFailedEvent {\n jobId: number;\n printerId: number;\n fileName: string;\n reason: string;\n failedAt: Date;\n}\n\nexport interface PrintJobCancelledEvent {\n jobId: number;\n printerId: number;\n fileName: string;\n cancelledAt: Date;\n}\n\nexport interface IPrintJobService {\n handleFileAnalyzed(jobId: number, metadata: PrintJobMetadata, thumbnails?: any[]): Promise<PrintJob>;\n handlePrintStarted(printerId: number, fileName: string, jobId?: number, printerName?: string): Promise<PrintJob>;\n handlePrintProgress(\n printerId: number,\n progress: number,\n currentLayer?: number,\n totalLayers?: number,\n ): Promise<PrintJob | null>;\n handlePrintCompleted(printerId: number, fileName?: string): Promise<PrintJob | null>;\n handlePrintFailed(printerId: number, reason: string, fileName?: string): Promise<PrintJob | null>;\n handlePrintCancelled(printerId: number, reason?: string): Promise<PrintJob | null>;\n handlePrintPaused(printerId: number): Promise<PrintJob | null>;\n handlePrintResumed(printerId: number): Promise<PrintJob | null>;\n cleanupStaleJobs(): Promise<void>;\n getActivePrintJob(printerId: number): Promise<PrintJob | null>;\n getPrintJobHistory(printerId: number, limit?: number): Promise<PrintJob[]>;\n\n // Helper methods for printer middleware\n markStarted(printerId: number, fileName: string, printerName?: string): Promise<PrintJob>;\n markProgress(\n printerId: number,\n fileName: string,\n progress: number,\n currentLayer?: number,\n totalLayers?: number,\n ): Promise<PrintJob | null>;\n markFinished(printerId: number, fileName: string): Promise<PrintJob | null>;\n markFailed(printerId: number, fileName: string, reason: string): Promise<PrintJob | null>;\n updateJobMetadata(printerId: number, fileName: string, partialMetadata: Partial<PrintJobMetadata>): Promise<void>;\n\n // Search methods for API\n searchPrintJobs(searchPrinter?: string, searchFile?: string, startDate?: Date, endDate?: Date): Promise<PrintJob[]>;\n searchPrintJobsPaged(\n searchPrinter?: string,\n searchFile?: string,\n startDate?: Date,\n endDate?: Date,\n page?: number,\n pageSize?: number,\n ): Promise<[PrintJob[], number]>;\n\n // Job creation\n createPendingJob(\n printerId: number,\n fileName: string,\n metadata: PrintJobMetadata,\n printerName?: string,\n ): Promise<PrintJob>;\n\n // File download and analysis\n triggerFileAnalysis(jobId: number): Promise<void>;\n\n // Manual status updates\n markAsCompleted(jobId: number, reason?: string): Promise<PrintJob>;\n markAsFailed(jobId: number, reason: string): Promise<PrintJob>;\n markAsCancelled(jobId: number, reason?: string): Promise<PrintJob>;\n markAsUnknown(jobId: number, reason?: string): Promise<PrintJob>;\n\n // File reference counting\n countJobsReferencingFile(fileStorageId: string): Promise<number>;\n\n // Job management\n updateJob(job: PrintJob): Promise<PrintJob>;\n deleteJob(job: PrintJob): Promise<void>;\n}\n\nexport class PrintJobService implements IPrintJobService {\n printJobRepository: Repository<PrintJob>;\n eventEmitter2: EventEmitter2;\n private readonly logger;\n\n constructor(loggerFactory: ILoggerFactory, typeormService: TypeormService, eventEmitter2: EventEmitter2) {\n this.printJobRepository = typeormService.getDataSource().getRepository(PrintJob);\n this.eventEmitter2 = eventEmitter2;\n this.logger = loggerFactory(PrintJobService.name);\n }\n\n async getJobByIdOrFail(id: number, relations?: string[]): Promise<PrintJob> {\n const job = await this.printJobRepository.findOne({\n where: { id },\n relations,\n });\n if (!job) {\n throw new NotFoundException(`Job ${id} not found`);\n }\n return job;\n }\n\n async handleFileAnalyzed(jobId: number, metadata: PrintJobMetadata, thumbnails?: any[]): Promise<PrintJob> {\n const job = await this.printJobRepository.findOne({ where: { id: jobId } });\n if (!job) {\n throw new Error(`Print job ${jobId} not found`);\n }\n\n job.metadata = metadata;\n job.analysisState = \"ANALYZED\";\n job.analyzedAt = new Date();\n // Status remains unchanged - analysis state is separate from job status\n job.fileFormat = metadata.fileFormat;\n\n await this.printJobRepository.save(job);\n\n this.eventEmitter2.emit(\"printJob.analyzed\", {\n jobId: job.id,\n printerId: job.printerId,\n metadata,\n } as PrintJobAnalyzedEvent);\n\n this.logger.log(`Print job ${jobId} analyzed: ${metadata.fileName}`);\n return job;\n }\n\n async handlePrintStarted(\n printerId: number,\n fileName: string,\n jobId?: number,\n printerName?: string,\n ): Promise<PrintJob> {\n const existingJob = await this.printJobRepository.findOne({\n where: { printerId, status: \"PRINTING\" },\n order: { startedAt: \"DESC\" },\n });\n\n if (existingJob?.fileName === fileName && !jobId) {\n return existingJob;\n }\n\n // Different file is printing - mark the old one as UNKNOWN\n if (existingJob && existingJob.fileName !== fileName && !jobId) {\n existingJob.status = \"UNKNOWN\";\n existingJob.statusReason =\n \"Print state unknown - printer started new job while previous job was still marked as printing. \" +\n \"This may indicate a disconnect, server restart, or manual printer control.\";\n existingJob.endedAt = new Date();\n await this.printJobRepository.save(existingJob);\n\n this.logger.warn(\n `Printer ${printerId} started new print \"${fileName}\" while job ${existingJob.id} was PRINTING \"${existingJob.fileName}\". ` +\n `Marked job ${existingJob.id} as UNKNOWN.`,\n );\n }\n\n let job: PrintJob | null;\n if (jobId) {\n job = await this.printJobRepository.findOne({ where: { id: jobId } });\n if (!job) {\n throw new Error(`Print job ${jobId} not found`);\n }\n } else {\n // First, look for a PENDING job (created during upload)\n job = await this.printJobRepository.findOne({\n where: { printerId, fileName, status: \"PENDING\" },\n order: { createdAt: \"DESC\" },\n });\n\n // If no pending/analyzed job, look for any recent job with this filename\n if (!job) {\n job = await this.printJobRepository.findOne({\n where: { printerId, fileName },\n order: { createdAt: \"DESC\" },\n });\n }\n\n // If still no job, create a new one\n if (!job) {\n this.logger.log(`Creating new job for ${fileName} - no pending or existing job found`);\n job = this.printJobRepository.create({\n printerId,\n printerName: printerName || null,\n fileName,\n status: \"PRINTING\",\n analysisState: \"NOT_ANALYZED\",\n });\n } else if (job.status === \"PENDING\") {\n this.logger.log(`Promoting pending job ${job.id} to PRINTING`);\n } else if (job.status === \"COMPLETED\" || job.status === \"FAILED\" || job.status === \"CANCELLED\") {\n // Don't reuse completed jobs - this is a re-print, create new job\n this.logger.log(`Creating new job for re-print of ${fileName} (previous job ${job.id} was ${job.status})`);\n job = this.printJobRepository.create({\n printerId,\n printerName: printerName || job.printerName || null,\n fileName,\n status: \"PRINTING\",\n analysisState: job.analysisState, // Keep analysis state from previous print\n metadata: job.metadata, // Reuse metadata from previous print\n fileFormat: job.fileFormat,\n });\n }\n }\n\n job.status = \"PRINTING\";\n job.startedAt = new Date();\n job.progress = 0;\n\n // Set/update printerName if provided\n if (printerName && !job.printerName) {\n job.printerName = printerName;\n }\n\n if (!job.statistics) {\n job.statistics = {\n startedAt: new Date(),\n endedAt: null,\n actualPrintTimeSeconds: null,\n progress: 0,\n };\n } else {\n job.statistics.startedAt = new Date();\n job.statistics.progress = 0;\n }\n\n await this.printJobRepository.save(job);\n\n this.eventEmitter2.emit(\"printJob.started\", {\n jobId: job.id,\n printerId,\n fileName,\n startedAt: job.startedAt,\n } as PrintJobStartedEvent);\n\n this.logger.log(`Print job ${job.id} started on printer ${printerId}: ${fileName}`);\n return job;\n }\n\n async handlePrintProgress(printerId: number, progress: number): Promise<PrintJob | null> {\n const job = await this.printJobRepository.findOne({\n where: { printerId, status: \"PRINTING\" },\n order: { startedAt: \"DESC\" },\n });\n\n if (!job) {\n return null;\n }\n\n job.progress = Math.min(100, Math.max(0, progress));\n\n if (job.statistics) {\n job.statistics.progress = job.progress;\n } else {\n job.statistics = {\n startedAt: job.startedAt,\n endedAt: null,\n actualPrintTimeSeconds: null,\n progress: job.progress,\n };\n }\n\n await this.printJobRepository.save(job);\n\n this.eventEmitter2.emit(\"printJob.progress\", {\n jobId: job.id,\n printerId,\n progress: job.progress,\n } as PrintJobProgressEvent);\n\n return job;\n }\n\n async handlePrintCompleted(printerId: number, fileName?: string): Promise<PrintJob | null> {\n const job = await this.printJobRepository.findOne({\n where: { printerId, status: \"PRINTING\" },\n order: { startedAt: \"DESC\" },\n });\n\n if (!job) {\n this.logger.warn(`No active print job found for printer ${printerId} on completion`);\n return null;\n }\n\n if (fileName && job.fileName !== fileName) {\n this.logger.warn(`Filename mismatch on completion: expected \"${job.fileName}\", got \"${fileName}\"`);\n }\n\n const endedAt = new Date();\n const actualTimeSeconds = calculateJobDuration(job.startedAt, endedAt);\n\n job.status = \"COMPLETED\";\n updateStatisticsForCompletion(job, endedAt);\n\n await this.printJobRepository.save(job);\n\n this.eventEmitter2.emit(\"printJob.completed\", {\n jobId: job.id,\n printerId,\n fileName: job.fileName,\n actualTimeSeconds,\n estimatedTimeSeconds: job.metadata?.gcodePrintTimeSeconds,\n } as PrintJobCompletedEvent);\n\n this.logger.log(\n `Print job ${job.id} completed on printer ${printerId}: ${job.fileName} ` +\n `(${actualTimeSeconds?.toFixed(0)}s actual, ${job.metadata?.gcodePrintTimeSeconds}s estimated)`,\n );\n\n return job;\n }\n\n async handlePrintFailed(printerId: number, reason: string, fileName?: string): Promise<PrintJob | null> {\n const job = await this.printJobRepository.findOne({\n where: { printerId, status: \"PRINTING\" },\n order: { startedAt: \"DESC\" },\n });\n\n if (!job) {\n this.logger.warn(`No active print job found for printer ${printerId} on failure`);\n return null;\n }\n\n const endedAt = new Date();\n\n job.status = \"FAILED\";\n updateStatisticsForFailure(job, reason, endedAt);\n\n await this.printJobRepository.save(job);\n\n this.eventEmitter2.emit(\"printJob.failed\", {\n jobId: job.id,\n printerId,\n fileName: job.fileName,\n reason,\n failedAt: endedAt,\n } as PrintJobFailedEvent);\n\n this.logger.log(`Print job ${job.id} failed on printer ${printerId}: ${job.fileName} - ${reason}`);\n\n return job;\n }\n\n async handlePrintCancelled(printerId: number, reason?: string): Promise<PrintJob | null> {\n const job = await this.printJobRepository.findOne({\n where: { printerId, status: \"PRINTING\" },\n order: { startedAt: \"DESC\" },\n });\n\n if (!job) {\n this.logger.warn(`No active print job found for printer ${printerId} on cancellation`);\n return null;\n }\n\n const endedAt = new Date();\n\n job.status = \"CANCELLED\";\n updateStatisticsForCancellation(job, reason, endedAt);\n\n await this.printJobRepository.save(job);\n\n this.eventEmitter2.emit(\"printJob.cancelled\", {\n jobId: job.id,\n printerId,\n fileName: job.fileName,\n cancelledAt: endedAt,\n } as PrintJobCancelledEvent);\n\n this.logger.log(`Print job ${job.id} cancelled on printer ${printerId}: ${job.fileName}`);\n return job;\n }\n\n async handlePrintPaused(printerId: number): Promise<PrintJob | null> {\n const job = await this.printJobRepository.findOne({\n where: { printerId, status: \"PRINTING\" },\n order: { startedAt: \"DESC\" },\n });\n\n if (!job) {\n this.logger.warn(`No active print job found for printer ${printerId} on pause`);\n return null;\n }\n\n job.status = \"PAUSED\";\n await this.printJobRepository.save(job);\n\n this.logger.log(`Print job ${job.id} paused on printer ${printerId}`);\n return job;\n }\n\n async handlePrintResumed(printerId: number): Promise<PrintJob | null> {\n const job = await this.printJobRepository.findOne({\n where: { printerId, status: \"PAUSED\" },\n order: { startedAt: \"DESC\" },\n });\n\n if (!job) {\n this.logger.warn(`No paused print job found for printer ${printerId} on resume`);\n return null;\n }\n\n job.status = \"PRINTING\";\n await this.printJobRepository.save(job);\n\n this.logger.log(`Print job ${job.id} resumed on printer ${printerId}`);\n return job;\n }\n\n async cleanupStaleJobs(): Promise<void> {\n const staleJobs = await this.printJobRepository.find({\n where: { status: \"PRINTING\" },\n });\n\n for (const job of staleJobs) {\n job.status = \"UNKNOWN\";\n job.statusReason =\n \"Print state unknown after server restart. The print may have completed, \" +\n \"failed, or still be running. Check your printer for current status.\";\n await this.printJobRepository.save(job);\n\n this.logger.warn(\n `Marked job ${job.id} (printer ${job.printerId}) as UNKNOWN after startup - ` +\n `was PRINTING before server stopped`,\n );\n }\n\n if (staleJobs.length > 0) {\n this.logger.log(`Cleaned up ${staleJobs.length} stale print job(s) on startup`);\n }\n }\n\n async getActivePrintJob(printerId: number): Promise<PrintJob | null> {\n return this.printJobRepository.findOne({\n where: { printerId, status: \"PRINTING\" },\n order: { startedAt: \"DESC\" },\n });\n }\n\n async getPrintJobHistory(printerId: number, limit: number = 50): Promise<PrintJob[]> {\n return this.printJobRepository.find({\n where: { printerId },\n order: { createdAt: \"DESC\" },\n take: limit,\n });\n }\n\n async markStarted(printerId: number, fileName: string, printerName?: string): Promise<PrintJob> {\n return await this.handlePrintStarted(printerId, fileName, undefined, printerName);\n }\n\n async markProgress(printerId: number, fileName: string, progress: number): Promise<PrintJob | null> {\n return await this.handlePrintProgress(printerId, progress);\n }\n\n /**\n * Mark print as finished by fileName\n * Used by printer middleware when print completes successfully\n */\n async markFinished(printerId: number, fileName: string): Promise<PrintJob | null> {\n return await this.handlePrintCompleted(printerId, fileName);\n }\n\n /**\n * Mark print as failed by fileName\n * Used by printer middleware when print fails\n */\n async markFailed(printerId: number, fileName: string, reason: string): Promise<PrintJob | null> {\n return await this.handlePrintFailed(printerId, reason, fileName);\n }\n\n /**\n * Update job metadata with partial data from printer middleware\n * Useful for updating estimates from OctoPrint/Moonraker during print\n * Only updates if job has no metadata or to supplement existing metadata\n */\n async updateJobMetadata(\n printerId: number,\n fileName: string,\n partialMetadata: Partial<PrintJobMetadata>,\n ): Promise<void> {\n const job = await this.printJobRepository.findOne({\n where: { printerId, fileName, status: \"PRINTING\" },\n order: { startedAt: \"DESC\" },\n });\n\n if (!job) {\n this.logger.debug(`No active job found for printer ${printerId}, file ${fileName} - skipping metadata update`);\n return;\n }\n\n // If job already has ANALYZED metadata, don't overwrite with incomplete data\n if (job.analysisState === \"ANALYZED\" && job.metadata) {\n this.logger.debug(`Job ${job.id} already has analyzed metadata, merging only missing fields`);\n // Only merge in fields that are currently null/undefined\n const updatedMetadata = { ...job.metadata } as Record<string, any>;\n for (const [key, value] of Object.entries(partialMetadata)) {\n if (value != null && (updatedMetadata[key] == null || updatedMetadata[key] === null)) {\n updatedMetadata[key] = value;\n }\n }\n job.metadata = updatedMetadata as PrintJobMetadata;\n } else if (job.metadata) {\n // Merge partial metadata with existing metadata\n job.metadata = {\n ...job.metadata,\n ...partialMetadata,\n } as PrintJobMetadata;\n } else {\n // No metadata exists - only create if we have meaningful data\n // Don't create metadata structure with mostly nulls\n const hasData = Object.values(partialMetadata).some((v) => v !== null);\n if (!hasData) {\n this.logger.debug(`Skipping metadata creation for job ${job.id} - no meaningful data provided`);\n return;\n }\n\n // Create minimal metadata structure\n job.metadata = {\n fileName,\n fileFormat: job.fileFormat || \"gcode\", // Use existing fileFormat or default\n ...partialMetadata,\n } as PrintJobMetadata;\n }\n\n await this.printJobRepository.save(job);\n this.logger.debug(`Updated metadata for job ${job.id}`);\n }\n\n // ========== Search methods for API ==========\n\n /**\n * Search print jobs with optional filters\n */\n async searchPrintJobs(\n searchPrinter?: string,\n searchFile?: string,\n startDate?: Date,\n endDate?: Date,\n ): Promise<PrintJob[]> {\n const query = this.printJobRepository.createQueryBuilder(\"job\");\n\n if (searchPrinter) {\n query.andWhere(\"job.printerId = :printerId\", { printerId: Number.parseInt(searchPrinter, 10) });\n }\n\n if (searchFile) {\n query.andWhere(\"job.fileName LIKE :fileName\", { fileName: `%${searchFile}%` });\n }\n\n if (startDate) {\n query.andWhere(\"job.startedAt >= :startDate\", { startDate });\n }\n\n if (endDate) {\n query.andWhere(\"job.startedAt <= :endDate\", { endDate });\n }\n\n return await query.orderBy(\"job.startedAt\", \"DESC\").getMany();\n }\n\n /**\n * Search print jobs with pagination\n */\n async searchPrintJobsPaged(\n searchPrinter?: string,\n searchFile?: string,\n startDate?: Date,\n endDate?: Date,\n page: number = 1,\n pageSize: number = 50,\n ): Promise<[PrintJob[], number]> {\n const query = this.printJobRepository.createQueryBuilder(\"job\");\n\n if (searchPrinter) {\n query.andWhere(\"job.printerId = :printerId\", { printerId: Number.parseInt(searchPrinter, 10) });\n }\n\n if (searchFile) {\n query.andWhere(\"job.fileName LIKE :fileName\", { fileName: `%${searchFile}%` });\n }\n\n if (startDate) {\n query.andWhere(\"job.startedAt >= :startDate\", { startDate });\n }\n\n if (endDate) {\n query.andWhere(\"job.startedAt <= :endDate\", { endDate });\n }\n\n return await query\n .orderBy(\"job.startedAt\", \"DESC\")\n .skip((page - 1) * pageSize)\n .take(pageSize)\n .getManyAndCount();\n }\n\n // ========== Job creation ==========\n\n /**\n * Create a pending print job (typically called when file is uploaded)\n * Used when a file is uploaded to a printer, creating a job ready for analysis\n */\n async createPendingJob(\n printerId: number,\n fileName: string,\n metadata: PrintJobMetadata,\n printerName?: string,\n ): Promise<PrintJob> {\n // Determine if metadata contains meaningful analysis data\n // (more than just basic file info)\n const hasAnalysisData =\n metadata.gcodePrintTimeSeconds !== null ||\n metadata.filamentUsedGrams !== null ||\n metadata.totalFilamentUsedGrams !== null ||\n metadata.layerHeight !== null ||\n metadata.totalLayers !== null;\n\n const analysisState = hasAnalysisData ? \"ANALYZED\" : \"NOT_ANALYZED\";\n // Status is always PENDING for newly created jobs (analysis state is separate)\n const status = \"PENDING\";\n\n const job = this.printJobRepository.create({\n printerId,\n printerName: printerName || null,\n fileName,\n status,\n analysisState,\n metadata,\n fileFormat: metadata.fileFormat,\n fileSize: metadata.fileSize,\n analyzedAt: hasAnalysisData ? new Date() : null,\n });\n\n await this.printJobRepository.save(job);\n\n this.logger.log(\n `Created ${analysisState.toLowerCase()} print job ${job.id} for printer ${printerId}: ${fileName} (format: ${metadata.fileFormat})`,\n );\n return job;\n }\n\n /**\n * Trigger file analysis for a job (emits event for async processing)\n * Used when a print starts from a file on the printer that we don't have locally\n */\n async triggerFileAnalysis(jobId: number): Promise<void> {\n this.eventEmitter2.emit(\"printJob.needsFileDownload\", { jobId });\n this.logger.log(`Triggered file download and analysis for job ${jobId}`);\n }\n\n async markAsCompleted(jobId: number, reason?: string): Promise<PrintJob> {\n const job = await this.getJobByIdOrFail(jobId);\n\n job.status = \"COMPLETED\";\n updateStatisticsForCompletion(job);\n\n if (reason) {\n job.statusReason = reason;\n } else {\n job.statusReason = \"Manually marked as completed by user\";\n }\n\n await this.printJobRepository.save(job);\n\n this.logger.log(`Job ${jobId} manually marked as COMPLETED`);\n\n return job;\n }\n\n async markAsFailed(jobId: number, reason: string): Promise<PrintJob> {\n const job = await this.getJobByIdOrFail(jobId);\n\n job.status = \"FAILED\";\n updateStatisticsForFailure(job, reason);\n\n await this.printJobRepository.save(job);\n\n this.logger.log(`Job ${jobId} manually marked as FAILED: ${reason}`);\n\n return job;\n }\n\n async markAsCancelled(jobId: number, reason?: string): Promise<PrintJob> {\n const job = await this.getJobByIdOrFail(jobId);\n\n job.status = \"CANCELLED\";\n updateStatisticsForCancellation(job, reason);\n\n await this.printJobRepository.save(job);\n\n this.logger.log(`Job ${jobId} manually marked as CANCELLED`);\n\n return job;\n }\n\n async markAsUnknown(jobId: number, reason?: string): Promise<PrintJob> {\n const job = await this.getJobByIdOrFail(jobId);\n\n job.status = \"UNKNOWN\";\n job.statusReason = reason || \"Manually marked as unknown by user (state uncertain)\";\n\n await this.printJobRepository.save(job);\n\n this.logger.log(`Job ${jobId} manually marked as UNKNOWN`);\n\n return job;\n }\n\n async countJobsReferencingFile(fileStorageId: string): Promise<number> {\n return await this.printJobRepository.count({\n where: { fileStorageId },\n });\n }\n\n async updateJob(job: PrintJob): Promise<PrintJob> {\n return await this.printJobRepository.save(job);\n }\n\n async deleteJob(job: PrintJob): Promise<void> {\n await this.printJobRepository.remove(job);\n }\n}\n"],"mappings":";;;;AA6HA,IAAa,kBAAb,MAAa,gBAA4C;CACvD;CACA;CACA;CAEA,YAAY,eAA+B,gBAAgC,eAA8B;AACvG,OAAK,qBAAqB,eAAe,eAAe,CAAC,cAAc,SAAS;AAChF,OAAK,gBAAgB;AACrB,OAAK,SAAS,cAAc,gBAAgB,KAAK;;CAGnD,MAAM,iBAAiB,IAAY,WAAyC;EAC1E,MAAM,MAAM,MAAM,KAAK,mBAAmB,QAAQ;GAChD,OAAO,EAAE,IAAI;GACb;GACD,CAAC;AACF,MAAI,CAAC,IACH,OAAM,IAAI,kBAAkB,OAAO,GAAG,YAAY;AAEpD,SAAO;;CAGT,MAAM,mBAAmB,OAAe,UAA4B,YAAuC;EACzG,MAAM,MAAM,MAAM,KAAK,mBAAmB,QAAQ,EAAE,OAAO,EAAE,IAAI,OAAO,EAAE,CAAC;AAC3E,MAAI,CAAC,IACH,OAAM,IAAI,MAAM,aAAa,MAAM,YAAY;AAGjD,MAAI,WAAW;AACf,MAAI,gBAAgB;AACpB,MAAI,6BAAa,IAAI,MAAM;AAE3B,MAAI,aAAa,SAAS;AAE1B,QAAM,KAAK,mBAAmB,KAAK,IAAI;AAEvC,OAAK,cAAc,KAAK,qBAAqB;GAC3C,OAAO,IAAI;GACX,WAAW,IAAI;GACf;GACD,CAA0B;AAE3B,OAAK,OAAO,IAAI,aAAa,MAAM,aAAa,SAAS,WAAW;AACpE,SAAO;;CAGT,MAAM,mBACJ,WACA,UACA,OACA,aACmB;EACnB,MAAM,cAAc,MAAM,KAAK,mBAAmB,QAAQ;GACxD,OAAO;IAAE;IAAW,QAAQ;IAAY;GACxC,OAAO,EAAE,WAAW,QAAQ;GAC7B,CAAC;AAEF,MAAI,aAAa,aAAa,YAAY,CAAC,MACzC,QAAO;AAIT,MAAI,eAAe,YAAY,aAAa,YAAY,CAAC,OAAO;AAC9D,eAAY,SAAS;AACrB,eAAY,eACV;AAEF,eAAY,0BAAU,IAAI,MAAM;AAChC,SAAM,KAAK,mBAAmB,KAAK,YAAY;AAE/C,QAAK,OAAO,KACV,WAAW,UAAU,sBAAsB,SAAS,cAAc,YAAY,GAAG,iBAAiB,YAAY,SAAS,gBACvG,YAAY,GAAG,cAChC;;EAGH,IAAI;AACJ,MAAI,OAAO;AACT,SAAM,MAAM,KAAK,mBAAmB,QAAQ,EAAE,OAAO,EAAE,IAAI,OAAO,EAAE,CAAC;AACrE,OAAI,CAAC,IACH,OAAM,IAAI,MAAM,aAAa,MAAM,YAAY;SAE5C;AAEL,SAAM,MAAM,KAAK,mBAAmB,QAAQ;IAC1C,OAAO;KAAE;KAAW;KAAU,QAAQ;KAAW;IACjD,OAAO,EAAE,WAAW,QAAQ;IAC7B,CAAC;AAGF,OAAI,CAAC,IACH,OAAM,MAAM,KAAK,mBAAmB,QAAQ;IAC1C,OAAO;KAAE;KAAW;KAAU;IAC9B,OAAO,EAAE,WAAW,QAAQ;IAC7B,CAAC;AAIJ,OAAI,CAAC,KAAK;AACR,SAAK,OAAO,IAAI,wBAAwB,SAAS,qCAAqC;AACtF,UAAM,KAAK,mBAAmB,OAAO;KACnC;KACA,aAAa,eAAe;KAC5B;KACA,QAAQ;KACR,eAAe;KAChB,CAAC;cACO,IAAI,WAAW,UACxB,MAAK,OAAO,IAAI,yBAAyB,IAAI,GAAG,cAAc;YACrD,IAAI,WAAW,eAAe,IAAI,WAAW,YAAY,IAAI,WAAW,aAAa;AAE9F,SAAK,OAAO,IAAI,oCAAoC,SAAS,iBAAiB,IAAI,GAAG,OAAO,IAAI,OAAO,GAAG;AAC1G,UAAM,KAAK,mBAAmB,OAAO;KACnC;KACA,aAAa,eAAe,IAAI,eAAe;KAC/C;KACA,QAAQ;KACR,eAAe,IAAI;KACnB,UAAU,IAAI;KACd,YAAY,IAAI;KACjB,CAAC;;;AAIN,MAAI,SAAS;AACb,MAAI,4BAAY,IAAI,MAAM;AAC1B,MAAI,WAAW;AAGf,MAAI,eAAe,CAAC,IAAI,YACtB,KAAI,cAAc;AAGpB,MAAI,CAAC,IAAI,WACP,KAAI,aAAa;GACf,2BAAW,IAAI,MAAM;GACrB,SAAS;GACT,wBAAwB;GACxB,UAAU;GACX;OACI;AACL,OAAI,WAAW,4BAAY,IAAI,MAAM;AACrC,OAAI,WAAW,WAAW;;AAG5B,QAAM,KAAK,mBAAmB,KAAK,IAAI;AAEvC,OAAK,cAAc,KAAK,oBAAoB;GAC1C,OAAO,IAAI;GACX;GACA;GACA,WAAW,IAAI;GAChB,CAAyB;AAE1B,OAAK,OAAO,IAAI,aAAa,IAAI,GAAG,sBAAsB,UAAU,IAAI,WAAW;AACnF,SAAO;;CAGT,MAAM,oBAAoB,WAAmB,UAA4C;EACvF,MAAM,MAAM,MAAM,KAAK,mBAAmB,QAAQ;GAChD,OAAO;IAAE;IAAW,QAAQ;IAAY;GACxC,OAAO,EAAE,WAAW,QAAQ;GAC7B,CAAC;AAEF,MAAI,CAAC,IACH,QAAO;AAGT,MAAI,WAAW,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,SAAS,CAAC;AAEnD,MAAI,IAAI,WACN,KAAI,WAAW,WAAW,IAAI;MAE9B,KAAI,aAAa;GACf,WAAW,IAAI;GACf,SAAS;GACT,wBAAwB;GACxB,UAAU,IAAI;GACf;AAGH,QAAM,KAAK,mBAAmB,KAAK,IAAI;AAEvC,OAAK,cAAc,KAAK,qBAAqB;GAC3C,OAAO,IAAI;GACX;GACA,UAAU,IAAI;GACf,CAA0B;AAE3B,SAAO;;CAGT,MAAM,qBAAqB,WAAmB,UAA6C;EACzF,MAAM,MAAM,MAAM,KAAK,mBAAmB,QAAQ;GAChD,OAAO;IAAE;IAAW,QAAQ;IAAY;GACxC,OAAO,EAAE,WAAW,QAAQ;GAC7B,CAAC;AAEF,MAAI,CAAC,KAAK;AACR,QAAK,OAAO,KAAK,yCAAyC,UAAU,gBAAgB;AACpF,UAAO;;AAGT,MAAI,YAAY,IAAI,aAAa,SAC/B,MAAK,OAAO,KAAK,8CAA8C,IAAI,SAAS,UAAU,SAAS,GAAG;EAGpG,MAAM,0BAAU,IAAI,MAAM;EAC1B,MAAM,oBAAoB,qBAAqB,IAAI,WAAW,QAAQ;AAEtE,MAAI,SAAS;AACb,gCAA8B,KAAK,QAAQ;AAE3C,QAAM,KAAK,mBAAmB,KAAK,IAAI;AAEvC,OAAK,cAAc,KAAK,sBAAsB;GAC5C,OAAO,IAAI;GACX;GACA,UAAU,IAAI;GACd;GACA,sBAAsB,IAAI,UAAU;GACrC,CAA2B;AAE5B,OAAK,OAAO,IACV,aAAa,IAAI,GAAG,wBAAwB,UAAU,IAAI,IAAI,SAAS,IACjE,mBAAmB,QAAQ,EAAE,CAAC,YAAY,IAAI,UAAU,sBAAsB,cACrF;AAED,SAAO;;CAGT,MAAM,kBAAkB,WAAmB,QAAgB,UAA6C;EACtG,MAAM,MAAM,MAAM,KAAK,mBAAmB,QAAQ;GAChD,OAAO;IAAE;IAAW,QAAQ;IAAY;GACxC,OAAO,EAAE,WAAW,QAAQ;GAC7B,CAAC;AAEF,MAAI,CAAC,KAAK;AACR,QAAK,OAAO,KAAK,yCAAyC,UAAU,aAAa;AACjF,UAAO;;EAGT,MAAM,0BAAU,IAAI,MAAM;AAE1B,MAAI,SAAS;AACb,6BAA2B,KAAK,QAAQ,QAAQ;AAEhD,QAAM,KAAK,mBAAmB,KAAK,IAAI;AAEvC,OAAK,cAAc,KAAK,mBAAmB;GACzC,OAAO,IAAI;GACX;GACA,UAAU,IAAI;GACd;GACA,UAAU;GACX,CAAwB;AAEzB,OAAK,OAAO,IAAI,aAAa,IAAI,GAAG,qBAAqB,UAAU,IAAI,IAAI,SAAS,KAAK,SAAS;AAElG,SAAO;;CAGT,MAAM,qBAAqB,WAAmB,QAA2C;EACvF,MAAM,MAAM,MAAM,KAAK,mBAAmB,QAAQ;GAChD,OAAO;IAAE;IAAW,QAAQ;IAAY;GACxC,OAAO,EAAE,WAAW,QAAQ;GAC7B,CAAC;AAEF,MAAI,CAAC,KAAK;AACR,QAAK,OAAO,KAAK,yCAAyC,UAAU,kBAAkB;AACtF,UAAO;;EAGT,MAAM,0BAAU,IAAI,MAAM;AAE1B,MAAI,SAAS;AACb,kCAAgC,KAAK,QAAQ,QAAQ;AAErD,QAAM,KAAK,mBAAmB,KAAK,IAAI;AAEvC,OAAK,cAAc,KAAK,sBAAsB;GAC5C,OAAO,IAAI;GACX;GACA,UAAU,IAAI;GACd,aAAa;GACd,CAA2B;AAE5B,OAAK,OAAO,IAAI,aAAa,IAAI,GAAG,wBAAwB,UAAU,IAAI,IAAI,WAAW;AACzF,SAAO;;CAGT,MAAM,kBAAkB,WAA6C;EACnE,MAAM,MAAM,MAAM,KAAK,mBAAmB,QAAQ;GAChD,OAAO;IAAE;IAAW,QAAQ;IAAY;GACxC,OAAO,EAAE,WAAW,QAAQ;GAC7B,CAAC;AAEF,MAAI,CAAC,KAAK;AACR,QAAK,OAAO,KAAK,yCAAyC,UAAU,WAAW;AAC/E,UAAO;;AAGT,MAAI,SAAS;AACb,QAAM,KAAK,mBAAmB,KAAK,IAAI;AAEvC,OAAK,OAAO,IAAI,aAAa,IAAI,GAAG,qBAAqB,YAAY;AACrE,SAAO;;CAGT,MAAM,mBAAmB,WAA6C;EACpE,MAAM,MAAM,MAAM,KAAK,mBAAmB,QAAQ;GAChD,OAAO;IAAE;IAAW,QAAQ;IAAU;GACtC,OAAO,EAAE,WAAW,QAAQ;GAC7B,CAAC;AAEF,MAAI,CAAC,KAAK;AACR,QAAK,OAAO,KAAK,yCAAyC,UAAU,YAAY;AAChF,UAAO;;AAGT,MAAI,SAAS;AACb,QAAM,KAAK,mBAAmB,KAAK,IAAI;AAEvC,OAAK,OAAO,IAAI,aAAa,IAAI,GAAG,sBAAsB,YAAY;AACtE,SAAO;;CAGT,MAAM,mBAAkC;EACtC,MAAM,YAAY,MAAM,KAAK,mBAAmB,KAAK,EACnD,OAAO,EAAE,QAAQ,YAAY,EAC9B,CAAC;AAEF,OAAK,MAAM,OAAO,WAAW;AAC3B,OAAI,SAAS;AACb,OAAI,eACF;AAEF,SAAM,KAAK,mBAAmB,KAAK,IAAI;AAEvC,QAAK,OAAO,KACV,cAAc,IAAI,GAAG,YAAY,IAAI,UAAU,iEAEhD;;AAGH,MAAI,UAAU,SAAS,EACrB,MAAK,OAAO,IAAI,cAAc,UAAU,OAAO,gCAAgC;;CAInF,MAAM,kBAAkB,WAA6C;AACnE,SAAO,KAAK,mBAAmB,QAAQ;GACrC,OAAO;IAAE;IAAW,QAAQ;IAAY;GACxC,OAAO,EAAE,WAAW,QAAQ;GAC7B,CAAC;;CAGJ,MAAM,mBAAmB,WAAmB,QAAgB,IAAyB;AACnF,SAAO,KAAK,mBAAmB,KAAK;GAClC,OAAO,EAAE,WAAW;GACpB,OAAO,EAAE,WAAW,QAAQ;GAC5B,MAAM;GACP,CAAC;;CAGJ,MAAM,YAAY,WAAmB,UAAkB,aAAyC;AAC9F,SAAO,MAAM,KAAK,mBAAmB,WAAW,UAAU,KAAA,GAAW,YAAY;;CAGnF,MAAM,aAAa,WAAmB,UAAkB,UAA4C;AAClG,SAAO,MAAM,KAAK,oBAAoB,WAAW,SAAS;;;;;;CAO5D,MAAM,aAAa,WAAmB,UAA4C;AAChF,SAAO,MAAM,KAAK,qBAAqB,WAAW,SAAS;;;;;;CAO7D,MAAM,WAAW,WAAmB,UAAkB,QAA0C;AAC9F,SAAO,MAAM,KAAK,kBAAkB,WAAW,QAAQ,SAAS;;;;;;;CAQlE,MAAM,kBACJ,WACA,UACA,iBACe;EACf,MAAM,MAAM,MAAM,KAAK,mBAAmB,QAAQ;GAChD,OAAO;IAAE;IAAW;IAAU,QAAQ;IAAY;GAClD,OAAO,EAAE,WAAW,QAAQ;GAC7B,CAAC;AAEF,MAAI,CAAC,KAAK;AACR,QAAK,OAAO,MAAM,mCAAmC,UAAU,SAAS,SAAS,6BAA6B;AAC9G;;AAIF,MAAI,IAAI,kBAAkB,cAAc,IAAI,UAAU;AACpD,QAAK,OAAO,MAAM,OAAO,IAAI,GAAG,6DAA6D;GAE7F,MAAM,kBAAkB,EAAE,GAAG,IAAI,UAAU;AAC3C,QAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,gBAAgB,CACxD,KAAI,SAAS,SAAS,gBAAgB,QAAQ,QAAQ,gBAAgB,SAAS,MAC7E,iBAAgB,OAAO;AAG3B,OAAI,WAAW;aACN,IAAI,SAEb,KAAI,WAAW;GACb,GAAG,IAAI;GACP,GAAG;GACJ;OACI;AAIL,OAAI,CADY,OAAO,OAAO,gBAAgB,CAAC,MAAM,MAAM,MAAM,KACrD,EAAE;AACZ,SAAK,OAAO,MAAM,sCAAsC,IAAI,GAAG,gCAAgC;AAC/F;;AAIF,OAAI,WAAW;IACb;IACA,YAAY,IAAI,cAAc;IAC9B,GAAG;IACJ;;AAGH,QAAM,KAAK,mBAAmB,KAAK,IAAI;AACvC,OAAK,OAAO,MAAM,4BAA4B,IAAI,KAAK;;;;;CAQzD,MAAM,gBACJ,eACA,YACA,WACA,SACqB;EACrB,MAAM,QAAQ,KAAK,mBAAmB,mBAAmB,MAAM;AAE/D,MAAI,cACF,OAAM,SAAS,8BAA8B,EAAE,WAAW,OAAO,SAAS,eAAe,GAAG,EAAE,CAAC;AAGjG,MAAI,WACF,OAAM,SAAS,+BAA+B,EAAE,UAAU,IAAI,WAAW,IAAI,CAAC;AAGhF,MAAI,UACF,OAAM,SAAS,+BAA+B,EAAE,WAAW,CAAC;AAG9D,MAAI,QACF,OAAM,SAAS,6BAA6B,EAAE,SAAS,CAAC;AAG1D,SAAO,MAAM,MAAM,QAAQ,iBAAiB,OAAO,CAAC,SAAS;;;;;CAM/D,MAAM,qBACJ,eACA,YACA,WACA,SACA,OAAe,GACf,WAAmB,IACY;EAC/B,MAAM,QAAQ,KAAK,mBAAmB,mBAAmB,MAAM;AAE/D,MAAI,cACF,OAAM,SAAS,8BAA8B,EAAE,WAAW,OAAO,SAAS,eAAe,GAAG,EAAE,CAAC;AAGjG,MAAI,WACF,OAAM,SAAS,+BAA+B,EAAE,UAAU,IAAI,WAAW,IAAI,CAAC;AAGhF,MAAI,UACF,OAAM,SAAS,+BAA+B,EAAE,WAAW,CAAC;AAG9D,MAAI,QACF,OAAM,SAAS,6BAA6B,EAAE,SAAS,CAAC;AAG1D,SAAO,MAAM,MACV,QAAQ,iBAAiB,OAAO,CAChC,MAAM,OAAO,KAAK,SAAS,CAC3B,KAAK,SAAS,CACd,iBAAiB;;;;;;CAStB,MAAM,iBACJ,WACA,UACA,UACA,aACmB;EAGnB,MAAM,kBACJ,SAAS,0BAA0B,QACnC,SAAS,sBAAsB,QAC/B,SAAS,2BAA2B,QACpC,SAAS,gBAAgB,QACzB,SAAS,gBAAgB;EAE3B,MAAM,gBAAgB,kBAAkB,aAAa;EAIrD,MAAM,MAAM,KAAK,mBAAmB,OAAO;GACzC;GACA,aAAa,eAAe;GAC5B;GACA,QAAA;GACA;GACA;GACA,YAAY,SAAS;GACrB,UAAU,SAAS;GACnB,YAAY,kCAAkB,IAAI,MAAM,GAAG;GAC5C,CAAC;AAEF,QAAM,KAAK,mBAAmB,KAAK,IAAI;AAEvC,OAAK,OAAO,IACV,WAAW,cAAc,aAAa,CAAC,aAAa,IAAI,GAAG,eAAe,UAAU,IAAI,SAAS,YAAY,SAAS,WAAW,GAClI;AACD,SAAO;;;;;;CAOT,MAAM,oBAAoB,OAA8B;AACtD,OAAK,cAAc,KAAK,8BAA8B,EAAE,OAAO,CAAC;AAChE,OAAK,OAAO,IAAI,gDAAgD,QAAQ;;CAG1E,MAAM,gBAAgB,OAAe,QAAoC;EACvE,MAAM,MAAM,MAAM,KAAK,iBAAiB,MAAM;AAE9C,MAAI,SAAS;AACb,gCAA8B,IAAI;AAElC,MAAI,OACF,KAAI,eAAe;MAEnB,KAAI,eAAe;AAGrB,QAAM,KAAK,mBAAmB,KAAK,IAAI;AAEvC,OAAK,OAAO,IAAI,OAAO,MAAM,+BAA+B;AAE5D,SAAO;;CAGT,MAAM,aAAa,OAAe,QAAmC;EACnE,MAAM,MAAM,MAAM,KAAK,iBAAiB,MAAM;AAE9C,MAAI,SAAS;AACb,6BAA2B,KAAK,OAAO;AAEvC,QAAM,KAAK,mBAAmB,KAAK,IAAI;AAEvC,OAAK,OAAO,IAAI,OAAO,MAAM,8BAA8B,SAAS;AAEpE,SAAO;;CAGT,MAAM,gBAAgB,OAAe,QAAoC;EACvE,MAAM,MAAM,MAAM,KAAK,iBAAiB,MAAM;AAE9C,MAAI,SAAS;AACb,kCAAgC,KAAK,OAAO;AAE5C,QAAM,KAAK,mBAAmB,KAAK,IAAI;AAEvC,OAAK,OAAO,IAAI,OAAO,MAAM,+BAA+B;AAE5D,SAAO;;CAGT,MAAM,cAAc,OAAe,QAAoC;EACrE,MAAM,MAAM,MAAM,KAAK,iBAAiB,MAAM;AAE9C,MAAI,SAAS;AACb,MAAI,eAAe,UAAU;AAE7B,QAAM,KAAK,mBAAmB,KAAK,IAAI;AAEvC,OAAK,OAAO,IAAI,OAAO,MAAM,6BAA6B;AAE1D,SAAO;;CAGT,MAAM,yBAAyB,eAAwC;AACrE,SAAO,MAAM,KAAK,mBAAmB,MAAM,EACzC,OAAO,EAAE,eAAe,EACzB,CAAC;;CAGJ,MAAM,UAAU,KAAkC;AAChD,SAAO,MAAM,KAAK,mBAAmB,KAAK,IAAI;;CAGhD,MAAM,UAAU,KAA8B;AAC5C,QAAM,KAAK,mBAAmB,OAAO,IAAI"}
|
|
1
|
+
{"version":3,"file":"print-job.service.js","names":[],"sources":["../../../src/services/orm/print-job.service.ts"],"sourcesContent":["import { Repository } from \"typeorm\";\nimport { PrintJob, PrintJobMetadata } from \"@/entities/print-job.entity\";\nimport EventEmitter2 from \"eventemitter2\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { TypeormService } from \"@/services/typeorm/typeorm.service\";\nimport { NotFoundException } from \"@/exceptions/runtime.exceptions\";\nimport {\n updateStatisticsForCompletion,\n updateStatisticsForFailure,\n updateStatisticsForCancellation,\n calculateJobDuration,\n} from \"@/utils/job-stats.util\";\n\n// Events emitted by this service\nexport interface PrintJobAnalyzedEvent {\n jobId: number;\n printerId: number;\n metadata: PrintJobMetadata;\n}\n\nexport interface PrintJobStartedEvent {\n jobId: number;\n printerId: number;\n fileName: string;\n startedAt: Date;\n}\n\nexport interface PrintJobProgressEvent {\n jobId: number;\n printerId: number;\n progress: number;\n currentLayer?: number;\n totalLayers?: number;\n}\n\nexport interface PrintJobCompletedEvent {\n jobId: number;\n printerId: number;\n fileName: string;\n actualTimeSeconds: number;\n estimatedTimeSeconds?: number;\n}\n\nexport interface PrintJobFailedEvent {\n jobId: number;\n printerId: number;\n fileName: string;\n reason: string;\n failedAt: Date;\n}\n\nexport interface PrintJobCancelledEvent {\n jobId: number;\n printerId: number;\n fileName: string;\n cancelledAt: Date;\n}\n\nexport interface IPrintJobService {\n handleFileAnalyzed(jobId: number, metadata: PrintJobMetadata, thumbnails?: any[]): Promise<PrintJob>;\n handlePrintStarted(printerId: number, fileName: string, jobId?: number, printerName?: string): Promise<PrintJob>;\n handlePrintProgress(\n printerId: number,\n progress: number,\n currentLayer?: number,\n totalLayers?: number,\n ): Promise<PrintJob | null>;\n handlePrintCompleted(printerId: number, fileName?: string): Promise<PrintJob | null>;\n handlePrintFailed(printerId: number, reason: string, fileName?: string): Promise<PrintJob | null>;\n handlePrintCancelled(printerId: number, reason?: string): Promise<PrintJob | null>;\n handlePrintPaused(printerId: number): Promise<PrintJob | null>;\n handlePrintResumed(printerId: number): Promise<PrintJob | null>;\n cleanupStaleJobs(): Promise<void>;\n getActivePrintJob(printerId: number): Promise<PrintJob | null>;\n getPrintJobHistory(printerId: number, limit?: number): Promise<PrintJob[]>;\n\n // Helper methods for printer middleware\n markStarted(printerId: number, fileName: string, printerName?: string): Promise<PrintJob>;\n markProgress(\n printerId: number,\n fileName: string,\n progress: number,\n currentLayer?: number,\n totalLayers?: number,\n ): Promise<PrintJob | null>;\n markFinished(printerId: number, fileName: string): Promise<PrintJob | null>;\n markFailed(printerId: number, fileName: string, reason: string): Promise<PrintJob | null>;\n updateJobMetadata(printerId: number, fileName: string, partialMetadata: Partial<PrintJobMetadata>): Promise<void>;\n\n // Search methods for API\n searchPrintJobs(searchPrinter?: string, searchFile?: string, startDate?: Date, endDate?: Date): Promise<PrintJob[]>;\n searchPrintJobsPaged(\n searchPrinter?: string,\n searchFile?: string,\n startDate?: Date,\n endDate?: Date,\n page?: number,\n pageSize?: number,\n ): Promise<[PrintJob[], number]>;\n\n // Job creation\n createPendingJob(\n printerId: number,\n fileName: string,\n metadata: PrintJobMetadata,\n printerName?: string,\n ): Promise<PrintJob>;\n\n // File download and analysis\n triggerFileAnalysis(jobId: number): Promise<void>;\n\n // Manual status updates\n markAsCompleted(jobId: number, reason?: string): Promise<PrintJob>;\n markAsFailed(jobId: number, reason: string): Promise<PrintJob>;\n markAsCancelled(jobId: number, reason?: string): Promise<PrintJob>;\n markAsUnknown(jobId: number, reason?: string): Promise<PrintJob>;\n\n // File reference counting\n countJobsReferencingFile(fileStorageId: string): Promise<number>;\n\n // Job management\n updateJob(job: PrintJob): Promise<PrintJob>;\n deleteJob(job: PrintJob): Promise<void>;\n}\n\nexport class PrintJobService implements IPrintJobService {\n printJobRepository: Repository<PrintJob>;\n eventEmitter2: EventEmitter2;\n private readonly logger;\n\n constructor(loggerFactory: ILoggerFactory, typeormService: TypeormService, eventEmitter2: EventEmitter2) {\n this.printJobRepository = typeormService.getDataSource().getRepository(PrintJob);\n this.eventEmitter2 = eventEmitter2;\n this.logger = loggerFactory(PrintJobService.name);\n }\n\n async getJobByIdOrFail(id: number, relations?: string[]): Promise<PrintJob> {\n const job = await this.printJobRepository.findOne({\n where: { id },\n relations,\n });\n if (!job) {\n throw new NotFoundException(`Job ${id} not found`);\n }\n return job;\n }\n\n async handleFileAnalyzed(jobId: number, metadata: PrintJobMetadata, thumbnails?: any[]): Promise<PrintJob> {\n const job = await this.printJobRepository.findOne({ where: { id: jobId } });\n if (!job) {\n throw new Error(`Print job ${jobId} not found`);\n }\n\n job.metadata = metadata;\n job.analysisState = \"ANALYZED\";\n job.analyzedAt = new Date();\n // Status remains unchanged - analysis state is separate from job status\n job.fileFormat = metadata.fileFormat;\n\n await this.printJobRepository.save(job);\n\n this.eventEmitter2.emit(\"printJob.analyzed\", {\n jobId: job.id,\n printerId: job.printerId,\n metadata,\n } as PrintJobAnalyzedEvent);\n\n this.logger.log(`Print job ${jobId} analyzed: ${metadata.fileName}`);\n return job;\n }\n\n async handlePrintStarted(\n printerId: number,\n fileName: string,\n jobId?: number,\n printerName?: string,\n ): Promise<PrintJob> {\n const existingJob = await this.printJobRepository.findOne({\n where: { printerId, status: \"PRINTING\" },\n order: { startedAt: \"DESC\" },\n });\n\n if (existingJob?.fileName === fileName && !jobId) {\n return existingJob;\n }\n\n // Different file is printing - mark the old one as UNKNOWN\n if (existingJob && existingJob.fileName !== fileName && !jobId) {\n existingJob.status = \"UNKNOWN\";\n existingJob.statusReason =\n \"Print state unknown - printer started new job while previous job was still marked as printing. \" +\n \"This may indicate a disconnect, server restart, or manual printer control.\";\n existingJob.endedAt = new Date();\n await this.printJobRepository.save(existingJob);\n\n this.logger.warn(\n `Printer ${printerId} started new print \"${fileName}\" while job ${existingJob.id} was PRINTING \"${existingJob.fileName}\". ` +\n `Marked job ${existingJob.id} as UNKNOWN.`,\n );\n }\n\n let job: PrintJob | null;\n if (jobId) {\n job = await this.printJobRepository.findOne({ where: { id: jobId } });\n if (!job) {\n throw new Error(`Print job ${jobId} not found`);\n }\n } else {\n // First, look for a PENDING job (created during upload)\n job = await this.printJobRepository.findOne({\n where: { printerId, fileName, status: \"PENDING\" },\n order: { createdAt: \"DESC\" },\n });\n\n // If no pending/analyzed job, look for any recent job with this filename\n if (!job) {\n job = await this.printJobRepository.findOne({\n where: { printerId, fileName },\n order: { createdAt: \"DESC\" },\n });\n }\n\n // If still no job, create a new one\n if (!job) {\n this.logger.log(`Creating new job for ${fileName} - no pending or existing job found`);\n job = this.printJobRepository.create({\n printerId,\n printerName: printerName || null,\n fileName,\n status: \"PRINTING\",\n analysisState: \"NOT_ANALYZED\",\n });\n } else if (job.status === \"PENDING\") {\n this.logger.log(`Promoting pending job ${job.id} to PRINTING`);\n } else if (job.status === \"COMPLETED\" || job.status === \"FAILED\" || job.status === \"CANCELLED\") {\n // Don't reuse completed jobs - this is a re-print, create new job\n this.logger.log(`Creating new job for re-print of ${fileName} (previous job ${job.id} was ${job.status})`);\n job = this.printJobRepository.create({\n printerId,\n printerName: printerName || job.printerName || null,\n fileName,\n status: \"PRINTING\",\n analysisState: job.analysisState, // Keep analysis state from previous print\n metadata: job.metadata, // Reuse metadata from previous print\n fileFormat: job.fileFormat,\n });\n }\n }\n\n job.status = \"PRINTING\";\n job.startedAt = new Date();\n job.progress = 0;\n\n // Set/update printerName if provided\n if (printerName && !job.printerName) {\n job.printerName = printerName;\n }\n\n if (!job.statistics) {\n job.statistics = {\n startedAt: new Date(),\n endedAt: null,\n actualPrintTimeSeconds: null,\n progress: 0,\n };\n } else {\n job.statistics.startedAt = new Date();\n job.statistics.progress = 0;\n }\n\n await this.printJobRepository.save(job);\n\n this.eventEmitter2.emit(\"printJob.started\", {\n jobId: job.id,\n printerId,\n fileName,\n startedAt: job.startedAt,\n } as PrintJobStartedEvent);\n\n this.logger.log(`Print job ${job.id} started on printer ${printerId}: ${fileName}`);\n return job;\n }\n\n async handlePrintProgress(printerId: number, progress: number): Promise<PrintJob | null> {\n const job = await this.printJobRepository.findOne({\n where: { printerId, status: \"PRINTING\" },\n order: { startedAt: \"DESC\" },\n });\n\n if (!job) {\n return null;\n }\n\n job.progress = Math.min(100, Math.max(0, progress));\n\n if (job.statistics) {\n job.statistics.progress = job.progress;\n } else {\n job.statistics = {\n startedAt: job.startedAt,\n endedAt: null,\n actualPrintTimeSeconds: null,\n progress: job.progress,\n };\n }\n\n await this.printJobRepository.save(job);\n\n this.eventEmitter2.emit(\"printJob.progress\", {\n jobId: job.id,\n printerId,\n progress: job.progress,\n } as PrintJobProgressEvent);\n\n return job;\n }\n\n async handlePrintCompleted(printerId: number, fileName?: string): Promise<PrintJob | null> {\n const job = await this.printJobRepository.findOne({\n where: { printerId, status: \"PRINTING\" },\n order: { startedAt: \"DESC\" },\n });\n\n if (!job) {\n this.logger.warn(`No active print job found for printer ${printerId} on completion`);\n return null;\n }\n\n if (fileName && job.fileName !== fileName) {\n this.logger.warn(`Filename mismatch on completion: expected \"${job.fileName}\", got \"${fileName}\"`);\n }\n\n const endedAt = new Date();\n const actualTimeSeconds = calculateJobDuration(job.startedAt, endedAt);\n\n job.status = \"COMPLETED\";\n updateStatisticsForCompletion(job, endedAt);\n\n await this.printJobRepository.save(job);\n\n this.eventEmitter2.emit(\"printJob.completed\", {\n jobId: job.id,\n printerId,\n fileName: job.fileName,\n actualTimeSeconds,\n estimatedTimeSeconds: job.metadata?.gcodePrintTimeSeconds,\n } as PrintJobCompletedEvent);\n\n this.logger.log(\n `Print job ${job.id} completed on printer ${printerId}: ${job.fileName} ` +\n `(${actualTimeSeconds?.toFixed(0)}s actual, ${job.metadata?.gcodePrintTimeSeconds}s estimated)`,\n );\n\n return job;\n }\n\n async handlePrintFailed(printerId: number, reason: string, fileName?: string): Promise<PrintJob | null> {\n const job = await this.printJobRepository.findOne({\n where: { printerId, status: \"PRINTING\" },\n order: { startedAt: \"DESC\" },\n });\n\n if (!job) {\n this.logger.warn(`No active print job found for printer ${printerId} on failure`);\n return null;\n }\n\n const endedAt = new Date();\n\n job.status = \"FAILED\";\n updateStatisticsForFailure(job, reason, endedAt);\n\n await this.printJobRepository.save(job);\n\n this.eventEmitter2.emit(\"printJob.failed\", {\n jobId: job.id,\n printerId,\n fileName: job.fileName,\n reason,\n failedAt: endedAt,\n } as PrintJobFailedEvent);\n\n this.logger.log(`Print job ${job.id} failed on printer ${printerId}: ${job.fileName} - ${reason}`);\n\n return job;\n }\n\n async handlePrintCancelled(printerId: number, reason?: string): Promise<PrintJob | null> {\n const job = await this.printJobRepository.findOne({\n where: { printerId, status: \"PRINTING\" },\n order: { startedAt: \"DESC\" },\n });\n\n if (!job) {\n this.logger.warn(`No active print job found for printer ${printerId} on cancellation`);\n return null;\n }\n\n const endedAt = new Date();\n\n job.status = \"CANCELLED\";\n updateStatisticsForCancellation(job, reason, endedAt);\n\n await this.printJobRepository.save(job);\n\n this.eventEmitter2.emit(\"printJob.cancelled\", {\n jobId: job.id,\n printerId,\n fileName: job.fileName,\n cancelledAt: endedAt,\n } as PrintJobCancelledEvent);\n\n this.logger.log(`Print job ${job.id} cancelled on printer ${printerId}: ${job.fileName}`);\n return job;\n }\n\n async handlePrintPaused(printerId: number): Promise<PrintJob | null> {\n const job = await this.printJobRepository.findOne({\n where: { printerId, status: \"PRINTING\" },\n order: { startedAt: \"DESC\" },\n });\n\n if (!job) {\n this.logger.warn(`No active print job found for printer ${printerId} on pause`);\n return null;\n }\n\n job.status = \"PAUSED\";\n await this.printJobRepository.save(job);\n\n this.logger.log(`Print job ${job.id} paused on printer ${printerId}`);\n return job;\n }\n\n async handlePrintResumed(printerId: number): Promise<PrintJob | null> {\n const job = await this.printJobRepository.findOne({\n where: { printerId, status: \"PAUSED\" },\n order: { startedAt: \"DESC\" },\n });\n\n if (!job) {\n this.logger.warn(`No paused print job found for printer ${printerId} on resume`);\n return null;\n }\n\n job.status = \"PRINTING\";\n await this.printJobRepository.save(job);\n\n this.logger.log(`Print job ${job.id} resumed on printer ${printerId}`);\n return job;\n }\n\n async cleanupStaleJobs(): Promise<void> {\n const staleJobs = await this.printJobRepository.find({\n where: { status: \"PRINTING\" },\n });\n\n for (const job of staleJobs) {\n job.status = \"UNKNOWN\";\n job.statusReason =\n \"Print state unknown after server restart. The print may have completed, \" +\n \"failed, or still be running. Check your printer for current status.\";\n await this.printJobRepository.save(job);\n\n this.logger.warn(\n `Marked job ${job.id} (printer ${job.printerId}) as UNKNOWN after startup - ` +\n `was PRINTING before server stopped`,\n );\n }\n\n if (staleJobs.length > 0) {\n this.logger.log(`Cleaned up ${staleJobs.length} stale print job(s) on startup`);\n }\n }\n\n async getActivePrintJob(printerId: number): Promise<PrintJob | null> {\n return this.printJobRepository.findOne({\n where: { printerId, status: \"PRINTING\" },\n order: { startedAt: \"DESC\" },\n });\n }\n\n async getPrintJobHistory(printerId: number, limit: number = 50): Promise<PrintJob[]> {\n return this.printJobRepository.find({\n where: { printerId },\n order: { createdAt: \"DESC\" },\n take: limit,\n });\n }\n\n async markStarted(printerId: number, fileName: string, printerName?: string): Promise<PrintJob> {\n return await this.handlePrintStarted(printerId, fileName, undefined, printerName);\n }\n\n async markProgress(printerId: number, fileName: string, progress: number): Promise<PrintJob | null> {\n return await this.handlePrintProgress(printerId, progress);\n }\n\n /**\n * Mark print as finished by fileName\n * Used by printer middleware when print completes successfully\n */\n async markFinished(printerId: number, fileName: string): Promise<PrintJob | null> {\n return await this.handlePrintCompleted(printerId, fileName);\n }\n\n /**\n * Mark print as failed by fileName\n * Used by printer middleware when print fails\n */\n async markFailed(printerId: number, fileName: string, reason: string): Promise<PrintJob | null> {\n return await this.handlePrintFailed(printerId, reason, fileName);\n }\n\n /**\n * Update job metadata with partial data from printer middleware\n * Useful for updating estimates from OctoPrint/Moonraker during print\n * Only updates if job has no metadata or to supplement existing metadata\n */\n async updateJobMetadata(\n printerId: number,\n fileName: string,\n partialMetadata: Partial<PrintJobMetadata>,\n ): Promise<void> {\n const job = await this.printJobRepository.findOne({\n where: { printerId, fileName, status: \"PRINTING\" },\n order: { startedAt: \"DESC\" },\n });\n\n if (!job) {\n this.logger.debug(`No active job found for printer ${printerId}, file ${fileName} - skipping metadata update`);\n return;\n }\n\n // If job already has ANALYZED metadata, don't overwrite with incomplete data\n if (job.analysisState === \"ANALYZED\" && job.metadata) {\n this.logger.debug(`Job ${job.id} already has analyzed metadata, merging only missing fields`);\n // Only merge in fields that are currently null/undefined\n const updatedMetadata = { ...job.metadata } as Record<string, any>;\n for (const [key, value] of Object.entries(partialMetadata)) {\n if (value != null && (updatedMetadata[key] == null || updatedMetadata[key] === null)) {\n updatedMetadata[key] = value;\n }\n }\n job.metadata = updatedMetadata as PrintJobMetadata;\n } else if (job.metadata) {\n // Merge partial metadata with existing metadata\n job.metadata = {\n ...job.metadata,\n ...partialMetadata,\n } as PrintJobMetadata;\n } else {\n // No metadata exists - only create if we have meaningful data\n // Don't create metadata structure with mostly nulls\n const hasData = Object.values(partialMetadata).some((v) => v !== null);\n if (!hasData) {\n this.logger.debug(`Skipping metadata creation for job ${job.id} - no meaningful data provided`);\n return;\n }\n\n // Create minimal metadata structure\n job.metadata = {\n fileName,\n fileFormat: job.fileFormat || \"gcode\", // Use existing fileFormat or default\n ...partialMetadata,\n } as PrintJobMetadata;\n }\n\n await this.printJobRepository.save(job);\n this.logger.debug(`Updated metadata for job ${job.id}`);\n }\n\n // ========== Search methods for API ==========\n\n /**\n * Search print jobs with optional filters\n */\n async searchPrintJobs(\n searchPrinter?: string,\n searchFile?: string,\n startDate?: Date,\n endDate?: Date,\n ): Promise<PrintJob[]> {\n const query = this.printJobRepository.createQueryBuilder(\"job\");\n\n if (searchPrinter) {\n query.andWhere(\"job.printerId = :printerId\", { printerId: Number.parseInt(searchPrinter, 10) });\n }\n\n if (searchFile) {\n query.andWhere(\"job.fileName LIKE :fileName\", { fileName: `%${searchFile}%` });\n }\n\n if (startDate) {\n query.andWhere(\"job.startedAt >= :startDate\", { startDate });\n }\n\n if (endDate) {\n query.andWhere(\"job.startedAt <= :endDate\", { endDate });\n }\n\n return await query.orderBy(\"job.startedAt\", \"DESC\").getMany();\n }\n\n /**\n * Search print jobs with pagination\n */\n async searchPrintJobsPaged(\n searchPrinter?: string,\n searchFile?: string,\n startDate?: Date,\n endDate?: Date,\n page: number = 1,\n pageSize: number = 50,\n ): Promise<[PrintJob[], number]> {\n const query = this.printJobRepository.createQueryBuilder(\"job\");\n\n if (searchPrinter) {\n query.andWhere(\"job.printerId = :printerId\", { printerId: Number.parseInt(searchPrinter, 10) });\n }\n\n if (searchFile) {\n query.andWhere(\"job.fileName LIKE :fileName\", { fileName: `%${searchFile}%` });\n }\n\n if (startDate) {\n query.andWhere(\"job.startedAt >= :startDate\", { startDate });\n }\n\n if (endDate) {\n query.andWhere(\"job.startedAt <= :endDate\", { endDate });\n }\n\n return await query\n .orderBy(\"job.startedAt\", \"DESC\")\n .skip((page - 1) * pageSize)\n .take(pageSize)\n .getManyAndCount();\n }\n\n // ========== Job creation ==========\n\n /**\n * Create a pending print job (typically called when file is uploaded)\n * Used when a file is uploaded to a printer, creating a job ready for analysis\n */\n async createPendingJob(\n printerId: number,\n fileName: string,\n metadata: PrintJobMetadata,\n printerName?: string,\n ): Promise<PrintJob> {\n // Determine if metadata contains meaningful analysis data\n // (more than just basic file info)\n const hasAnalysisData =\n metadata.gcodePrintTimeSeconds !== null ||\n metadata.filamentUsedGrams !== null ||\n metadata.totalFilamentUsedGrams !== null ||\n metadata.layerHeight !== null ||\n metadata.totalLayers !== null;\n\n const analysisState = hasAnalysisData ? \"ANALYZED\" : \"NOT_ANALYZED\";\n // Status is always PENDING for newly created jobs (analysis state is separate)\n const status = \"PENDING\";\n\n const job = this.printJobRepository.create({\n printerId,\n printerName: printerName || null,\n fileName,\n status,\n analysisState,\n metadata,\n fileFormat: metadata.fileFormat,\n fileSize: metadata.fileSize,\n analyzedAt: hasAnalysisData ? new Date() : null,\n });\n\n await this.printJobRepository.save(job);\n\n this.logger.log(\n `Created ${analysisState.toLowerCase()} print job ${job.id} for printer ${printerId}: ${fileName} (format: ${metadata.fileFormat})`,\n );\n return job;\n }\n\n /**\n * Trigger file analysis for a job (emits event for async processing)\n * Used when a print starts from a file on the printer that we don't have locally\n */\n async triggerFileAnalysis(jobId: number): Promise<void> {\n this.eventEmitter2.emit(\"printJob.needsFileDownload\", { jobId });\n this.logger.log(`Triggered file download and analysis for job ${jobId}`);\n }\n\n async markAsCompleted(jobId: number, reason?: string): Promise<PrintJob> {\n const job = await this.getJobByIdOrFail(jobId);\n\n job.status = \"COMPLETED\";\n updateStatisticsForCompletion(job);\n\n if (reason) {\n job.statusReason = reason;\n } else {\n job.statusReason = \"Manually marked as completed by user\";\n }\n\n await this.printJobRepository.save(job);\n\n this.logger.log(`Job ${jobId} manually marked as COMPLETED`);\n\n return job;\n }\n\n async markAsFailed(jobId: number, reason: string): Promise<PrintJob> {\n const job = await this.getJobByIdOrFail(jobId);\n\n job.status = \"FAILED\";\n updateStatisticsForFailure(job, reason);\n\n await this.printJobRepository.save(job);\n\n this.logger.log(`Job ${jobId} manually marked as FAILED: ${reason}`);\n\n return job;\n }\n\n async markAsCancelled(jobId: number, reason?: string): Promise<PrintJob> {\n const job = await this.getJobByIdOrFail(jobId);\n\n job.status = \"CANCELLED\";\n updateStatisticsForCancellation(job, reason);\n\n await this.printJobRepository.save(job);\n\n this.logger.log(`Job ${jobId} manually marked as CANCELLED`);\n\n return job;\n }\n\n async markAsUnknown(jobId: number, reason?: string): Promise<PrintJob> {\n const job = await this.getJobByIdOrFail(jobId);\n\n job.status = \"UNKNOWN\";\n job.statusReason = reason || \"Manually marked as unknown by user (state uncertain)\";\n\n await this.printJobRepository.save(job);\n\n this.logger.log(`Job ${jobId} manually marked as UNKNOWN`);\n\n return job;\n }\n\n async countJobsReferencingFile(fileStorageId: string): Promise<number> {\n return await this.printJobRepository.count({\n where: { fileStorageId },\n });\n }\n\n async updateJob(job: PrintJob): Promise<PrintJob> {\n return await this.printJobRepository.save(job);\n }\n\n async deleteJob(job: PrintJob): Promise<void> {\n await this.printJobRepository.remove(job);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"printer-maintenance-log.service.js","names":[],"sources":["../../../src/services/orm/printer-maintenance-log.service.ts"],"sourcesContent":["import { Repository } from \"typeorm\";\nimport { PrinterMaintenanceLog } from \"@/entities/printer-maintenance-log.entity\";\nimport { TypeormService } from \"@/services/typeorm/typeorm.service\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport {\n CreateMaintenanceLogDto,\n PrinterMaintenanceLogDto,\n CompleteMaintenanceLogDto,\n} from \"@/services/interfaces/printer-maintenance-log.dto\";\nimport { BadRequestException, NotFoundException } from \"@/exceptions/runtime.exceptions\";\nimport type { IPrinterService } from \"@/services/interfaces/printer.service.interface\";\n\nexport class PrinterMaintenanceLogService {\n private readonly logger: LoggerService;\n private readonly repository: Repository<PrinterMaintenanceLog>;\n\n constructor(\n loggerFactory: ILoggerFactory,\n typeormService: TypeormService,\n private readonly printerService: IPrinterService,\n ) {\n this.logger = loggerFactory(PrinterMaintenanceLogService.name);\n this.repository = typeormService.getDataSource().getRepository(PrinterMaintenanceLog);\n }\n\n toDto(entity: PrinterMaintenanceLog): PrinterMaintenanceLogDto {\n return {\n id: entity.id,\n createdAt: entity.createdAt,\n createdBy: entity.createdBy,\n createdByUserId: entity.createdByUserId,\n printerId: entity.printerId,\n printerName: entity.printerName,\n printerUrl: entity.printerUrl,\n metadata: entity.metadata,\n completed: entity.completed,\n completedAt: entity.completedAt,\n completedByUserId: entity.completedByUserId,\n completedBy: entity.completedBy,\n };\n }\n\n async create(dto: CreateMaintenanceLogDto, userId: number | null, username: string): Promise<PrinterMaintenanceLog> {\n // Get printer details\n const printer = await this.printerService.get(dto.printerId);\n\n // Check if there's already an active maintenance log for this printer\n const existingLog = await this.repository.findOne({\n where: {\n printerId: dto.printerId,\n completed: false,\n },\n });\n\n if (existingLog) {\n throw new BadRequestException(`Printer ${printer.name} already has an active maintenance log`);\n }\n\n const log = this.repository.create({\n printerId: dto.printerId,\n printerName: printer.name,\n printerUrl: printer.printerURL,\n metadata: dto.metadata,\n createdBy: username,\n createdByUserId: userId,\n completed: false,\n });\n\n await this.repository.save(log);\n\n // Update printer disabled reason for backwards compatibility\n await this.printerService.updateDisabledReason(dto.printerId, this.buildDisabledReasonFromMetadata(dto.metadata));\n\n return log;\n }\n\n async complete(\n logId: number,\n dto: CompleteMaintenanceLogDto,\n userId: number | null,\n username: string,\n ): Promise<PrinterMaintenanceLog> {\n const log = await this.repository.findOne({ where: { id: logId } });\n\n if (!log) {\n throw new NotFoundException(`Maintenance log with id ${logId} not found`);\n }\n\n if (log.completed) {\n throw new BadRequestException(`Maintenance log ${logId} is already completed`);\n }\n\n // Update metadata with completion notes\n log.metadata = {\n ...log.metadata,\n completionNotes: dto.completionNotes,\n };\n\n log.completed = true;\n log.completedAt = new Date();\n log.completedBy = username;\n log.completedByUserId = userId;\n\n await this.repository.save(log);\n\n // Clear printer disabled reason\n if (log.printerId) {\n await this.printerService.updateDisabledReason(log.printerId, null);\n }\n\n return log;\n }\n\n async get(logId: number): Promise<PrinterMaintenanceLog> {\n const log = await this.repository.findOne({ where: { id: logId } });\n\n if (!log) {\n throw new NotFoundException(`Maintenance log with id ${logId} not found`);\n }\n\n return log;\n }\n\n async getActiveByPrinterId(printerId: number): Promise<PrinterMaintenanceLog | null> {\n return this.repository.findOne({\n where: {\n printerId,\n completed: false,\n },\n });\n }\n\n async list(filters: {\n printerId?: number;\n completed?: boolean;\n page?: number;\n pageSize?: number;\n }): Promise<{ logs: PrinterMaintenanceLog[]; total: number }> {\n const { printerId, completed, page = 1, pageSize = 20 } = filters;\n\n const queryBuilder = this.repository.createQueryBuilder(\"log\");\n\n if (printerId !== undefined) {\n queryBuilder.andWhere(\"log.printerId = :printerId\", { printerId });\n }\n\n if (completed !== undefined) {\n queryBuilder.andWhere(\"log.completed = :completed\", { completed });\n }\n\n queryBuilder.orderBy(\"log.createdAt\", \"DESC\");\n queryBuilder.skip((page - 1) * pageSize);\n queryBuilder.take(pageSize);\n\n const [logs, total] = await queryBuilder.getManyAndCount();\n\n return { logs, total };\n }\n\n async delete(logId: number): Promise<void> {\n const log = await this.get(logId);\n\n // Clear printer disabled reason if log is active\n if (!log.completed && log.printerId) {\n await this.printerService.updateDisabledReason(log.printerId, null);\n }\n\n await this.repository.delete(logId);\n }\n\n private buildDisabledReasonFromMetadata(metadata: {\n cause?: string;\n notes?: string;\n partsInvolved?: string[];\n }): string {\n const parts: string[] = [];\n\n if (metadata.cause) {\n parts.push(metadata.cause);\n }\n\n if (metadata.partsInvolved && metadata.partsInvolved.length > 0) {\n parts.push(`Parts: ${metadata.partsInvolved.join(\", \")}`);\n }\n\n if (metadata.notes) {\n parts.push(metadata.notes);\n }\n\n return parts.join(\" - \") || \"Under maintenance\";\n }\n}\n"],"mappings":";;;AAaA,IAAa,+BAAb,MAAa,6BAA6B;CACxC;CACA;CAEA,YACE,eACA,gBACA,gBACA;
|
|
1
|
+
{"version":3,"file":"printer-maintenance-log.service.js","names":[],"sources":["../../../src/services/orm/printer-maintenance-log.service.ts"],"sourcesContent":["import { Repository } from \"typeorm\";\nimport { PrinterMaintenanceLog } from \"@/entities/printer-maintenance-log.entity\";\nimport { TypeormService } from \"@/services/typeorm/typeorm.service\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport {\n CreateMaintenanceLogDto,\n PrinterMaintenanceLogDto,\n CompleteMaintenanceLogDto,\n} from \"@/services/interfaces/printer-maintenance-log.dto\";\nimport { BadRequestException, NotFoundException } from \"@/exceptions/runtime.exceptions\";\nimport type { IPrinterService } from \"@/services/interfaces/printer.service.interface\";\n\nexport class PrinterMaintenanceLogService {\n private readonly logger: LoggerService;\n private readonly repository: Repository<PrinterMaintenanceLog>;\n\n constructor(\n loggerFactory: ILoggerFactory,\n typeormService: TypeormService,\n private readonly printerService: IPrinterService,\n ) {\n this.logger = loggerFactory(PrinterMaintenanceLogService.name);\n this.repository = typeormService.getDataSource().getRepository(PrinterMaintenanceLog);\n }\n\n toDto(entity: PrinterMaintenanceLog): PrinterMaintenanceLogDto {\n return {\n id: entity.id,\n createdAt: entity.createdAt,\n createdBy: entity.createdBy,\n createdByUserId: entity.createdByUserId,\n printerId: entity.printerId,\n printerName: entity.printerName,\n printerUrl: entity.printerUrl,\n metadata: entity.metadata,\n completed: entity.completed,\n completedAt: entity.completedAt,\n completedByUserId: entity.completedByUserId,\n completedBy: entity.completedBy,\n };\n }\n\n async create(dto: CreateMaintenanceLogDto, userId: number | null, username: string): Promise<PrinterMaintenanceLog> {\n // Get printer details\n const printer = await this.printerService.get(dto.printerId);\n\n // Check if there's already an active maintenance log for this printer\n const existingLog = await this.repository.findOne({\n where: {\n printerId: dto.printerId,\n completed: false,\n },\n });\n\n if (existingLog) {\n throw new BadRequestException(`Printer ${printer.name} already has an active maintenance log`);\n }\n\n const log = this.repository.create({\n printerId: dto.printerId,\n printerName: printer.name,\n printerUrl: printer.printerURL,\n metadata: dto.metadata,\n createdBy: username,\n createdByUserId: userId,\n completed: false,\n });\n\n await this.repository.save(log);\n\n // Update printer disabled reason for backwards compatibility\n await this.printerService.updateDisabledReason(dto.printerId, this.buildDisabledReasonFromMetadata(dto.metadata));\n\n return log;\n }\n\n async complete(\n logId: number,\n dto: CompleteMaintenanceLogDto,\n userId: number | null,\n username: string,\n ): Promise<PrinterMaintenanceLog> {\n const log = await this.repository.findOne({ where: { id: logId } });\n\n if (!log) {\n throw new NotFoundException(`Maintenance log with id ${logId} not found`);\n }\n\n if (log.completed) {\n throw new BadRequestException(`Maintenance log ${logId} is already completed`);\n }\n\n // Update metadata with completion notes\n log.metadata = {\n ...log.metadata,\n completionNotes: dto.completionNotes,\n };\n\n log.completed = true;\n log.completedAt = new Date();\n log.completedBy = username;\n log.completedByUserId = userId;\n\n await this.repository.save(log);\n\n // Clear printer disabled reason\n if (log.printerId) {\n await this.printerService.updateDisabledReason(log.printerId, null);\n }\n\n return log;\n }\n\n async get(logId: number): Promise<PrinterMaintenanceLog> {\n const log = await this.repository.findOne({ where: { id: logId } });\n\n if (!log) {\n throw new NotFoundException(`Maintenance log with id ${logId} not found`);\n }\n\n return log;\n }\n\n async getActiveByPrinterId(printerId: number): Promise<PrinterMaintenanceLog | null> {\n return this.repository.findOne({\n where: {\n printerId,\n completed: false,\n },\n });\n }\n\n async list(filters: {\n printerId?: number;\n completed?: boolean;\n page?: number;\n pageSize?: number;\n }): Promise<{ logs: PrinterMaintenanceLog[]; total: number }> {\n const { printerId, completed, page = 1, pageSize = 20 } = filters;\n\n const queryBuilder = this.repository.createQueryBuilder(\"log\");\n\n if (printerId !== undefined) {\n queryBuilder.andWhere(\"log.printerId = :printerId\", { printerId });\n }\n\n if (completed !== undefined) {\n queryBuilder.andWhere(\"log.completed = :completed\", { completed });\n }\n\n queryBuilder.orderBy(\"log.createdAt\", \"DESC\");\n queryBuilder.skip((page - 1) * pageSize);\n queryBuilder.take(pageSize);\n\n const [logs, total] = await queryBuilder.getManyAndCount();\n\n return { logs, total };\n }\n\n async delete(logId: number): Promise<void> {\n const log = await this.get(logId);\n\n // Clear printer disabled reason if log is active\n if (!log.completed && log.printerId) {\n await this.printerService.updateDisabledReason(log.printerId, null);\n }\n\n await this.repository.delete(logId);\n }\n\n private buildDisabledReasonFromMetadata(metadata: {\n cause?: string;\n notes?: string;\n partsInvolved?: string[];\n }): string {\n const parts: string[] = [];\n\n if (metadata.cause) {\n parts.push(metadata.cause);\n }\n\n if (metadata.partsInvolved && metadata.partsInvolved.length > 0) {\n parts.push(`Parts: ${metadata.partsInvolved.join(\", \")}`);\n }\n\n if (metadata.notes) {\n parts.push(metadata.notes);\n }\n\n return parts.join(\" - \") || \"Under maintenance\";\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"printer-tag.service.js","names":[],"sources":["../../../src/services/orm/printer-tag.service.ts"],"sourcesContent":["import { BaseService } from \"@/services/orm/base.service\";\nimport { PrinterTag } from \"@/entities/printer-tag.entity\";\nimport { CreateTagDto, PrinterTagDto } from \"@/services/interfaces/printer-tag.dto\";\nimport type { IPrinterTagService } from \"@/services/interfaces/printer-tag.service.interface\";\nimport { Repository } from \"typeorm\";\nimport { Tag } from \"@/entities/tag.entity\";\nimport { TypeormService } from \"@/services/typeorm/typeorm.service\";\nimport { validate } from \"class-validator\";\nimport { NotFoundException } from \"@/exceptions/runtime.exceptions\";\nimport { TagWithPrintersDto } from \"@/services/interfaces/tag.dto\";\n\nexport class PrinterTagService extends BaseService(PrinterTag, PrinterTagDto) implements IPrinterTagService {\n private readonly tagRepository: Repository<Tag>;\n\n constructor(typeormService: TypeormService) {\n super(typeormService);\n\n this.tagRepository = typeormService.getDataSource().getRepository(Tag);\n }\n\n async listTags(): Promise<TagWithPrintersDto[]> {\n const tags = await this.tagRepository.find();\n const tagRecords: Record<number, TagWithPrintersDto> = {};\n for (const tag of tags) {\n tagRecords[tag.id] = {\n id: tag.id,\n name: tag.name,\n color: tag.color,\n printers: [] as PrinterTagDto[],\n };\n }\n\n for (const tag of tags) {\n tagRecords[tag.id].printers = await this.repository.findBy({ tagId: tag.id });\n }\n\n return Object.values(tagRecords);\n }\n\n async getTag(tagId: number) {\n const tag = await this.tagRepository.findOneBy({ id: tagId });\n if (!tag) {\n throw new NotFoundException(\"Tag does not exist\");\n }\n return tag;\n }\n\n async getPrintersByTag(tagId: number): Promise<TagWithPrintersDto> {\n const tag = await this.getTag(tagId);\n const printerTags = await this.repository.findBy({ tagId: tag.id });\n return {\n id: tag.id,\n name: tag.name,\n color: tag.color,\n printers: printerTags,\n };\n }\n\n async createTag(dto: CreateTagDto): Promise<TagWithPrintersDto> {\n await validate(dto);\n const entity = this.tagRepository.create(dto);\n await validate(entity);\n const tag = await this.tagRepository.save(entity);\n\n return await this.getPrintersByTag(tag.id);\n }\n\n async updateTagName(tagId: number, name: string): Promise<void> {\n const entity = await this.getTag(tagId);\n const updateDto = { name };\n await validate(updateDto);\n await validate(Object.assign(entity, updateDto));\n await this.tagRepository.update(entity.id, updateDto);\n }\n\n async updateTagColor(tagId: number, color: string): Promise<void> {\n const entity = await this.getTag(tagId);\n const updateDto = { color };\n await validate(updateDto);\n await validate(Object.assign(entity, updateDto));\n await this.tagRepository.update(entity.id, updateDto);\n }\n\n async deleteTag(tagId: number): Promise<void> {\n const tag = await this.getTag(tagId);\n await this.tagRepository.delete({ id: tag.id });\n }\n\n async addPrinterToTag(tagId: number, printerId: number): Promise<PrinterTag> {\n const tag = await this.getTag(tagId);\n const alreadyExisting = await this.repository.findOneBy({\n tagId: tag.id,\n printerId,\n });\n if (alreadyExisting) return alreadyExisting;\n\n return await this.create({\n tagId: tag.id,\n printerId,\n });\n }\n\n async removePrinterFromTag(tagId: number, printerId: number): Promise<void> {\n await this.getTag(tagId);\n await this.repository.delete({ tagId, printerId });\n }\n\n toDto(entity: PrinterTag): PrinterTagDto {\n return {\n printerId: entity.printerId,\n tagId: entity.tagId,\n };\n }\n}\n"],"mappings":";;;;;;;AAWA,IAAa,oBAAb,cAAuC,YAAY,YAAY,cAAc,CAA+B;CAC1G;CAEA,YAAY,gBAAgC;
|
|
1
|
+
{"version":3,"file":"printer-tag.service.js","names":[],"sources":["../../../src/services/orm/printer-tag.service.ts"],"sourcesContent":["import { BaseService } from \"@/services/orm/base.service\";\nimport { PrinterTag } from \"@/entities/printer-tag.entity\";\nimport { CreateTagDto, PrinterTagDto } from \"@/services/interfaces/printer-tag.dto\";\nimport type { IPrinterTagService } from \"@/services/interfaces/printer-tag.service.interface\";\nimport { Repository } from \"typeorm\";\nimport { Tag } from \"@/entities/tag.entity\";\nimport { TypeormService } from \"@/services/typeorm/typeorm.service\";\nimport { validate } from \"class-validator\";\nimport { NotFoundException } from \"@/exceptions/runtime.exceptions\";\nimport { TagWithPrintersDto } from \"@/services/interfaces/tag.dto\";\n\nexport class PrinterTagService extends BaseService(PrinterTag, PrinterTagDto) implements IPrinterTagService {\n private readonly tagRepository: Repository<Tag>;\n\n constructor(typeormService: TypeormService) {\n super(typeormService);\n\n this.tagRepository = typeormService.getDataSource().getRepository(Tag);\n }\n\n async listTags(): Promise<TagWithPrintersDto[]> {\n const tags = await this.tagRepository.find();\n const tagRecords: Record<number, TagWithPrintersDto> = {};\n for (const tag of tags) {\n tagRecords[tag.id] = {\n id: tag.id,\n name: tag.name,\n color: tag.color,\n printers: [] as PrinterTagDto[],\n };\n }\n\n for (const tag of tags) {\n tagRecords[tag.id].printers = await this.repository.findBy({ tagId: tag.id });\n }\n\n return Object.values(tagRecords);\n }\n\n async getTag(tagId: number) {\n const tag = await this.tagRepository.findOneBy({ id: tagId });\n if (!tag) {\n throw new NotFoundException(\"Tag does not exist\");\n }\n return tag;\n }\n\n async getPrintersByTag(tagId: number): Promise<TagWithPrintersDto> {\n const tag = await this.getTag(tagId);\n const printerTags = await this.repository.findBy({ tagId: tag.id });\n return {\n id: tag.id,\n name: tag.name,\n color: tag.color,\n printers: printerTags,\n };\n }\n\n async createTag(dto: CreateTagDto): Promise<TagWithPrintersDto> {\n await validate(dto);\n const entity = this.tagRepository.create(dto);\n await validate(entity);\n const tag = await this.tagRepository.save(entity);\n\n return await this.getPrintersByTag(tag.id);\n }\n\n async updateTagName(tagId: number, name: string): Promise<void> {\n const entity = await this.getTag(tagId);\n const updateDto = { name };\n await validate(updateDto);\n await validate(Object.assign(entity, updateDto));\n await this.tagRepository.update(entity.id, updateDto);\n }\n\n async updateTagColor(tagId: number, color: string): Promise<void> {\n const entity = await this.getTag(tagId);\n const updateDto = { color };\n await validate(updateDto);\n await validate(Object.assign(entity, updateDto));\n await this.tagRepository.update(entity.id, updateDto);\n }\n\n async deleteTag(tagId: number): Promise<void> {\n const tag = await this.getTag(tagId);\n await this.tagRepository.delete({ id: tag.id });\n }\n\n async addPrinterToTag(tagId: number, printerId: number): Promise<PrinterTag> {\n const tag = await this.getTag(tagId);\n const alreadyExisting = await this.repository.findOneBy({\n tagId: tag.id,\n printerId,\n });\n if (alreadyExisting) return alreadyExisting;\n\n return await this.create({\n tagId: tag.id,\n printerId,\n });\n }\n\n async removePrinterFromTag(tagId: number, printerId: number): Promise<void> {\n await this.getTag(tagId);\n await this.repository.delete({ tagId, printerId });\n }\n\n toDto(entity: PrinterTag): PrinterTagDto {\n return {\n printerId: entity.printerId,\n tagId: entity.tagId,\n };\n }\n}\n"],"mappings":";;;;;;;AAWA,IAAa,oBAAb,cAAuC,YAAY,YAAY,cAAc,CAA+B;CAC1G;CAEA,YAAY,gBAAgC;EAC1C,MAAM,eAAe;EAErB,KAAK,gBAAgB,eAAe,eAAe,CAAC,cAAc,IAAI;;CAGxE,MAAM,WAA0C;EAC9C,MAAM,OAAO,MAAM,KAAK,cAAc,MAAM;EAC5C,MAAM,aAAiD,EAAE;EACzD,KAAK,MAAM,OAAO,MAChB,WAAW,IAAI,MAAM;GACnB,IAAI,IAAI;GACR,MAAM,IAAI;GACV,OAAO,IAAI;GACX,UAAU,EAAE;GACb;EAGH,KAAK,MAAM,OAAO,MAChB,WAAW,IAAI,IAAI,WAAW,MAAM,KAAK,WAAW,OAAO,EAAE,OAAO,IAAI,IAAI,CAAC;EAG/E,OAAO,OAAO,OAAO,WAAW;;CAGlC,MAAM,OAAO,OAAe;EAC1B,MAAM,MAAM,MAAM,KAAK,cAAc,UAAU,EAAE,IAAI,OAAO,CAAC;EAC7D,IAAI,CAAC,KACH,MAAM,IAAI,kBAAkB,qBAAqB;EAEnD,OAAO;;CAGT,MAAM,iBAAiB,OAA4C;EACjE,MAAM,MAAM,MAAM,KAAK,OAAO,MAAM;EACpC,MAAM,cAAc,MAAM,KAAK,WAAW,OAAO,EAAE,OAAO,IAAI,IAAI,CAAC;EACnE,OAAO;GACL,IAAI,IAAI;GACR,MAAM,IAAI;GACV,OAAO,IAAI;GACX,UAAU;GACX;;CAGH,MAAM,UAAU,KAAgD;EAC9D,MAAM,SAAS,IAAI;EACnB,MAAM,SAAS,KAAK,cAAc,OAAO,IAAI;EAC7C,MAAM,SAAS,OAAO;EACtB,MAAM,MAAM,MAAM,KAAK,cAAc,KAAK,OAAO;EAEjD,OAAO,MAAM,KAAK,iBAAiB,IAAI,GAAG;;CAG5C,MAAM,cAAc,OAAe,MAA6B;EAC9D,MAAM,SAAS,MAAM,KAAK,OAAO,MAAM;EACvC,MAAM,YAAY,EAAE,MAAM;EAC1B,MAAM,SAAS,UAAU;EACzB,MAAM,SAAS,OAAO,OAAO,QAAQ,UAAU,CAAC;EAChD,MAAM,KAAK,cAAc,OAAO,OAAO,IAAI,UAAU;;CAGvD,MAAM,eAAe,OAAe,OAA8B;EAChE,MAAM,SAAS,MAAM,KAAK,OAAO,MAAM;EACvC,MAAM,YAAY,EAAE,OAAO;EAC3B,MAAM,SAAS,UAAU;EACzB,MAAM,SAAS,OAAO,OAAO,QAAQ,UAAU,CAAC;EAChD,MAAM,KAAK,cAAc,OAAO,OAAO,IAAI,UAAU;;CAGvD,MAAM,UAAU,OAA8B;EAC5C,MAAM,MAAM,MAAM,KAAK,OAAO,MAAM;EACpC,MAAM,KAAK,cAAc,OAAO,EAAE,IAAI,IAAI,IAAI,CAAC;;CAGjD,MAAM,gBAAgB,OAAe,WAAwC;EAC3E,MAAM,MAAM,MAAM,KAAK,OAAO,MAAM;EACpC,MAAM,kBAAkB,MAAM,KAAK,WAAW,UAAU;GACtD,OAAO,IAAI;GACX;GACD,CAAC;EACF,IAAI,iBAAiB,OAAO;EAE5B,OAAO,MAAM,KAAK,OAAO;GACvB,OAAO,IAAI;GACX;GACD,CAAC;;CAGJ,MAAM,qBAAqB,OAAe,WAAkC;EAC1E,MAAM,KAAK,OAAO,MAAM;EACxB,MAAM,KAAK,WAAW,OAAO;GAAE;GAAO;GAAW,CAAC;;CAGpD,MAAM,QAAmC;EACvC,OAAO;GACL,WAAW,OAAO;GAClB,OAAO,OAAO;GACf"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"printer.service.js","names":[],"sources":["../../../src/services/orm/printer.service.ts"],"sourcesContent":["import { LoggerService } from \"@/handlers/logger\";\nimport { CreatePrinterDto, PrinterDto } from \"@/services/interfaces/printer.dto\";\nimport { Printer } from \"@/entities/printer.entity\";\nimport { BaseService } from \"@/services/orm/base.service\";\nimport { TypeormService } from \"@/services/typeorm/typeorm.service\";\nimport type { IPrinterService } from \"@/services/interfaces/printer.service.interface\";\nimport { validateInput } from \"@/handlers/validators\";\nimport {\n BatchPrinterCreatedEvent,\n PrinterCreatedEvent,\n printerEvents,\n PrintersDeletedEvent,\n PrinterUpdatedEvent,\n} from \"@/constants/event.constants\";\nimport EventEmitter2 from \"eventemitter2\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { normalizeUrl } from \"@/utils/normalize-url\";\nimport { defaultHttpProtocol } from \"@/utils/url.utils\";\nimport {\n createPrinterSchema,\n updatePrinterDisabledReasonSchema,\n updatePrinterEnabledSchema,\n} from \"@/services/validators/printer-service.validation\";\nimport { PrinterType } from \"@/services/printer-api.interface\";\nimport { z } from \"zod\";\n\nexport class PrinterService extends BaseService(Printer, PrinterDto, CreatePrinterDto) implements IPrinterService {\n private readonly logger: LoggerService;\n\n constructor(\n loggerFactory: ILoggerFactory,\n typeormService: TypeormService,\n private readonly eventEmitter2: EventEmitter2,\n ) {\n super(typeormService);\n this.logger = loggerFactory(PrinterService.name);\n }\n\n toDto(entity: Printer): PrinterDto {\n return {\n id: entity.id,\n name: entity.name,\n enabled: entity.enabled,\n disabledReason: entity.disabledReason,\n dateAdded: entity.dateAdded,\n apiKey: entity.apiKey,\n username: entity.username,\n password: entity.password,\n printerURL: entity.printerURL,\n printerType: entity.printerType as PrinterType,\n };\n }\n\n async list(): Promise<Printer[]> {\n return this.repository.find({\n order: {\n dateAdded: \"ASC\",\n },\n });\n }\n\n async create(newPrinter: z.infer<typeof createPrinterSchema>, emitEvent = true): Promise<Printer> {\n const mergedPrinter = await this.validateAndDefault(newPrinter);\n mergedPrinter.dateAdded = Date.now();\n const printer = await super.create(mergedPrinter);\n if (emitEvent) {\n this.eventEmitter2.emit(printerEvents.printerCreated, { printer } satisfies PrinterCreatedEvent);\n }\n return printer;\n }\n\n /**\n * Explicit patching of printer document\n */\n async update(printerId: number, partial: Partial<Printer>): Promise<Printer> {\n const printer = await this.get(printerId);\n if (partial.printerURL) {\n partial.printerURL = normalizeUrl(partial.printerURL, { defaultProtocol: defaultHttpProtocol });\n }\n Object.assign(printer, partial);\n const { printerURL, apiKey, enabled, name, printerType, password, username } = await validateInput(\n printer,\n createPrinterSchema,\n );\n\n const updatedPrinter = await super.update(printerId, {\n printerURL,\n name,\n apiKey,\n enabled,\n printerType,\n password: password ?? undefined,\n username: username ?? undefined,\n });\n this.eventEmitter2.emit(printerEvents.printerUpdated, { printer } satisfies PrinterUpdatedEvent);\n return updatedPrinter;\n }\n\n async batchImport(printers: Partial<Printer>[]): Promise<Printer[]> {\n if (!printers?.length) return [];\n\n const validatedPrinters = [];\n for (let printer of printers) {\n const validated = await this.validateAndDefault(printer as z.infer<typeof createPrinterSchema>);\n validatedPrinters.push(validated);\n }\n\n this.logger.log(\"Validation passed\");\n // We've passed validation completely - creation will likely succeed\n const newPrinters = [];\n for (let printer of validatedPrinters) {\n const createdPrinter = await this.create(printer, false);\n newPrinters.push(createdPrinter);\n }\n\n this.logger.log(\"Batch create succeeded\");\n this.eventEmitter2.emit(printerEvents.batchPrinterCreated, {\n printers: newPrinters,\n } satisfies BatchPrinterCreatedEvent);\n return newPrinters;\n }\n\n override async delete(printerId: number, emitEvent = true): Promise<void> {\n await this.repository.delete([printerId]);\n if (emitEvent) {\n this.eventEmitter2.emit(printerEvents.printersDeleted, {\n printerIds: [printerId],\n } satisfies PrintersDeletedEvent);\n }\n }\n\n async deleteMany(printerIds: number[], emitEvent = true): Promise<void> {\n await this.repository.delete(printerIds);\n if (emitEvent) {\n this.eventEmitter2.emit(printerEvents.printersDeleted, { printerIds } satisfies PrintersDeletedEvent);\n }\n }\n\n async updateDisabledReason(printerId: number, disabledReason: string | null): Promise<Printer> {\n await validateInput({ disabledReason }, updatePrinterDisabledReasonSchema);\n return this.update(printerId, { disabledReason });\n }\n\n async updateEnabled(printerId: number, enabled: boolean): Promise<Printer> {\n await validateInput({ enabled }, updatePrinterEnabledSchema);\n return this.update(printerId, { enabled });\n }\n\n updateFeedRate(printerId: number, feedRate: number): Promise<Printer> {\n return this.update(printerId, { feedRate });\n }\n\n updateFlowRate(printerId: number, flowRate: number): Promise<Printer> {\n return this.update(printerId, { flowRate });\n }\n\n private async validateAndDefault(printer: z.infer<typeof createPrinterSchema>) {\n const mergedPrinter = {\n enabled: true,\n ...printer,\n };\n if (mergedPrinter.printerURL?.length) {\n mergedPrinter.printerURL = normalizeUrl(mergedPrinter.printerURL, { defaultProtocol: defaultHttpProtocol });\n }\n return await validateInput(mergedPrinter, createPrinterSchema);\n }\n}\n"],"mappings":";;;;;;;;;AA0BA,IAAa,iBAAb,MAAa,uBAAuB,YAAY,SAAS,YAAY,iBAAiB,CAA4B;CAChH;CAEA,YACE,eACA,gBACA,eACA;
|
|
1
|
+
{"version":3,"file":"printer.service.js","names":[],"sources":["../../../src/services/orm/printer.service.ts"],"sourcesContent":["import { LoggerService } from \"@/handlers/logger\";\nimport { CreatePrinterDto, PrinterDto } from \"@/services/interfaces/printer.dto\";\nimport { Printer } from \"@/entities/printer.entity\";\nimport { BaseService } from \"@/services/orm/base.service\";\nimport { TypeormService } from \"@/services/typeorm/typeorm.service\";\nimport type { IPrinterService } from \"@/services/interfaces/printer.service.interface\";\nimport { validateInput } from \"@/handlers/validators\";\nimport {\n BatchPrinterCreatedEvent,\n PrinterCreatedEvent,\n printerEvents,\n PrintersDeletedEvent,\n PrinterUpdatedEvent,\n} from \"@/constants/event.constants\";\nimport EventEmitter2 from \"eventemitter2\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { normalizeUrl } from \"@/utils/normalize-url\";\nimport { defaultHttpProtocol } from \"@/utils/url.utils\";\nimport {\n createPrinterSchema,\n updatePrinterDisabledReasonSchema,\n updatePrinterEnabledSchema,\n} from \"@/services/validators/printer-service.validation\";\nimport { PrinterType } from \"@/services/printer-api.interface\";\nimport { z } from \"zod\";\n\nexport class PrinterService extends BaseService(Printer, PrinterDto, CreatePrinterDto) implements IPrinterService {\n private readonly logger: LoggerService;\n\n constructor(\n loggerFactory: ILoggerFactory,\n typeormService: TypeormService,\n private readonly eventEmitter2: EventEmitter2,\n ) {\n super(typeormService);\n this.logger = loggerFactory(PrinterService.name);\n }\n\n toDto(entity: Printer): PrinterDto {\n return {\n id: entity.id,\n name: entity.name,\n enabled: entity.enabled,\n disabledReason: entity.disabledReason,\n dateAdded: entity.dateAdded,\n apiKey: entity.apiKey,\n username: entity.username,\n password: entity.password,\n printerURL: entity.printerURL,\n printerType: entity.printerType as PrinterType,\n };\n }\n\n async list(): Promise<Printer[]> {\n return this.repository.find({\n order: {\n dateAdded: \"ASC\",\n },\n });\n }\n\n async create(newPrinter: z.infer<typeof createPrinterSchema>, emitEvent = true): Promise<Printer> {\n const mergedPrinter = await this.validateAndDefault(newPrinter);\n mergedPrinter.dateAdded = Date.now();\n const printer = await super.create(mergedPrinter);\n if (emitEvent) {\n this.eventEmitter2.emit(printerEvents.printerCreated, { printer } satisfies PrinterCreatedEvent);\n }\n return printer;\n }\n\n /**\n * Explicit patching of printer document\n */\n async update(printerId: number, partial: Partial<Printer>): Promise<Printer> {\n const printer = await this.get(printerId);\n if (partial.printerURL) {\n partial.printerURL = normalizeUrl(partial.printerURL, { defaultProtocol: defaultHttpProtocol });\n }\n Object.assign(printer, partial);\n const { printerURL, apiKey, enabled, name, printerType, password, username } = await validateInput(\n printer,\n createPrinterSchema,\n );\n\n const updatedPrinter = await super.update(printerId, {\n printerURL,\n name,\n apiKey,\n enabled,\n printerType,\n password: password ?? undefined,\n username: username ?? undefined,\n });\n this.eventEmitter2.emit(printerEvents.printerUpdated, { printer } satisfies PrinterUpdatedEvent);\n return updatedPrinter;\n }\n\n async batchImport(printers: Partial<Printer>[]): Promise<Printer[]> {\n if (!printers?.length) return [];\n\n const validatedPrinters = [];\n for (let printer of printers) {\n const validated = await this.validateAndDefault(printer as z.infer<typeof createPrinterSchema>);\n validatedPrinters.push(validated);\n }\n\n this.logger.log(\"Validation passed\");\n // We've passed validation completely - creation will likely succeed\n const newPrinters = [];\n for (let printer of validatedPrinters) {\n const createdPrinter = await this.create(printer, false);\n newPrinters.push(createdPrinter);\n }\n\n this.logger.log(\"Batch create succeeded\");\n this.eventEmitter2.emit(printerEvents.batchPrinterCreated, {\n printers: newPrinters,\n } satisfies BatchPrinterCreatedEvent);\n return newPrinters;\n }\n\n override async delete(printerId: number, emitEvent = true): Promise<void> {\n await this.repository.delete([printerId]);\n if (emitEvent) {\n this.eventEmitter2.emit(printerEvents.printersDeleted, {\n printerIds: [printerId],\n } satisfies PrintersDeletedEvent);\n }\n }\n\n async deleteMany(printerIds: number[], emitEvent = true): Promise<void> {\n await this.repository.delete(printerIds);\n if (emitEvent) {\n this.eventEmitter2.emit(printerEvents.printersDeleted, { printerIds } satisfies PrintersDeletedEvent);\n }\n }\n\n async updateDisabledReason(printerId: number, disabledReason: string | null): Promise<Printer> {\n await validateInput({ disabledReason }, updatePrinterDisabledReasonSchema);\n return this.update(printerId, { disabledReason });\n }\n\n async updateEnabled(printerId: number, enabled: boolean): Promise<Printer> {\n await validateInput({ enabled }, updatePrinterEnabledSchema);\n return this.update(printerId, { enabled });\n }\n\n updateFeedRate(printerId: number, feedRate: number): Promise<Printer> {\n return this.update(printerId, { feedRate });\n }\n\n updateFlowRate(printerId: number, flowRate: number): Promise<Printer> {\n return this.update(printerId, { flowRate });\n }\n\n private async validateAndDefault(printer: z.infer<typeof createPrinterSchema>) {\n const mergedPrinter = {\n enabled: true,\n ...printer,\n };\n if (mergedPrinter.printerURL?.length) {\n mergedPrinter.printerURL = normalizeUrl(mergedPrinter.printerURL, { defaultProtocol: defaultHttpProtocol });\n }\n return await validateInput(mergedPrinter, createPrinterSchema);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"refresh-token.service.js","names":["uuidv4"],"sources":["../../../src/services/orm/refresh-token.service.ts"],"sourcesContent":["import { BaseService } from \"@/services/orm/base.service\";\nimport { RefreshToken } from \"@/entities\";\nimport type { IRefreshTokenService } from \"@/services/interfaces/refresh-token.service.interface\";\nimport { RefreshTokenDto } from \"@/services/interfaces/refresh-token.dto\";\nimport { AuthenticationError } from \"@/exceptions/runtime.exceptions\";\nimport { TypeormService } from \"@/services/typeorm/typeorm.service\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport { AppConstants } from \"@/server.constants\";\nimport { SettingsStore } from \"@/state/settings.store\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport { LessThan } from \"typeorm\";\nimport { AUTH_ERROR_REASON } from \"@/constants/authorization.constants\";\n\nexport class RefreshTokenService extends BaseService(RefreshToken, RefreshTokenDto) implements IRefreshTokenService {\n private readonly logger: LoggerService;\n\n constructor(\n private readonly settingsStore: SettingsStore,\n loggerFactory: ILoggerFactory,\n typeormService: TypeormService,\n ) {\n super(typeormService);\n this.logger = loggerFactory(RefreshTokenService.name);\n }\n\n toDto(entity: RefreshToken): RefreshTokenDto {\n return {\n id: entity.id,\n userId: entity.userId,\n expiresAt: entity.expiresAt,\n // Sensitive data\n // refreshToken: entity.refreshToken,\n refreshAttemptsUsed: entity.refreshAttemptsUsed,\n };\n }\n\n async getRefreshToken(refreshToken: string): Promise<RefreshToken> {\n const entity = await this.repository.findOneBy({ refreshToken });\n if (!entity) {\n throw new AuthenticationError(\n `The entity ${RefreshToken.name} by provided refresh token is not found`,\n AUTH_ERROR_REASON.InvalidOrExpiredRefreshToken,\n );\n }\n\n return entity;\n }\n\n async createRefreshTokenForUserId(userId: number): Promise<string> {\n const { refreshTokenExpiry } = await this.settingsStore.getCredentialSettings();\n const refreshToken = uuidv4();\n\n const timespan = refreshTokenExpiry ?? AppConstants.DEFAULT_REFRESH_TOKEN_EXPIRY;\n if (!refreshTokenExpiry) {\n this.logger.warn(\"Refresh token expiry not set in Settings:credentials, using default value\");\n }\n\n await this.create({\n userId,\n expiresAt: Date.now() + timespan * 1000,\n refreshToken,\n refreshAttemptsUsed: 0,\n });\n\n return refreshToken;\n }\n\n async updateRefreshTokenAttempts(refreshToken: string, refreshAttemptsUsed: number): Promise<void> {\n await this.getRefreshToken(refreshToken);\n\n await this.repository.update({ refreshToken }, { refreshAttemptsUsed });\n }\n\n async purgeAllOutdatedRefreshTokens(): Promise<void> {\n const result = await this.repository.delete({\n expiresAt: LessThan(Date.now()),\n });\n\n if (result.affected) {\n this.logger.debug(`Removed ${result.affected} outdated refresh tokens`);\n }\n }\n\n async deleteRefreshTokenByUserId(userId: number): Promise<void> {\n const result = await this.repository.delete({\n userId,\n });\n\n if (result.affected) {\n this.logger.debug(`Removed ${result.affected} login refresh tokens for user`);\n }\n }\n\n async deleteRefreshToken(refreshToken: string): Promise<void> {\n const result = await this.repository.delete({\n refreshToken,\n });\n\n if (result.affected) {\n this.logger.debug(`Removed ${result.affected} login refresh tokens`);\n }\n }\n\n async purgeOutdatedRefreshTokensByUserId(userId: number): Promise<void> {\n const result = await this.repository.delete({\n userId,\n expiresAt: LessThan(Date.now()),\n });\n\n if (result.affected) {\n this.logger.debug(`Removed ${result.affected} outdated login refresh tokens for user`);\n }\n }\n}\n"],"mappings":";;;;;;;;;;AAcA,IAAa,sBAAb,MAAa,4BAA4B,YAAY,cAAc,gBAAgB,CAAiC;CAClH;CAEA,YACE,eACA,eACA,gBACA;
|
|
1
|
+
{"version":3,"file":"refresh-token.service.js","names":["uuidv4"],"sources":["../../../src/services/orm/refresh-token.service.ts"],"sourcesContent":["import { BaseService } from \"@/services/orm/base.service\";\nimport { RefreshToken } from \"@/entities\";\nimport type { IRefreshTokenService } from \"@/services/interfaces/refresh-token.service.interface\";\nimport { RefreshTokenDto } from \"@/services/interfaces/refresh-token.dto\";\nimport { AuthenticationError } from \"@/exceptions/runtime.exceptions\";\nimport { TypeormService } from \"@/services/typeorm/typeorm.service\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport { AppConstants } from \"@/server.constants\";\nimport { SettingsStore } from \"@/state/settings.store\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport { LessThan } from \"typeorm\";\nimport { AUTH_ERROR_REASON } from \"@/constants/authorization.constants\";\n\nexport class RefreshTokenService extends BaseService(RefreshToken, RefreshTokenDto) implements IRefreshTokenService {\n private readonly logger: LoggerService;\n\n constructor(\n private readonly settingsStore: SettingsStore,\n loggerFactory: ILoggerFactory,\n typeormService: TypeormService,\n ) {\n super(typeormService);\n this.logger = loggerFactory(RefreshTokenService.name);\n }\n\n toDto(entity: RefreshToken): RefreshTokenDto {\n return {\n id: entity.id,\n userId: entity.userId,\n expiresAt: entity.expiresAt,\n // Sensitive data\n // refreshToken: entity.refreshToken,\n refreshAttemptsUsed: entity.refreshAttemptsUsed,\n };\n }\n\n async getRefreshToken(refreshToken: string): Promise<RefreshToken> {\n const entity = await this.repository.findOneBy({ refreshToken });\n if (!entity) {\n throw new AuthenticationError(\n `The entity ${RefreshToken.name} by provided refresh token is not found`,\n AUTH_ERROR_REASON.InvalidOrExpiredRefreshToken,\n );\n }\n\n return entity;\n }\n\n async createRefreshTokenForUserId(userId: number): Promise<string> {\n const { refreshTokenExpiry } = await this.settingsStore.getCredentialSettings();\n const refreshToken = uuidv4();\n\n const timespan = refreshTokenExpiry ?? AppConstants.DEFAULT_REFRESH_TOKEN_EXPIRY;\n if (!refreshTokenExpiry) {\n this.logger.warn(\"Refresh token expiry not set in Settings:credentials, using default value\");\n }\n\n await this.create({\n userId,\n expiresAt: Date.now() + timespan * 1000,\n refreshToken,\n refreshAttemptsUsed: 0,\n });\n\n return refreshToken;\n }\n\n async updateRefreshTokenAttempts(refreshToken: string, refreshAttemptsUsed: number): Promise<void> {\n await this.getRefreshToken(refreshToken);\n\n await this.repository.update({ refreshToken }, { refreshAttemptsUsed });\n }\n\n async purgeAllOutdatedRefreshTokens(): Promise<void> {\n const result = await this.repository.delete({\n expiresAt: LessThan(Date.now()),\n });\n\n if (result.affected) {\n this.logger.debug(`Removed ${result.affected} outdated refresh tokens`);\n }\n }\n\n async deleteRefreshTokenByUserId(userId: number): Promise<void> {\n const result = await this.repository.delete({\n userId,\n });\n\n if (result.affected) {\n this.logger.debug(`Removed ${result.affected} login refresh tokens for user`);\n }\n }\n\n async deleteRefreshToken(refreshToken: string): Promise<void> {\n const result = await this.repository.delete({\n refreshToken,\n });\n\n if (result.affected) {\n this.logger.debug(`Removed ${result.affected} login refresh tokens`);\n }\n }\n\n async purgeOutdatedRefreshTokensByUserId(userId: number): Promise<void> {\n const result = await this.repository.delete({\n userId,\n expiresAt: LessThan(Date.now()),\n });\n\n if (result.affected) {\n this.logger.debug(`Removed ${result.affected} outdated login refresh tokens for user`);\n }\n }\n}\n"],"mappings":";;;;;;;;;;AAcA,IAAa,sBAAb,MAAa,4BAA4B,YAAY,cAAc,gBAAgB,CAAiC;CAClH;CAEA,YACE,eACA,eACA,gBACA;EACA,MAAM,eAAe;EAJJ,KAAA,gBAAA;EAKjB,KAAK,SAAS,cAAc,oBAAoB,KAAK;;CAGvD,MAAM,QAAuC;EAC3C,OAAO;GACL,IAAI,OAAO;GACX,QAAQ,OAAO;GACf,WAAW,OAAO;GAGlB,qBAAqB,OAAO;GAC7B;;CAGH,MAAM,gBAAgB,cAA6C;EACjE,MAAM,SAAS,MAAM,KAAK,WAAW,UAAU,EAAE,cAAc,CAAC;EAChE,IAAI,CAAC,QACH,MAAM,IAAI,oBACR,cAAc,aAAa,KAAK,0CAChC,kBAAkB,6BACnB;EAGH,OAAO;;CAGT,MAAM,4BAA4B,QAAiC;EACjE,MAAM,EAAE,uBAAuB,MAAM,KAAK,cAAc,uBAAuB;EAC/E,MAAM,eAAeA,IAAQ;EAE7B,MAAM,WAAW,sBAAsB,aAAa;EACpD,IAAI,CAAC,oBACH,KAAK,OAAO,KAAK,4EAA4E;EAG/F,MAAM,KAAK,OAAO;GAChB;GACA,WAAW,KAAK,KAAK,GAAG,WAAW;GACnC;GACA,qBAAqB;GACtB,CAAC;EAEF,OAAO;;CAGT,MAAM,2BAA2B,cAAsB,qBAA4C;EACjG,MAAM,KAAK,gBAAgB,aAAa;EAExC,MAAM,KAAK,WAAW,OAAO,EAAE,cAAc,EAAE,EAAE,qBAAqB,CAAC;;CAGzE,MAAM,gCAA+C;EACnD,MAAM,SAAS,MAAM,KAAK,WAAW,OAAO,EAC1C,WAAW,SAAS,KAAK,KAAK,CAAC,EAChC,CAAC;EAEF,IAAI,OAAO,UACT,KAAK,OAAO,MAAM,WAAW,OAAO,SAAS,0BAA0B;;CAI3E,MAAM,2BAA2B,QAA+B;EAC9D,MAAM,SAAS,MAAM,KAAK,WAAW,OAAO,EAC1C,QACD,CAAC;EAEF,IAAI,OAAO,UACT,KAAK,OAAO,MAAM,WAAW,OAAO,SAAS,gCAAgC;;CAIjF,MAAM,mBAAmB,cAAqC;EAC5D,MAAM,SAAS,MAAM,KAAK,WAAW,OAAO,EAC1C,cACD,CAAC;EAEF,IAAI,OAAO,UACT,KAAK,OAAO,MAAM,WAAW,OAAO,SAAS,uBAAuB;;CAIxE,MAAM,mCAAmC,QAA+B;EACtE,MAAM,SAAS,MAAM,KAAK,WAAW,OAAO;GAC1C;GACA,WAAW,SAAS,KAAK,KAAK,CAAC;GAChC,CAAC;EAEF,IAAI,OAAO,UACT,KAAK,OAAO,MAAM,WAAW,OAAO,SAAS,yCAAyC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role.service.js","names":[],"sources":["../../../src/services/orm/role.service.ts"],"sourcesContent":["import { BaseService } from \"@/services/orm/base.service\";\nimport type { IRoleService } from \"@/services/interfaces/role-service.interface\";\nimport { Role } from \"@/entities\";\nimport { SettingsStore } from \"@/state/settings.store\";\nimport { TypeormService } from \"@/services/typeorm/typeorm.service\";\nimport { RoleDto } from \"@/services/interfaces/role.dto\";\nimport { NotFoundException } from \"@/exceptions/runtime.exceptions\";\nimport { ROLE_PERMS, ROLES, type PermissionName, type RoleName } from \"@/constants/authorization.constants\";\n\nexport class RoleService extends BaseService(Role, RoleDto) implements IRoleService {\n constructor(\n typeormService: TypeormService,\n private readonly appDefaultRole: RoleName,\n private readonly appDefaultRoleNoLogin: RoleName,\n private readonly settingsStore: SettingsStore,\n ) {\n super(typeormService);\n }\n\n private _roles: Role[] = [];\n\n get roles() {\n return this._roles;\n }\n\n async getAppDefaultRole(): Promise<RoleName> {\n if (await this.settingsStore.getLoginRequired()) {\n return this.appDefaultRole;\n }\n return this.appDefaultRoleNoLogin;\n }\n\n async getAppDefaultRoleNames(): Promise<RoleName[]> {\n if (!this._roles?.length) {\n await this.syncRoles();\n }\n\n return [await this.getAppDefaultRole()];\n }\n\n toDto(role: Role): RoleDto {\n return {\n id: role.id,\n name: role.name as RoleName,\n };\n }\n\n getRolesPermissions(roleNames: RoleName[]): PermissionName[] {\n let permissions: PermissionName[] = [];\n if (!roleNames?.length) return [];\n\n for (const roleName of roleNames) {\n const rolePermissions = this.getRolePermissions(roleName);\n permissions = [...new Set([...permissions, ...rolePermissions])];\n }\n\n return permissions;\n }\n\n /**\n * Checks if any of the required roles are in the assigned roles\n * @param requiredRoles list of role names that would grant access\n * @param assignedRoles user's assigned role names\n * @param subset if true, any match grants access (OR); if false, all must match (AND)\n */\n authorizeRoles(requiredRoles: RoleName[], assignedRoles: RoleName[], subset: boolean): boolean {\n if (!requiredRoles?.length) return true;\n\n let isAuthorized = !subset; // Start with false for OR, true for AND\n for (const requiredRole of requiredRoles) {\n const result = this.authorizeRole(requiredRole, assignedRoles);\n isAuthorized = subset ? isAuthorized || result : isAuthorized && result;\n }\n\n return isAuthorized;\n }\n\n authorizeRole(requiredRole: RoleName, assignedRoles: RoleName[]): boolean {\n return assignedRoles.includes(requiredRole);\n }\n\n getManyRoles(roleIds: number[]): Role[] {\n return roleIds.map((roleId) => this.getRole(roleId));\n }\n\n getRole(roleId: number): Role {\n const role = this._roles.find((r) => r.id === roleId);\n if (!role) throw new NotFoundException(`Role by provided id was not found`);\n\n return role;\n }\n\n getRoleByName(roleName: RoleName): Role {\n const role = this._roles.find((r) => r.name === roleName);\n if (!role) throw new NotFoundException(`Role by provided name was not found`);\n\n return role;\n }\n\n getRolePermissions(roleName: RoleName): PermissionName[] {\n if (!roleName) {\n return [];\n }\n return ROLE_PERMS[roleName] ?? [];\n }\n\n roleIdsToRoleNames(roleIds: number[]): RoleName[] {\n return roleIds\n .map((roleId) => {\n const role = this._roles.find((r) => r.id === roleId);\n return role?.name as RoleName | undefined;\n })\n .filter((name): name is RoleName => name !== undefined);\n }\n\n async getSynchronizedRoleByName(roleName: RoleName): Promise<Role> {\n if (!this._roles?.length) {\n await this.syncRoles();\n }\n\n return this.getRoleByName(roleName);\n }\n\n async syncRoles(): Promise<void> {\n this._roles = [];\n for (const roleName of Object.values(ROLES)) {\n const storedRole = await this.repository.findOneBy({ name: roleName });\n if (storedRole) {\n this._roles.push(storedRole);\n } else {\n const newRole = await this.create({\n name: roleName,\n });\n this._roles.push(newRole);\n }\n }\n }\n}\n"],"mappings":";;;;;;;AASA,IAAa,cAAb,cAAiC,YAAY,MAAM,QAAQ,CAAyB;CAClF,YACE,gBACA,gBACA,uBACA,eACA;
|
|
1
|
+
{"version":3,"file":"role.service.js","names":[],"sources":["../../../src/services/orm/role.service.ts"],"sourcesContent":["import { BaseService } from \"@/services/orm/base.service\";\nimport type { IRoleService } from \"@/services/interfaces/role-service.interface\";\nimport { Role } from \"@/entities\";\nimport { SettingsStore } from \"@/state/settings.store\";\nimport { TypeormService } from \"@/services/typeorm/typeorm.service\";\nimport { RoleDto } from \"@/services/interfaces/role.dto\";\nimport { NotFoundException } from \"@/exceptions/runtime.exceptions\";\nimport { ROLE_PERMS, ROLES, type PermissionName, type RoleName } from \"@/constants/authorization.constants\";\n\nexport class RoleService extends BaseService(Role, RoleDto) implements IRoleService {\n constructor(\n typeormService: TypeormService,\n private readonly appDefaultRole: RoleName,\n private readonly appDefaultRoleNoLogin: RoleName,\n private readonly settingsStore: SettingsStore,\n ) {\n super(typeormService);\n }\n\n private _roles: Role[] = [];\n\n get roles() {\n return this._roles;\n }\n\n async getAppDefaultRole(): Promise<RoleName> {\n if (await this.settingsStore.getLoginRequired()) {\n return this.appDefaultRole;\n }\n return this.appDefaultRoleNoLogin;\n }\n\n async getAppDefaultRoleNames(): Promise<RoleName[]> {\n if (!this._roles?.length) {\n await this.syncRoles();\n }\n\n return [await this.getAppDefaultRole()];\n }\n\n toDto(role: Role): RoleDto {\n return {\n id: role.id,\n name: role.name as RoleName,\n };\n }\n\n getRolesPermissions(roleNames: RoleName[]): PermissionName[] {\n let permissions: PermissionName[] = [];\n if (!roleNames?.length) return [];\n\n for (const roleName of roleNames) {\n const rolePermissions = this.getRolePermissions(roleName);\n permissions = [...new Set([...permissions, ...rolePermissions])];\n }\n\n return permissions;\n }\n\n /**\n * Checks if any of the required roles are in the assigned roles\n * @param requiredRoles list of role names that would grant access\n * @param assignedRoles user's assigned role names\n * @param subset if true, any match grants access (OR); if false, all must match (AND)\n */\n authorizeRoles(requiredRoles: RoleName[], assignedRoles: RoleName[], subset: boolean): boolean {\n if (!requiredRoles?.length) return true;\n\n let isAuthorized = !subset; // Start with false for OR, true for AND\n for (const requiredRole of requiredRoles) {\n const result = this.authorizeRole(requiredRole, assignedRoles);\n isAuthorized = subset ? isAuthorized || result : isAuthorized && result;\n }\n\n return isAuthorized;\n }\n\n authorizeRole(requiredRole: RoleName, assignedRoles: RoleName[]): boolean {\n return assignedRoles.includes(requiredRole);\n }\n\n getManyRoles(roleIds: number[]): Role[] {\n return roleIds.map((roleId) => this.getRole(roleId));\n }\n\n getRole(roleId: number): Role {\n const role = this._roles.find((r) => r.id === roleId);\n if (!role) throw new NotFoundException(`Role by provided id was not found`);\n\n return role;\n }\n\n getRoleByName(roleName: RoleName): Role {\n const role = this._roles.find((r) => r.name === roleName);\n if (!role) throw new NotFoundException(`Role by provided name was not found`);\n\n return role;\n }\n\n getRolePermissions(roleName: RoleName): PermissionName[] {\n if (!roleName) {\n return [];\n }\n return ROLE_PERMS[roleName] ?? [];\n }\n\n roleIdsToRoleNames(roleIds: number[]): RoleName[] {\n return roleIds\n .map((roleId) => {\n const role = this._roles.find((r) => r.id === roleId);\n return role?.name as RoleName | undefined;\n })\n .filter((name): name is RoleName => name !== undefined);\n }\n\n async getSynchronizedRoleByName(roleName: RoleName): Promise<Role> {\n if (!this._roles?.length) {\n await this.syncRoles();\n }\n\n return this.getRoleByName(roleName);\n }\n\n async syncRoles(): Promise<void> {\n this._roles = [];\n for (const roleName of Object.values(ROLES)) {\n const storedRole = await this.repository.findOneBy({ name: roleName });\n if (storedRole) {\n this._roles.push(storedRole);\n } else {\n const newRole = await this.create({\n name: roleName,\n });\n this._roles.push(newRole);\n }\n }\n }\n}\n"],"mappings":";;;;;;;AASA,IAAa,cAAb,cAAiC,YAAY,MAAM,QAAQ,CAAyB;CAClF,YACE,gBACA,gBACA,uBACA,eACA;EACA,MAAM,eAAe;EAJJ,KAAA,iBAAA;EACA,KAAA,wBAAA;EACA,KAAA,gBAAA;;CAKnB,SAAyB,EAAE;CAE3B,IAAI,QAAQ;EACV,OAAO,KAAK;;CAGd,MAAM,oBAAuC;EAC3C,IAAI,MAAM,KAAK,cAAc,kBAAkB,EAC7C,OAAO,KAAK;EAEd,OAAO,KAAK;;CAGd,MAAM,yBAA8C;EAClD,IAAI,CAAC,KAAK,QAAQ,QAChB,MAAM,KAAK,WAAW;EAGxB,OAAO,CAAC,MAAM,KAAK,mBAAmB,CAAC;;CAGzC,MAAM,MAAqB;EACzB,OAAO;GACL,IAAI,KAAK;GACT,MAAM,KAAK;GACZ;;CAGH,oBAAoB,WAAyC;EAC3D,IAAI,cAAgC,EAAE;EACtC,IAAI,CAAC,WAAW,QAAQ,OAAO,EAAE;EAEjC,KAAK,MAAM,YAAY,WAAW;GAChC,MAAM,kBAAkB,KAAK,mBAAmB,SAAS;GACzD,cAAc,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,aAAa,GAAG,gBAAgB,CAAC,CAAC;;EAGlE,OAAO;;;;;;;;CAST,eAAe,eAA2B,eAA2B,QAA0B;EAC7F,IAAI,CAAC,eAAe,QAAQ,OAAO;EAEnC,IAAI,eAAe,CAAC;EACpB,KAAK,MAAM,gBAAgB,eAAe;GACxC,MAAM,SAAS,KAAK,cAAc,cAAc,cAAc;GAC9D,eAAe,SAAS,gBAAgB,SAAS,gBAAgB;;EAGnE,OAAO;;CAGT,cAAc,cAAwB,eAAoC;EACxE,OAAO,cAAc,SAAS,aAAa;;CAG7C,aAAa,SAA2B;EACtC,OAAO,QAAQ,KAAK,WAAW,KAAK,QAAQ,OAAO,CAAC;;CAGtD,QAAQ,QAAsB;EAC5B,MAAM,OAAO,KAAK,OAAO,MAAM,MAAM,EAAE,OAAO,OAAO;EACrD,IAAI,CAAC,MAAM,MAAM,IAAI,kBAAkB,oCAAoC;EAE3E,OAAO;;CAGT,cAAc,UAA0B;EACtC,MAAM,OAAO,KAAK,OAAO,MAAM,MAAM,EAAE,SAAS,SAAS;EACzD,IAAI,CAAC,MAAM,MAAM,IAAI,kBAAkB,sCAAsC;EAE7E,OAAO;;CAGT,mBAAmB,UAAsC;EACvD,IAAI,CAAC,UACH,OAAO,EAAE;EAEX,OAAO,WAAW,aAAa,EAAE;;CAGnC,mBAAmB,SAA+B;EAChD,OAAO,QACJ,KAAK,WAAW;GAEf,OADa,KAAK,OAAO,MAAM,MAAM,EAAE,OAAO,OACnC,EAAE;IACb,CACD,QAAQ,SAA2B,SAAS,KAAA,EAAU;;CAG3D,MAAM,0BAA0B,UAAmC;EACjE,IAAI,CAAC,KAAK,QAAQ,QAChB,MAAM,KAAK,WAAW;EAGxB,OAAO,KAAK,cAAc,SAAS;;CAGrC,MAAM,YAA2B;EAC/B,KAAK,SAAS,EAAE;EAChB,KAAK,MAAM,YAAY,OAAO,OAAO,MAAM,EAAE;GAC3C,MAAM,aAAa,MAAM,KAAK,WAAW,UAAU,EAAE,MAAM,UAAU,CAAC;GACtE,IAAI,YACF,KAAK,OAAO,KAAK,WAAW;QACvB;IACL,MAAM,UAAU,MAAM,KAAK,OAAO,EAChC,MAAM,UACP,CAAC;IACF,KAAK,OAAO,KAAK,QAAQ"}
|