diff --git a/global.d.ts b/global.d.ts index a59b0203..ac4e281d 100644 --- a/global.d.ts +++ b/global.d.ts @@ -1,10 +1,13 @@ import { ModelType } from '@typegoose/typegoose/lib/types' import { Document, PaginateModel } from 'mongoose' +import 'zx/globals' declare global { export type KV = Record // @ts-ignore export type MongooseModel = ModelType & PaginateModel + + export const isDev: boolean } export {} diff --git a/package.json b/package.json index 32d579a5..cd97aca7 100644 --- a/package.json +++ b/package.json @@ -60,13 +60,16 @@ "chalk": "*", "class-transformer": "^0.4.0", "class-validator": "^0.13.1", + "cos-nodejs-sdk-v5": "2.10.0", "dayjs": "^1.10.6", "dotenv": "*", "ejs": "^3.1.6", + "fastify-multipart": "^4.0.7", "fastify-swagger": "^4.9.0", "image-size": "^1.0.0", "inquirer": "*", "lodash": "*", + "mkdirp": "*", "mongoose": "*", "mongoose-lean-id": "^0.2.0", "mongoose-lean-virtuals": "^0.8.0", @@ -82,9 +85,11 @@ "reflect-metadata": "^0.1.13", "rxjs": "^7.3.0", "snakecase-keys": "^4.0.2", - "ua-parser-js": "^0.7.28" + "ua-parser-js": "^0.7.28", + "zx": "^4.1.1" }, "devDependencies": { + "rimraf": "^3.0.2", "@innei-util/eslint-config-ts": "^0.2.3", "@innei-util/prettier": "^0.1.3", "@nestjs/cli": "^8.1.1", @@ -111,7 +116,6 @@ "jest": "27.1.0", "lint-staged": "^11.1.2", "prettier": "^2.3.2", - "rimraf": "^3.0.2", "run-script-webpack-plugin": "^0.0.11", "socket.io": "*", "supertest": "^6.1.6", diff --git a/paw.paw b/paw.paw index 87dc68d6..d6d893f3 100644 Binary files a/paw.paw and b/paw.paw differ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4a50011b..3abdb788 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,12 +40,14 @@ specifiers: chalk: '*' class-transformer: ^0.4.0 class-validator: ^0.13.1 + cos-nodejs-sdk-v5: 2.10.0 cross-env: ^7.0.3 dayjs: ^1.10.6 dotenv: '*' ejs: ^3.1.6 eslint: ^7.32.0 fastify: '*' + fastify-multipart: ^4.0.7 fastify-swagger: ^4.9.0 husky: ^7.0.1 image-size: ^1.0.0 @@ -54,6 +56,7 @@ specifiers: jest: 27.1.0 lint-staged: ^11.1.2 lodash: '*' + mkdirp: '*' mongoose: '*' mongoose-lean-id: ^0.2.0 mongoose-lean-virtuals: ^0.8.0 @@ -82,6 +85,7 @@ specifiers: ua-parser-js: ^0.7.28 webpack: ^5.51.1 webpack-node-externals: ^3.0.0 + zx: ^4.1.1 dependencies: '@nestjs/common': 8.0.6_4d0c20d2c2a765e9ff99ebac79ad2484 @@ -105,13 +109,16 @@ dependencies: chalk: 4.1.2 class-transformer: 0.4.0 class-validator: 0.13.1 + cos-nodejs-sdk-v5: 2.10.0 dayjs: 1.10.6 dotenv: 10.0.0 ejs: 3.1.6 + fastify-multipart: 4.0.7 fastify-swagger: 4.9.1 image-size: 1.0.0 inquirer: 8.1.1 lodash: 4.17.21 + mkdirp: 1.0.4 mongoose: 5.13.8 mongoose-lean-id: 0.2.0_mongoose@5.13.8 mongoose-lean-virtuals: 0.8.0_mongoose@5.13.8 @@ -128,6 +135,7 @@ dependencies: rxjs: 7.3.0 snakecase-keys: 4.0.2 ua-parser-js: 0.7.28 + zx: 4.1.1 devDependencies: '@innei-util/eslint-config-ts': 0.2.3_typescript@4.4.2 @@ -1280,12 +1288,10 @@ packages: dependencies: '@nodelib/fs.stat': 2.0.5 run-parallel: 1.2.0 - dev: true /@nodelib/fs.stat/2.0.5: resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} engines: {node: '>= 8'} - dev: true /@nodelib/fs.walk/1.2.8: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} @@ -1293,7 +1299,6 @@ packages: dependencies: '@nodelib/fs.scandir': 2.1.5 fastq: 1.12.0 - dev: true /@nuxtjs/opencollective/0.3.2: resolution: {integrity: sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==} @@ -1468,6 +1473,12 @@ packages: '@types/serve-static': 1.13.10 dev: true + /@types/fs-extra/9.0.12: + resolution: {integrity: sha512-I+bsBr67CurCGnSenZZ7v94gd3tc3+Aj2taxMT4yu4ABLuOgOjeFxX3dokG24ztSRg5tnT00sL8BszO7gSMoIw==} + dependencies: + '@types/node': 16.7.8 + dev: false + /@types/graceful-fs/4.1.5: resolution: {integrity: sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==} dependencies: @@ -1531,6 +1542,10 @@ packages: resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==} dev: true + /@types/minimist/1.2.2: + resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} + dev: false + /@types/mongodb/3.6.20: resolution: {integrity: sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ==} dependencies: @@ -1550,10 +1565,21 @@ packages: '@types/node': 16.7.8 dev: true + /@types/node-fetch/2.5.12: + resolution: {integrity: sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==} + dependencies: + '@types/node': 16.7.8 + form-data: 3.0.1 + dev: false + /@types/node/10.17.60: resolution: {integrity: sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==} dev: false + /@types/node/14.17.14: + resolution: {integrity: sha512-rsAj2u8Xkqfc332iXV12SqIsjVi07H479bOP4q94NAcjzmAvapumEhuVIt53koEf7JFrpjgNKjBga5Pnn/GL8A==} + dev: false + /@types/node/16.7.8: resolution: {integrity: sha512-8upnoQU0OPzbIkm+ZMM0zCeFCkw2s3mS0IWdx0+AAaWqm4fkBb0UJp8Edl7FVKRamYbpJC/aVsHpKWBIbiC7Zg==} @@ -2031,6 +2057,15 @@ packages: indent-string: 4.0.0 dev: true + /ajv-formats/1.6.1: + resolution: {integrity: sha512-4CjkH20If1lhR5CGtqkrVg3bbOtFEG80X9v6jDOIUhbzzbB+UzPBGy8GQhUNVZ0yvMHdMpawCOcy5ydGMsagGQ==} + peerDependenciesMeta: + ajv: + optional: true + dependencies: + ajv: 7.2.4 + dev: false + /ajv-formats/2.0.2: resolution: {integrity: sha512-Brah4Uo5/U8v76c6euTwtjVFFaVishwnJrQBYpev1JRh4vjA1F4HY3UzQez41YUCszUCXKagG8v6eVRBHV1gkw==} peerDependenciesMeta: @@ -2065,6 +2100,15 @@ packages: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + /ajv/7.2.4: + resolution: {integrity: sha512-nBeQgg/ZZA3u3SYxyaDvpvDtgZ/EZPF547ARgZBrG9Bhu1vKDwAIjtIf+sDtJUKa2zOcEbmRLBRSyMraS/Oy1A==} + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + dev: false + /ajv/8.2.0: resolution: {integrity: sha512-WSNGFuyWd//XO8n/m/EaOlNLtO0yL8EXT/74LqT4khdhpZjP7lkj/kT5uwRmGitKEVp/Oj7ZUHeGfPtgHhQ5CA==} dependencies: @@ -2184,6 +2228,11 @@ packages: engines: {node: '>=8'} dev: true + /array-union/3.0.1: + resolution: {integrity: sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==} + engines: {node: '>=12'} + dev: false + /array.prototype.flat/1.2.3: resolution: {integrity: sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==} engines: {node: '>= 0.4'} @@ -2192,6 +2241,17 @@ packages: es-abstract: 1.18.5 dev: false + /asn1/0.2.4: + resolution: {integrity: sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==} + dependencies: + safer-buffer: 2.1.2 + dev: false + + /assert-plus/1.0.0: + resolution: {integrity: sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=} + engines: {node: '>=0.8'} + dev: false + /astral-regex/2.0.0: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} @@ -2207,7 +2267,6 @@ packages: /asynckit/0.4.0: resolution: {integrity: sha1-x57Zf380y48robyXkLzDZkdLS3k=} - dev: true /at-least-node/1.0.0: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} @@ -2218,6 +2277,11 @@ packages: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} engines: {node: '>=8.0.0'} + /atomically/1.7.0: + resolution: {integrity: sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==} + engines: {node: '>=10.12.0'} + dev: false + /author-regex/1.0.0: resolution: {integrity: sha1-0IiFvmubv5Q5/gh8dihyRfCoFFA=} engines: {node: '>=0.8'} @@ -2233,6 +2297,14 @@ packages: transitivePeerDependencies: - supports-color + /aws-sign2/0.7.0: + resolution: {integrity: sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=} + dev: false + + /aws4/1.11.0: + resolution: {integrity: sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==} + dev: false + /axios/0.21.1: resolution: {integrity: sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==} dependencies: @@ -2328,6 +2400,12 @@ packages: resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} engines: {node: ^4.5.0 || >= 5.9} + /bcrypt-pbkdf/1.0.2: + resolution: {integrity: sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=} + dependencies: + tweetnacl: 0.14.5 + dev: false + /bcrypt/5.0.1: resolution: {integrity: sha512-9BTgmrhZM2t1bNuDtrtIMVSmmxZBrJ71n8Wg+YgdjHuIWYF7SjjmCPZFB+/5i/o/PIeRpwVJR3P+NrpIItUjqw==} engines: {node: '>= 10.0.0'} @@ -2377,7 +2455,6 @@ packages: engines: {node: '>=8'} dependencies: fill-range: 7.0.1 - dev: true /browser-process-hrtime/1.0.0: resolution: {integrity: sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==} @@ -2432,6 +2509,13 @@ packages: base64-js: 1.5.1 ieee754: 1.2.1 + /busboy/0.3.1: + resolution: {integrity: sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==} + engines: {node: '>=4.5.0'} + dependencies: + dicer: 0.3.0 + dev: false + /cache-manager-ioredis/2.1.0: resolution: {integrity: sha512-TCxbp9ceuFveTKWuNaCX8QjoC41rAlHen4s63u9Yd+iXlw3efYmimc/u935PKPxSdhkXpnMes4mxtK3/yb0L4g==} engines: {node: '>=6.0.0'} @@ -2474,6 +2558,10 @@ packages: resolution: {integrity: sha512-I56jhWDGMtdILQORdusxBOH+Nl/KgQSdDmpJezYddnAkVOmnoU8zwjTV9xAjMIYxr0iPreEAVylCGcmHCjfaOw==} dev: true + /caseless/0.12.0: + resolution: {integrity: sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=} + dev: false + /chalk/2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -2649,7 +2737,6 @@ packages: engines: {node: '>= 0.8'} dependencies: delayed-stream: 1.0.0 - dev: true /commander/2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -2671,6 +2758,23 @@ packages: /concat-map/0.0.1: resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} + /conf/9.0.2: + resolution: {integrity: sha512-rLSiilO85qHgaTBIIHQpsv8z+NnVfZq3cKuYNCXN1AOqPzced0GWZEe/A517VldRLyQYXUMyV+vszavE2jSAqw==} + engines: {node: '>=10'} + dependencies: + ajv: 7.2.4 + ajv-formats: 1.6.1 + atomically: 1.7.0 + debounce-fn: 4.0.0 + dot-prop: 6.0.1 + env-paths: 2.2.1 + json-schema-typed: 7.0.3 + make-dir: 3.1.0 + onetime: 5.1.2 + pkg-up: 3.1.0 + semver: 7.3.5 + dev: false + /consola/2.15.3: resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} dev: false @@ -2711,6 +2815,17 @@ packages: object-assign: 4.1.1 vary: 1.1.2 + /cos-nodejs-sdk-v5/2.10.0: + resolution: {integrity: sha512-FA/lrydYCQSXzo8An4vITOsADxO995Km0YY+2lcKxH161cP1ztPRXp9uTwP3y0Bv85rzmif6By1rlTduu318TQ==} + engines: {node: '>= 6'} + dependencies: + '@types/node': 14.17.14 + conf: 9.0.2 + mime-types: 2.1.32 + request: 2.88.2 + xml2js: 0.4.23 + dev: false + /cosmiconfig/6.0.0: resolution: {integrity: sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==} engines: {node: '>=8'} @@ -2775,6 +2890,13 @@ packages: cssom: 0.3.8 dev: true + /dashdash/1.14.1: + resolution: {integrity: sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=} + engines: {node: '>=0.10'} + dependencies: + assert-plus: 1.0.0 + dev: false + /data-urls/2.0.0: resolution: {integrity: sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==} engines: {node: '>=10'} @@ -2788,6 +2910,13 @@ packages: resolution: {integrity: sha512-AztC/IOW4L1Q41A86phW5Thhcrco3xuAA+YX/BLpLWWjRcTj5TOt/QImBLmCKlrF7u7k47arTnOyL6GnbG8Hvw==} dev: false + /debounce-fn/4.0.0: + resolution: {integrity: sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==} + engines: {node: '>=10'} + dependencies: + mimic-fn: 3.1.0 + dev: false + /debug/2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} dependencies: @@ -2842,7 +2971,6 @@ packages: /delayed-stream/1.0.0: resolution: {integrity: sha1-3zrhmayt+31ECqrgsp4icrJOxhk=} engines: {node: '>=0.4.0'} - dev: true /delegates/1.0.0: resolution: {integrity: sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=} @@ -2872,6 +3000,13 @@ packages: engines: {node: '>=8'} dev: true + /dicer/0.3.0: + resolution: {integrity: sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==} + engines: {node: '>=4.5.0'} + dependencies: + streamsearch: 0.1.2 + dev: false + /diff-sequences/27.0.6: resolution: {integrity: sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -2887,7 +3022,6 @@ packages: engines: {node: '>=8'} dependencies: path-type: 4.0.0 - dev: true /doctrine/3.0.0: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} @@ -2914,6 +3048,13 @@ packages: tslib: 2.3.1 dev: false + /dot-prop/6.0.1: + resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} + engines: {node: '>=10'} + dependencies: + is-obj: 2.0.0 + dev: false + /dotenv-expand/5.1.0: resolution: {integrity: sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==} dev: false @@ -2923,6 +3064,17 @@ packages: engines: {node: '>=10'} dev: false + /duplexer/0.1.2: + resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + dev: false + + /ecc-jsbn/0.1.2: + resolution: {integrity: sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=} + dependencies: + jsbn: 0.1.1 + safer-buffer: 2.1.2 + dev: false + /ecdsa-sig-formatter/1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} dependencies: @@ -2967,7 +3119,6 @@ packages: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} dependencies: once: 1.4.0 - dev: true /engine.io-parser/4.0.3: resolution: {integrity: sha512-xEAAY0msNnESNPc00e19y5heTPX4y/TJ36gr8t1voOaNmTojP9b3oK3BbJLFufW2XFPQaaijpFewm2g2Um3uqA==} @@ -3006,6 +3157,11 @@ packages: ansi-colors: 4.1.1 dev: true + /env-paths/2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + dev: false + /error-ex/1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: @@ -3226,6 +3382,18 @@ packages: engines: {node: '>= 0.6'} dev: false + /event-stream/3.3.4: + resolution: {integrity: sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=} + dependencies: + duplexer: 0.1.2 + from: 0.1.7 + map-stream: 0.1.0 + pause-stream: 0.0.11 + split: 0.3.3 + stream-combiner: 0.0.4 + through: 2.3.8 + dev: false + /events/3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} @@ -3282,6 +3450,10 @@ packages: jest-regex-util: 27.0.6 dev: true + /extend/3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + dev: false + /external-editor/3.1.0: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} @@ -3290,6 +3462,11 @@ packages: iconv-lite: 0.4.24 tmp: 0.0.33 + /extsprintf/1.3.0: + resolution: {integrity: sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=} + engines: {'0': node >=0.6.0} + dev: false + /fast-decode-uri-component/1.0.1: resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} @@ -3305,7 +3482,6 @@ packages: glob-parent: 5.1.2 merge2: 1.4.1 micromatch: 4.0.4 - dev: true /fast-json-stable-stringify/2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -3346,6 +3522,18 @@ packages: fastify-plugin: 3.0.0 dev: false + /fastify-multipart/4.0.7: + resolution: {integrity: sha512-bV6QB3mmDYdrYwIrUrkGplutQLUUSzXFXtkCL2HRw5eLXyv9+DD5Gb5GJNDtKYCiCtyaGLbJmt2rLsF6A3WCUw==} + dependencies: + busboy: 0.3.1 + deepmerge: 4.2.2 + end-of-stream: 1.4.4 + fastify-error: 0.3.1 + fastify-plugin: 3.0.0 + hexoid: 1.0.0 + stream-wormhole: 1.1.0 + dev: false + /fastify-plugin/3.0.0: resolution: {integrity: sha512-ZdCvKEEd92DNLps5n0v231Bha8bkz1DjnPP/aEz37rz/q42Z5JVLmgnqR4DYuNn3NXAO3IDCPyRvgvxtJ4Ym4w==} dev: false @@ -3438,7 +3626,6 @@ packages: engines: {node: '>=8'} dependencies: to-regex-range: 5.0.1 - dev: true /find-my-way/4.3.3: resolution: {integrity: sha512-5E4bRdaATB1MewjOCBjx4xvD205a4t2ripCnXB+YFhYEJ0NABtrcC7XLXLq0TPoFe/WYGUFqys3Qk3HCOGeNcw==} @@ -3449,6 +3636,13 @@ packages: safe-regex2: 2.0.0 semver-store: 0.3.0 + /find-up/3.0.0: + resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} + engines: {node: '>=6'} + dependencies: + locate-path: 3.0.0 + dev: false + /find-up/4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -3482,6 +3676,10 @@ packages: optional: true dev: false + /forever-agent/0.6.1: + resolution: {integrity: sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=} + dev: false + /fork-ts-checker-webpack-plugin/6.3.1: resolution: {integrity: sha512-uxqlKTEeSJ5/JRr0zaCiw2U+kOV8F4/MhCnnRf6vbxj4ZU3Or0DLl/0CNtXro7uLWDssnuR7wUN7fU9w1I0REA==} engines: {node: '>=10', yarn: '>=1.0.0'} @@ -3501,6 +3699,15 @@ packages: tapable: 1.1.3 dev: true + /form-data/2.3.3: + resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} + engines: {node: '>= 0.12'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.32 + dev: false + /form-data/3.0.1: resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} engines: {node: '>= 6'} @@ -3508,7 +3715,6 @@ packages: asynckit: 0.4.0 combined-stream: 1.0.8 mime-types: 2.1.32 - dev: true /formidable/1.2.2: resolution: {integrity: sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==} @@ -3523,6 +3729,10 @@ packages: engines: {node: '>= 0.6'} dev: false + /from/0.1.7: + resolution: {integrity: sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=} + dev: false + /fs-extra/10.0.0: resolution: {integrity: sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==} engines: {node: '>=12'} @@ -3530,7 +3740,6 @@ packages: graceful-fs: 4.2.8 jsonfile: 6.1.0 universalify: 2.0.0 - dev: true /fs-extra/9.1.0: resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} @@ -3621,6 +3830,12 @@ packages: engines: {node: '>=10'} dev: true + /getpass/0.1.7: + resolution: {integrity: sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=} + dependencies: + assert-plus: 1.0.0 + dev: false + /gifwrap/0.9.2: resolution: {integrity: sha512-fcIswrPaiCDAyO8xnWvHSZdWChjKXUanKKpAiWWJ/UTkEi/aYKn5+90e7DE820zbEaVR9CE2y4z9bzhQijZ0BA==} dependencies: @@ -3633,7 +3848,6 @@ packages: engines: {node: '>= 6'} dependencies: is-glob: 4.0.1 - dev: true /glob-to-regexp/0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} @@ -3680,9 +3894,34 @@ packages: slash: 3.0.0 dev: true + /globby/12.0.2: + resolution: {integrity: sha512-lAsmb/5Lww4r7MM9nCCliDZVIKbZTavrsunAsHLr9oHthrZP1qi7/gAnHOsUs9bLvEt2vKVJhHmxuL7QbDuPdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + array-union: 3.0.1 + dir-glob: 3.0.1 + fast-glob: 3.2.7 + ignore: 5.1.8 + merge2: 1.4.1 + slash: 4.0.0 + dev: false + /graceful-fs/4.2.8: resolution: {integrity: sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==} - dev: true + + /har-schema/2.0.0: + resolution: {integrity: sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=} + engines: {node: '>=4'} + dev: false + + /har-validator/5.1.5: + resolution: {integrity: sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==} + engines: {node: '>=6'} + deprecated: this library is no longer supported + dependencies: + ajv: 6.12.6 + har-schema: 2.0.0 + dev: false /has-bigints/1.0.1: resolution: {integrity: sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==} @@ -3717,6 +3956,11 @@ packages: dependencies: function-bind: 1.1.1 + /hexoid/1.0.0: + resolution: {integrity: sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==} + engines: {node: '>=8'} + dev: false + /html-encoding-sniffer/2.0.1: resolution: {integrity: sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==} engines: {node: '>=10'} @@ -3750,6 +3994,15 @@ packages: - supports-color dev: true + /http-signature/1.2.0: + resolution: {integrity: sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=} + engines: {node: '>=0.8', npm: '>=1.3.7'} + dependencies: + assert-plus: 1.0.0 + jsprim: 1.4.1 + sshpk: 1.16.1 + dev: false + /https-proxy-agent/5.0.0: resolution: {integrity: sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==} engines: {node: '>= 6'} @@ -3792,7 +4045,6 @@ packages: /ignore/5.1.8: resolution: {integrity: sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==} engines: {node: '>= 4'} - dev: true /image-q/1.1.1: resolution: {integrity: sha1-/IQJlmRGC5DKhi2TALa/u7+/gFY=} @@ -3974,7 +4226,6 @@ packages: /is-extglob/2.1.1: resolution: {integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=} engines: {node: '>=0.10.0'} - dev: true /is-fullwidth-code-point/1.0.0: resolution: {integrity: sha1-754xOG8DGn8NZDr4L95QxFfvAMs=} @@ -4006,7 +4257,6 @@ packages: engines: {node: '>=0.10.0'} dependencies: is-extglob: 2.1.1 - dev: true /is-interactive/1.0.0: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} @@ -4027,13 +4277,17 @@ packages: /is-number/7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - dev: true /is-obj/1.0.1: resolution: {integrity: sha1-PkcprB9f3gJc19g6iW2rn09n2w8=} engines: {node: '>=0.10.0'} dev: true + /is-obj/2.0.0: + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} + engines: {node: '>=8'} + dev: false + /is-potential-custom-element-name/1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} dev: true @@ -4072,7 +4326,6 @@ packages: /is-typedarray/1.0.0: resolution: {integrity: sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=} - dev: true /is-unicode-supported/0.1.0: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} @@ -4084,7 +4337,10 @@ packages: /isexe/2.0.0: resolution: {integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=} - dev: true + + /isstream/0.1.2: + resolution: {integrity: sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=} + dev: false /istanbul-lib-coverage/3.0.0: resolution: {integrity: sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==} @@ -4642,6 +4898,10 @@ packages: argparse: 2.0.1 dev: false + /jsbn/0.1.1: + resolution: {integrity: sha1-peZUwuWi3rXyAdls77yoDA7y9RM=} + dev: false + /jsdom/16.7.0: resolution: {integrity: sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==} engines: {node: '>=10'} @@ -4714,12 +4974,23 @@ packages: /json-schema-traverse/1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - dev: true + + /json-schema-typed/7.0.3: + resolution: {integrity: sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==} + dev: false + + /json-schema/0.2.3: + resolution: {integrity: sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=} + dev: false /json-stable-stringify-without-jsonify/1.0.1: resolution: {integrity: sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=} dev: true + /json-stringify-safe/5.0.1: + resolution: {integrity: sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=} + dev: false + /json5/1.0.1: resolution: {integrity: sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==} hasBin: true @@ -4745,7 +5016,6 @@ packages: universalify: 2.0.0 optionalDependencies: graceful-fs: 4.2.8 - dev: true /jsonwebtoken/8.5.1: resolution: {integrity: sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==} @@ -4763,6 +5033,16 @@ packages: semver: 5.7.1 dev: false + /jsprim/1.4.1: + resolution: {integrity: sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=} + engines: {'0': node >=0.6.0} + dependencies: + assert-plus: 1.0.0 + extsprintf: 1.3.0 + json-schema: 0.2.3 + verror: 1.10.0 + dev: false + /jwa/1.4.1: resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} dependencies: @@ -4881,6 +5161,14 @@ packages: engines: {node: '>=6.11.5'} dev: true + /locate-path/3.0.0: + resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} + engines: {node: '>=6'} + dependencies: + p-locate: 3.0.0 + path-exists: 3.0.0 + dev: false + /locate-path/5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -5022,6 +5310,10 @@ packages: engines: {node: '>=8'} dev: false + /map-stream/0.1.0: + resolution: {integrity: sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=} + dev: false + /memfs/3.2.2: resolution: {integrity: sha512-RE0CwmIM3CEvpcdK3rZ19BC4E6hv9kADkMN5rPduRak58cNArWLi/9jFLsa4rhsjfVxMP3v0jO7FHXq7SvFY5Q==} engines: {node: '>= 4.0.0'} @@ -5041,7 +5333,6 @@ packages: /merge2/1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - dev: true /methods/1.1.2: resolution: {integrity: sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=} @@ -5054,7 +5345,6 @@ packages: dependencies: braces: 3.0.2 picomatch: 2.3.0 - dev: true /middie/5.3.0: resolution: {integrity: sha512-uq6Ob4dmmHeT6rJpBDWVwSxBzxzKlBvnrZdLSRJeuhHzljvZ6ccgLP/HaShgfiYrQvekRH0KUe/G1WTu/IrXsQ==} @@ -5091,6 +5381,11 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} + /mimic-fn/3.1.0: + resolution: {integrity: sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==} + engines: {node: '>=8'} + dev: false + /min-document/2.19.0: resolution: {integrity: sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=} dependencies: @@ -5397,6 +5692,10 @@ packages: resolution: {integrity: sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==} dev: true + /oauth-sign/0.9.0: + resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==} + dev: false + /object-assign/4.1.1: resolution: {integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=} engines: {node: '>=0.10.0'} @@ -5534,7 +5833,6 @@ packages: engines: {node: '>=6'} dependencies: p-try: 2.2.0 - dev: true /p-limit/3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} @@ -5543,6 +5841,13 @@ packages: yocto-queue: 0.1.0 dev: true + /p-locate/3.0.0: + resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} + engines: {node: '>=6'} + dependencies: + p-limit: 2.3.0 + dev: false + /p-locate/4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} @@ -5564,7 +5869,6 @@ packages: /p-try/2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} - dev: true /pako/1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} @@ -5637,6 +5941,11 @@ packages: pause: 0.0.1 dev: false + /path-exists/3.0.0: + resolution: {integrity: sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=} + engines: {node: '>=4'} + dev: false + /path-exists/4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -5666,12 +5975,21 @@ packages: /path-type/4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - dev: true + + /pause-stream/0.0.11: + resolution: {integrity: sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=} + dependencies: + through: 2.3.8 + dev: false /pause/0.0.1: resolution: {integrity: sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=} dev: false + /performance-now/2.1.0: + resolution: {integrity: sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=} + dev: false + /phin/2.9.3: resolution: {integrity: sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==} dev: false @@ -5679,7 +5997,6 @@ packages: /picomatch/2.3.0: resolution: {integrity: sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==} engines: {node: '>=8.6'} - dev: true /pino-std-serializers/3.2.0: resolution: {integrity: sha512-EqX4pwDPrt3MuOAAUBMU0Tk5kR/YcCM5fNPEzgCO2zJ5HfX0vbiH9HbJglnyeQsN96Kznae6MWD47pZB5avTrg==} @@ -5717,6 +6034,13 @@ packages: find-up: 4.1.0 dev: true + /pkg-up/3.1.0: + resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} + engines: {node: '>=8'} + dependencies: + find-up: 3.0.0 + dev: false + /please-upgrade-node/3.2.0: resolution: {integrity: sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==} dependencies: @@ -5811,9 +6135,16 @@ packages: forwarded: 0.2.0 ipaddr.js: 1.9.1 + /ps-tree/1.2.0: + resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==} + engines: {node: '>= 0.10'} + hasBin: true + dependencies: + event-stream: 3.3.4 + dev: false + /psl/1.8.0: resolution: {integrity: sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==} - dev: true /pump/3.0.0: resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} @@ -5837,6 +6168,11 @@ packages: side-channel: 1.0.4 dev: true + /qs/6.5.2: + resolution: {integrity: sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==} + engines: {node: '>=0.6'} + dev: false + /querystring/0.2.0: resolution: {integrity: sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=} engines: {node: '>=0.4.x'} @@ -5944,6 +6280,33 @@ packages: engines: {node: '>=8'} dev: true + /request/2.88.2: + resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==} + engines: {node: '>= 6'} + deprecated: request has been deprecated, see https://github.com/request/request/issues/3142 + dependencies: + aws-sign2: 0.7.0 + aws4: 1.11.0 + caseless: 0.12.0 + combined-stream: 1.0.8 + extend: 3.0.2 + forever-agent: 0.6.1 + form-data: 2.3.3 + har-validator: 5.1.5 + http-signature: 1.2.0 + is-typedarray: 1.0.0 + isstream: 0.1.2 + json-stringify-safe: 5.0.1 + mime-types: 2.1.32 + oauth-sign: 0.9.0 + performance-now: 2.1.0 + qs: 6.5.2 + safe-buffer: 5.2.1 + tough-cookie: 2.5.0 + tunnel-agent: 0.6.0 + uuid: 3.4.0 + dev: false + /require-directory/2.1.1: resolution: {integrity: sha1-jGStX9MNqxyXbiNE/+f3kqam30I=} engines: {node: '>=0.10.0'} @@ -5952,7 +6315,6 @@ packages: /require-from-string/2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - dev: true /resolve-cwd/3.0.0: resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} @@ -6010,7 +6372,6 @@ packages: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: queue-microtask: 1.2.3 - dev: true /run-script-webpack-plugin/0.0.11: resolution: {integrity: sha512-QmuBhiqBPmhQLpO5vMBHVTAGyoPBnrCM5gQ3IzgieiImBXiBbXcIv4kysCT1gilFNFxQk22oKQfiIhWbT/zXCw==} @@ -6187,6 +6548,11 @@ packages: engines: {node: '>=8'} dev: true + /slash/4.0.0: + resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} + engines: {node: '>=12'} + dev: false + /slice-ansi/3.0.0: resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} engines: {node: '>=8'} @@ -6306,10 +6672,32 @@ packages: dev: false optional: true + /split/0.3.3: + resolution: {integrity: sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=} + dependencies: + through: 2.3.8 + dev: false + /sprintf-js/1.0.3: resolution: {integrity: sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=} dev: true + /sshpk/1.16.1: + resolution: {integrity: sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==} + engines: {node: '>=0.10.0'} + hasBin: true + dependencies: + asn1: 0.2.4 + assert-plus: 1.0.0 + bcrypt-pbkdf: 1.0.2 + dashdash: 1.14.1 + ecc-jsbn: 0.1.2 + getpass: 0.1.7 + jsbn: 0.1.1 + safer-buffer: 2.1.2 + tweetnacl: 0.14.5 + dev: false + /stack-utils/2.0.3: resolution: {integrity: sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==} engines: {node: '>=10'} @@ -6325,6 +6713,22 @@ packages: engines: {node: '>= 0.6'} dev: false + /stream-combiner/0.0.4: + resolution: {integrity: sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=} + dependencies: + duplexer: 0.1.2 + dev: false + + /stream-wormhole/1.1.0: + resolution: {integrity: sha512-gHFfL3px0Kctd6Po0M8TzEvt3De/xu6cnRrjlfYNhwbhLPLwigI2t1nc6jrzNuaYg5C4YF78PPFuQPzRiqn9ew==} + engines: {node: '>=4.0.0'} + dev: false + + /streamsearch/0.1.2: + resolution: {integrity: sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=} + engines: {node: '>=0.8.0'} + dev: false + /string-argv/0.3.1: resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==} engines: {node: '>=0.6.19'} @@ -6639,13 +7043,20 @@ packages: engines: {node: '>=8.0'} dependencies: is-number: 7.0.0 - dev: true /toidentifier/1.0.0: resolution: {integrity: sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==} engines: {node: '>=0.6'} dev: false + /tough-cookie/2.5.0: + resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==} + engines: {node: '>=0.8'} + dependencies: + psl: 1.8.0 + punycode: 2.1.1 + dev: false + /tough-cookie/4.0.0: resolution: {integrity: sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==} engines: {node: '>=6'} @@ -6793,6 +7204,16 @@ packages: typescript: 4.4.2 dev: true + /tunnel-agent/0.6.0: + resolution: {integrity: sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /tweetnacl/0.14.5: + resolution: {integrity: sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=} + dev: false + /type-check/0.3.2: resolution: {integrity: sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=} engines: {node: '>= 0.8.0'} @@ -6860,7 +7281,6 @@ packages: /universalify/2.0.0: resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} engines: {node: '>= 10.0.0'} - dev: true /uri-js/4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -6883,6 +7303,12 @@ packages: /util-deprecate/1.0.2: resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=} + /uuid/3.4.0: + resolution: {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. + hasBin: true + dev: false + /uuid/8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true @@ -6910,6 +7336,15 @@ packages: resolution: {integrity: sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=} engines: {node: '>= 0.8'} + /verror/1.10.0: + resolution: {integrity: sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=} + engines: {'0': node >=0.6.0} + dependencies: + assert-plus: 1.0.0 + core-util-is: 1.0.2 + extsprintf: 1.3.0 + dev: false + /w3c-hr-time/1.0.2: resolution: {integrity: sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==} dependencies: @@ -7076,7 +7511,6 @@ packages: hasBin: true dependencies: isexe: 2.0.0 - dev: true /wide-align/1.1.3: resolution: {integrity: sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==} @@ -7230,3 +7664,21 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} dev: true + + /zx/4.1.1: + resolution: {integrity: sha512-KMC3s+Dk2IpAxvpgb0dKzLNE3NuFRz9LIVrJamLTpLtN1S3waiEsaRMYrc3SOJN/tT3V6elkwXg2xozHTeO83g==} + engines: {node: '>= 14.8.0'} + hasBin: true + dependencies: + '@types/fs-extra': 9.0.12 + '@types/minimist': 1.2.2 + '@types/node': 16.7.8 + '@types/node-fetch': 2.5.12 + chalk: 4.1.2 + fs-extra: 10.0.0 + globby: 12.0.2 + minimist: 1.2.5 + node-fetch: 2.6.1 + ps-tree: 1.2.0 + which: 2.0.2 + dev: false diff --git a/src/app.module.ts b/src/app.module.ts index 8467b823..ad24d9fb 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -20,6 +20,7 @@ import { SecurityMiddleware } from './common/middlewares/security.middleware' import { AnalyzeModule } from './modules/analyze/analyze.module' import { AuthModule } from './modules/auth/auth.module' import { RolesGuard } from './modules/auth/roles.guard' +import { BackupModule } from './modules/backup/backup.module' import { CategoryModule } from './modules/category/category.module' import { CommentModule } from './modules/comment/comment.module' import { ConfigsModule } from './modules/configs/configs.module' @@ -55,6 +56,7 @@ import { HelperModule } from './processors/helper/helper.module' AnalyzeModule, AuthModule, + BackupModule, CategoryModule, CommentModule, ConfigsModule, diff --git a/src/common/adapt/fastify.ts b/src/common/adapt/fastify.ts index cd20ae96..227b88f6 100644 --- a/src/common/adapt/fastify.ts +++ b/src/common/adapt/fastify.ts @@ -1,9 +1,17 @@ import { FastifyAdapter } from '@nestjs/platform-fastify' - +import FastifyMultipart from 'fastify-multipart' export const fastifyApp: FastifyAdapter = new FastifyAdapter({ trustProxy: true, }) +fastifyApp.register(FastifyMultipart, { + limits: { + fields: 10, // Max number of non-file fields + fileSize: 1024 * 1024 * 6, // limit size 6M + files: 5, // Max number of file fields + }, +}) + fastifyApp.getInstance().addHook('onRequest', (request, reply, done) => { const origin = request.headers.origin if (!origin) { diff --git a/src/common/decorator/http.decorator.ts b/src/common/decorator/http.decorator.ts index 695d9703..9c80854f 100644 --- a/src/common/decorator/http.decorator.ts +++ b/src/common/decorator/http.decorator.ts @@ -1,6 +1,8 @@ -import { SetMetadata } from '@nestjs/common' +import { applyDecorators, SetMetadata } from '@nestjs/common' +import { ApiBody, ApiConsumes } from '@nestjs/swagger' import { HTTP_RES_TRANSFORM_PAGINATE } from '~/constants/meta.constant' - +import * as SYSTEM from '~/constants/system.constant' +import { FileUploadDto } from '~/shared/dto/file.dto' export const Paginator: MethodDecorator = ( target, key, @@ -8,3 +10,34 @@ export const Paginator: MethodDecorator = ( ) => { SetMetadata(HTTP_RES_TRANSFORM_PAGINATE, true)(descriptor.value) } + +/** + * @description 跳过响应体处理 + */ +export const Bypass: MethodDecorator = ( + target, + key, + descriptor: PropertyDescriptor, +) => { + SetMetadata(SYSTEM.RESPONSE_PASSTHROUGH_METADATA, true)(descriptor.value) +} + +export declare interface FileDecoratorProps { + description: string +} + +export function FileUpload({ description }: FileDecoratorProps) { + return applyDecorators( + ApiConsumes('multipart/form-data'), + ApiBody({ + description, + type: FileUploadDto, + }), + ) +} + +export const HTTPDecorators = { + Paginator, + Bypass, + FileUpload, +} diff --git a/src/common/guard/spider.guard.ts b/src/common/guard/spider.guard.ts index d05b99e7..803ef29b 100644 --- a/src/common/guard/spider.guard.ts +++ b/src/common/guard/spider.guard.ts @@ -1,12 +1,8 @@ -/* - * @Author: Innei - * @Date: 2020-04-30 19:09:37 - * @LastEditTime: 2020-07-08 21:35:06 - * @LastEditors: Innei - * @FilePath: /mx-server/src/core/guards/spider.guard.ts - * @Coding with Love +/** + * @module common/guard/spider.guard + * @description 禁止爬虫的守卫 + * @author Innei */ - import { CanActivate, ExecutionContext, diff --git a/src/common/interceptors/cache.interceptor.ts b/src/common/interceptors/cache.interceptor.ts index 2a1ee31c..766ae6e5 100644 --- a/src/common/interceptors/cache.interceptor.ts +++ b/src/common/interceptors/cache.interceptor.ts @@ -3,6 +3,7 @@ * @file 缓存拦截器 * @module interceptor/cache * @author Surmon + * @author Innei */ import { diff --git a/src/common/interceptors/response.interceptors.ts b/src/common/interceptors/response.interceptors.ts index 85882159..2045a69b 100644 --- a/src/common/interceptors/response.interceptors.ts +++ b/src/common/interceptors/response.interceptors.ts @@ -16,8 +16,8 @@ import { Observable } from 'rxjs' import { map } from 'rxjs/operators' import snakecaseKeys from 'snakecase-keys' import { HTTP_RES_TRANSFORM_PAGINATE } from '~/constants/meta.constant' +import * as SYSTEM from '~/constants/system.constant' import { Paginator } from '~/shared/model/base.model' - export interface Response { data: T } @@ -29,6 +29,17 @@ export class ResponseInterceptor implements NestInterceptor> { context: ExecutionContext, next: CallHandler, ): Observable> { + const handler = context.getHandler() + + // 跳过 bypass 装饰的请求 + const bypass = this.reflector.get( + SYSTEM.RESPONSE_PASSTHROUGH_METADATA, + handler, + ) + if (bypass) { + return next.handle() + } + const reorganize = (data) => { if (!data) { throw new UnprocessableEntityException('数据丢失了(。 ́︿ ̀。)') @@ -37,13 +48,12 @@ export class ResponseInterceptor implements NestInterceptor> { ? { ...data } : { data } } - const handler = context.getHandler() return next.handle().pipe( map((data) => { if (typeof data === 'undefined') { context.switchToHttp().getResponse().status(204) - return + return data } // 分页转换 if (this.reflector.get(HTTP_RES_TRANSFORM_PAGINATE, handler)) { diff --git a/src/common/middlewares/analyze.middleware.ts b/src/common/middlewares/analyze.middleware.ts index 2dca143d..3d65e3bb 100644 --- a/src/common/middlewares/analyze.middleware.ts +++ b/src/common/middlewares/analyze.middleware.ts @@ -5,7 +5,7 @@ import { IncomingMessage, ServerResponse } from 'http' import { InjectModel } from 'nestjs-typegoose' import { UAParser } from 'ua-parser-js' import { RedisKeys } from '~/constants/cache.constant' -import { localBotListDataFilePath } from '~/constants/path.constant' +import { LOCAL_BOT_LIST_DATA_FILE_PATH } from '~/constants/path.constant' import { AnalyzeModel } from '~/modules/analyze/analyze.model' import { OptionModel } from '~/modules/configs/configs.model' import { CacheService } from '~/processors/cache/cache.service' @@ -40,7 +40,7 @@ export class AnalyzeMiddleware implements NestMiddleware { try { return this.pickPattern2Regexp( JSON.parse( - readFileSync(localBotListDataFilePath, { + readFileSync(LOCAL_BOT_LIST_DATA_FILE_PATH, { encoding: 'utf-8', }), ), diff --git a/src/constants/path.constant.ts b/src/constants/path.constant.ts index 470935e6..2232d9bc 100644 --- a/src/constants/path.constant.ts +++ b/src/constants/path.constant.ts @@ -12,4 +12,8 @@ export const DATA_DIR = isDev export const LOGGER_DIR = join(DATA_DIR, 'log') -export const localBotListDataFilePath = join(DATA_DIR, 'bot_list.json') +export const LOCAL_BOT_LIST_DATA_FILE_PATH = join(DATA_DIR, 'bot_list.json') + +export const BACKUP_DIR = !isDev + ? join(DATA_DIR, 'backup') + : join(TEMP_DIR, 'backup') diff --git a/src/constants/system.constant.ts b/src/constants/system.constant.ts index 7db35dd0..ad5b58d3 100644 --- a/src/constants/system.constant.ts +++ b/src/constants/system.constant.ts @@ -1,2 +1,4 @@ export const HTTP_ADAPTER_HOST = 'HttpAdapterHost' export const REFLECTOR = 'Reflector' + +export const RESPONSE_PASSTHROUGH_METADATA = '__responsePassthrough__' diff --git a/src/main.ts b/src/main.ts index 55760364..7c183653 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,11 +7,13 @@ import { AppModule } from './app.module' import { fastifyApp } from './common/adapt/fastify' import { SpiderGuard } from './common/guard/spider.guard' import { LoggingInterceptor } from './common/interceptors/logging.interceptor' -import { isDev } from './utils/index.util' -// const PORT = parseInt(process.env.PORT) || 2333 +import './utils/global.util' +import './zx.global-fix' + const PORT = 2333 const APIVersion = 1 const Origin = CROSS_DOMAIN.allowedOrigins + declare const module: any async function bootstrap() { diff --git a/src/modules/backup/backup.controller.ts b/src/modules/backup/backup.controller.ts new file mode 100644 index 00000000..568b0871 --- /dev/null +++ b/src/modules/backup/backup.controller.ts @@ -0,0 +1,106 @@ +import { + Controller, + Delete, + Get, + Param, + Patch, + Post, + Query, + Req, + Res, + Scope, + UnprocessableEntityException, +} from '@nestjs/common' +import { ApiProperty, ApiResponseProperty } from '@nestjs/swagger' +import { FastifyReply, FastifyRequest } from 'fastify' +import { Readable } from 'stream' +import { Auth } from '~/common/decorator/auth.decorator' +import { HTTPDecorators } from '~/common/decorator/http.decorator' +import { ApiName } from '~/common/decorator/openapi.decorator' +import { CronService } from '~/processors/helper/helper.cron.service' +import { UploadService } from '~/processors/helper/helper.upload.service' +import { BackupService } from './backup.service' + +@Controller({ path: 'backups', scope: Scope.REQUEST }) +@ApiName +@Auth() +export class BackupController { + constructor( + private readonly backupService: BackupService, + private readonly uploadService: UploadService, + private readonly cronService: CronService, + ) {} + + @Get('/new') + @ApiResponseProperty({ type: 'string', format: 'binary' }) + @HTTPDecorators.Bypass + async createNewBackup(@Res() res: FastifyReply) { + const buffer = await this.cronService.backupDB({ uploadCOS: false }) + const stream = new Readable() + + stream.push(buffer) + stream.push(null) + res + .header( + 'Content-Disposition', + `attachment; filename="backup-${new Date().toISOString()}.zip"`, + ) + .type('application/zip') + .send(stream) + } + + @Get('/') + async get() { + return this.backupService.list() + } + + @HTTPDecorators.Bypass + @Get('/:dirname') + async download(@Param('dirname') dirname: string, @Res() res: FastifyReply) { + res.send(this.backupService.getFileStream(dirname)) + } + + @Post(['/rollback/', '/']) + @ApiProperty({ description: '上传备份恢复' }) + @HTTPDecorators.FileUpload({ description: 'Upload backup and restore' }) + async uploadAndRestore(@Req() req: FastifyRequest) { + const data = await this.uploadService.validMultipartField(req) + const { mimetype } = data + if (mimetype !== 'application/zip') { + throw new UnprocessableEntityException('备份格式必须为 application/zip') + } + + await this.backupService.saveTempBackupByUpload(await data.toBuffer()) + + return + } + @Patch(['/rollback/:dirname', '/:dirname']) + async rollback(@Param('dirname') dirname: string) { + if (!dirname) { + throw new UnprocessableEntityException('参数有误') + } + + return await this.backupService.rollbackTo(dirname) + } + + @Delete('/') + async deleteBackups(@Query('files') files: string) { + if (!files) { + return + } + const _files = files.split(',') + for await (const f of _files) { + await this.backupService.deleteBackup(f) + } + return + } + + @Delete('/:filename') + async delete(@Param('filename') filename: string) { + if (!filename) { + return + } + await this.backupService.deleteBackup(filename) + return + } +} diff --git a/src/modules/backup/backup.module.ts b/src/modules/backup/backup.module.ts new file mode 100644 index 00000000..b380c390 --- /dev/null +++ b/src/modules/backup/backup.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common' +import { BackupController } from './backup.controller' +import { BackupService } from './backup.service' + +@Module({ + controllers: [BackupController], + providers: [BackupService], + exports: [BackupService], +}) +export class BackupModule {} diff --git a/src/modules/backup/backup.service.ts b/src/modules/backup/backup.service.ts new file mode 100644 index 00000000..37589489 --- /dev/null +++ b/src/modules/backup/backup.service.ts @@ -0,0 +1,151 @@ +import { + BadRequestException, + Injectable, + InternalServerErrorException, + Logger, +} from '@nestjs/common' +import { + existsSync, + readdirSync, + readFileSync, + rmSync, + statSync, + writeFileSync, +} from 'fs' +import mkdirp from 'mkdirp' +import { resolve } from 'path' +import { join } from 'path/posix' +import { Readable } from 'stream' +import { BACKUP_DIR } from '~/constants/path.constant' +import { AdminEventsGateway } from '~/processors/gateway/admin/events.gateway' +import { EventTypes } from '~/processors/gateway/events.types' +import { getFolderSize } from '~/utils/system.util' + +@Injectable() +export class BackupService { + private logger: Logger + + constructor(private readonly adminGateway: AdminEventsGateway) { + this.logger = new Logger(BackupService.name) + } + async list() { + const backupPath = BACKUP_DIR + if (!existsSync(backupPath)) { + return [] + } + const backupFilenames = readdirSync(backupPath) + const backups = [] + + for (const filename of backupFilenames) { + const path = resolve(backupPath, filename) + if (!statSync(path).isDirectory()) { + continue + } + backups.push({ + filename, + path, + }) + } + return Promise.all( + backups.map(async (item) => { + const { path } = item + const { stdout } = await getFolderSize(path) + delete item.path + return { ...item, size: stdout } + }), + ) + } + + getFileStream(dirname: string) { + const path = this.checkBackupExist(dirname) + const stream = new Readable() + + stream.push(readFileSync(path)) + stream.push(null) + + return stream + } + + checkBackupExist(dirname: string) { + const path = join(BACKUP_DIR, dirname, 'backup-' + dirname + '.zip') + if (!existsSync(path)) { + throw new BadRequestException('文件不存在') + } + return path + } + + async saveTempBackupByUpload(buffer: Buffer) { + const tempDirPath = '/tmp/mx-space/backup' + const tempBackupPath = join(tempDirPath, 'backup.zip') + mkdirp.sync(tempDirPath) + writeFileSync(tempBackupPath, buffer) + + try { + cd(tempDirPath) + await $`unzip backup.zip` + await $`mongorestore -h ${ + process.env.DB_URL || '127.0.0.1' + } -d mx-space ./mx-space --drop >/dev/null 2>&1` + + this.logger.debug('恢复成功') + await this.adminGateway.broadcast( + EventTypes.CONTENT_REFRESH, + 'restore_done', + ) + } catch (e) { + const logDir = '/tmp/mx-space/log' + mkdirp.sync(logDir) + writeFileSync(logDir, e.message, { encoding: 'utf-8', flag: 'a+' }) + throw new InternalServerErrorException(e.message) + } finally { + rmSync(tempDirPath, { recursive: true }) + } + } + + async rollbackTo(dirname: string) { + const bakFilePath = this.checkBackupExist(dirname) // zip file path + const dirPath = join(BACKUP_DIR, dirname) + try { + if (existsSync(join(join(dirPath, 'mx-space')))) { + rmSync(join(dirPath, 'mx-space'), { recursive: true }) + } + + cd(dirPath) + await $`unzip ${bakFilePath}` + } catch { + throw new InternalServerErrorException('服务端 unzip 命令未找到') + } + try { + if (!existsSync(join(dirPath, 'mx-space'))) { + throw new InternalServerErrorException('备份文件错误, 目录不存在') + } + + cd(dirPath) + await $`mongorestore -h ${ + process.env.DB_URL || '127.0.0.1' + } -d mx-space ./mx-space --drop >/dev/null 2>&1` + } catch (e) { + this.logger.error(e) + throw e + } finally { + try { + rmSync(join(dirPath, 'mx-space'), { recursive: true }) + } catch {} + } + + await this.adminGateway.broadcast( + EventTypes.CONTENT_REFRESH, + 'restore_done', + ) + } + + async deleteBackup(filename) { + const path = join(BACKUP_DIR, filename) + if (!existsSync(path)) { + throw new BadRequestException('文件不存在') + } + + rmSync(path, { recursive: true }) + return true + } +} diff --git a/src/processors/helper/helper.cron.service.ts b/src/processors/helper/helper.cron.service.ts index 602f1315..8819ff04 100644 --- a/src/processors/helper/helper.cron.service.ts +++ b/src/processors/helper/helper.cron.service.ts @@ -1,15 +1,31 @@ import { Injectable, Logger } from '@nestjs/common' import { Cron, CronExpression } from '@nestjs/schedule' -import { writeFileSync } from 'fs' -import { localBotListDataFilePath } from '~/constants/path.constant' +import COS from 'cos-nodejs-sdk-v5' +import dayjs from 'dayjs' +import { existsSync, readFileSync, writeFileSync } from 'fs' +import mkdirp from 'mkdirp' +import { join } from 'path' +import { $, cd } from 'zx' +import { + BACKUP_DIR, + LOCAL_BOT_LIST_DATA_FILE_PATH, +} from '~/constants/path.constant' +import { ConfigsService } from '~/modules/configs/configs.service' +import { isDev } from '~/utils/index.util' import { HttpService } from './helper.http.service' @Injectable() export class CronService { private logger: Logger - constructor(private readonly http: HttpService) { + constructor( + private readonly http: HttpService, + private readonly configs: ConfigsService, + ) { this.logger = new Logger(CronService.name) } - + /** + * + * @description 每天凌晨更新 Bot 列表 + */ @Cron(CronExpression.EVERY_WEEK) async updateBotList() { try { @@ -17,7 +33,7 @@ export class CronService { 'https://cdn.jsdelivr.net/gh/atmire/COUNTER-Robots@master/COUNTER_Robots_list.json', ) - writeFileSync(localBotListDataFilePath, JSON.stringify(json), { + writeFileSync(LOCAL_BOT_LIST_DATA_FILE_PATH, JSON.stringify(json), { encoding: 'utf-8', flag: 'w+', }) @@ -27,4 +43,81 @@ export class CronService { this.logger.warn('更新 Bot 列表错误') } } + + @Cron(CronExpression.EVERY_DAY_AT_1AM, { name: 'backup' }) + async backupDB({ uploadCOS = true }: { uploadCOS?: boolean } = {}) { + if (!this.configs.get('backupOptions').enable) { + return + } + this.logger.log('--> 备份数据库中') + + const dateDir = this.nowStr + + const backupDirPath = join(BACKUP_DIR, dateDir) + mkdirp.sync(backupDirPath) + try { + await $`mongodump -h 127.0.0.1 -d mx-space -o ${backupDirPath} >/dev/null 2>&1` + cd(backupDirPath) + await $`zip -r backup-${dateDir} mx-space/* && rm -r mx-space` + + this.logger.log('--> 备份成功') + } catch (e) { + if (isDev) { + console.log(e) + } + this.logger.error( + '--> 备份失败, 请确保已安装 zip 或 mongo-tools, mongo-tools 的版本需要与 mongod 版本一致', + ) + return + } + + // 开始上传 COS + process.nextTick(() => { + if (!uploadCOS) { + return + } + const backupOptions = this.configs.get('backupOptions') + if ( + !backupOptions.Bucket || + !backupOptions.Region || + !backupOptions.SecretId || + !backupOptions.SecretKey + ) { + return + } + const backupFilePath = join(backupDirPath, 'backup-' + dateDir + '.zip') + + if (!existsSync(backupFilePath)) { + this.logger.warn('文件不存在, 无法上传到 COS') + return + } + this.logger.log('--> 开始上传到 COS') + const cos = new COS({ + SecretId: backupOptions.SecretId, + SecretKey: backupOptions.SecretKey, + }) + // 分片上传 + cos.sliceUploadFile( + { + Bucket: backupOptions.Bucket, + Region: backupOptions.Region, + Key: `backup-${dateDir}.zip`, + FilePath: backupFilePath, + }, + (err) => { + if (!err) { + this.logger.log('--> 上传成功') + } else { + this.logger.error('--> 上传失败了' + err) + } + }, + ) + }) + + return readFileSync(join(backupDirPath, 'backup-' + dateDir + '.zip')) + } + + private get nowStr() { + return dayjs().format('YYYY-MM-DD-HH:mm:ss') + } } diff --git a/src/processors/helper/helper.module.ts b/src/processors/helper/helper.module.ts index e981b554..7c776fef 100644 --- a/src/processors/helper/helper.module.ts +++ b/src/processors/helper/helper.module.ts @@ -5,6 +5,7 @@ import { CronService } from './helper.cron.service' import { EmailService } from './helper.email.service' import { HttpService } from './helper.http.service' import { ImageService } from './helper.image.service' +import { UploadService } from './helper.upload.service' const providers: Provider[] = [ EmailService, @@ -12,6 +13,7 @@ const providers: Provider[] = [ ImageService, CronService, CountingService, + UploadService, ] @Module({ diff --git a/src/processors/helper/helper.upload.service.ts b/src/processors/helper/helper.upload.service.ts new file mode 100644 index 00000000..b2c975bb --- /dev/null +++ b/src/processors/helper/helper.upload.service.ts @@ -0,0 +1,20 @@ +import { BadRequestException, Injectable } from '@nestjs/common' +import { FastifyRequest } from 'fastify' +import { MultipartFile } from 'fastify-multipart' +@Injectable() +export class UploadService { + public async validMultipartField( + req: FastifyRequest, + ): Promise { + const data = await req.file() + + if (!data) { + throw new BadRequestException('仅供上传文件!') + } + if (data.fieldname != 'file') { + throw new BadRequestException('字段必须为 file') + } + + return data + } +} diff --git a/src/shared/dto/file.dto.ts b/src/shared/dto/file.dto.ts new file mode 100644 index 00000000..525e4b0f --- /dev/null +++ b/src/shared/dto/file.dto.ts @@ -0,0 +1,6 @@ +import { ApiProperty } from '@nestjs/swagger' + +export class FileUploadDto { + @ApiProperty({ type: 'string', format: 'binary' }) + file: any +} diff --git a/src/utils/global.util.ts b/src/utils/global.util.ts new file mode 100644 index 00000000..f7037ca8 --- /dev/null +++ b/src/utils/global.util.ts @@ -0,0 +1,5 @@ +import { isDev } from './index.util' + +Object.assign(globalThis, { + isDev: isDev, +}) diff --git a/src/utils/ip.util.ts b/src/utils/ip.util.ts index 8a5534ce..8e792723 100644 --- a/src/utils/ip.util.ts +++ b/src/utils/ip.util.ts @@ -1,12 +1,7 @@ -/* - * @Author: Innei - * @Date: 2020-05-10 15:31:44 - * @LastEditTime: 2020-07-08 21:42:06 - * @LastEditors: Innei - * @FilePath: /mx-server/src/utils/ip.ts - * @Coding with Love +/** + * @module utils/ip + * @description IP utility functions */ - import type { FastifyRequest } from 'fastify' import { IncomingMessage } from 'http' export const getIp = (request: FastifyRequest | IncomingMessage) => { diff --git a/src/utils/system.util.ts b/src/utils/system.util.ts new file mode 100644 index 00000000..e537e042 --- /dev/null +++ b/src/utils/system.util.ts @@ -0,0 +1,3 @@ +export function getFolderSize(folderPath: string) { + return $`du -shc ${folderPath} | head -n 1 | cut -f1` +} diff --git a/src/zx.global-fix.ts b/src/zx.global-fix.ts new file mode 100644 index 00000000..113d7073 --- /dev/null +++ b/src/zx.global-fix.ts @@ -0,0 +1,12 @@ +// @ts-nocheck +import { registerGlobals } from 'zx' +import { isDev } from './utils/index.util' + +// FIX: zx 4.1.1 import 'zx/globals' error +// ERROR: Package subpath './globals.mjs' is not defined by "exports" in /Users/xiaoxun/github/innei-repo/mx-space/server-next/node_modules/zx/package.json +// FIXME: registerGlobals manally +registerGlobals() + +/// config for zx + +$.verbose = isDev diff --git a/tsconfig.json b/tsconfig.json index 34aa8064..53c06266 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,4 +24,8 @@ ] }, }, + "exclude": [ + "dist", + "tmp" + ] } \ No newline at end of file