diff --git a/LocalMind-Backend/package-lock.json b/LocalMind-Backend/package-lock.json index e4ec022..913e23e 100644 --- a/LocalMind-Backend/package-lock.json +++ b/LocalMind-Backend/package-lock.json @@ -19,6 +19,7 @@ "@types/jsonwebtoken": "^9.0.10", "@types/mongoose": "^5.11.97", "@types/morgan": "^1.9.10", + "@types/multer": "^2.0.0", "argon2": "^0.44.0", "axios": "^1.12.2", "bcrypt": "^6.0.0", @@ -34,9 +35,11 @@ "localtunnel": "^2.0.2", "mongoose": "^8.19.1", "morgan": "^1.10.1", + "multer": "^2.0.2", "ngrok": "5.0.0-beta.2", "nodemailer": "^7.0.10", "ora": "^9.0.0", + "pdf-parse": "^2.4.5", "zod": "^4.1.12" }, "devDependencies": { @@ -3300,6 +3303,190 @@ "sparse-bitfield": "^3.0.3" } }, + "node_modules/@napi-rs/canvas": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.80.tgz", + "integrity": "sha512-DxuT1ClnIPts1kQx8FBmkk4BQDTfI5kIzywAaMjQSXfNnra5UFU9PwurXrl+Je3bJ6BGsp/zmshVVFbCmyI+ww==", + "license": "MIT", + "workspaces": [ + "e2e/*" + ], + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@napi-rs/canvas-android-arm64": "0.1.80", + "@napi-rs/canvas-darwin-arm64": "0.1.80", + "@napi-rs/canvas-darwin-x64": "0.1.80", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.80", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.80", + "@napi-rs/canvas-linux-arm64-musl": "0.1.80", + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.80", + "@napi-rs/canvas-linux-x64-gnu": "0.1.80", + "@napi-rs/canvas-linux-x64-musl": "0.1.80", + "@napi-rs/canvas-win32-x64-msvc": "0.1.80" + } + }, + "node_modules/@napi-rs/canvas-android-arm64": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.80.tgz", + "integrity": "sha512-sk7xhN/MoXeuExlggf91pNziBxLPVUqF2CAVnB57KLG/pz7+U5TKG8eXdc3pm0d7Od0WreB6ZKLj37sX9muGOQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-darwin-arm64": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.80.tgz", + "integrity": "sha512-O64APRTXRUiAz0P8gErkfEr3lipLJgM6pjATwavZ22ebhjYl/SUbpgM0xcWPQBNMP1n29afAC/Us5PX1vg+JNQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-darwin-x64": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.80.tgz", + "integrity": "sha512-FqqSU7qFce0Cp3pwnTjVkKjjOtxMqRe6lmINxpIZYaZNnVI0H5FtsaraZJ36SiTHNjZlUB69/HhxNDT1Aaa9vA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.80.tgz", + "integrity": "sha512-eyWz0ddBDQc7/JbAtY4OtZ5SpK8tR4JsCYEZjCE3dI8pqoWUC8oMwYSBGCYfsx2w47cQgQCgMVRVTFiiO38hHQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-gnu": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.80.tgz", + "integrity": "sha512-qwA63t8A86bnxhuA/GwOkK3jvb+XTQaTiVML0vAWoHyoZYTjNs7BzoOONDgTnNtr8/yHrq64XXzUoLqDzU+Uuw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-musl": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.80.tgz", + "integrity": "sha512-1XbCOz/ymhj24lFaIXtWnwv/6eFHXDrjP0jYkc6iHQ9q8oXKzUX1Lc6bu+wuGiLhGh2GS/2JlfORC5ZcXimRcg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.80.tgz", + "integrity": "sha512-XTzR125w5ZMs0lJcxRlS1K3P5RaZ9RmUsPtd1uGt+EfDyYMu4c6SEROYsxyatbbu/2+lPe7MPHOO/0a0x7L/gw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-gnu": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.80.tgz", + "integrity": "sha512-BeXAmhKg1kX3UCrJsYbdQd3hIMDH/K6HnP/pG2LuITaXhXBiNdh//TVVVVCBbJzVQaV5gK/4ZOCMrQW9mvuTqA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-musl": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.80.tgz", + "integrity": "sha512-x0XvZWdHbkgdgucJsRxprX/4o4sEed7qo9rCQA9ugiS9qE2QvP0RIiEugtZhfLH3cyI+jIRFJHV4Fuz+1BHHMg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-win32-x64-msvc": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.80.tgz", + "integrity": "sha512-Z8jPsM6df5V8B1HrCHB05+bDiCxjE9QA//3YrkKIdVDEwn5RKaqOxCJDRJkl48cJbylcrJbW4HxZbTte8juuPg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.12", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", @@ -4411,7 +4598,6 @@ "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "dev": true, "license": "MIT", "dependencies": { "@types/connect": "*", @@ -4443,7 +4629,6 @@ "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -4469,7 +4654,6 @@ "version": "5.0.6", "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", - "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", @@ -4481,7 +4665,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -4500,7 +4683,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "dev": true, "license": "MIT" }, "node_modules/@types/istanbul-lib-coverage": { @@ -4588,6 +4770,15 @@ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", "license": "MIT" }, + "node_modules/@types/multer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.0.0.tgz", + "integrity": "sha512-C3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw==", + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/node": { "version": "24.10.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.4.tgz", @@ -4622,14 +4813,12 @@ "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", - "dev": true, "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, "license": "MIT" }, "node_modules/@types/responselike": { @@ -4651,7 +4840,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -4661,7 +4849,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/http-errors": "*", @@ -4993,72 +5180,331 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "license": "ISC" - }, - "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", - "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", - "cpu": [ - "arm" - ], + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.52.0.tgz", + "integrity": "sha512-okqtOgqu2qmZJ5iN4TWlgfF171dZmx2FzdOv2K/ixL2LZWDStL8+JgQerI2sa8eAEfoydG9+0V96m7V+P8yE1Q==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.52.0", + "@typescript-eslint/type-utils": "8.52.0", + "@typescript-eslint/utils": "8.52.0", + "@typescript-eslint/visitor-keys": "8.52.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.52.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } }, - "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", - "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", - "cpu": [ - "arm64" - ], + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "engines": { + "node": ">= 4" + } }, - "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", - "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", - "cpu": [ - "arm64" - ], + "node_modules/@typescript-eslint/parser": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.52.0.tgz", + "integrity": "sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "dependencies": { + "@typescript-eslint/scope-manager": "8.52.0", + "@typescript-eslint/types": "8.52.0", + "@typescript-eslint/typescript-estree": "8.52.0", + "@typescript-eslint/visitor-keys": "8.52.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } }, - "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", - "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", - "cpu": [ - "x64" - ], + "node_modules/@typescript-eslint/project-service": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.52.0.tgz", + "integrity": "sha512-xD0MfdSdEmeFa3OmVqonHi+Cciab96ls1UhIF/qX/O/gPu5KXD0bY9lu33jj04fjzrXHcuvjBcBC+D3SNSadaw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", - "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.52.0", + "@typescript-eslint/types": "^8.52.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.52.0.tgz", + "integrity": "sha512-ixxqmmCcc1Nf8S0mS0TkJ/3LKcC8mruYJPOU6Ia2F/zUUR4pApW7LzrpU3JmtePbRUTes9bEqRc1Gg4iyRnDzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.52.0", + "@typescript-eslint/visitor-keys": "8.52.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.52.0.tgz", + "integrity": "sha512-jl+8fzr/SdzdxWJznq5nvoI7qn2tNYV/ZBAEcaFMVXf+K6jmXvAFrgo/+5rxgnL152f//pDEAYAhhBAZGrVfwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.52.0.tgz", + "integrity": "sha512-JD3wKBRWglYRQkAtsyGz1AewDu3mTc7NtRjR/ceTyGoPqmdS5oCdx/oZMWD5Zuqmo6/MpsYs0wp6axNt88/2EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.52.0", + "@typescript-eslint/typescript-estree": "8.52.0", + "@typescript-eslint/utils": "8.52.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.52.0.tgz", + "integrity": "sha512-LWQV1V4q9V4cT4H5JCIx3481iIFxH1UkVk+ZkGGAV1ZGcjGI9IoFOfg3O6ywz8QqCDEp7Inlg6kovMofsNRaGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.52.0.tgz", + "integrity": "sha512-XP3LClsCc0FsTK5/frGjolyADTh3QmsLp6nKd476xNI9CsSsLnmn4f0jrzNoAulmxlmNIpeXuHYeEQv61Q6qeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.52.0", + "@typescript-eslint/tsconfig-utils": "8.52.0", + "@typescript-eslint/types": "8.52.0", + "@typescript-eslint/visitor-keys": "8.52.0", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.52.0.tgz", + "integrity": "sha512-wYndVMWkweqHpEpwPhwqE2lnD2DxC6WVLupU/DOt/0/v+/+iQbbzO3jOHjmBMnhu0DgLULvOaU4h4pwHYi2oRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.52.0", + "@typescript-eslint/types": "8.52.0", + "@typescript-eslint/typescript-estree": "8.52.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.52.0.tgz", + "integrity": "sha512-ink3/Zofus34nmBsPjow63FP5M7IGff0RKAgqR6+CFpdk22M7aLwC9gOcLGYqr7MczLPzZVERW9hRog3O4n1sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.52.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", "cpu": [ "x64" ], @@ -5417,6 +5863,12 @@ "node": ">= 8" } }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -5859,9 +6311,19 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, "license": "MIT" }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -6235,6 +6697,21 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, "node_modules/console-table-printer": { "version": "2.15.0", "resolved": "https://registry.npmjs.org/console-table-printer/-/console-table-printer-2.15.0.tgz", @@ -6627,85 +7104,364 @@ "once": "^1.4.0" } }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "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": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, "license": "MIT", "dependencies": { - "is-arrayish": "^0.2.1" + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, "engines": { - "node": ">= 0.4" + "node": "*" } }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, "engines": { - "node": ">= 0.4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "license": "MIT", "dependencies": { - "es-errors": "^1.3.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=8" } }, - "node_modules/es-set-tostringtag": { - "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", + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, "engines": { - "node": ">=6" + "node": ">=4" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "license": "MIT", + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, "engines": { - "node": ">=10" + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=4.0" } }, "node_modules/eslint": { @@ -6953,12 +7709,18 @@ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, "engines": { - "node": ">=4" + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" } }, "node_modules/esquery": { @@ -10419,7 +11181,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -10596,6 +11357,79 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/multer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", + "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.6.0", + "concat-stream": "^2.0.0", + "mkdirp": "^0.5.6", + "object-assign": "^4.1.1", + "type-is": "^1.6.18", + "xtend": "^4.0.2" + }, + "engines": { + "node": ">= 10.16.0" + } + }, + "node_modules/multer/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/multer/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mustache": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", @@ -13737,6 +14571,15 @@ "inBundle": true, "license": "ISC" }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -13840,12 +14683,6 @@ "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", "license": "MIT" }, - "node_modules/openurl": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/openurl/-/openurl-1.1.1.tgz", - "integrity": "sha512-d/gTkTb1i1GKz5k3XE3XFV/PxQ1k45zDqGP2OA7YhgsaLoqm6qRvARAZOFer1fcXritWlGBRCu/UgeS4HAnXAA==", - "license": "MIT" - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -14169,6 +15006,38 @@ "node": ">=8" } }, + "node_modules/pdf-parse": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-2.4.5.tgz", + "integrity": "sha512-mHU89HGh7v+4u2ubfnevJ03lmPgQ5WU4CxAVmTSh/sxVTEDYd1er/dKS/A6vg77NX47KTEoihq8jZBLr8Cxuwg==", + "license": "Apache-2.0", + "dependencies": { + "@napi-rs/canvas": "0.1.80", + "pdfjs-dist": "5.4.296" + }, + "bin": { + "pdf-parse": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.16.0 <21 || >=22.3.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/mehmet-kozan" + } + }, + "node_modules/pdfjs-dist": { + "version": "5.4.296", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.4.296.tgz", + "integrity": "sha512-DlOzet0HO7OEnmUmB6wWGJrrdvbyJKftI1bhMitK7O2N8W2gc757yyYBbINy9IDafXAV9wmKr9t7xsTaNKRG5Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=20.16.0 || >=22.3.0" + }, + "optionalDependencies": { + "@napi-rs/canvas": "^0.1.80" + } + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -14436,6 +15305,20 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "license": "MIT" }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -15084,6 +15967,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -15678,6 +16578,12 @@ "node": ">= 0.6" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -15820,6 +16726,12 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", @@ -16083,7 +16995,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.4" diff --git a/LocalMind-Backend/package.json b/LocalMind-Backend/package.json index b7a3717..7251979 100644 --- a/LocalMind-Backend/package.json +++ b/LocalMind-Backend/package.json @@ -55,6 +55,7 @@ "@types/jsonwebtoken": "^9.0.10", "@types/mongoose": "^5.11.97", "@types/morgan": "^1.9.10", + "@types/multer": "^2.0.0", "argon2": "^0.44.0", "axios": "^1.12.2", "bcrypt": "^6.0.0", @@ -70,9 +71,11 @@ "localtunnel": "^2.0.2", "mongoose": "^8.19.1", "morgan": "^1.10.1", + "multer": "^2.0.2", "ngrok": "5.0.0-beta.2", "nodemailer": "^7.0.10", "ora": "^9.0.0", + "pdf-parse": "^2.4.5", "zod": "^4.1.12" } } diff --git a/LocalMind-Backend/src/api/v1/DataSet/v1/DataSet.controller.ts b/LocalMind-Backend/src/api/v1/DataSet/v1/DataSet.controller.ts index 834f41c..9a1e1a0 100644 --- a/LocalMind-Backend/src/api/v1/DataSet/v1/DataSet.controller.ts +++ b/LocalMind-Backend/src/api/v1/DataSet/v1/DataSet.controller.ts @@ -1,27 +1,106 @@ import { Request, Response } from 'express' - -import { CSVLoader } from '@langchain/community/document_loaders/fs/csv' import path from 'path' +import * as fs from 'fs' import { SendResponse } from '../../../../utils/SendResponse.utils' import DataSetService from './DataSet.service' +import FileLoaderUtils from './DataSet.fileLoader' +import { FileFormat } from './DataSet.type' class DataSetController { + /** + * Upload and process dataset with support for multiple file formats + * Supports: CSV, PDF, TXT, JSON, TSV + */ public async uploadDataSet(req: Request, res: Response): Promise { try { - const filePath = path.join(path.resolve(), 'src', 'data', 'Sample.csv') + // Check if file was uploaded + if (!req.file) { + SendResponse.error(res, 'No file uploaded', 400) + return + } + + const uploadedFile = req.file + const filePath = uploadedFile.path + + // Detect file format + const fileFormat = FileLoaderUtils.detectFileFormat( + uploadedFile.originalname, + uploadedFile.mimetype + ) + + if (!fileFormat) { + // Clean up uploaded file + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath) + } + SendResponse.error( + res, + 'Unsupported file format. Supported formats: CSV, PDF, TXT, JSON, TSV', + 400 + ) + return + } + + // Load documents from file + const documents = await FileLoaderUtils.loadFile(filePath, fileFormat) + + if (!documents || documents.length === 0) { + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath) + } + SendResponse.error(res, 'No data found in the uploaded file', 400) + return + } - const loader = new CSVLoader(filePath) - const documents = await loader.load() + // Process the dataset + const processedData = await DataSetService.Prepate_DataSet(documents) - const Prepare_dataSet = await DataSetService.Prepate_DataSet(documents) + // Clean up uploaded file after processing + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath) + } SendResponse.success( res, - 'Dataset uploaded and processed successfully', - JSON.parse(Prepare_dataSet) + `Dataset uploaded and processed successfully (Format: ${fileFormat.toUpperCase()})`, + JSON.parse(processedData) ) } catch (error: any) { - SendResponse.error(res, 'Failed to upload dataset', 500, error) + // Clean up uploaded file on error + if (req.file && fs.existsSync(req.file.path)) { + fs.unlinkSync(req.file.path) + } + SendResponse.error( + res, + 'Failed to upload and process dataset', + 500, + error.message + ) + } + } + + /** + * Get list of supported file formats + */ + public async getSupportedFormats( + req: Request, + res: Response + ): Promise { + try { + const formats = Object.values(FileFormat) + SendResponse.success(res, 'Supported file formats', { + formats, + description: { + csv: 'Comma-separated values file', + xlsx: 'Excel spreadsheet (not yet fully supported)', + tsv: 'Tab-separated values file', + json: 'JSON file with Q&A pairs', + pdf: 'PDF document', + txt: 'Plain text file', + }, + }) + } catch (error: any) { + SendResponse.error(res, 'Failed to get supported formats', 500, error) } } } diff --git a/LocalMind-Backend/src/api/v1/DataSet/v1/DataSet.fileLoader.ts b/LocalMind-Backend/src/api/v1/DataSet/v1/DataSet.fileLoader.ts new file mode 100644 index 0000000..f8717b1 --- /dev/null +++ b/LocalMind-Backend/src/api/v1/DataSet/v1/DataSet.fileLoader.ts @@ -0,0 +1,237 @@ +import { CSVLoader } from '@langchain/community/document_loaders/fs/csv' +import { PDFLoader } from '@langchain/community/document_loaders/fs/pdf' +import { Document } from '@langchain/core/documents' +import { FileFormat, UploadedFileMetadata } from './DataSet.type' +import * as fs from 'fs' +import * as path from 'path' + +/** + * File loader class to handle different file formats + */ +class FileLoaderUtils { + /** + * Detect file format from file extension or MIME type + */ + public detectFileFormat(filename: string, mimeType?: string): FileFormat | null { + const extension = path.extname(filename).toLowerCase().slice(1) + + const formatMap: Record = { + csv: FileFormat.CSV, + xlsx: FileFormat.XLSX, + xls: FileFormat.XLSX, + tsv: FileFormat.TSV, + json: FileFormat.JSON, + pdf: FileFormat.PDF, + txt: FileFormat.TXT, + } + + // Try by extension first + if (formatMap[extension]) { + return formatMap[extension] + } + + // Try by MIME type + if (mimeType) { + const mimeFormatMap: Record = { + 'text/csv': FileFormat.CSV, + 'application/vnd.ms-excel': FileFormat.XLSX, + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': + FileFormat.XLSX, + 'text/tab-separated-values': FileFormat.TSV, + 'application/json': FileFormat.JSON, + 'application/pdf': FileFormat.PDF, + 'text/plain': FileFormat.TXT, + } + + if (mimeFormatMap[mimeType]) { + return mimeFormatMap[mimeType] + } + } + + return null + } + + /** + * Load documents from a CSV file + */ + private async loadCSV(filePath: string): Promise { + const loader = new CSVLoader(filePath) + return await loader.load() + } + + /** + * Load documents from a PDF file + */ + private async loadPDF(filePath: string): Promise { + const loader = new PDFLoader(filePath, { + splitPages: false, // Load entire PDF as one document + }) + return await loader.load() + } + + /** + * Load documents from a TXT file + */ + private async loadTXT(filePath: string): Promise { + try { + const fileContent = fs.readFileSync(filePath, 'utf-8') + + // Split by double newlines to separate Q&A pairs or paragraphs + const sections = fileContent + .split(/\n\n+/) + .filter((section) => section.trim().length > 0) + + if (sections.length === 0) { + // If no double newlines, treat entire content as one document + return [ + new Document({ + pageContent: fileContent, + metadata: { source: filePath }, + }), + ] + } + + // Create a document for each section + const documents = sections.map((section, index) => { + return new Document({ + pageContent: section.trim(), + metadata: { source: filePath, section: index }, + }) + }) + + return documents + } catch (error) { + throw new Error(`Failed to load TXT file: ${error}`) + } + } + + /** + * Load documents from a JSON file + */ + private async loadJSON(filePath: string): Promise { + try { + // Read the JSON file + const fileContent = fs.readFileSync(filePath, 'utf-8') + const jsonData = JSON.parse(fileContent) + + // Check if it's an array of Q&A pairs + if (Array.isArray(jsonData)) { + // Convert array to documents + const documents = jsonData.map((item, index) => { + const pageContent = JSON.stringify(item) + return new Document({ + pageContent, + metadata: { source: filePath, row: index }, + }) + }) + return documents + } + + // If it's a single object, wrap it in an array + const pageContent = JSON.stringify(jsonData) + return [ + new Document({ + pageContent, + metadata: { source: filePath }, + }), + ] + } catch (error) { + throw new Error(`Failed to parse JSON file: ${error}`) + } + } + + /** + * Load documents from a TSV file + */ + private async loadTSV(filePath: string): Promise { + // TSV is similar to CSV but with tab delimiter + const loader = new CSVLoader(filePath, { + separator: '\t', + }) + return await loader.load() + } + + /** + * Main method to load file based on format + */ + public async loadFile( + filePath: string, + format: FileFormat + ): Promise { + // Validate file exists + if (!fs.existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`) + } + + try { + switch (format) { + case FileFormat.CSV: + return await this.loadCSV(filePath) + + case FileFormat.PDF: + return await this.loadPDF(filePath) + + case FileFormat.TXT: + return await this.loadTXT(filePath) + + case FileFormat.JSON: + return await this.loadJSON(filePath) + + case FileFormat.TSV: + return await this.loadTSV(filePath) + + case FileFormat.XLSX: + // For Excel files, you'll need to convert to CSV first + // or use a library like 'xlsx' to parse them + throw new Error( + 'XLSX format not yet implemented. Please convert to CSV.' + ) + + default: + throw new Error(`Unsupported file format: ${format}`) + } + } catch (error) { + throw new Error(`Failed to load file: ${error}`) + } + } + + /** + * Get file metadata + */ + public getFileMetadata(filePath: string): UploadedFileMetadata { + const stats = fs.statSync(filePath) + const filename = path.basename(filePath) + const format = this.detectFileFormat(filename) + + if (!format) { + throw new Error(`Unable to detect file format for: ${filename}`) + } + + return { + originalName: filename, + mimeType: this.getMimeType(format), + size: stats.size, + path: filePath, + format, + } + } + + /** + * Get MIME type from file format + */ + private getMimeType(format: FileFormat): string { + const mimeTypes: Record = { + [FileFormat.CSV]: 'text/csv', + [FileFormat.XLSX]: + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + [FileFormat.TSV]: 'text/tab-separated-values', + [FileFormat.JSON]: 'application/json', + [FileFormat.PDF]: 'application/pdf', + [FileFormat.TXT]: 'text/plain', + } + + return mimeTypes[format] || 'application/octet-stream' + } +} + +export default new FileLoaderUtils() diff --git a/LocalMind-Backend/src/api/v1/DataSet/v1/DataSet.multer.ts b/LocalMind-Backend/src/api/v1/DataSet/v1/DataSet.multer.ts new file mode 100644 index 0000000..c28a3e3 --- /dev/null +++ b/LocalMind-Backend/src/api/v1/DataSet/v1/DataSet.multer.ts @@ -0,0 +1,59 @@ +import multer from 'multer' +import path from 'path' +import * as fs from 'fs' + +// Ensure uploads directory exists +const uploadsDir = path.join(process.cwd(), 'uploads', 'datasets') +if (!fs.existsSync(uploadsDir)) { + fs.mkdirSync(uploadsDir, { recursive: true }) +} + +// Configure storage +const storage = multer.diskStorage({ + destination: (req, file, cb) => { + cb(null, uploadsDir) + }, + filename: (req, file, cb) => { + // Generate unique filename with timestamp + const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9) + const ext = path.extname(file.originalname) + const basename = path.basename(file.originalname, ext) + cb(null, `${basename}-${uniqueSuffix}${ext}`) + }, +}) + +// File filter to accept only specific file types +const fileFilter = ( + req: Express.Request, + file: Express.Multer.File, + cb: multer.FileFilterCallback +) => { + const allowedMimeTypes = [ + 'text/csv', + 'application/pdf', + 'text/plain', + 'application/json', + 'text/tab-separated-values', + 'application/vnd.ms-excel', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + ] + + if (allowedMimeTypes.includes(file.mimetype)) { + cb(null, true) + } else { + cb( + new Error( + 'Invalid file type. Only CSV, PDF, TXT, JSON, and TSV files are allowed.' + ) + ) + } +} + +// Configure multer +export const upload = multer({ + storage, + fileFilter, + limits: { + fileSize: 10 * 1024 * 1024, // 10MB max file size + }, +}) diff --git a/LocalMind-Backend/src/api/v1/DataSet/v1/DataSet.routes.ts b/LocalMind-Backend/src/api/v1/DataSet/v1/DataSet.routes.ts index 4fe3502..c453523 100644 --- a/LocalMind-Backend/src/api/v1/DataSet/v1/DataSet.routes.ts +++ b/LocalMind-Backend/src/api/v1/DataSet/v1/DataSet.routes.ts @@ -1,7 +1,27 @@ import { Router } from 'express' import DataSetController from './DataSet.controller' +import DataSetValidator from './DataSet.validator' +import { upload } from './DataSet.multer' + const router: Router = Router() -router.get('/upload', DataSetController.uploadDataSet) +/** + * @route POST /api/v1/dataset/upload + * @desc Upload and process a dataset file (CSV, PDF, TXT, JSON, TSV) + * @access Public (add authentication if needed) + */ +router.post( + '/upload', + upload.single('file'), // 'file' is the field name for the uploaded file + DataSetValidator.validateFileUpload, + DataSetController.uploadDataSet +) + +/** + * @route GET /api/v1/dataset/formats + * @desc Get list of supported file formats + * @access Public + */ +router.get('/formats', DataSetController.getSupportedFormats) export { router as DataSetRoutes } diff --git a/LocalMind-Backend/src/api/v1/DataSet/v1/DataSet.type.ts b/LocalMind-Backend/src/api/v1/DataSet/v1/DataSet.type.ts index e69de29..7061a17 100644 --- a/LocalMind-Backend/src/api/v1/DataSet/v1/DataSet.type.ts +++ b/LocalMind-Backend/src/api/v1/DataSet/v1/DataSet.type.ts @@ -0,0 +1,48 @@ +/** + * Supported file formats for dataset upload + */ +export enum FileFormat { + CSV = 'csv', + XLSX = 'xlsx', + TSV = 'tsv', + JSON = 'json', + PDF = 'pdf', + TXT = 'txt', +} + +/** + * Interface for question-answer pair extracted from dataset + */ +export interface QuestionAnswerPair { + question: string + answer: string +} + +/** + * Interface for validation error + */ +export interface ValidationError { + row?: number + fieldName: string + error_message: string +} + +/** + * Interface for dataset processing result + */ +export interface DataSetProcessingResult { + success: boolean + data?: QuestionAnswerPair[] + errors?: ValidationError[] +} + +/** + * Interface for file upload metadata + */ +export interface UploadedFileMetadata { + originalName: string + mimeType: string + size: number + path: string + format: FileFormat +} diff --git a/LocalMind-Backend/src/api/v1/DataSet/v1/DataSet.validator.ts b/LocalMind-Backend/src/api/v1/DataSet/v1/DataSet.validator.ts index e69de29..9395e65 100644 --- a/LocalMind-Backend/src/api/v1/DataSet/v1/DataSet.validator.ts +++ b/LocalMind-Backend/src/api/v1/DataSet/v1/DataSet.validator.ts @@ -0,0 +1,119 @@ +import { Request, Response, NextFunction } from 'express' +import { SendResponse } from '../../../../utils/SendResponse.utils' +import { FileFormat } from './DataSet.type' + +/** + * File upload validation middleware + */ +class DataSetValidator { + /** + * Allowed file extensions + */ + private readonly ALLOWED_EXTENSIONS = ['.csv', '.pdf', '.txt', '.json', '.tsv'] + + /** + * Allowed MIME types + */ + private readonly ALLOWED_MIME_TYPES = [ + 'text/csv', + 'application/pdf', + 'text/plain', + 'application/json', + 'text/tab-separated-values', + 'application/vnd.ms-excel', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + ] + + /** + * Maximum file size (10MB) + */ + private readonly MAX_FILE_SIZE = 10 * 1024 * 1024 // 10MB in bytes + + /** + * Validate uploaded file + */ + public validateFileUpload = ( + req: Request, + res: Response, + next: NextFunction + ): void => { + try { + // Check if file exists in request + if (!req.file) { + SendResponse.error(res, 'No file uploaded', 400) + return + } + + const file = req.file + + // Validate file size + if (file.size > this.MAX_FILE_SIZE) { + SendResponse.error( + res, + `File size exceeds maximum limit of ${this.MAX_FILE_SIZE / (1024 * 1024)}MB`, + 400 + ) + return + } + + // Validate file size (minimum) + if (file.size === 0) { + SendResponse.error(res, 'Uploaded file is empty', 400) + return + } + + // Validate file extension + const fileExtension = file.originalname + .toLowerCase() + .substring(file.originalname.lastIndexOf('.')) + + if (!this.ALLOWED_EXTENSIONS.includes(fileExtension)) { + SendResponse.error( + res, + `Invalid file extension. Allowed extensions: ${this.ALLOWED_EXTENSIONS.join(', ')}`, + 400 + ) + return + } + + // Validate MIME type + if (!this.ALLOWED_MIME_TYPES.includes(file.mimetype)) { + SendResponse.error( + res, + `Invalid file type. Allowed types: ${this.ALLOWED_MIME_TYPES.join(', ')}`, + 400 + ) + return + } + + // All validations passed + next() + } catch (error: any) { + SendResponse.error(res, 'File validation failed', 400, error.message) + } + } + + /** + * Validate file format parameter + */ + public validateFileFormat = ( + req: Request, + res: Response, + next: NextFunction + ): void => { + const { format } = req.query + + if (format && !Object.values(FileFormat).includes(format as FileFormat)) { + SendResponse.error( + res, + `Invalid format. Supported formats: ${Object.values(FileFormat).join(', ')}`, + 400 + ) + return + } + + next() + } +} + +export default new DataSetValidator() diff --git a/LocalMind-Backend/src/api/v1/DataSet/v1/README.md b/LocalMind-Backend/src/api/v1/DataSet/v1/README.md new file mode 100644 index 0000000..07debf3 --- /dev/null +++ b/LocalMind-Backend/src/api/v1/DataSet/v1/README.md @@ -0,0 +1,345 @@ +# RAG Dataset Enhancement - Multi-Format File Upload Support + +## ๐ŸŽฏ Overview + +This feature adds support for multiple file formats to the LocalMind RAG (Retrieval-Augmented Generation) system, enabling users to upload and process datasets in various formats beyond the original CSV support. + +## โœจ New Features + +### Supported File Formats + +| Format | Extension | Description | Status | +|--------|-----------|-------------|--------| +| **CSV** | `.csv` | Comma-separated values | โœ… Supported | +| **JSON** | `.json` | JSON file with Q&A pairs | โœ… **NEW** | +| **PDF** | `.pdf` | PDF documents | โœ… **NEW** | +| **TXT** | `.txt` | Plain text files | โœ… **NEW** | +| **TSV** | `.tsv` | Tab-separated values | โœ… **NEW** | +| **XLSX** | `.xlsx` | Excel spreadsheet | ๐Ÿ”œ Coming Soon | + +## ๐Ÿ—๏ธ Architecture + +### New Files Created + +``` +LocalMind-Backend/src/api/v1/DataSet/v1/ +โ”œโ”€โ”€ DataSet.type.ts # TypeScript interfaces and enums (NEW) +โ”œโ”€โ”€ DataSet.fileLoader.ts # File loader utility for all formats (NEW) +โ”œโ”€โ”€ DataSet.multer.ts # Multer configuration for file uploads (NEW) +โ”œโ”€โ”€ DataSet.validator.ts # File validation middleware (UPDATED) +โ”œโ”€โ”€ DataSet.controller.ts # Updated controller with multi-format support (UPDATED) +โ”œโ”€โ”€ DataSet.routes.ts # Updated routes with POST endpoint (UPDATED) +โ”œโ”€โ”€ DataSet.service.ts # Service layer (EXISTING) +โ”œโ”€โ”€ DataSet.utils.ts # Utility functions (EXISTING) +โ””โ”€โ”€ test-samples/ # Sample test files (NEW) + โ”œโ”€โ”€ sample-qa.csv + โ”œโ”€โ”€ sample-qa.json + โ”œโ”€โ”€ sample-qa.txt + โ””โ”€โ”€ sample-qa.tsv +``` + +## ๐Ÿ“ฆ New Dependencies + +- `pdf-parse` - PDF parsing library +- `multer` - File upload middleware +- `@types/multer` - TypeScript types for multer + +## ๐Ÿš€ Usage + +### API Endpoints + +#### 1. Upload Dataset + +**Endpoint:** `POST /api/v1/dataset/upload` + +**Description:** Upload and process a dataset file + +**Request:** +- Method: `POST` +- Content-Type: `multipart/form-data` +- Body: Form data with `file` field + +**Example using cURL:** + +```bash +# Upload CSV file +curl -X POST http://localhost:5000/api/v1/dataset/upload \ + -F "file=@sample-qa.csv" + +# Upload JSON file +curl -X POST http://localhost:5000/api/v1/dataset/upload \ + -F "file=@sample-qa.json" + +# Upload PDF file +curl -X POST http://localhost:5000/api/v1/dataset/upload \ + -F "file=@sample-qa.pdf" + +# Upload TXT file +curl -X POST http://localhost:5000/api/v1/dataset/upload \ + -F "file=@sample-qa.txt" + +# Upload TSV file +curl -X POST http://localhost:5000/api/v1/dataset/upload \ + -F "file=@sample-qa.tsv" +``` + +**Example using JavaScript (Fetch API):** + +```javascript +const formData = new FormData(); +formData.append('file', fileInput.files[0]); + +fetch('http://localhost:5000/api/v1/dataset/upload', { + method: 'POST', + body: formData +}) + .then(response => response.json()) + .then(data => console.log(data)) + .catch(error => console.error('Error:', error)); +``` + +**Response (Success):** + +```json +{ + "success": true, + "message": "Dataset uploaded and processed successfully (Format: JSON)", + "data": [ + { + "question": "What is React?", + "answer": "React is a JavaScript library for building user interfaces..." + } + ] +} +``` + +**Response (Error):** + +```json +{ + "success": false, + "message": "No file uploaded", + "error": null +} +``` + +#### 2. Get Supported Formats + +**Endpoint:** `GET /api/v1/dataset/formats` + +**Description:** Get list of all supported file formats + +**Example:** + +```bash +curl http://localhost:5000/api/v1/dataset/formats +``` + +**Response:** + +```json +{ + "success": true, + "message": "Supported file formats", + "data": { + "formats": ["csv", "xlsx", "tsv", "json", "pdf", "txt"], + "description": { + "csv": "Comma-separated values file", + "xlsx": "Excel spreadsheet (not yet fully supported)", + "tsv": "Tab-separated values file", + "json": "JSON file with Q&A pairs", + "pdf": "PDF document", + "txt": "Plain text file" + } + } +} +``` + +## ๐Ÿ“‹ File Format Requirements + +### CSV Format + +```csv +question,answer +"What is AI?","Artificial intelligence is..." +``` + +### JSON Format + +```json +[ + { + "question": "What is AI?", + "answer": "Artificial intelligence is..." + } +] +``` + +### TXT Format + +```text +Q: What is AI? +A: Artificial intelligence is... + +Q: What is ML? +A: Machine learning is... +``` + +### TSV Format + +```tsv +question answer +What is AI? Artificial intelligence is... +``` + +### PDF Format + +Any standard PDF document. The text will be extracted and processed. + +## ๐Ÿ”’ Validation Rules + +- **Maximum file size:** 10MB +- **Minimum file size:** Must not be empty +- **Allowed extensions:** `.csv`, `.pdf`, `.txt`, `.json`, `.tsv` +- **Allowed MIME types:** + - `text/csv` + - `application/pdf` + - `text/plain` + - `application/json` + - `text/tab-separated-values` + +## ๐Ÿงช Testing + +### Manual Testing + +1. Start the backend server: +```bash +cd LocalMind-Backend +npm run dev +``` + +2. Test with sample files: +```bash +# From the test-samples directory +cd src/api/v1/DataSet/v1/test-samples + +# Test CSV +curl -X POST http://localhost:5000/api/v1/dataset/upload \ + -F "file=@sample-qa.csv" + +# Test JSON +curl -X POST http://localhost:5000/api/v1/dataset/upload \ + -F "file=@sample-qa.json" + +# Test TXT +curl -X POST http://localhost:5000/api/v1/dataset/upload \ + -F "file=@sample-qa.txt" + +# Test TSV +curl -X POST http://localhost:5000/api/v1/dataset/upload \ + -F "file=@sample-qa.tsv" +``` + +### Using Postman + +1. Open Postman +2. Create a new POST request to `http://localhost:5000/api/v1/dataset/upload` +3. Go to Body โ†’ form-data +4. Add key `file` with type `File` +5. Choose a file from test-samples +6. Send the request + +## ๐Ÿ› ๏ธ Code Structure + +### FileLoaderUtils Class + +Handles loading different file formats: + +```typescript +class FileLoaderUtils { + detectFileFormat(filename: string, mimeType?: string): FileFormat | null + loadFile(filePath: string, format: FileFormat): Promise + getFileMetadata(filePath: string): UploadedFileMetadata +} +``` + +### DataSetValidator Class + +Validates uploaded files: + +```typescript +class DataSetValidator { + validateFileUpload(req, res, next): void + validateFileFormat(req, res, next): void +} +``` + +### Updated DataSetController + +```typescript +class DataSetController { + uploadDataSet(req, res): Promise // Updated with multi-format support + getSupportedFormats(req, res): Promise // New endpoint +} +``` + +## ๐Ÿšจ Error Handling + +The system handles various error scenarios: + +- **No file uploaded:** Returns 400 error +- **File too large:** Returns 400 error with size limit info +- **Invalid file type:** Returns 400 error with allowed types +- **Empty file:** Returns 400 error +- **File processing error:** Returns 500 error with details +- **File upload cleanup:** Automatically removes uploaded files after processing or on error + +## ๐Ÿ”„ Migration Notes + +### Breaking Changes + +- **Route change:** The upload endpoint changed from `GET /upload` to `POST /upload` +- **File upload required:** Now requires file upload via multipart/form-data instead of hardcoded file path + +### Backward Compatibility + +The old CSV functionality is fully preserved and enhanced with new formats. + +## ๐Ÿ“ˆ Future Enhancements + +- [ ] Full Excel (.xlsx) support with xlsx library +- [ ] Support for Word documents (.docx) +- [ ] Support for markdown files (.md) +- [ ] Batch file upload +- [ ] File preview before processing +- [ ] Progress tracking for large files +- [ ] Database storage for uploaded datasets +- [ ] Dataset versioning +- [ ] Export processed data + +## ๐Ÿค Contributing + +To contribute to this feature: + +1. Follow the branch naming convention: `username-feature-name` +2. Test all file formats before submitting PR +3. Update documentation if adding new formats +4. Ensure TypeScript types are properly defined +5. Add error handling for edge cases + +## ๐Ÿ“ License + +This feature is part of LocalMind and follows the same license (MIT). + +## ๐Ÿ‘ค Author + +- **GitHub:** @yash12991 +- **Branch:** `yash12991-add-pdf-txt-json-rag-support` +- **Feature:** RAG Multi-Format File Upload Support + +## ๐Ÿ“ž Support + +For issues or questions: +- Open an issue on GitHub +- Check existing documentation +- Review test samples for format examples diff --git a/LocalMind-Backend/src/api/v1/DataSet/v1/test-samples/sample-qa.csv b/LocalMind-Backend/src/api/v1/DataSet/v1/test-samples/sample-qa.csv new file mode 100644 index 0000000..8e5676b --- /dev/null +++ b/LocalMind-Backend/src/api/v1/DataSet/v1/test-samples/sample-qa.csv @@ -0,0 +1,6 @@ +question,answer +"What is artificial intelligence?","Artificial intelligence (AI) is the simulation of human intelligence processes by machines, especially computer systems." +"What is machine learning?","Machine learning is a subset of AI that enables systems to learn and improve from experience without being explicitly programmed." +"What is deep learning?","Deep learning is a subset of machine learning that uses neural networks with multiple layers to analyze various factors of data." +"What is natural language processing?","Natural language processing (NLP) is a branch of AI that helps computers understand, interpret, and manipulate human language." +"What is computer vision?","Computer vision is a field of AI that trains computers to interpret and understand the visual world from digital images or videos." diff --git a/LocalMind-Backend/src/api/v1/DataSet/v1/test-samples/sample-qa.json b/LocalMind-Backend/src/api/v1/DataSet/v1/test-samples/sample-qa.json new file mode 100644 index 0000000..af22ccc --- /dev/null +++ b/LocalMind-Backend/src/api/v1/DataSet/v1/test-samples/sample-qa.json @@ -0,0 +1,22 @@ +[ + { + "question": "What is React?", + "answer": "React is a JavaScript library for building user interfaces, developed by Facebook." + }, + { + "question": "What is Node.js?", + "answer": "Node.js is a JavaScript runtime built on Chrome's V8 JavaScript engine that allows you to run JavaScript on the server." + }, + { + "question": "What is TypeScript?", + "answer": "TypeScript is a typed superset of JavaScript that compiles to plain JavaScript, adding optional static typing." + }, + { + "question": "What is MongoDB?", + "answer": "MongoDB is a NoSQL database that stores data in flexible, JSON-like documents." + }, + { + "question": "What is Express.js?", + "answer": "Express.js is a minimal and flexible Node.js web application framework that provides robust features for web and mobile applications." + } +] diff --git a/LocalMind-Backend/src/api/v1/DataSet/v1/test-samples/sample-qa.tsv b/LocalMind-Backend/src/api/v1/DataSet/v1/test-samples/sample-qa.tsv new file mode 100644 index 0000000..74d115f --- /dev/null +++ b/LocalMind-Backend/src/api/v1/DataSet/v1/test-samples/sample-qa.tsv @@ -0,0 +1,6 @@ +question answer +What is Python? Python is a high-level, interpreted programming language known for its simplicity and readability. +What is Java? Java is a class-based, object-oriented programming language designed to have as few implementation dependencies as possible. +What is C++? C++ is a general-purpose programming language created as an extension of C with object-oriented features. +What is JavaScript? JavaScript is a high-level, interpreted programming language that is one of the core technologies of the World Wide Web. +What is Go? Go (or Golang) is a statically typed, compiled programming language designed at Google for building scalable systems. diff --git a/LocalMind-Backend/src/api/v1/DataSet/v1/test-samples/sample-qa.txt b/LocalMind-Backend/src/api/v1/DataSet/v1/test-samples/sample-qa.txt new file mode 100644 index 0000000..e3e306c --- /dev/null +++ b/LocalMind-Backend/src/api/v1/DataSet/v1/test-samples/sample-qa.txt @@ -0,0 +1,14 @@ +Q: What is Docker? +A: Docker is a platform that uses containerization to make it easier to create, deploy, and run applications by using containers. + +Q: What is Kubernetes? +A: Kubernetes is an open-source container orchestration platform that automates deploying, scaling, and managing containerized applications. + +Q: What is Git? +A: Git is a distributed version control system for tracking changes in source code during software development. + +Q: What is REST API? +A: REST (Representational State Transfer) API is an architectural style for designing networked applications using HTTP requests to access and manipulate data. + +Q: What is GraphQL? +A: GraphQL is a query language for APIs and a runtime for executing those queries with your existing data.