From cd446a9ca3e5d96dd32dc8c7253adbe4340cc6d1 Mon Sep 17 00:00:00 2001 From: Rishi Ghan Date: Tue, 17 Feb 2026 12:05:28 -0500 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A0=20Fixes=20for=20GraphQL=20related?= =?UTF-8?q?=20schema=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- constants/directories.ts | 4 +- dependencies.docker-compose.yml | 14 +- models/comic.model.ts | 4 + package-lock.json | 536 ++++++++++++++++++++++++++++++-- services/jobqueue.service.ts | 4 +- services/library.service.ts | 7 +- utils/file.utils.ts | 8 +- utils/uncompression.utils.ts | 512 +++++++++++++++++++++--------- 8 files changed, 894 insertions(+), 195 deletions(-) diff --git a/constants/directories.ts b/constants/directories.ts index c912665..2adff44 100644 --- a/constants/directories.ts +++ b/constants/directories.ts @@ -1,2 +1,2 @@ -export const COMICS_DIRECTORY = "./comics"; -export const USERDATA_DIRECTORY = "./userdata"; \ No newline at end of file +export const COMICS_DIRECTORY = process.env.COMICS_DIRECTORY || "./comics"; +export const USERDATA_DIRECTORY = process.env.USERDATA_DIRECTORY || "./userdata"; \ No newline at end of file diff --git a/dependencies.docker-compose.yml b/dependencies.docker-compose.yml index 65691a3..785d5ff 100644 --- a/dependencies.docker-compose.yml +++ b/dependencies.docker-compose.yml @@ -17,23 +17,15 @@ services: hostname: kafka1 container_name: kafka1 ports: - - "9092:9092" - - "29092:29092" - - "9999:9999" + - "127.0.0.1:9092:9092" # exposed ONLY to host localhost environment: - KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka1:19092,EXTERNAL://${DOCKER_HOST_IP:-127.0.0.1}:9092,DOCKER://host.docker.internal:29092 - KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT,DOCKER:PLAINTEXT - KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL + KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092 + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092 KAFKA_ZOOKEEPER_CONNECT: "zoo1:2181" KAFKA_BROKER_ID: 1 - KAFKA_LOG4J_LOGGERS: "kafka.controller=INFO,kafka.producer.async.DefaultEventHandler=INFO,state.change.logger=INFO" KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 - KAFKA_JMX_PORT: 9999 - KAFKA_JMX_HOSTNAME: ${DOCKER_HOST_IP:-127.0.0.1} - KAFKA_AUTHORIZER_CLASS_NAME: kafka.security.authorizer.AclAuthorizer - KAFKA_ALLOW_EVERYONE_IF_NO_ACL_FOUND: "true" depends_on: - zoo1 networks: diff --git a/models/comic.model.ts b/models/comic.model.ts index 0a8a0b3..595cedb 100644 --- a/models/comic.model.ts +++ b/models/comic.model.ts @@ -129,6 +129,10 @@ const ComicSchema = mongoose.Schema( wanted: wantedSchema, acquisition: { + source: { + wanted: { type: Boolean, default: false }, + name: { type: String, default: null }, + }, release: {}, directconnect: { downloads: { diff --git a/package-lock.json b/package-lock.json index 17bbe86..0d432f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1734,6 +1734,16 @@ "node": ">=16" } }, + "node_modules/@emnapi/runtime": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", + "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@es-joy/jsdoccomment": { "version": "0.39.4", "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.39.4.tgz", @@ -1916,6 +1926,32 @@ "@img/sharp-libvips-darwin-arm64": "1.0.2" } }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.3.tgz", + "integrity": "sha512-2QeSl7QDK9ru//YBT4sQkoq7L0EAJZA3rtV+v9p8xTKl4U1bUqTIaCnoC7Ctx2kCjQgwFXDasOtPTCT8eCTXvw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.2" + } + }, "node_modules/@img/sharp-libvips-darwin-arm64": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.2.tgz", @@ -1937,6 +1973,382 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.2.tgz", + "integrity": "sha512-Ofw+7oaWa0HiiMiKWqqaZbaYV3/UGL2wAPeLuJTx+9cXpCRdvQhCLG0IH8YGwM0yGWGLpsF4Su9vM1o6aer+Fw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "macos": ">=10.13", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.2.tgz", + "integrity": "sha512-iLWCvrKgeFoglQxdEwzu1eQV04o8YeYGFXtfWU26Zr2wWT3q3MTzC+QTCO3ZQfWd3doKHT4Pm2kRmLbupT+sZw==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.2.tgz", + "integrity": "sha512-x7kCt3N00ofFmmkkdshwj3vGPCnmiDh7Gwnd4nUwZln2YjqPxV1NlTyZOvoDWdKQVDL911487HOueBvrpflagw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.2.tgz", + "integrity": "sha512-cmhQ1J4qVhfmS6szYW7RT+gLJq9dH2i4maq+qyXayUSn9/3iY2ZeWpbAgSpSVbV2E1JUL2Gg7pwnYQ1h8rQIog==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.2.tgz", + "integrity": "sha512-E441q4Qdb+7yuyiADVi5J+44x8ctlrqn8XgkDTwr4qPJzWkaHwD489iZ4nGDgcuya4iMN3ULV6NwbhRZJ9Z7SQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.2.tgz", + "integrity": "sha512-3CAkndNpYUrlDqkCM5qhksfE+qSIREVpyoeHIU6jd48SJZViAmznoQQLAv4hVXF7xyUB9zf+G++e2v1ABjCbEQ==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.2.tgz", + "integrity": "sha512-VI94Q6khIHqHWNOh6LLdm9s2Ry4zdjWJwH56WoiJU7NTeDwyApdZZ8c+SADC8OH98KWNQXnE01UdJ9CSfZvwZw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.3.tgz", + "integrity": "sha512-Q7Ee3fFSC9P7vUSqVEF0zccJsZ8GiiCJYGWDdhEjdlOeS9/jdkyJ6sUSPj+bL8VuOYFSbofrW0t/86ceVhx32w==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.2" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.3.tgz", + "integrity": "sha512-Zf+sF1jHZJKA6Gor9hoYG2ljr4wo9cY4twaxgFDvlG0Xz9V7sinsPp8pFd1XtlhTzYo0IhDbl3rK7P6MzHpnYA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.2" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.3.tgz", + "integrity": "sha512-vFk441DKRFepjhTEH20oBlFrHcLjPfI8B0pMIxGm3+yilKyYeHEVvrZhYFdqIseSclIqbQ3SnZMwEMWonY5XFA==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.2" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.3.tgz", + "integrity": "sha512-Q4I++herIJxJi+qmbySd072oDPRkCg/SClLEIDh5IL9h1zjhqjv82H0Seupd+q2m0yOfD+/fJnjSoDFtKiHu2g==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.2" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.3.tgz", + "integrity": "sha512-qnDccehRDXadhM9PM5hLvcPRYqyFCBN31kq+ErBSZtZlsAc1U4Z85xf/RXv1qolkdu+ibw64fUDaRdktxTNP9A==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.2" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.3.tgz", + "integrity": "sha512-Jhchim8kHWIU/GZ+9poHMWRcefeaxFIs9EBqf9KtcC14Ojk6qua7ghKiPs0sbeLbLj/2IGBtDcxHyjCdYWkk2w==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.2" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.3.tgz", + "integrity": "sha512-68zivsdJ0koE96stdUfM+gmyaK/NcoSZK5dV5CAjES0FUXS9lchYt8LAB5rTbM7nlWtxaU/2GON0HVN6/ZYJAQ==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.1.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.3.tgz", + "integrity": "sha512-CyimAduT2whQD8ER4Ux7exKrtfoaUiVr7HG0zZvO0XTFn2idUWljjxv58GxNTkFb8/J9Ub9AqITGkJD6ZginxQ==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.3.tgz", + "integrity": "sha512-viT4fUIDKnli3IfOephGnolMzhz5VaTvDRkYqtZxOMIoMQ4MrAziO7pT1nVnOt2FAm7qW5aa+CCc13aEY6Le0g==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@ioredis/commands": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", @@ -1981,10 +2393,11 @@ } }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -2435,6 +2848,71 @@ "darwin" ] }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.2.tgz", + "integrity": "sha512-lwriRAHm1Yg4iDf23Oxm9n/t5Zpw1lVnxYU3HnJPTi2lJRkKTrps1KVgvL6m7WvmhYVt/FIsssWay+k45QHeuw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.2.tgz", + "integrity": "sha512-MOI9Dlfrpi2Cuc7i5dXdxPbFIgbDBGgKR5F2yWEa6FVEtSWncfVNKW5AKjImAQ6CZlBK9tympdsZJ2xThBiWWA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.2.tgz", + "integrity": "sha512-FU20Bo66/f7He9Fp9sP2zaJ1Q8L9uLPZQDub/WlUip78JlPeMbVL8546HbZfcW9LNciEXc8d+tThSJjSC+tmsg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.2.tgz", + "integrity": "sha512-gsWNDCklNy7Ajk0vBBf9jEx04RUxuDQfBse918Ww+Qb9HCPoGzS+XJTLe96iN3BVK7grnLiYghP/M4L8VsaHeA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.2.tgz", + "integrity": "sha512-O+6Gs8UeDbyFpbSh2CPEz/UOrrdWPTBYNblZK5CxxLisYt4kGX3Sc+czffFonyjiGSq3jWLwJS/CCJc7tBr4sQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -4297,13 +4775,13 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", - "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, @@ -6030,13 +6508,15 @@ } }, "node_modules/es-set-tostringtag": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", - "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "has-tostringtag": "^1.0.0" + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -7253,12 +7733,15 @@ } }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -9519,9 +10002,10 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -14360,14 +14844,14 @@ } }, "node_modules/r2-shared-js": { - "version": "1.0.84", - "resolved": "https://registry.npmjs.org/r2-shared-js/-/r2-shared-js-1.0.84.tgz", - "integrity": "sha512-Nm5ywbfndertd0zgu/CjSXcfW8T5Sm0ZnLIL/b9enrFw+9Xy2NAiHjz0Yu3pFLLD+zjvVKLUrFj6MnQpJwLEPw==", + "version": "1.0.85", + "resolved": "https://registry.npmjs.org/r2-shared-js/-/r2-shared-js-1.0.85.tgz", + "integrity": "sha512-5+0gdzG85fD8bmyDg3CBWGQzbfuZXqZC2bC13ML6Tr29kKY/I+6TcfAv5IxE04bxoY3o6rMzFPhq6D2PDZNUWA==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { "@xmldom/xmldom": "^0.9.8", - "debug": "^4.4.1", + "debug": "^4.4.3", "fast-deep-equal": "^3.1.3", "he": "^1.2.0", "image-size": "^2.0.2", @@ -14390,9 +14874,9 @@ } }, "node_modules/r2-shared-js/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" diff --git a/services/jobqueue.service.ts b/services/jobqueue.service.ts index 830e18c..43f2c0e 100644 --- a/services/jobqueue.service.ts +++ b/services/jobqueue.service.ts @@ -191,7 +191,9 @@ export default class JobQueueService extends Service { }; } catch (error) { console.error( - `An error occurred processing Job ID ${ctx.locals.job.id}` + `An error occurred processing Job ID ${ctx.locals.job.id}:`, + error instanceof Error ? error.message : error, + error instanceof Error ? error.stack : "" ); throw new MoleculerError( error, diff --git a/services/library.service.ts b/services/library.service.ts index ecf6c44..b918960 100644 --- a/services/library.service.ts +++ b/services/library.service.ts @@ -174,8 +174,13 @@ export default class ImportService extends Service { try { // Get params to be passed to the import jobs const { sessionId } = ctx.params; + const resolvedPath = path.resolve(COMICS_DIRECTORY); + console.log(`Walking comics directory: ${resolvedPath}`); // 1. Walk the Source folder - klaw(path.resolve(COMICS_DIRECTORY)) + klaw(resolvedPath) + .on("error", (err) => { + console.error(`Error walking directory ${resolvedPath}:`, err); + }) // 1.1 Filter on .cb* extensions .pipe( through2.obj(function (item, enc, next) { diff --git a/utils/file.utils.ts b/utils/file.utils.ts index 14c9130..a6561f7 100644 --- a/utils/file.utils.ts +++ b/utils/file.utils.ts @@ -174,14 +174,16 @@ export const getMimeType = async (filePath: string) => { */ export const createDirectory = async (options: any, directoryPath: string) => { try { - await fse.ensureDir(directoryPath, options); + await fse.ensureDir(directoryPath); console.info(`Directory [ %s ] was created.`, directoryPath); } catch (error) { + const errMsg = error instanceof Error ? error.message : String(error); + console.error(`Failed to create directory [ ${directoryPath} ]:`, error); throw new Errors.MoleculerError( - "Failed to create directory", + `Failed to create directory: ${directoryPath} - ${errMsg}`, 500, "FileOpsError", - error + { directoryPath, originalError: errMsg } ); } }; diff --git a/utils/uncompression.utils.ts b/utils/uncompression.utils.ts index ea6f174..02c8367 100644 --- a/utils/uncompression.utils.ts +++ b/utils/uncompression.utils.ts @@ -32,6 +32,7 @@ SOFTWARE. */ import { createReadStream, createWriteStream, existsSync, statSync } from "fs"; +import { execFile } from "child_process"; import { isEmpty, isNil, isUndefined, remove, each, map, reject } from "lodash"; import * as p7zip from "p7zip-threetwo"; import path from "path"; @@ -88,124 +89,335 @@ export const extractComicInfoXMLFromRar = async ( )}`; await createDirectory(directoryOptions, targetDirectory); - const archive = new Unrar({ - path: path.resolve(filePath), - bin: `${UNRAR_BIN_PATH}`, // this will change depending on Docker base OS - arguments: ["-v"], - }); - const filesInArchive: [RarFile] = await new Promise( - (resolve, reject) => { - return archive.list((err, entries) => { - if (err) { - console.log(`DEBUG: ${JSON.stringify(err, null, 2)}`); - reject(err); - } - resolve(entries); - }); - } - ); + // Try unrar-based extraction first, fall back to p7zip if it fails + let unrarError: Error | null = null; + try { + const result = await extractComicInfoXMLFromRarUsingUnrar( + filePath, + mimeType, + targetDirectory, + fileNameWithoutExtension, + extension + ); + return result; + } catch (err) { + unrarError = err; + console.warn( + `unrar-based extraction failed for ${filePath}: ${err.message}. Falling back to p7zip.` + ); + } - remove(filesInArchive, ({ type }) => type === "Directory"); - const comicInfoXML = remove( - filesInArchive, - ({ name }) => path.basename(name).toLowerCase() === "comicinfo.xml" - ); + try { + const result = await extractComicInfoXMLFromRarUsingP7zip( + filePath, + mimeType, + targetDirectory, + fileNameWithoutExtension, + extension + ); + return result; + } catch (p7zipError) { + console.error( + `p7zip-based extraction also failed for ${filePath}: ${p7zipError.message}` + ); + throw new Error( + `Failed to extract RAR archive: ${filePath}. ` + + `unrar error: ${unrarError?.message}. ` + + `p7zip error: ${p7zipError.message}. ` + + `Ensure 'unrar' is installed at ${UNRAR_BIN_PATH} or '7z' is available via SEVENZ_BINARY_PATH.` + ); + } + } catch (err) { + throw err; + } +}; - remove( - filesInArchive, - ({ name }) => - !IMPORT_IMAGE_FILE_FORMATS.includes( - path.extname(name).toLowerCase() - ) - ); - const files = filesInArchive.sort((a, b) => { - if (!isUndefined(a) && !isUndefined(b)) { - return path - .basename(a.name) - .toLowerCase() - .localeCompare(path.basename(b.name).toLowerCase()); - } - }); - const comicInfoXMLFilePromise = new Promise((resolve, reject) => { - let comicinfostring = ""; - if (!isUndefined(comicInfoXML[0])) { - const comicInfoXMLFileName = path.basename( - comicInfoXML[0].name - ); - const writeStream = createWriteStream( - `${targetDirectory}/${comicInfoXMLFileName}` - ); - - archive.stream(comicInfoXML[0]["name"]).pipe(writeStream); - writeStream.on("finish", async () => { - console.log(`Attempting to write comicInfo.xml...`); - const readStream = createReadStream( - `${targetDirectory}/${comicInfoXMLFileName}` - ); - readStream.on("data", (data) => { - comicinfostring += data; - }); - readStream.on("error", (error) => reject(error)); - readStream.on("end", async () => { - if ( - existsSync( - `${targetDirectory}/${comicInfoXMLFileName}` - ) - ) { - const comicInfoJSON = await convertXMLToJSON( - comicinfostring.toString() - ); - console.log( - `comicInfo.xml successfully written: ${comicInfoJSON.comicinfo}` - ); - resolve({ comicInfoJSON: comicInfoJSON.comicinfo }); - } - }); - }); - } else { - resolve({ comicInfoJSON: null }); - } - }); - - const coverFilePromise = new Promise((resolve, reject) => { - const coverFile = path.basename(files[0].name); - const sharpStream = sharp().resize(275).toFormat("png"); - const coverExtractionStream = archive.stream(files[0].name); - const resizeStream = coverExtractionStream.pipe(sharpStream); - resizeStream.toFile( - `${targetDirectory}/${coverFile}`, - (err, info) => { - if (err) { - reject(err); - } - checkFileExists(`${targetDirectory}/${coverFile}`).then( - (bool) => { - console.log(`${coverFile} exists: ${bool}`); - // orchestrate result - resolve({ - filePath, - name: fileNameWithoutExtension, - extension, - containedIn: targetDirectory, - fileSize: fse.statSync(filePath).size, - mimeType, - cover: { - filePath: path.relative( - process.cwd(), - `${targetDirectory}/${coverFile}` - ), - }, - }); - } +/** + * List files in a RAR archive using the unrar binary directly. + * Uses `unrar lb` (bare list) for reliable output — one filename per line. + */ +const listRarFiles = (filePath: string): Promise => { + return new Promise((resolve, reject) => { + execFile( + UNRAR_BIN_PATH, + ["lb", path.resolve(filePath)], + { maxBuffer: 10 * 1024 * 1024 }, + (err, stdout, stderr) => { + if (err) { + return reject( + new Error( + `unrar lb failed for ${filePath}: ${err.message}${stderr ? ` (stderr: ${stderr})` : ""}` + ) ); } - ); - }); + const files = stdout + .split(/\r?\n/) + .map((line) => line.trim()) + .filter((line) => line.length > 0); + resolve(files); + } + ); + }); +}; - return Promise.all([comicInfoXMLFilePromise, coverFilePromise]); - } catch (err) { - reject(err); +/** + * Extract a single file from a RAR archive to stdout as a Buffer. + * Uses `unrar p -inul` (print to stdout, no messages). + */ +const extractRarFileToBuffer = ( + filePath: string, + entryName: string +): Promise => { + return new Promise((resolve, reject) => { + execFile( + UNRAR_BIN_PATH, + ["p", "-inul", path.resolve(filePath), entryName], + { maxBuffer: 50 * 1024 * 1024, encoding: "buffer" }, + (err, stdout, stderr) => { + if (err) { + return reject( + new Error( + `unrar p failed for ${entryName} in ${filePath}: ${err.message}` + ) + ); + } + resolve(stdout as unknown as Buffer); + } + ); + }); +}; + +/** + * Extract comic info and cover from a RAR archive using the unrar binary directly. + * Bypasses the `unrar` npm package which has parsing bugs. + */ +const extractComicInfoXMLFromRarUsingUnrar = async ( + filePath: string, + mimeType: string, + targetDirectory: string, + fileNameWithoutExtension: string, + extension: string +): Promise => { + // List all files in the archive using bare listing + const allFiles = await listRarFiles(filePath); + + console.log( + `RAR (unrar direct): ${allFiles.length} total entries in ${filePath}` + ); + + // Find ComicInfo.xml + const comicInfoXMLEntry = allFiles.find( + (name) => path.basename(name).toLowerCase() === "comicinfo.xml" + ); + + // Filter to image files only + const imageFiles = allFiles + .filter((name) => + IMPORT_IMAGE_FILE_FORMATS.includes( + path.extname(name).toLowerCase() + ) + ) + .sort((a, b) => + path + .basename(a) + .toLowerCase() + .localeCompare(path.basename(b).toLowerCase()) + ); + + if (imageFiles.length === 0) { + throw new Error( + `No image files found via unrar in RAR archive: ${filePath}` + ); } + + // Extract and parse ComicInfo.xml if present + let comicInfoResult: { comicInfoJSON: any } = { comicInfoJSON: null }; + if (comicInfoXMLEntry) { + try { + const xmlBuffer = await extractRarFileToBuffer( + filePath, + comicInfoXMLEntry + ); + const comicInfoJSON = await convertXMLToJSON( + xmlBuffer.toString("utf-8") + ); + console.log( + `comicInfo.xml successfully extracted: ${comicInfoJSON.comicinfo}` + ); + comicInfoResult = { comicInfoJSON: comicInfoJSON.comicinfo }; + } catch (xmlErr) { + console.warn( + `Failed to extract ComicInfo.xml from ${filePath}: ${xmlErr.message}` + ); + } + } + + // Extract and resize cover image (first image file) + const coverEntryName = imageFiles[0]; + const coverFile = path.basename(coverEntryName); + const coverBaseName = sanitize(path.basename(coverFile, path.extname(coverFile))); + const coverOutputFile = `${targetDirectory}/${coverBaseName}.png`; + + const coverBuffer = await extractRarFileToBuffer( + filePath, + coverEntryName + ); + + await sharp(coverBuffer) + .resize(275) + .toFormat("png") + .toFile(coverOutputFile); + + console.log(`${coverFile} cover written to: ${coverOutputFile}`); + + const relativeCoverPath = path.relative(process.cwd(), coverOutputFile); + console.log(`RAR cover path (relative): ${relativeCoverPath}`); + console.log(`RAR cover file exists: ${existsSync(coverOutputFile)}`); + + const coverResult = { + filePath, + name: fileNameWithoutExtension, + extension, + containedIn: targetDirectory, + fileSize: fse.statSync(filePath).size, + mimeType, + cover: { + filePath: relativeCoverPath, + }, + }; + + return [comicInfoResult, coverResult]; +}; + +/** + * Fallback: Extract comic info and cover from a RAR archive using p7zip (7z). + * Uses the same approach as extractComicInfoXMLFromZip since p7zip handles RAR files. + */ +const extractComicInfoXMLFromRarUsingP7zip = async ( + filePath: string, + mimeType: string, + targetDirectory: string, + fileNameWithoutExtension: string, + extension: string +): Promise => { + let filesToWriteToDisk = { coverFile: null, comicInfoXML: null }; + const extractionTargets = []; + + // read the archive using p7zip (supports RAR) + let filesFromArchive = await p7zip.read(path.resolve(filePath)); + + console.log( + `RAR (p7zip): ${filesFromArchive.files.length} total entries in ${filePath}` + ); + + // detect ComicInfo.xml + const comicInfoXMLFileObject = remove( + filesFromArchive.files, + (file) => path.basename(file.name.toLowerCase()) === "comicinfo.xml" + ); + // only allow allowed image formats + remove( + filesFromArchive.files, + ({ name }) => + !IMPORT_IMAGE_FILE_FORMATS.includes( + path.extname(name).toLowerCase() + ) + ); + + // Natural sort + const files = filesFromArchive.files.sort((a, b) => { + if (!isUndefined(a) && !isUndefined(b)) { + return path + .basename(a.name) + .toLowerCase() + .localeCompare(path.basename(b.name).toLowerCase()); + } + }); + + if (files.length === 0) { + throw new Error(`No image files found in RAR archive: ${filePath}`); + } + + // Push the first file (cover) to our extraction target + extractionTargets.push(files[0].name); + filesToWriteToDisk.coverFile = path.basename(files[0].name); + + if (!isEmpty(comicInfoXMLFileObject)) { + filesToWriteToDisk.comicInfoXML = comicInfoXMLFileObject[0].name; + extractionTargets.push(filesToWriteToDisk.comicInfoXML); + } + // Extract the files. + await p7zip.extract( + filePath, + targetDirectory, + extractionTargets, + "", + false + ); + + // ComicInfoXML detection, parsing and conversion to JSON + const comicInfoXMLPromise = new Promise((resolve, reject) => { + if ( + !isNil(filesToWriteToDisk.comicInfoXML) && + existsSync( + `${targetDirectory}/${path.basename( + filesToWriteToDisk.comicInfoXML + )}` + ) + ) { + let comicinfoString = ""; + const comicInfoXMLStream = createReadStream( + `${targetDirectory}/${path.basename( + filesToWriteToDisk.comicInfoXML + )}` + ); + comicInfoXMLStream.on( + "data", + (data) => (comicinfoString += data) + ); + comicInfoXMLStream.on("end", async () => { + const comicInfoJSON = await convertXMLToJSON( + comicinfoString.toString() + ); + resolve({ + comicInfoJSON: comicInfoJSON.comicinfo, + }); + }); + } else { + resolve({ + comicInfoJSON: null, + }); + } + }); + + // Write the cover to disk + const coverBaseName = sanitize(path.basename( + filesToWriteToDisk.coverFile, + path.extname(filesToWriteToDisk.coverFile) + )); + const coverOutputFile = `${targetDirectory}/${coverBaseName}.png`; + const coverInputFile = `${targetDirectory}/${filesToWriteToDisk.coverFile}`; + + await sharp(coverInputFile) + .resize(275) + .toFormat("png") + .toFile(coverOutputFile); + + const comicInfoResult = await comicInfoXMLPromise; + + const coverResult = { + filePath, + name: fileNameWithoutExtension, + extension, + mimeType, + containedIn: targetDirectory, + fileSize: fse.statSync(filePath).size, + cover: { + filePath: path.relative(process.cwd(), coverOutputFile), + }, + }; + + return [comicInfoResult, coverResult]; }; export const extractComicInfoXMLFromZip = async ( @@ -252,6 +464,11 @@ export const extractComicInfoXMLFromZip = async ( .localeCompare(path.basename(b.name).toLowerCase()); } }); + + if (files.length === 0) { + throw new Error(`No image files found in ZIP archive: ${filePath}`); + } + // Push the first file (cover) to our extraction target extractionTargets.push(files[0].name); filesToWriteToDisk.coverFile = path.basename(files[0].name); @@ -306,45 +523,32 @@ export const extractComicInfoXMLFromZip = async ( } }); // Write the cover to disk - const coverFilePromise = new Promise((resolve, reject) => { - const sharpStream = sharp().resize(275).toFormat("png"); - const coverStream = createReadStream( - `${targetDirectory}/${filesToWriteToDisk.coverFile}` - ); - coverStream - .pipe(sharpStream) - .toFile( - `${targetDirectory}/${path.basename( - filesToWriteToDisk.coverFile - )}`, - (err, info) => { - if (err) { - reject(err); - } - // Update metadata - resolve({ - filePath, - name: fileNameWithoutExtension, - extension, - mimeType, - containedIn: targetDirectory, - fileSize: fse.statSync(filePath).size, - cover: { - filePath: path.relative( - process.cwd(), - `${targetDirectory}/${path.basename( - filesToWriteToDisk.coverFile - )}` - ), - }, - }); - } - ); - }); + const coverBaseName = sanitize(path.basename(filesToWriteToDisk.coverFile, path.extname(filesToWriteToDisk.coverFile))); + const coverOutputFile = `${targetDirectory}/${coverBaseName}.png`; + const coverInputFile = `${targetDirectory}/${filesToWriteToDisk.coverFile}`; - return Promise.all([comicInfoXMLPromise, coverFilePromise]); + await sharp(coverInputFile) + .resize(275) + .toFormat("png") + .toFile(coverOutputFile); + + const comicInfoResult = await comicInfoXMLPromise; + + const coverResult = { + filePath, + name: fileNameWithoutExtension, + extension, + mimeType, + containedIn: targetDirectory, + fileSize: fse.statSync(filePath).size, + cover: { + filePath: path.relative(process.cwd(), coverOutputFile), + }, + }; + + return [comicInfoResult, coverResult]; } catch (err) { - reject(err); + throw err; } }; @@ -361,6 +565,9 @@ export const extractFromArchive = async (filePath: string) => { filePath, mimeType ); + if (!Array.isArray(cbzResult)) { + throw new Error(`extractComicInfoXMLFromZip returned a non-iterable result for: ${filePath}`); + } return Object.assign({}, ...cbzResult); case "application/x-rar; charset=binary": @@ -368,6 +575,9 @@ export const extractFromArchive = async (filePath: string) => { filePath, mimeType ); + if (!Array.isArray(cbrResult)) { + throw new Error(`extractComicInfoXMLFromRar returned a non-iterable result for: ${filePath}`); + } return Object.assign({}, ...cbrResult); default: @@ -419,7 +629,7 @@ export const uncompressZipArchive = async (filePath: string, options: any) => { mode: 0o2775, }; const { fileNameWithoutExtension } = getFileConstituents(filePath); - const targetDirectory = `${USERDATA_DIRECTORY}/expanded/${options.purpose}/${fileNameWithoutExtension}`; + const targetDirectory = `${USERDATA_DIRECTORY}/expanded/${options.purpose}/${sanitize(fileNameWithoutExtension)}`; await createDirectory(directoryOptions, targetDirectory); await p7zip.extract(filePath, targetDirectory, [], "", false); @@ -433,7 +643,7 @@ export const uncompressRarArchive = async (filePath: string, options: any) => { }; const { fileNameWithoutExtension, extension } = getFileConstituents(filePath); - const targetDirectory = `${USERDATA_DIRECTORY}/expanded/${options.purpose}/${fileNameWithoutExtension}`; + const targetDirectory = `${USERDATA_DIRECTORY}/expanded/${options.purpose}/${sanitize(fileNameWithoutExtension)}`; await createDirectory(directoryOptions, targetDirectory); const archive = new Unrar({