diff --git a/.env.example b/.env.example index 6bc037a..2c54cf5 100644 --- a/.env.example +++ b/.env.example @@ -26,3 +26,9 @@ SWAGGER_PASSWORD=change_me STORAGE_DRIVER=local STORAGE_LOCAL_PATH=./uploads + +STORAGE_COS_SECRET_ID=your-cos-secret-id +STORAGE_COS_SECRET_KEY=your-cos-secret-key +STORAGE_COS_BUCKET=your-bucket-name-appid +STORAGE_COS_REGION=ap-guangzhou +STORAGE_COS_DOMAIN=your-custom-domain.example.com diff --git a/package-lock.json b/package-lock.json index 7dd2aa1..ace372d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "bcryptjs": "^3.0.3", "class-transformer": "^0.5.1", "class-validator": "^0.15.1", + "cos-nodejs-sdk-v5": "^2.15.4", "helmet": "^8.1.0", "ioredis": "^5.10.1", "jose": "^6.2.3", @@ -4083,7 +4084,6 @@ "version": "6.15.0", "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.15.0.tgz", "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", - "dev": true, "license": "MIT", "peer": true, "dependencies": { @@ -4271,11 +4271,52 @@ "dev": true, "license": "MIT" }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmmirror.com/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, + "license": "MIT" + }, + "node_modules/atomically": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/atomically/-/atomically-1.7.0.tgz", + "integrity": "sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==", + "license": "MIT", + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmmirror.com/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", "license": "MIT" }, "node_modules/babel-jest": { @@ -4418,6 +4459,15 @@ "node": ">=6.0.0" } }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, "node_modules/bcryptjs": { "version": "3.0.3", "resolved": "https://registry.npmmirror.com/bcryptjs/-/bcryptjs-3.0.3.tgz", @@ -4690,6 +4740,12 @@ ], "license": "CC-BY-4.0" }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmmirror.com/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "license": "Apache-2.0" + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", @@ -4938,7 +4994,6 @@ "version": "1.0.8", "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -5003,6 +5058,94 @@ "typedarray": "^0.0.6" } }, + "node_modules/conf": { + "version": "9.0.2", + "resolved": "https://registry.npmmirror.com/conf/-/conf-9.0.2.tgz", + "integrity": "sha512-rLSiilO85qHgaTBIIHQpsv8z+NnVfZq3cKuYNCXN1AOqPzced0GWZEe/A517VldRLyQYXUMyV+vszavE2jSAqw==", + "license": "MIT", + "dependencies": { + "ajv": "^7.0.3", + "ajv-formats": "^1.5.1", + "atomically": "^1.7.0", + "debounce-fn": "^4.0.0", + "dot-prop": "^6.0.1", + "env-paths": "^2.2.0", + "json-schema-typed": "^7.0.3", + "make-dir": "^3.1.0", + "onetime": "^5.1.2", + "pkg-up": "^3.1.0", + "semver": "^7.3.4" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/conf/node_modules/ajv": { + "version": "7.2.4", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-7.2.4.tgz", + "integrity": "sha512-nBeQgg/ZZA3u3SYxyaDvpvDtgZ/EZPF547ARgZBrG9Bhu1vKDwAIjtIf+sDtJUKa2zOcEbmRLBRSyMraS/Oy1A==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/conf/node_modules/ajv-formats": { + "version": "1.6.1", + "resolved": "https://registry.npmmirror.com/ajv-formats/-/ajv-formats-1.6.1.tgz", + "integrity": "sha512-4CjkH20If1lhR5CGtqkrVg3bbOtFEG80X9v6jDOIUhbzzbB+UzPBGy8GQhUNVZ0yvMHdMpawCOcy5ydGMsagGQ==", + "license": "MIT", + "dependencies": { + "ajv": "^7.0.0" + }, + "peerDependencies": { + "ajv": "^7.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/conf/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/conf/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/conf/node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/consola": { "version": "3.4.2", "resolved": "https://registry.npmmirror.com/consola/-/consola-3.4.2.tgz", @@ -5066,6 +5209,12 @@ "dev": true, "license": "MIT" }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "license": "MIT" + }, "node_modules/cors": { "version": "2.8.6", "resolved": "https://registry.npmmirror.com/cors/-/cors-2.8.6.tgz", @@ -5083,6 +5232,42 @@ "url": "https://opencollective.com/express" } }, + "node_modules/cos-nodejs-sdk-v5": { + "version": "2.15.4", + "resolved": "https://registry.npmmirror.com/cos-nodejs-sdk-v5/-/cos-nodejs-sdk-v5-2.15.4.tgz", + "integrity": "sha512-TP/iYTvKKKhRK89on9SRfSMGEw/9SFAAU8EC1kdT5Fmpx7dAwaCNM2+R2H1TSYoQt+03rwOs8QEfNkX8GOHjHQ==", + "license": "ISC", + "dependencies": { + "conf": "^9.0.0", + "fast-xml-parser": "4.2.5", + "mime-types": "^2.1.24", + "request": "^2.88.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/cos-nodejs-sdk-v5/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cos-nodejs-sdk-v5/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/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/cosmiconfig": { "version": "8.3.6", "resolved": "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz", @@ -5144,6 +5329,42 @@ "node": ">= 8" } }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/debounce-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/debounce-fn/-/debounce-fn-4.0.0.tgz", + "integrity": "sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/debounce-fn/node_modules/mimic-fn": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-3.1.0.tgz", + "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", @@ -5210,7 +5431,6 @@ "version": "1.0.0", "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -5275,6 +5495,21 @@ "node": ">=0.3.1" } }, + "node_modules/dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/dotenv": { "version": "17.4.1", "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-17.4.1.tgz", @@ -5335,6 +5570,16 @@ "dev": true, "license": "MIT" }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "license": "MIT", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmmirror.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -5400,6 +5645,15 @@ "node": ">=10.13.0" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/error-ex": { "version": "1.3.4", "resolved": "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.4.tgz", @@ -5831,11 +6085,25 @@ "url": "https://opencollective.com/express" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-diff": { @@ -5849,7 +6117,6 @@ "version": "2.1.0", "resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { @@ -5882,6 +6149,28 @@ ], "license": "BSD-3-Clause" }, + "node_modules/fast-xml-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmmirror.com/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz", + "integrity": "sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==", + "funding": [ + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + }, + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmmirror.com/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -6030,6 +6319,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, "node_modules/fork-ts-checker-webpack-plugin": { "version": "9.1.0", "resolved": "https://registry.npmmirror.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.1.0.tgz", @@ -6267,6 +6565,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmmirror.com/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, "node_modules/glob": { "version": "13.0.6", "resolved": "https://registry.npmmirror.com/glob/-/glob-13.0.6.tgz", @@ -6408,6 +6715,29 @@ "node": ">=0.10.0" } }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmmirror.com/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "license": "MIT", + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", @@ -6494,6 +6824,21 @@ "url": "https://opencollective.com/express" } }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmmirror.com/human-signals/-/human-signals-2.1.0.tgz", @@ -6718,6 +7063,15 @@ "node": ">=0.12.0" } }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-promise": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/is-promise/-/is-promise-4.0.0.tgz", @@ -6737,6 +7091,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "license": "MIT" + }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -6757,6 +7117,12 @@ "dev": true, "license": "ISC" }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "license": "MIT" + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmmirror.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -7627,6 +7993,12 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "license": "MIT" + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", @@ -7654,13 +8026,24 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, "license": "MIT" }, + "node_modules/json-schema-typed": { + "version": "7.0.3", + "resolved": "https://registry.npmmirror.com/json-schema-typed/-/json-schema-typed-7.0.3.tgz", + "integrity": "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==", + "license": "BSD-2-Clause" + }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -7668,6 +8051,12 @@ "dev": true, "license": "MIT" }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", @@ -7723,6 +8112,21 @@ "npm": ">=6" } }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmmirror.com/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "license": "MIT", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/jwa": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/jwa/-/jwa-2.0.1.tgz", @@ -8122,7 +8526,6 @@ "version": "2.1.0", "resolved": "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -8377,6 +8780,15 @@ "node": ">=8" } }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmmirror.com/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", @@ -8423,7 +8835,6 @@ "version": "5.1.2", "resolved": "https://registry.npmmirror.com/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" @@ -8513,7 +8924,6 @@ "version": "2.2.0", "resolved": "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -8686,6 +9096,12 @@ "resolved": "https://registry.npmmirror.com/pause/-/pause-0.0.1.tgz", "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", @@ -8785,6 +9201,79 @@ "node": ">=8" } }, + "node_modules/pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-up/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-up/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/pluralize": { "version": "8.0.0", "resolved": "https://registry.npmmirror.com/pluralize/-/pluralize-8.0.0.tgz", @@ -8898,11 +9387,22 @@ "node": ">= 0.10" } }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmmirror.com/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -9045,6 +9545,82 @@ "license": "Apache-2.0", "peer": true }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmmirror.com/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "license": "Apache-2.0", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request/node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/request/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/request/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/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/request/node_modules/qs": { + "version": "6.5.5", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.5.5.tgz", + "integrity": "sha512-mzR4sElr1bfCaPJe7m8ilJ6ZXdDaGoObcYR0ZHSsktM/Lt21MVHj5De30GQH2eiZ1qGRTO7LCAzQsUeXTNexWQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.6" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz", @@ -9059,7 +9635,6 @@ "version": "2.0.2", "resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -9409,6 +9984,31 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmmirror.com/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "license": "MIT", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmmirror.com/stack-utils/-/stack-utils-2.0.6.tgz", @@ -9569,6 +10169,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/strtok3": { "version": "10.3.5", "resolved": "https://registry.npmmirror.com/strtok3/-/strtok3-10.3.5.tgz", @@ -9993,6 +10605,19 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/ts-api-utils": { "version": "2.5.0", "resolved": "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-2.5.0.tgz", @@ -10185,6 +10810,24 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmmirror.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmmirror.com/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "license": "Unlicense" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz", @@ -10413,7 +11056,6 @@ "version": "4.4.1", "resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -10434,6 +11076,16 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "license": "MIT", + "bin": { + "uuid": "bin/uuid" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmmirror.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -10474,6 +11126,20 @@ "node": ">= 0.8" } }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmmirror.com/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmmirror.com/walker/-/walker-1.0.8.tgz", diff --git a/package.json b/package.json index e683989..f7168e7 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "bcryptjs": "^3.0.3", "class-transformer": "^0.5.1", "class-validator": "^0.15.1", + "cos-nodejs-sdk-v5": "^2.15.4", "helmet": "^8.1.0", "ioredis": "^5.10.1", "jose": "^6.2.3", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 62ed893..9217349 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -227,6 +227,8 @@ model UploadedFile { filename String @db.VarChar(255) mimeType String? @db.VarChar(100) storagePath String @db.VarChar(500) + objectKey String? @db.VarChar(500) + bucket String? @db.VarChar(100) sizeBytes BigInt @default(0) checksum String? @db.VarChar(255) createdAt DateTime @default(now()) @@ -234,6 +236,7 @@ model UploadedFile { user User @relation(fields: [userId], references: [id]) @@index([userId]) + @@index([objectKey]) } model DocumentImport { diff --git a/src/app.module.ts b/src/app.module.ts index cca0c7f..8c0b181 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -24,6 +24,7 @@ import { FocusItemsModule } from './modules/focus-items/focus-items.module'; import { LearningActivityModule } from './modules/learning-activity/learning-activity.module'; import { NotificationsModule } from './modules/notifications/notifications.module'; import { FeedbackModule } from './modules/feedback/feedback.module'; +import { FilesModule } from './modules/files/files.module'; import { WaitlistModule } from './modules/waitlist/waitlist.module'; import { JwtAuthGuard } from './common/guards/jwt-auth.guard'; @@ -88,6 +89,7 @@ import appleConfig from './config/apple.config'; LearningActivityModule, NotificationsModule, FeedbackModule, + FilesModule, WaitlistModule, ], providers: [ diff --git a/src/config/storage.config.ts b/src/config/storage.config.ts index 829dfea..4dca2cf 100644 --- a/src/config/storage.config.ts +++ b/src/config/storage.config.ts @@ -3,6 +3,13 @@ import { registerAs } from '@nestjs/config'; export default registerAs('storage', () => ({ driver: process.env.STORAGE_DRIVER || 'local', localPath: process.env.STORAGE_LOCAL_PATH || './uploads', + cos: { + secretId: process.env.STORAGE_COS_SECRET_ID || '', + secretKey: process.env.STORAGE_COS_SECRET_KEY || '', + bucket: process.env.STORAGE_COS_BUCKET || 'zhixi-1259685406', + region: process.env.STORAGE_COS_REGION || 'ap-guangzhou', + domain: process.env.STORAGE_COS_DOMAIN || 'cos.longde.cloud', + }, s3: { bucket: process.env.STORAGE_S3_BUCKET, region: process.env.STORAGE_S3_REGION, diff --git a/src/infrastructure/storage/cos-storage.provider.ts b/src/infrastructure/storage/cos-storage.provider.ts new file mode 100644 index 0000000..1d52560 --- /dev/null +++ b/src/infrastructure/storage/cos-storage.provider.ts @@ -0,0 +1,173 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import COS from 'cos-nodejs-sdk-v5'; + +export interface CosUploadUrlResult { + uploadUrl: string; + bucket: string; + region: string; + objectKey: string; + expireSeconds: number; +} + +export interface CosObjectInfo { + size: number; + etag: string; + contentType: string; +} + +@Injectable() +export class CosStorageProvider { + private readonly logger = new Logger(CosStorageProvider.name); + private readonly cos: COS; + private readonly bucket: string; + private readonly region: string; + + constructor(private readonly configService: ConfigService) { + const secretId = this.configService.get('storage.cos.secretId', ''); + const secretKey = this.configService.get('storage.cos.secretKey', ''); + const domain = this.configService.get('storage.cos.domain', ''); + this.bucket = this.configService.get('storage.cos.bucket', ''); + this.region = this.configService.get('storage.cos.region', ''); + + this.cos = new COS({ + SecretId: secretId, + SecretKey: secretKey, + Domain: domain, + Protocol: 'https:', + }); + + this.logger.log(`COS provider initialized, bucket=${this.bucket}, region=${this.region}`); + } + + getBucket(): string { + return this.bucket; + } + + getRegion(): string { + return this.region; + } + + async generateUploadUrl( + objectKey: string, + expiresInSeconds = 3600, + ): Promise { + return new Promise((resolve, reject) => { + this.cos.getObjectUrl( + { + Bucket: this.bucket, + Region: this.region, + Key: objectKey, + Method: 'PUT', + Sign: true, + Expires: expiresInSeconds, + }, + (err, data) => { + if (err) { + this.logger.error(`Failed to generate upload URL for ${objectKey}`, err); + return reject(err); + } + resolve({ + uploadUrl: data.Url, + bucket: this.bucket, + region: this.region, + objectKey, + expireSeconds: expiresInSeconds, + }); + }, + ); + }); + } + + async generateDownloadUrl( + objectKey: string, + expiresInSeconds = 86400, + ): Promise { + return new Promise((resolve, reject) => { + this.cos.getObjectUrl( + { + Bucket: this.bucket, + Region: this.region, + Key: objectKey, + Method: 'GET', + Sign: true, + Expires: expiresInSeconds, + }, + (err, data) => { + if (err) { + this.logger.error(`Failed to generate download URL for ${objectKey}`, err); + return reject(err); + } + resolve(data.Url); + }, + ); + }); + } + + async headObject(objectKey: string): Promise { + return new Promise((resolve, reject) => { + this.cos.headObject( + { + Bucket: this.bucket, + Region: this.region, + Key: objectKey, + }, + (err, data) => { + if (err) { + if (err.statusCode === 404) { + return resolve(null); + } + this.logger.error(`headObject failed for ${objectKey}`, err); + return reject(err); + } + resolve({ + size: parseInt(data.headers?.['content-length'] || '0', 10), + etag: (data.ETag || '').replace(/"/g, ''), + contentType: data.headers?.['content-type'] || 'application/octet-stream', + }); + }, + ); + }); + } + + async deleteObject(objectKey: string): Promise { + return new Promise((resolve, reject) => { + this.cos.deleteObject( + { + Bucket: this.bucket, + Region: this.region, + Key: objectKey, + }, + (err) => { + if (err) { + if (err.statusCode === 404) { + this.logger.warn(`deleteObject: object not found ${objectKey}`); + return resolve(); + } + this.logger.error(`deleteObject failed for ${objectKey}`, err); + return reject(err); + } + resolve(); + }, + ); + }); + } + + async healthCheck(): Promise { + try { + await new Promise((resolve, reject) => { + this.cos.headBucket( + { Bucket: this.bucket, Region: this.region }, + (err) => { + if (err) return reject(err); + resolve(); + }, + ); + }); + return true; + } catch (err) { + this.logger.warn('COS health check failed', err); + return false; + } + } +} diff --git a/src/infrastructure/storage/storage.module.ts b/src/infrastructure/storage/storage.module.ts index 8151be1..6ae07a4 100644 --- a/src/infrastructure/storage/storage.module.ts +++ b/src/infrastructure/storage/storage.module.ts @@ -1,9 +1,10 @@ import { Global, Module } from '@nestjs/common'; +import { CosStorageProvider } from './cos-storage.provider'; import { StorageService } from './storage.service'; @Global() @Module({ - providers: [StorageService], - exports: [StorageService], + providers: [CosStorageProvider, StorageService], + exports: [CosStorageProvider, StorageService], }) export class StorageModule {} diff --git a/src/infrastructure/storage/storage.service.ts b/src/infrastructure/storage/storage.service.ts index 683c888..291b760 100644 --- a/src/infrastructure/storage/storage.service.ts +++ b/src/infrastructure/storage/storage.service.ts @@ -1,9 +1,39 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, BadRequestException } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; +import { CosStorageProvider } from './cos-storage.provider'; +import { RateLimitService } from '../../common/utils/rate-limit.service'; +import { + sanitizeFilename, + validateFileUpload, +} from '../../common/utils/security.util'; + +export interface CreateUploadUrlInput { + filename: string; + mimeType: string; + sizeBytes: number; +} + +export interface UploadUrlResult { + uploadUrl: string; + objectKey: string; + bucket: string; + region: string; + expiresIn: number; +} + +export interface VerifiedUpload { + size: number; + etag: string; + contentType: string; +} @Injectable() export class StorageService { - constructor(private configService: ConfigService) {} + constructor( + private readonly cos: CosStorageProvider, + private readonly configService: ConfigService, + private readonly rateLimit: RateLimitService, + ) {} getUploadPath(filename: string): string { const basePath = this.configService.get( @@ -13,7 +43,53 @@ export class StorageService { return `${basePath}/${filename}`; } + generateObjectKey(userId: string, originalFilename: string): string { + const date = new Date(); + const yearMonth = `${date.getFullYear()}${String(date.getMonth() + 1).padStart(2, '0')}`; + const safeName = sanitizeFilename(originalFilename); + return `${userId}/${yearMonth}/${safeName}`; + } + + async createUploadUrl( + userId: string, + input: CreateUploadUrlInput, + expiresIn = 3600, + ): Promise { + validateFileUpload(input.mimeType, input.sizeBytes); + await this.rateLimit.fileUploadLimit(userId); + + const objectKey = this.generateObjectKey(userId, input.filename); + const result = await this.cos.generateUploadUrl(objectKey, expiresIn); + + return { + uploadUrl: result.uploadUrl, + objectKey: result.objectKey, + bucket: result.bucket, + region: result.region, + expiresIn: expiresIn, + }; + } + + async verifyUpload(objectKey: string): Promise { + const info = await this.cos.headObject(objectKey); + if (!info) { + throw new BadRequestException('文件未被上传到存储服务'); + } + return info; + } + + async getDownloadUrl( + objectKey: string, + expiresInSeconds?: number, + ): Promise { + return this.cos.generateDownloadUrl(objectKey, expiresInSeconds); + } + + async deleteObject(objectKey: string): Promise { + await this.cos.deleteObject(objectKey); + } + async healthCheck(): Promise { - return true; + return this.cos.healthCheck(); } } diff --git a/src/main.ts b/src/main.ts index 2f2dd8b..63c1d77 100644 --- a/src/main.ts +++ b/src/main.ts @@ -44,6 +44,7 @@ async function bootstrap() { .addTag('learning-activity', '学习活跃') .addTag('notifications', '消息通知') .addTag('feedback', '用户反馈') + .addTag('files', '文件管理') .addTag('waitlist', '等待名单') .build(); diff --git a/src/modules/files/dto/complete-upload.dto.ts b/src/modules/files/dto/complete-upload.dto.ts new file mode 100644 index 0000000..22c5a88 --- /dev/null +++ b/src/modules/files/dto/complete-upload.dto.ts @@ -0,0 +1,13 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsString, IsOptional } from 'class-validator'; + +export class CompleteUploadDto { + @ApiProperty({ description: 'COS 对象键(上传 URL 响应中返回)' }) + @IsString() + objectKey: string; + + @ApiPropertyOptional({ description: '文件 SHA256 校验和' }) + @IsOptional() + @IsString() + checksum?: string; +} diff --git a/src/modules/files/dto/create-upload-url.dto.ts b/src/modules/files/dto/create-upload-url.dto.ts new file mode 100644 index 0000000..ca82eb4 --- /dev/null +++ b/src/modules/files/dto/create-upload-url.dto.ts @@ -0,0 +1,19 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsNumber, Min, Max } from 'class-validator'; +import { MAX_FILE_SIZE } from '../../../common/utils/security.util'; + +export class CreateUploadUrlDto { + @ApiProperty({ description: '原始文件名', example: 'document.pdf' }) + @IsString() + filename: string; + + @ApiProperty({ description: 'MIME 类型', example: 'application/pdf' }) + @IsString() + mimeType: string; + + @ApiProperty({ description: '文件大小(字节)', example: 1048576 }) + @IsNumber() + @Min(1) + @Max(MAX_FILE_SIZE) + sizeBytes: number; +} diff --git a/src/modules/files/dto/index.ts b/src/modules/files/dto/index.ts new file mode 100644 index 0000000..44f1f69 --- /dev/null +++ b/src/modules/files/dto/index.ts @@ -0,0 +1,2 @@ +export { CreateUploadUrlDto } from './create-upload-url.dto'; +export { CompleteUploadDto } from './complete-upload.dto'; diff --git a/src/modules/files/files.controller.ts b/src/modules/files/files.controller.ts new file mode 100644 index 0000000..c53843b --- /dev/null +++ b/src/modules/files/files.controller.ts @@ -0,0 +1,58 @@ +import { + Controller, + Get, + Post, + Delete, + Body, + Param, +} from '@nestjs/common'; +import { ApiTags, ApiOperation, ApiBearerAuth, ApiResponse } from '@nestjs/swagger'; +import { FilesService } from './files.service'; +import { CreateUploadUrlDto, CompleteUploadDto } from './dto'; +import { CurrentUser } from '../../common/decorators/current-user.decorator'; +import type { UserPayload } from '../../common/types'; + +@ApiTags('files') +@Controller('files') +@ApiBearerAuth() +export class FilesController { + constructor(private readonly filesService: FilesService) {} + + @Post('upload-url') + @ApiOperation({ summary: '获取预签名上传 URL' }) + @ApiResponse({ status: 201, description: '返回预签名 URL,客户端直接 PUT 文件到 COS' }) + async createUploadUrl( + @CurrentUser() user: UserPayload, + @Body() dto: CreateUploadUrlDto, + ) { + return this.filesService.requestUploadUrl(user.id, dto); + } + + @Post('complete') + @ApiOperation({ summary: '确认上传完成' }) + @ApiResponse({ status: 201, description: '验证 COS 中文件存在并创建数据库记录' }) + async completeUpload( + @CurrentUser() user: UserPayload, + @Body() dto: CompleteUploadDto, + ) { + return this.filesService.confirmUpload(user.id, dto); + } + + @Get(':id') + @ApiOperation({ summary: '获取文件信息及下载 URL' }) + async getFile( + @CurrentUser() user: UserPayload, + @Param('id') id: string, + ) { + return this.filesService.getFile(user.id, id); + } + + @Delete(':id') + @ApiOperation({ summary: '删除文件(COS + 数据库)' }) + async deleteFile( + @CurrentUser() user: UserPayload, + @Param('id') id: string, + ) { + return this.filesService.deleteFile(user.id, id); + } +} diff --git a/src/modules/files/files.module.ts b/src/modules/files/files.module.ts new file mode 100644 index 0000000..3755115 --- /dev/null +++ b/src/modules/files/files.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { FilesController } from './files.controller'; +import { FilesService } from './files.service'; +import { FilesRepository } from './files.repository'; + +@Module({ + controllers: [FilesController], + providers: [FilesService, FilesRepository], + exports: [FilesService], +}) +export class FilesModule {} diff --git a/src/modules/files/files.repository.ts b/src/modules/files/files.repository.ts new file mode 100644 index 0000000..2180580 --- /dev/null +++ b/src/modules/files/files.repository.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '../../infrastructure/database/prisma.service'; + +@Injectable() +export class FilesRepository { + constructor(private readonly prisma: PrismaService) {} + + async create(data: { + userId: string; + filename: string; + mimeType?: string; + storagePath: string; + objectKey: string; + bucket: string; + sizeBytes: number; + checksum?: string; + }) { + return this.prisma.uploadedFile.create({ data }); + } + + async findById(id: string) { + return this.prisma.uploadedFile.findUnique({ where: { id } }); + } + + async findByObjectKey(objectKey: string) { + return this.prisma.uploadedFile.findFirst({ where: { objectKey } }); + } + + async findByUserId(userId: string) { + return this.prisma.uploadedFile.findMany({ + where: { userId }, + orderBy: { createdAt: 'desc' }, + }); + } + + async delete(id: string) { + return this.prisma.uploadedFile.delete({ where: { id } }); + } +} diff --git a/src/modules/files/files.service.ts b/src/modules/files/files.service.ts new file mode 100644 index 0000000..e089dbd --- /dev/null +++ b/src/modules/files/files.service.ts @@ -0,0 +1,74 @@ +import { + Injectable, + NotFoundException, + ForbiddenException, +} from '@nestjs/common'; +import { FilesRepository } from './files.repository'; +import { StorageService } from '../../infrastructure/storage/storage.service'; +import { CosStorageProvider } from '../../infrastructure/storage/cos-storage.provider'; +import { CreateUploadUrlDto, CompleteUploadDto } from './dto'; + +@Injectable() +export class FilesService { + constructor( + private readonly repository: FilesRepository, + private readonly storage: StorageService, + private readonly cos: CosStorageProvider, + ) {} + + async requestUploadUrl(userId: string, dto: CreateUploadUrlDto) { + return this.storage.createUploadUrl(userId, { + filename: dto.filename, + mimeType: dto.mimeType, + sizeBytes: dto.sizeBytes, + }); + } + + async confirmUpload(userId: string, dto: CompleteUploadDto) { + const info = await this.storage.verifyUpload(dto.objectKey); + + const parts = dto.objectKey.split('/'); + const originalFilename = parts[parts.length - 1]; + + return this.repository.create({ + userId, + filename: originalFilename, + mimeType: info.contentType, + storagePath: dto.objectKey, + objectKey: dto.objectKey, + bucket: this.cos.getBucket(), + sizeBytes: info.size, + checksum: dto.checksum, + }); + } + + async getFile(userId: string, fileId: string) { + const file = await this.repository.findById(fileId); + if (!file) { + throw new NotFoundException('文件不存在'); + } + if (file.userId !== userId) { + throw new ForbiddenException('无权访问该文件'); + } + + const downloadUrl = await this.storage.getDownloadUrl(file.objectKey!); + return { file, downloadUrl }; + } + + async deleteFile(userId: string, fileId: string) { + const file = await this.repository.findById(fileId); + if (!file) { + throw new NotFoundException('文件不存在'); + } + if (file.userId !== userId) { + throw new ForbiddenException('无权操作该文件'); + } + + await this.storage.deleteObject(file.objectKey!); + await this.repository.delete(fileId); + } + + async findByUserId(userId: string) { + return this.repository.findByUserId(userId); + } +}