From a9d0fbae6bab655f0a1a07eeb0e2babf151a64cd Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Sun, 4 Jun 2023 00:50:20 +0800 Subject: [PATCH 001/131] feat: add ESlint setting --- .eslintignore | 2 + .eslintrc.yml | 12 + _helpers.js | 8 +- app.js | 8 +- package-lock.json | 5169 ++++++++++++++++++--------------------------- package.json | 7 +- 6 files changed, 2067 insertions(+), 3139 deletions(-) create mode 100644 .eslintignore create mode 100644 .eslintrc.yml diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000000..ceaa7dc6e6 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +/node_modules/* +/tests/* \ No newline at end of file diff --git a/.eslintrc.yml b/.eslintrc.yml new file mode 100644 index 0000000000..b7d671868f --- /dev/null +++ b/.eslintrc.yml @@ -0,0 +1,12 @@ +env: + browser: true + commonjs: true + es2021: true +extends: + - standard +parserOptions: + ecmaVersion: 12 +rules: + arrow-parens: + - warn + - as-needed \ No newline at end of file diff --git a/_helpers.js b/_helpers.js index b7a88770e8..5973f3d100 100644 --- a/_helpers.js +++ b/_helpers.js @@ -1,8 +1,8 @@ -function getUser(req) { - return req.user; +function getUser (req) { + return req.user } module.exports = { - getUser, -}; \ No newline at end of file + getUser +} diff --git a/app.js b/app.js index 842c6bd679..4f08f5f645 100644 --- a/app.js +++ b/app.js @@ -1,13 +1,13 @@ const express = require('express') -const helpers = require('./_helpers'); +const helpers = require('./_helpers') const app = express() const port = 3000 // use helpers.getUser(req) to replace req.user -function authenticated(req, res, next){ - // passport.authenticate('jwt', { ses... -}; +// function authenticated (req, res, next) { +// // passport.authenticate('jwt', { ses... +// } app.get('/', (req, res) => res.send('Hello World!')) app.listen(port, () => console.log(`Example app listening on port ${port}!`)) diff --git a/package-lock.json b/package-lock.json index aa65e2e434..2307624fe6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,3074 +1,169 @@ { "name": "test", "version": "1.0.0", - "lockfileVersion": 2, + "lockfileVersion": 1, "requires": true, - "packages": { - "": { - "name": "test", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "bcrypt-nodejs": "0.0.3", - "body-parser": "^1.18.3", - "chai": "^4.2.0", - "connect-flash": "^0.1.1", - "express": "^4.16.4", - "express-session": "^1.15.6", - "faker": "^4.1.0", - "method-override": "^3.0.0", - "mocha": "^6.0.2", - "mysql2": "^1.6.4", - "passport": "^0.4.0", - "passport-local": "^1.0.0", - "sequelize": "^6.18.0", - "sequelize-cli": "^5.5.0", - "sinon": "^10.0.0", - "sinon-chai": "^3.3.0" - }, - "devDependencies": { - "proxyquire": "^2.1.3", - "sequelize-test-helpers": "^1.4.2", - "supertest": "^3.3.0" - } - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/@sinonjs/samsam": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.3.1.tgz", - "integrity": "sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg==", - "dependencies": { - "@sinonjs/commons": "^1.6.0", - "lodash.get": "^4.4.2", - "type-detect": "^4.0.8" - } - }, - "node_modules/@sinonjs/text-encoding": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", - "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==" - }, - "node_modules/@types/debug": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", - "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", - "dependencies": { - "@types/ms": "*" - } - }, - "node_modules/@types/ms": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", - "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" - }, - "node_modules/@types/node": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.24.tgz", - "integrity": "sha512-aveCYRQbgTH9Pssp1voEP7HiuWlD2jW2BO56w+bVrJn04i61yh6mRfoKO6hEYQD9vF+W8Chkwc6j1M36uPkx4g==" - }, - "node_modules/@types/validator": { - "version": "13.7.2", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.2.tgz", - "integrity": "sha512-KFcchQ3h0OPQgFirBRPZr5F/sVjxZsOrQHedj3zi8AH3Zv/hOLx2OLR4hxR5HcfoU+33n69ZuOfzthKVdMoTiw==" - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ansi-colors": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", - "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "engines": { - "node": "*" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/bcrypt-nodejs": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/bcrypt-nodejs/-/bcrypt-nodejs-0.0.3.tgz", - "integrity": "sha1-xgkX8m3CNWYVZsaBBhwwPCsohCs=", - "deprecated": "bcrypt-nodejs is no longer actively maintained. Please use bcrypt or bcryptjs. See https://github.com/kelektiv/node.bcrypt.js/wiki/bcrypt-vs-brypt.js to learn more about these two options" - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, - "node_modules/body-parser": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", - "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.10.3", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/chai": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", - "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "engines": { - "node": "*" - } - }, - "node_modules/cli-color": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz", - "integrity": "sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w==", - "dependencies": { - "ansi-regex": "^2.1.1", - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "memoizee": "^0.4.14", - "timers-ext": "^0.1.5" - } - }, - "node_modules/cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dependencies": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "engines": { - "node": ">=6" - } - }, - "node_modules/cliui/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "node_modules/config-chain": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", - "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "node_modules/connect-flash": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/connect-flash/-/connect-flash-0.1.1.tgz", - "integrity": "sha1-2GMPJtlaf4UfmVax6MxnMvO2qjA=", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "node_modules/cookiejar": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", - "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==", - "dev": true - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true - }, - "node_modules/d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dependencies": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/denque": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", - "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dottie": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.2.tgz", - "integrity": "sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg==" - }, - "node_modules/editorconfig": { - "version": "0.15.3", - "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", - "integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==", - "dependencies": { - "commander": "^2.19.0", - "lru-cache": "^4.1.5", - "semver": "^5.6.0", - "sigmund": "^1.0.1" - }, - "bin": { - "editorconfig": "bin/editorconfig" - } - }, - "node_modules/editorconfig/node_modules/lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "node_modules/editorconfig/node_modules/yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-abstract": { - "version": "1.19.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.5.tgz", - "integrity": "sha512-Aa2G2+Rd3b6kxEUKTF4TaW67czBLyAv3z7VOhYRU50YBx+bbsYZ9xQP4lMNazePuFlybXI0V4MruPos7qUo5fA==", - "dependencies": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-abstract/node_modules/object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es5-ext": { - "version": "0.10.60", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.60.tgz", - "integrity": "sha512-jpKNXIt60htYG59/9FGf2PYT3pwMpnEbNKysU+k/4FGwyGtMotOvcZOuW+EmXXYASRqYSXQfGL5cVIthOTgbkg==", - "hasInstallScript": true, - "dependencies": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "next-tick": "^1.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "dependencies": { - "d": "^1.0.1", - "ext": "^1.1.2" - } - }, - "node_modules/es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", - "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "node_modules/express": { - "version": "4.17.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", - "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.19.2", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.4.2", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.9.7", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.17.2", - "serve-static": "1.14.2", - "setprototypeof": "1.2.0", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express-session": { - "version": "1.17.2", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.2.tgz", - "integrity": "sha512-mPcYcLA0lvh7D4Oqr5aNJFMtBMKPLl++OKKxkHzZ0U0oDq1rpKBnkR5f5vCHR26VeArlTOEF9td4x5IjICksRQ==", - "dependencies": { - "cookie": "0.4.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-headers": "~1.0.2", - "parseurl": "~1.3.3", - "safe-buffer": "5.2.1", - "uid-safe": "~2.1.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/express-session/node_modules/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.8.1", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.9.7", - "raw-body": "2.4.3", - "type-is": "~1.6.18" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/express/node_modules/raw-body": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", - "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "1.8.1", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/ext": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", - "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", - "dependencies": { - "type": "^2.5.0" - } - }, - "node_modules/ext/node_modules/type": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.6.0.tgz", - "integrity": "sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ==" - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "node_modules/faker": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/faker/-/faker-4.1.0.tgz", - "integrity": "sha1-HkW7vsxndLPBlfrSg1EJxtdIzD8=" - }, - "node_modules/fill-keys": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", - "integrity": "sha1-mo+jb06K1jTjv2tPPIiCVRRS6yA=", - "dev": true, - "dependencies": { - "is-object": "~1.0.1", - "merge-descriptors": "~1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/flat": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz", - "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", - "dependencies": { - "is-buffer": "~2.0.3" - }, - "bin": { - "flat": "cli.js" - } - }, - "node_modules/form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/formidable": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz", - "integrity": "sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==", - "deprecated": "Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau", - "dev": true, - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "node_modules/generate-function": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", - "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", - "dependencies": { - "is-property": "^1.0.2" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", - "engines": { - "node": "*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" - }, - "node_modules/growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "engines": { - "node": ">=4.x" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "engines": { - "node": ">=4" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dependencies": { - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "bin": { - "he": "bin/he" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/inflection": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.13.2.tgz", - "integrity": "sha512-cmZlljCRTBFouT8UzMzrGcVEvkv6D/wBdcdKG7J1QH5cXjtU75Dm+P27v9EKu/Y43UYyCJd1WC4zLebRrC8NBw==", - "engines": [ - "node >= 0.4.0" - ] - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" - }, - "node_modules/internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", - "dependencies": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "engines": { - "node": ">=4" - } - }, - "node_modules/is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", - "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "engines": { - "node": ">=4" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", - "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-promise": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" - }, - "node_modules/is-property": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" - }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "node_modules/js-beautify": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.3.tgz", - "integrity": "sha512-f1ra8PHtOEu/70EBnmiUlV8nJePS58y9qKjl4JHfYWlFH6bo7ogZBz//FAZp7jDuXtYnGYKymZPlrg2I/9Zo4g==", - "dependencies": { - "config-chain": "^1.1.13", - "editorconfig": "^0.15.3", - "glob": "^7.1.3", - "nopt": "^5.0.0" - }, - "bin": { - "css-beautify": "js/bin/css-beautify.js", - "html-beautify": "js/bin/html-beautify.js", - "js-beautify": "js/bin/js-beautify.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/just-extend": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", - "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==" - }, - "node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" - }, - "node_modules/log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "dependencies": { - "chalk": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, - "node_modules/loupe": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", - "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", - "dependencies": { - "get-func-name": "^2.0.0" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/lru-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", - "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", - "dependencies": { - "es5-ext": "~0.10.2" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memoizee": { - "version": "0.4.15", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", - "integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==", - "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.53", - "es6-weak-map": "^2.0.3", - "event-emitter": "^0.3.5", - "is-promise": "^2.2.2", - "lru-queue": "^0.1.0", - "next-tick": "^1.1.0", - "timers-ext": "^0.1.7" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "node_modules/method-override": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/method-override/-/method-override-3.0.0.tgz", - "integrity": "sha512-IJ2NNN/mSl9w3kzWB92rcdHpz+HjkxhDJWNDBqSlas+zQdP8wBiJzITPg08M/k2uVvMow7Sk41atndNtt/PHSA==", - "dependencies": { - "debug": "3.1.0", - "methods": "~1.1.2", - "parseurl": "~1.3.2", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/method-override/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" - }, - "node_modules/mkdirp": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", - "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", - "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mocha": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.3.tgz", - "integrity": "sha512-0R/3FvjIGH3eEuG17ccFPk117XL2rWxatr81a57D+r/x2uTYZRbdZ4oVidEUMh2W2TJDa7MdAb12Lm2/qrKajg==", - "dependencies": { - "ansi-colors": "3.2.3", - "browser-stdout": "1.3.1", - "debug": "3.2.6", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "find-up": "3.0.0", - "glob": "7.1.3", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "3.13.1", - "log-symbols": "2.2.0", - "minimatch": "3.0.4", - "mkdirp": "0.5.4", - "ms": "2.1.1", - "node-environment-flags": "1.0.5", - "object.assign": "4.1.0", - "strip-json-comments": "2.0.1", - "supports-color": "6.0.0", - "which": "1.3.1", - "wide-align": "1.1.3", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", - "yargs-unparser": "1.6.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/mocha/node_modules/debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/mocha/node_modules/ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - }, - "node_modules/module-not-found-error": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz", - "integrity": "sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=", - "dev": true - }, - "node_modules/moment": { - "version": "2.29.3", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz", - "integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==", - "engines": { - "node": "*" - } - }, - "node_modules/moment-timezone": { - "version": "0.5.34", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.34.tgz", - "integrity": "sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg==", - "dependencies": { - "moment": ">= 2.9.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/mysql2": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-1.7.0.tgz", - "integrity": "sha512-xTWWQPjP5rcrceZQ7CSTKR/4XIDeH/cRkNH/uzvVGQ7W5c7EJ0dXeJUusk7OKhIoHj7uFKUxDVSCfLIl+jluog==", - "dependencies": { - "denque": "^1.4.1", - "generate-function": "^2.3.1", - "iconv-lite": "^0.5.0", - "long": "^4.0.0", - "lru-cache": "^5.1.1", - "named-placeholders": "^1.1.2", - "seq-queue": "^0.0.5", - "sqlstring": "^2.3.1" - }, - "engines": { - "node": ">= 8.0" - } - }, - "node_modules/mysql2/node_modules/iconv-lite": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz", - "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/named-placeholders": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.2.tgz", - "integrity": "sha512-wiFWqxoLL3PGVReSZpjLVxyJ1bRqe+KKJVbr4hGs1KWfTZTQyezHFBbuKj9hsizHyGV2ne7EMjHdxEGAybD5SA==", - "dependencies": { - "lru-cache": "^4.1.3" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/named-placeholders/node_modules/lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "node_modules/named-placeholders/node_modules/yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" - }, - "node_modules/nise": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/nise/-/nise-4.1.0.tgz", - "integrity": "sha512-eQMEmGN/8arp0xsvGoQ+B1qvSkR73B1nWSCh7nOt5neMCtwcQVYQGdzQMhcNscktTsWB54xnlSQFzOAPJD8nXA==", - "dependencies": { - "@sinonjs/commons": "^1.7.0", - "@sinonjs/fake-timers": "^6.0.0", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "path-to-regexp": "^1.7.0" - } - }, - "node_modules/nise/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "node_modules/nise/node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dependencies": { - "isarray": "0.0.1" - } - }, - "node_modules/node-environment-flags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", - "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", - "dependencies": { - "object.getownpropertydescriptors": "^2.0.3", - "semver": "^5.7.0" - } - }, - "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/object-inspect": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", - "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dependencies": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.getownpropertydescriptors": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.3.tgz", - "integrity": "sha512-VdDoCwvJI4QdC6ndjpqFmoL3/+HxffFBbcJzKi5hwLLqqx3mdbedRpfZDdK0SrOSauj8X4GzBvnDZl4vTN7dOw==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/passport": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/passport/-/passport-0.4.1.tgz", - "integrity": "sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg==", - "dependencies": { - "passport-strategy": "1.x.x", - "pause": "0.0.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/passport-local": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", - "integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=", - "dependencies": { - "passport-strategy": "1.x.x" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/passport-strategy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", - "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "engines": { - "node": ">=4" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "engines": { - "node": "*" - } - }, - "node_modules/pause": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", - "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" - }, - "node_modules/pg-connection-string": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", - "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "node_modules/proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxyquire": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-2.1.3.tgz", - "integrity": "sha512-BQWfCqYM+QINd+yawJz23tbBM40VIGXOdDw3X344KcclI/gtBbdWF6SlQ4nK/bYhF9d27KYug9WzljHC6B9Ysg==", - "dev": true, - "dependencies": { - "fill-keys": "^1.0.2", - "module-not-found-error": "^1.0.1", - "resolve": "^1.11.1" - } - }, - "node_modules/pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" - }, - "node_modules/qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/random-bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" - }, - "node_modules/resolve": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", - "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", - "dependencies": { - "is-core-module": "^2.8.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/retry-as-promised": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-5.0.0.tgz", - "integrity": "sha512-6S+5LvtTl2ggBumk04hBo/4Uf6fRJUwIgunGZ7CYEBCeufGFW1Pu6ucUf/UskHeWOIsUcLOGLFXPig5tR5V1nA==" - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/send": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", - "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", - "dependencies": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "1.8.1", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/send/node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "node_modules/send/node_modules/http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/send/node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/seq-queue": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", - "integrity": "sha1-1WgS4cAXpuTnw+Ojeh2m143TyT4=" - }, - "node_modules/sequelize": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.19.0.tgz", - "integrity": "sha512-B3oGIdpYBERDjRDm74h7Ky67f6ZLcmBXOA7HscYObiOSo4pD7VBc9mtm44wNV7unc0uk8I1d30nbZBTQCE377A==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/sequelize" - } - ], - "dependencies": { - "@types/debug": "^4.1.7", - "@types/validator": "^13.7.1", - "debug": "^4.3.3", - "dottie": "^2.0.2", - "inflection": "^1.13.2", - "lodash": "^4.17.21", - "moment": "^2.29.1", - "moment-timezone": "^0.5.34", - "pg-connection-string": "^2.5.0", - "retry-as-promised": "^5.0.0", - "semver": "^7.3.5", - "sequelize-pool": "^7.1.0", - "toposort-class": "^1.0.1", - "uuid": "^8.3.2", - "validator": "^13.7.0", - "wkx": "^0.5.0" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependenciesMeta": { - "ibm_db": { - "optional": true - }, - "mariadb": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "pg": { - "optional": true - }, - "pg-hstore": { - "optional": true - }, - "snowflake-sdk": { - "optional": true - }, - "sqlite3": { - "optional": true - }, - "tedious": { - "optional": true - } - } - }, - "node_modules/sequelize-cli": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/sequelize-cli/-/sequelize-cli-5.5.1.tgz", - "integrity": "sha512-ZM4kUZvY3y14y+Rq3cYxGH7YDJz11jWHcN2p2x7rhAIemouu4CEXr5ebw30lzTBtyXV4j2kTO+nUjZOqzG7k+Q==", - "dependencies": { - "bluebird": "^3.5.3", - "cli-color": "^1.4.0", - "fs-extra": "^7.0.1", - "js-beautify": "^1.8.8", - "lodash": "^4.17.5", - "resolve": "^1.5.0", - "umzug": "^2.1.0", - "yargs": "^13.1.0" - }, - "bin": { - "sequelize": "lib/sequelize", - "sequelize-cli": "lib/sequelize" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/sequelize-pool": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-7.1.0.tgz", - "integrity": "sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg==", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/sequelize-test-helpers": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/sequelize-test-helpers/-/sequelize-test-helpers-1.4.2.tgz", - "integrity": "sha512-v7Yy9DKjzFA/OHLtxvFClgN2CKA9cRwxn9+6ha6xoqUzRngXdsbrmle0KD1onSqnCwVIweWlRTLJxcEl1ueozA==", - "dev": true, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/davesag" - }, - "peerDependencies": { - "chai": ">= 4", - "sinon": ">= 10.0.0" - } - }, - "node_modules/sequelize/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/sequelize/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/sequelize/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/sequelize/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/sequelize/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/serve-static": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", - "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" - }, - "node_modules/sinon": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-10.0.0.tgz", - "integrity": "sha512-XAn5DxtGVJBlBWYrcYKEhWCz7FLwZGdyvANRyK06419hyEpdT0dMc5A8Vcxg5SCGHc40CsqoKsc1bt1CbJPfNw==", - "dependencies": { - "@sinonjs/commons": "^1.8.1", - "@sinonjs/fake-timers": "^6.0.1", - "@sinonjs/samsam": "^5.3.1", - "diff": "^4.0.2", - "nise": "^4.1.0", - "supports-color": "^7.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" - } - }, - "node_modules/sinon-chai": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.7.0.tgz", - "integrity": "sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g==", - "peerDependencies": { - "chai": "^4.0.0", - "sinon": ">=4.0.0" - } - }, - "node_modules/sinon/node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/sinon/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/sinon/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" - }, - "node_modules/sqlstring": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", - "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/superagent": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", - "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", - "deprecated": "Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at .", - "dev": true, - "dependencies": { - "component-emitter": "^1.2.0", - "cookiejar": "^2.1.0", - "debug": "^3.1.0", - "extend": "^3.0.0", - "form-data": "^2.3.1", - "formidable": "^1.2.0", - "methods": "^1.1.1", - "mime": "^1.4.1", - "qs": "^6.5.1", - "readable-stream": "^2.3.5" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/superagent/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/superagent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/supertest": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-3.4.2.tgz", - "integrity": "sha512-WZWbwceHUo2P36RoEIdXvmqfs47idNNZjCuJOqDz6rvtkk8ym56aU5oglORCpPeXGxT7l9rkJ41+O1lffQXYSA==", - "dev": true, - "dependencies": { - "methods": "^1.1.2", - "superagent": "^3.8.3" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/supports-color": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", - "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/timers-ext": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", - "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", - "dependencies": { - "es5-ext": "~0.10.46", - "next-tick": "1" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/toposort-class": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", - "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=" - }, - "node_modules/type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/uid-safe": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", - "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", - "dependencies": { - "random-bytes": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/umzug": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/umzug/-/umzug-2.3.0.tgz", - "integrity": "sha512-Z274K+e8goZK8QJxmbRPhl89HPO1K+ORFtm6rySPhFKfKc5GHhqdzD0SGhSWHkzoXasqJuItdhorSvY7/Cgflw==", - "dependencies": { - "bluebird": "^3.7.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", - "dependencies": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/validator": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", - "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" - }, - "node_modules/wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dependencies": { - "string-width": "^1.0.2 || 2" - } - }, - "node_modules/wkx": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", - "integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dependencies": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "engines": { - "node": ">=6" + "dependencies": { + "@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.3.0" } }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } + "@eslint-community/regexpp": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "dev": true }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" + "@eslint/eslintrc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", + "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.5.2", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + } } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + "@eslint/js": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.42.0.tgz", + "integrity": "sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==", + "dev": true }, - "node_modules/yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "@humanwhocodes/config-array": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, "dependencies": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, - "node_modules/yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true }, - "node_modules/yargs-unparser": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", - "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", - "dependencies": { - "flat": "^4.1.0", - "lodash": "^4.17.15", - "yargs": "^13.3.0" - }, - "engines": { - "node": ">=6" - } + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "engines": { - "node": ">=6" + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" } }, - "node_modules/yargs/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" } - } - }, - "dependencies": { + }, "@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -3108,6 +203,12 @@ "@types/ms": "*" } }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, "@types/ms": { "version": "0.7.31", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", @@ -3137,6 +238,30 @@ "negotiator": "0.6.3" } }, + "acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "ansi-colors": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", @@ -3163,11 +288,451 @@ "sprintf-js": "~1.0.2" } }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, + "array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + }, + "dependencies": { + "es-abstract": { + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", + "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.0", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.0", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + } + }, + "get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true + }, + "internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true + }, + "object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true + }, + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + } + } + }, + "array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "dependencies": { + "es-abstract": { + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", + "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.0", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.0", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + } + }, + "get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true + }, + "internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true + }, + "object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true + }, + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + } + } + }, + "array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "dependencies": { + "es-abstract": { + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", + "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.0", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.0", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + } + }, + "get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true + }, + "internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true + }, + "object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true + }, + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + } + } + }, "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -3179,6 +744,12 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3241,6 +812,12 @@ "get-intrinsic": "^1.0.2" } }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -3420,6 +997,28 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "dependencies": { + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", @@ -3450,6 +1049,12 @@ "type-detect": "^4.0.0" } }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, "define-properties": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", @@ -3485,6 +1090,15 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, "dottie": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.2.tgz", @@ -3559,84 +1173,601 @@ "unbox-primitive": "^1.0.1" }, "dependencies": { - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + } + } + }, + "es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "dependencies": { + "get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + } + } + } + }, + "es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es5-ext": { + "version": "0.10.60", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.60.tgz", + "integrity": "sha512-jpKNXIt60htYG59/9FGf2PYT3pwMpnEbNKysU+k/4FGwyGtMotOvcZOuW+EmXXYASRqYSXQfGL5cVIthOTgbkg==", + "requires": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "requires": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "eslint": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.42.0.tgz", + "integrity": "sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.3", + "@eslint/js": "8.42.0", + "@humanwhocodes/config-array": "^0.11.10", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.5.2", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "eslint-config-standard": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-16.0.3.tgz", + "integrity": "sha512-x4fmJL5hGqNJKGHSjnLdgA6U6h1YW/G2dW9fA+cyVur4SK6lyue8+UgNKWlZtUDTXvgKDD/Oa3GQjmB5kjtVvg==", + "dev": true + }, + "eslint-import-resolver-node": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", + "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "is-core-module": "^2.11.0", + "resolve": "^1.22.1" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "is-core-module": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, + "requires": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + } + } + }, + "eslint-module-utils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, + "requires": { + "debug": "^3.2.7" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" + "ms": "^2.1.1" } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true } } }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "dev": true, "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" } }, - "es5-ext": { - "version": "0.10.60", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.60.tgz", - "integrity": "sha512-jpKNXIt60htYG59/9FGf2PYT3pwMpnEbNKysU+k/4FGwyGtMotOvcZOuW+EmXXYASRqYSXQfGL5cVIthOTgbkg==", + "eslint-plugin-import": { + "version": "2.27.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", + "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", + "dev": true, "requires": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "next-tick": "^1.1.0" + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "array.prototype.flatmap": "^1.3.1", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-module-utils": "^2.7.4", + "has": "^1.0.3", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.6", + "resolve": "^1.22.1", + "semver": "^6.3.0", + "tsconfig-paths": "^3.14.1" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "is-core-module": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, + "requires": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, - "es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "eslint-plugin-promise": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.2.0.tgz", + "integrity": "sha512-SftLb1pUG01QYq2A/hGAWfDRXqYD82zE7j7TopDOyNdU+7SvvoXREls/+PRTY17vUXzXnZA/zfnyKgRH6x4JJw==", + "dev": true + }, + "eslint-scope": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "dev": true, "requires": { - "d": "^1.0.1", - "ext": "^1.1.2" + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" } }, - "es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, "requires": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } } }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + "eslint-visitor-keys": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "dev": true }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "espree": { + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", + "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", + "dev": true, + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + } }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, + "esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -3796,6 +1927,42 @@ "resolved": "https://registry.npmjs.org/faker/-/faker-4.1.0.tgz", "integrity": "sha1-HkW7vsxndLPBlfrSg1EJxtdIzD8=" }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, "fill-keys": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", @@ -3846,6 +2013,31 @@ "is-buffer": "~2.0.3" } }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "requires": { + "is-callable": "^1.1.3" + } + }, "form-data": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", @@ -3893,6 +2085,24 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true + }, "generate-function": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", @@ -3943,11 +2153,67 @@ "path-is-absolute": "^1.0.0" } }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3" + } + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3" + }, + "dependencies": { + "get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + } + } + } + }, "graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, "growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", @@ -3979,6 +2245,12 @@ "get-intrinsic": "^1.1.1" } }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true + }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -4024,6 +2296,28 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, "inflection": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.13.2.tgz", @@ -4063,6 +2357,31 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, + "is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "dependencies": { + "get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + } + } + } + }, "is-bigint": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", @@ -4106,11 +2425,26 @@ "has-tostringtag": "^1.0.0" } }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, "is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", @@ -4130,6 +2464,12 @@ "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", "dev": true }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, "is-promise": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", @@ -4170,7 +2510,20 @@ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", "requires": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.2" + } + }, + "is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" } }, "is-weakref": { @@ -4212,6 +2565,27 @@ "esprima": "^4.0.0" } }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -4225,6 +2599,16 @@ "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==" }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, "locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", @@ -4244,6 +2628,12 @@ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "log-symbols": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", @@ -4489,6 +2879,12 @@ } } }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -4574,6 +2970,148 @@ "es-abstract": "^1.19.1" } }, + "object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "dependencies": { + "es-abstract": { + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", + "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.0", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.0", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + } + }, + "get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true + }, + "internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true + }, + "object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true + }, + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + } + } + }, "on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -4595,6 +3133,20 @@ "wrappy": "1" } }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -4616,6 +3168,15 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -4653,6 +3214,12 @@ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, "path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -4678,6 +3245,12 @@ "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -4714,6 +3287,12 @@ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, + "punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true + }, "qs": { "version": "6.10.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", @@ -4722,6 +3301,12 @@ "side-channel": "^1.0.4" } }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, "random-bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", @@ -4766,6 +3351,35 @@ } } }, + "regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + }, + "dependencies": { + "define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + } + } + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -4786,16 +3400,71 @@ "supports-preserve-symlinks-flag": "^1.0.0" } }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, "retry-as-promised": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-5.0.0.tgz", "integrity": "sha512-6S+5LvtTl2ggBumk04hBo/4Uf6fRJUwIgunGZ7CYEBCeufGFW1Pu6ucUf/UskHeWOIsUcLOGLFXPig5tR5V1nA==" }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, + "safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "dependencies": { + "get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + } + } + } + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -4951,8 +3620,7 @@ "version": "1.4.2", "resolved": "https://registry.npmjs.org/sequelize-test-helpers/-/sequelize-test-helpers-1.4.2.tgz", "integrity": "sha512-v7Yy9DKjzFA/OHLtxvFClgN2CKA9cRwxn9+6ha6xoqUzRngXdsbrmle0KD1onSqnCwVIweWlRTLJxcEl1ueozA==", - "dev": true, - "requires": {} + "dev": true }, "serve-static": { "version": "1.14.2", @@ -4975,6 +3643,21 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, "side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -5026,8 +3709,7 @@ "sinon-chai": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.7.0.tgz", - "integrity": "sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g==", - "requires": {} + "integrity": "sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g==" }, "sprintf-js": { "version": "1.0.3", @@ -5044,23 +3726,6 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } - } - }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -5070,6 +3735,148 @@ "strip-ansi": "^4.0.0" } }, + "string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "dependencies": { + "es-abstract": { + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", + "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.0", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.0", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + } + }, + "get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true + }, + "internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true + }, + "object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true + }, + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + } + } + }, "string.prototype.trimend": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", @@ -5088,6 +3895,23 @@ "define-properties": "^1.1.3" } }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -5103,6 +3927,12 @@ } } }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + }, "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -5166,6 +3996,12 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, "timers-ext": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", @@ -5185,16 +4021,43 @@ "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=" }, + "tsconfig-paths": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", + "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, "type": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -5204,6 +4067,17 @@ "mime-types": "~2.1.24" } }, + "typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + } + }, "uid-safe": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", @@ -5241,6 +4115,15 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -5292,6 +4175,20 @@ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, + "which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + } + }, "wide-align": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", @@ -5308,6 +4205,12 @@ "@types/node": "*" } }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, "wrap-ansi": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", @@ -5418,6 +4321,12 @@ "lodash": "^4.17.15", "yargs": "^13.3.0" } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true } } } diff --git a/package.json b/package.json index f6a07e1a29..fed0c30a0c 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,11 @@ "devDependencies": { "proxyquire": "^2.1.3", "sequelize-test-helpers": "^1.4.2", - "supertest": "^3.3.0" + "supertest": "^3.3.0", + "eslint": "^8.1.0", + "eslint-config-standard": "^16.0.3", + "eslint-plugin-import": "^2.23.4", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^5.1.0" } } From aec8dbd287bd9ff94d709315be590eb7c5c1c6d6 Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Sun, 4 Jun 2023 22:47:56 +0800 Subject: [PATCH 002/131] feat: use ESLint to adjust the format and set associate id --- .../20190115071418-create-followship.js | 8 ++-- migrations/20190115071419-create-like.js | 22 +++++++--- migrations/20190115071420-create-reply.js | 22 +++++++--- migrations/20190115071420-create-tweet.js | 15 ++++--- migrations/20190115071421-create-user.js | 14 +++++-- models/followship.js | 21 +++++++--- models/index.js | 40 +++++++++---------- models/like.js | 12 +++--- models/reply.js | 12 +++--- models/tweet.js | 12 +++--- models/user.js | 12 +++--- 11 files changed, 115 insertions(+), 75 deletions(-) diff --git a/migrations/20190115071418-create-followship.js b/migrations/20190115071418-create-followship.js index 4e04770a7c..ca5835b907 100644 --- a/migrations/20190115071418-create-followship.js +++ b/migrations/20190115071418-create-followship.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' module.exports = { up: (queryInterface, Sequelize) => { return queryInterface.createTable('Followships', { @@ -22,9 +22,9 @@ module.exports = { allowNull: false, type: Sequelize.DATE } - }); + }) }, down: (queryInterface, Sequelize) => { - return queryInterface.dropTable('Followships'); + return queryInterface.dropTable('Followships') } -}; \ No newline at end of file +} diff --git a/migrations/20190115071419-create-like.js b/migrations/20190115071419-create-like.js index 08c9e524d5..35f85f7b89 100644 --- a/migrations/20190115071419-create-like.js +++ b/migrations/20190115071419-create-like.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' module.exports = { up: (queryInterface, Sequelize) => { return queryInterface.createTable('Likes', { @@ -9,10 +9,20 @@ module.exports = { type: Sequelize.INTEGER }, UserId: { - type: Sequelize.INTEGER + type: Sequelize.INTEGER, + allowNull: false, + reference: { + model: 'Users', + key: 'id' + } }, TweetId: { - type: Sequelize.INTEGER + type: Sequelize.INTEGER, + allowNull: false, + reference: { + model: 'Tweets', + key: 'id' + } }, createdAt: { allowNull: false, @@ -22,9 +32,9 @@ module.exports = { allowNull: false, type: Sequelize.DATE } - }); + }) }, down: (queryInterface, Sequelize) => { - return queryInterface.dropTable('Likes'); + return queryInterface.dropTable('Likes') } -}; \ No newline at end of file +} diff --git a/migrations/20190115071420-create-reply.js b/migrations/20190115071420-create-reply.js index ccfd119c53..50fea383e1 100644 --- a/migrations/20190115071420-create-reply.js +++ b/migrations/20190115071420-create-reply.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' module.exports = { up: (queryInterface, Sequelize) => { return queryInterface.createTable('Replies', { @@ -9,10 +9,20 @@ module.exports = { type: Sequelize.INTEGER }, UserId: { - type: Sequelize.INTEGER + type: Sequelize.INTEGER, + allowNull: false, + reference: { + model: 'Users', + key: 'id' + } }, TweetId: { - type: Sequelize.INTEGER + type: Sequelize.INTEGER, + allowNull: false, + reference: { + model: 'Tweets', + key: 'id' + } }, comment: { type: Sequelize.TEXT @@ -25,9 +35,9 @@ module.exports = { allowNull: false, type: Sequelize.DATE } - }); + }) }, down: (queryInterface, Sequelize) => { - return queryInterface.dropTable('Replies'); + return queryInterface.dropTable('Replies') } -}; \ No newline at end of file +} diff --git a/migrations/20190115071420-create-tweet.js b/migrations/20190115071420-create-tweet.js index 201c8e8245..68fdfe9eb6 100644 --- a/migrations/20190115071420-create-tweet.js +++ b/migrations/20190115071420-create-tweet.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' module.exports = { up: (queryInterface, Sequelize) => { return queryInterface.createTable('Tweets', { @@ -9,7 +9,12 @@ module.exports = { type: Sequelize.INTEGER }, UserId: { - type: Sequelize.INTEGER + type: Sequelize.INTEGER, + allowNull: false, + reference: { + model: 'Users', + key: 'id' + } }, description: { type: Sequelize.TEXT @@ -22,9 +27,9 @@ module.exports = { allowNull: false, type: Sequelize.DATE } - }); + }) }, down: (queryInterface, Sequelize) => { - return queryInterface.dropTable('Tweets'); + return queryInterface.dropTable('Tweets') } -}; \ No newline at end of file +} diff --git a/migrations/20190115071421-create-user.js b/migrations/20190115071421-create-user.js index 2376dbb50d..13809e791c 100644 --- a/migrations/20190115071421-create-user.js +++ b/migrations/20190115071421-create-user.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' module.exports = { up: (queryInterface, Sequelize) => { return queryInterface.createTable('Users', { @@ -9,21 +9,27 @@ module.exports = { type: Sequelize.INTEGER }, email: { + allowNull: false, type: Sequelize.STRING }, password: { + allowNull: false, type: Sequelize.STRING }, name: { + allowNull: false, type: Sequelize.STRING }, avatar: { + allowNull: false, type: Sequelize.STRING }, introduction: { + allowNull: false, type: Sequelize.TEXT }, role: { + allowNull: false, type: Sequelize.STRING }, createdAt: { @@ -34,9 +40,9 @@ module.exports = { allowNull: false, type: Sequelize.DATE } - }); + }) }, down: (queryInterface, Sequelize) => { - return queryInterface.dropTable('Users'); + return queryInterface.dropTable('Users') } -}; \ No newline at end of file +} diff --git a/models/followship.js b/models/followship.js index 790f3faa39..04e199b78e 100644 --- a/models/followship.js +++ b/models/followship.js @@ -1,8 +1,17 @@ -'use strict'; +'use strict' module.exports = (sequelize, DataTypes) => { const Followship = sequelize.define('Followship', { - }, {}); - Followship.associate = function(models) { - }; - return Followship; -}; \ No newline at end of file + }, {}) + Followship.associate = function (models) { + } + Followship.init({ + followerId: DataTypes.INTEGER, + followingId: DataTypes.INTEGER + }, { + sequelize, + modelName: 'Followship', + tableName: 'Followships', + underscored: true + }) + return Followship +} diff --git a/models/index.js b/models/index.js index 33f09e7764..e92dffac8b 100644 --- a/models/index.js +++ b/models/index.js @@ -1,37 +1,37 @@ -'use strict'; +'use strict' -const fs = require('fs'); -const path = require('path'); -const Sequelize = require('sequelize'); -const basename = path.basename(__filename); -const env = process.env.NODE_ENV || 'development'; -const config = require(__dirname + '/../config/config.json')[env]; -const db = {}; +const fs = require('fs') +const path = require('path') +const Sequelize = require('sequelize') +const basename = path.basename(__filename) +const env = process.env.NODE_ENV || 'development' +const config = require(__dirname + '/../config/config.json')[env] +const db = {} -let sequelize; +let sequelize if (config.use_env_variable) { - sequelize = new Sequelize(process.env[config.use_env_variable], config); + sequelize = new Sequelize(process.env[config.use_env_variable], config) } else { - sequelize = new Sequelize(config.database, config.username, config.password, config); + sequelize = new Sequelize(config.database, config.username, config.password, config) } fs .readdirSync(__dirname) .filter(file => { - return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js'); + return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js') }) .forEach(file => { - const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes); - db[model.name] = model; - }); + const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes) + db[model.name] = model + }) Object.keys(db).forEach(modelName => { if (db[modelName].associate) { - db[modelName].associate(db); + db[modelName].associate(db) } -}); +}) -db.sequelize = sequelize; -db.Sequelize = Sequelize; +db.sequelize = sequelize +db.Sequelize = Sequelize -module.exports = db; +module.exports = db diff --git a/models/like.js b/models/like.js index c8939de1fc..e3f4192c3c 100644 --- a/models/like.js +++ b/models/like.js @@ -1,8 +1,8 @@ -'use strict'; +'use strict' module.exports = (sequelize, DataTypes) => { const Like = sequelize.define('Like', { - }, {}); - Like.associate = function(models) { - }; - return Like; -}; \ No newline at end of file + }, {}) + Like.associate = function (models) { + } + return Like +} diff --git a/models/reply.js b/models/reply.js index 60387f164f..8c32f93735 100644 --- a/models/reply.js +++ b/models/reply.js @@ -1,8 +1,8 @@ -'use strict'; +'use strict' module.exports = (sequelize, DataTypes) => { const Reply = sequelize.define('Reply', { - }, {}); - Reply.associate = function(models) { - }; - return Reply; -}; \ No newline at end of file + }, {}) + Reply.associate = function (models) { + } + return Reply +} diff --git a/models/tweet.js b/models/tweet.js index a8b6600778..5c9246a391 100644 --- a/models/tweet.js +++ b/models/tweet.js @@ -1,8 +1,8 @@ -'use strict'; +'use strict' module.exports = (sequelize, DataTypes) => { const Tweet = sequelize.define('Tweet', { - }, {}); - Tweet.associate = function(models) { - }; - return Tweet; -}; \ No newline at end of file + }, {}) + Tweet.associate = function (models) { + } + return Tweet +} diff --git a/models/user.js b/models/user.js index 82c5f84c83..1e76a5353e 100644 --- a/models/user.js +++ b/models/user.js @@ -1,8 +1,8 @@ -'use strict'; +'use strict' module.exports = (sequelize, DataTypes) => { const User = sequelize.define('User', { - }, {}); - User.associate = function(models) { - }; - return User; -}; \ No newline at end of file + }, {}) + User.associate = function (models) { + } + return User +} From 21ad13c7bd67df70737357c5fb5a07bf002b590a Mon Sep 17 00:00:00 2001 From: s1030905 Date: Mon, 5 Jun 2023 10:34:55 +0800 Subject: [PATCH 003/131] add .env, .env.test, .env.example, set scripts for windows --- .env.example | 2 + _helpers.js | 5 + app.js | 7 +- package-lock.json | 853 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 7 +- 5 files changed, 860 insertions(+), 14 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000000..0486d8c813 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +IMGUR_CLIENT_ID= +JWT_SECRET= \ No newline at end of file diff --git a/_helpers.js b/_helpers.js index 5973f3d100..aafe88c3b9 100644 --- a/_helpers.js +++ b/_helpers.js @@ -6,3 +6,8 @@ function getUser (req) { module.exports = { getUser } + +// use helpers.getUser(req) to replace req.user +// function authenticated (req, res, next) { +// // passport.authenticate('jwt', { ses... +// } diff --git a/app.js b/app.js index 4f08f5f645..e894d58d6e 100644 --- a/app.js +++ b/app.js @@ -4,12 +4,7 @@ const helpers = require('./_helpers') const app = express() const port = 3000 -// use helpers.getUser(req) to replace req.user -// function authenticated (req, res, next) { -// // passport.authenticate('jwt', { ses... -// } - app.get('/', (req, res) => res.send('Hello World!')) -app.listen(port, () => console.log(`Example app listening on port ${port}!`)) +app.listen(port, () => console.log(`Example app listening on http://localhost:${port} !`)) module.exports = app diff --git a/package-lock.json b/package-lock.json index 2307624fe6..297295af3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -164,6 +164,11 @@ "fastq": "^1.6.0" } }, + "@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" + }, "@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -195,6 +200,14 @@ "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==" }, + "@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "requires": { + "defer-to-connect": "^1.0.1" + } + }, "@types/debug": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", @@ -262,6 +275,49 @@ "uri-js": "^4.2.2" } }, + "ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "requires": { + "string-width": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, "ansi-colors": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", @@ -280,6 +336,15 @@ "color-convert": "^1.9.0" } }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -760,6 +825,11 @@ "resolved": "https://registry.npmjs.org/bcrypt-nodejs/-/bcrypt-nodejs-0.0.3.tgz", "integrity": "sha1-xgkX8m3CNWYVZsaBBhwwPCsohCs=" }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" + }, "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -784,6 +854,104 @@ "unpipe": "1.0.0" } }, + "boxen": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", + "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", + "requires": { + "ansi-align": "^3.0.0", + "camelcase": "^5.3.1", + "chalk": "^3.0.0", + "cli-boxes": "^2.2.0", + "string-width": "^4.1.0", + "term-size": "^2.1.0", + "type-fest": "^0.8.1", + "widest-line": "^3.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" + } + } + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -793,6 +961,14 @@ "concat-map": "0.0.1" } }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, "browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -803,6 +979,35 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, + "cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + } + } + }, "call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -862,6 +1067,41 @@ "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=" }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" + }, + "cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==" + }, "cli-color": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz", @@ -910,6 +1150,14 @@ } } }, + "clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "requires": { + "mimic-response": "^1.0.0" + } + }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -957,6 +1205,19 @@ "proto-list": "~1.2.1" } }, + "configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "requires": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + } + }, "connect-flash": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/connect-flash/-/connect-flash-0.1.1.tgz", @@ -1019,6 +1280,11 @@ } } }, + "crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==" + }, "d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", @@ -1041,6 +1307,14 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", + "requires": { + "mimic-response": "^1.0.0" + } + }, "deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", @@ -1049,12 +1323,22 @@ "type-detect": "^4.0.0" } }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, "deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" + }, "define-properties": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", @@ -1099,11 +1383,29 @@ "esutils": "^2.0.2" } }, + "dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "requires": { + "is-obj": "^2.0.0" + } + }, + "dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==" + }, "dottie": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.2.tgz", "integrity": "sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg==" }, + "duplexer3": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", + "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==" + }, "editorconfig": { "version": "0.15.3", "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", @@ -1146,6 +1448,14 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, "es-abstract": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.5.tgz", @@ -1270,6 +1580,11 @@ "es6-symbol": "^3.1.1" } }, + "escape-goat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", + "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==" + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1973,6 +2288,14 @@ "merge-descriptors": "~1.0.0" } }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, "finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", @@ -2080,6 +2403,12 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "optional": true + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -2131,6 +2460,14 @@ "has-symbols": "^1.0.1" } }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + }, "get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", @@ -2162,6 +2499,21 @@ "is-glob": "^4.0.3" } }, + "global-dirs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz", + "integrity": "sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==", + "requires": { + "ini": "1.3.7" + }, + "dependencies": { + "ini": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", + "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==" + } + } + }, "globals": { "version": "13.20.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", @@ -2203,6 +2555,24 @@ } } }, + "got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "requires": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + } + }, "graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", @@ -2264,11 +2634,21 @@ "has-symbols": "^1.0.2" } }, + "has-yarn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", + "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==" + }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, + "http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" + }, "http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -2302,6 +2682,11 @@ "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==" + }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -2312,11 +2697,15 @@ "resolve-from": "^4.0.0" } }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==" + }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" }, "inflection": { "version": "1.13.2", @@ -2390,6 +2779,14 @@ "has-bigints": "^1.0.1" } }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "requires": { + "binary-extensions": "^2.0.0" + } + }, "is-boolean-object": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", @@ -2409,6 +2806,14 @@ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==" }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "requires": { + "ci-info": "^2.0.0" + } + }, "is-core-module": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", @@ -2428,8 +2833,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" }, "is-fullwidth-code-point": { "version": "2.0.0", @@ -2440,16 +2844,34 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "requires": { "is-extglob": "^2.1.1" } }, + "is-installed-globally": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", + "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", + "requires": { + "global-dirs": "^2.0.1", + "is-path-inside": "^3.0.1" + } + }, "is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==" }, + "is-npm": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", + "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==" + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, "is-number-object": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", @@ -2458,6 +2880,11 @@ "has-tostringtag": "^1.0.0" } }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" + }, "is-object": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", @@ -2467,8 +2894,7 @@ "is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==" }, "is-promise": { "version": "2.2.2", @@ -2526,6 +2952,11 @@ "has-tostringtag": "^1.0.0" } }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, "is-weakref": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", @@ -2534,6 +2965,11 @@ "call-bind": "^1.0.2" } }, + "is-yarn-global": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", + "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==" + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -2565,6 +3001,11 @@ "esprima": "^4.0.0" } }, + "json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==" + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -2599,6 +3040,22 @@ "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==" }, + "keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "requires": { + "json-buffer": "3.0.0" + } + }, + "latest-version": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", + "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", + "requires": { + "package-json": "^6.3.0" + } + }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2655,6 +3112,11 @@ "get-func-name": "^2.0.0" } }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" + }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -2671,6 +3133,21 @@ "es5-ext": "~0.10.2" } }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -2740,6 +3217,11 @@ "mime-db": "1.52.0" } }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -2931,6 +3413,46 @@ "semver": "^5.7.0" } }, + "nodemon": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.12.tgz", + "integrity": "sha512-egCTmNZdObdBxUBw6ZNwvZ/xzk24CKRs5K6d+5zbmrMr7rOpPmfPeF6OxM3DDpaRx331CQRFEktn+wrFFfBSOA==", + "requires": { + "chokidar": "^3.2.2", + "debug": "^3.2.6", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.7", + "semver": "^5.7.1", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.3", + "update-notifier": "^4.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -2939,6 +3461,16 @@ "abbrev": "1" } }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "normalize-url": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", + "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==" + }, "object-inspect": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", @@ -3147,6 +3679,11 @@ "word-wrap": "^1.2.3" } }, + "p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==" + }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -3168,6 +3705,24 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, + "package-json": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", + "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", + "requires": { + "got": "^9.6.0", + "registry-auth-token": "^4.0.0", + "registry-url": "^5.0.0", + "semver": "^6.2.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -3245,12 +3800,22 @@ "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==" + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -3287,12 +3852,34 @@ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, + "pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", "dev": true }, + "pupa": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", + "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", + "requires": { + "escape-goat": "^2.0.0" + } + }, "qs": { "version": "6.10.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", @@ -3328,6 +3915,17 @@ "unpipe": "1.0.0" } }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -3351,6 +3949,14 @@ } } }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "requires": { + "picomatch": "^2.2.1" + } + }, "regexp.prototype.flags": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", @@ -3380,6 +3986,22 @@ "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true }, + "registry-auth-token": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.2.tgz", + "integrity": "sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg==", + "requires": { + "rc": "1.2.8" + } + }, + "registry-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", + "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", + "requires": { + "rc": "^1.2.8" + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -3406,6 +4028,14 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, + "responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==", + "requires": { + "lowercase-keys": "^1.0.0" + } + }, "retry-as-promised": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-5.0.0.tgz", @@ -3475,6 +4105,21 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, + "semver-diff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", + "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "requires": { + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, "send": { "version": "0.17.2", "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", @@ -3673,6 +4318,11 @@ "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, "sinon": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/sinon/-/sinon-10.0.0.tgz", @@ -3996,6 +4646,11 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, + "term-size": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", + "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==" + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -4011,6 +4666,19 @@ "next-tick": "1" } }, + "to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==" + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } + }, "toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -4021,6 +4689,24 @@ "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=" }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "requires": { + "nopt": "~1.0.10" + }, + "dependencies": { + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "requires": { + "abbrev": "1" + } + } + } + }, "tsconfig-paths": { "version": "3.14.2", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", @@ -4078,6 +4764,14 @@ "is-typed-array": "^1.1.9" } }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "requires": { + "is-typedarray": "^1.0.0" + } + }, "uid-safe": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", @@ -4105,6 +4799,19 @@ "which-boxed-primitive": "^1.0.2" } }, + "undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" + }, + "unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "requires": { + "crypto-random-string": "^2.0.0" + } + }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -4115,6 +4822,71 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, + "update-notifier": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz", + "integrity": "sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==", + "requires": { + "boxen": "^4.2.0", + "chalk": "^3.0.0", + "configstore": "^5.0.1", + "has-yarn": "^2.1.0", + "import-lazy": "^2.1.0", + "is-ci": "^2.0.0", + "is-installed-globally": "^0.3.1", + "is-npm": "^4.0.0", + "is-yarn-global": "^0.3.0", + "latest-version": "^5.0.0", + "pupa": "^2.0.1", + "semver-diff": "^3.1.1", + "xdg-basedir": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -4124,6 +4896,14 @@ "punycode": "^2.1.0" } }, + "url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==", + "requires": { + "prepend-http": "^2.0.0" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -4197,6 +4977,49 @@ "string-width": "^1.0.2 || 2" } }, + "widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "requires": { + "string-width": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, "wkx": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", @@ -4251,6 +5074,22 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==" + }, "y18n": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", diff --git a/package.json b/package.json index fed0c30a0c..ec30fcf5de 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,10 @@ "scripts": { "start": "NODE_ENV=development node app.js", "dev": "NODE_ENV=development nodemon app.js", - "test": "mocha test --exit --recursive --timeout 5000" + "test": "mocha test --exit --recursive --timeout 5000", + "start1": "set NODE_ENV=development && nodemon app.js", + "dev1": "set NODE_ENV=development && nodemon app.js", + "test1": "set NODE_ENV=test && mocha test --exit --recursive --timeout 5000" }, "author": "", "license": "ISC", @@ -15,12 +18,14 @@ "body-parser": "^1.18.3", "chai": "^4.2.0", "connect-flash": "^0.1.1", + "dotenv": "^10.0.0", "express": "^4.16.4", "express-session": "^1.15.6", "faker": "^4.1.0", "method-override": "^3.0.0", "mocha": "^6.0.2", "mysql2": "^1.6.4", + "nodemon": "^2.0.12", "passport": "^0.4.0", "passport-local": "^1.0.0", "sequelize": "^6.18.0", From 4b880eb240e6e51ce574d1173310a476c3bbf083 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Mon, 5 Jun 2023 14:03:02 +0800 Subject: [PATCH 004/131] add relationship between models --- app.js | 9 ++++++++- models/index.js | 2 +- models/like.js | 11 +++++++++++ models/reply.js | 12 ++++++++++++ models/tweet.js | 13 +++++++++++++ models/user.js | 26 ++++++++++++++++++++++++++ 6 files changed, 71 insertions(+), 2 deletions(-) diff --git a/app.js b/app.js index e894d58d6e..8116825d38 100644 --- a/app.js +++ b/app.js @@ -1,10 +1,17 @@ +if (process.env.NODE_ENV !== 'production') { + require('dotenv').config() +} + const express = require('express') const helpers = require('./_helpers') const app = express() const port = 3000 +app.use(express.urlencoded({ extended: true })) +app.use(express.json()) + app.get('/', (req, res) => res.send('Hello World!')) -app.listen(port, () => console.log(`Example app listening on http://localhost:${port} !`)) +app.listen(port, () => console.log(`Example app listening on http://localhost:${port}`)) module.exports = app diff --git a/models/index.js b/models/index.js index e92dffac8b..427848cb0b 100644 --- a/models/index.js +++ b/models/index.js @@ -5,7 +5,7 @@ const path = require('path') const Sequelize = require('sequelize') const basename = path.basename(__filename) const env = process.env.NODE_ENV || 'development' -const config = require(__dirname + '/../config/config.json')[env] +const config = require(path.resolve(__dirname, '../config/config.json'))[env] const db = {} let sequelize diff --git a/models/like.js b/models/like.js index e3f4192c3c..9dd701352c 100644 --- a/models/like.js +++ b/models/like.js @@ -3,6 +3,17 @@ module.exports = (sequelize, DataTypes) => { const Like = sequelize.define('Like', { }, {}) Like.associate = function (models) { + Like.belongsTo(models.Tweet, { foreignKey: 'TweetId' }) + Like.belongsTo(models.User, { foreignKey: 'UserId' }) } + Like.init({ + UserId: DataTypes.INTEGER, + TweetId: DataTypes.INTEGER + }, { + sequelize, + modelName: 'Like', + tableName: 'Likes', + underscored: true + }) return Like } diff --git a/models/reply.js b/models/reply.js index 8c32f93735..300eeef677 100644 --- a/models/reply.js +++ b/models/reply.js @@ -3,6 +3,18 @@ module.exports = (sequelize, DataTypes) => { const Reply = sequelize.define('Reply', { }, {}) Reply.associate = function (models) { + Reply.belongsTo(models.Tweet, { foreignKey: 'TweetId' }) + Reply.belongsTo(models.User, { foreignKey: 'UserId' }) } + Reply.init({ + UserId: DataTypes.INTEGER, + TweetId: DataTypes.INTEGER, + comment: DataTypes.TEXT + }, { + sequelize, + modelName: 'Reply', + tableName: 'Replies', + underscored: true + }) return Reply } diff --git a/models/tweet.js b/models/tweet.js index 5c9246a391..05db6abe39 100644 --- a/models/tweet.js +++ b/models/tweet.js @@ -3,6 +3,19 @@ module.exports = (sequelize, DataTypes) => { const Tweet = sequelize.define('Tweet', { }, {}) Tweet.associate = function (models) { + Tweet.belongsTo(models.User, { foreignKey: 'UserId' }) + Tweet.hasMany(models.Rely, { foreignKey: 'TweetId' }) + Tweet.hasMany(models.Like, { foreignKey: 'TweetId' }) } + Tweet.init({ + UserId: DataTypes.INTEGER, + email: DataTypes.STRING, + description: DataTypes.TEXT + }, { + sequelize, + modelName: 'Tweet', + tableName: 'Tweets', + underscored: true + }) return Tweet } diff --git a/models/user.js b/models/user.js index 1e76a5353e..45bbef6f21 100644 --- a/models/user.js +++ b/models/user.js @@ -3,6 +3,32 @@ module.exports = (sequelize, DataTypes) => { const User = sequelize.define('User', { }, {}) User.associate = function (models) { + User.hasMany(models.Tweet, { foreignKey: 'UserId' }) + User.hasMany(models.Reply, { foreignKey: 'UserId' }) + User.hasMany(models.Like, { foreignKey: 'UserId' }) + User.belongsToMany(User, { + through: models.Followship, + foreignKey: 'followingId', + as: 'Followers' + }) + User.belongsToMany(User, { + through: models.Followship, + foreignKey: 'followerId', + as: 'Followings' + }) } + User.init({ + email: DataTypes.STRING, + password: DataTypes.STRING, + name: DataTypes.STRING, + avatar: DataTypes.STRING, + introduction: DataTypes.TEXT, + role: DataTypes.BOOLEAN + }, { + sequelize, + modelName: 'User', + tableName: 'Users', + underscored: true + }) return User } From 5a0812c304a616c5dde424688058020a29d62382 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Mon, 5 Jun 2023 16:14:52 +0800 Subject: [PATCH 005/131] create userseeds, add passport --- .env.example | 2 +- app.js | 26 ++++++- config/passport.js | 33 +++++++- .../20230605063323-add-account-to-User.js | 14 ++++ models/user.js | 3 +- package-lock.json | 11 ++- package.json | 3 +- seeders/20230605074630-users-seed-file.js | 76 +++++++++++++++++++ 8 files changed, 160 insertions(+), 8 deletions(-) create mode 100644 migrations/20230605063323-add-account-to-User.js create mode 100644 seeders/20230605074630-users-seed-file.js diff --git a/.env.example b/.env.example index 0486d8c813..ac93920992 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,2 @@ -IMGUR_CLIENT_ID= +SESSION_SECRET= JWT_SECRET= \ No newline at end of file diff --git a/app.js b/app.js index 8116825d38..6fca974b02 100644 --- a/app.js +++ b/app.js @@ -3,14 +3,38 @@ if (process.env.NODE_ENV !== 'production') { } const express = require('express') -const helpers = require('./_helpers') +const { getUser } = require('./_helpers') +const passport = require('./config/passport') +const session = require('express-session') +const flash = require('connect-flash') const app = express() const port = 3000 +// 解析request主體 app.use(express.urlencoded({ extended: true })) app.use(express.json()) +// session設定 +app.use(session({ + secret: process.env.SESSION_SECRET || 'NonSecret', + resave: false, + saveUninitialized: true +})) + +// passport初始化 +app.use(passport.initialize()) +app.use(passport.session()) + +// flash +app.use(flash()) + +// locals +app.use((req, res, next) => { + res.locals.error_messages = req.flash('error_messages') + res.locals.user = getUser(req) +}) + app.get('/', (req, res) => res.send('Hello World!')) app.listen(port, () => console.log(`Example app listening on http://localhost:${port}`)) diff --git a/config/passport.js b/config/passport.js index a2298f8964..f9cb57bb17 100644 --- a/config/passport.js +++ b/config/passport.js @@ -1,5 +1,36 @@ const passport = require('passport') +const LocalStrategy = require('passport-local').Strategy +const bcrypt = require('bcryptjs') +const { User } = require('../models') +// LocalStrategy Setting +passport.use(new LocalStrategy( + { usernameField: 'account', passwordField: 'password', passReqToCallback: true }, + async (req, account, password, cb) => { + try { + const user = await User.findOne({ where: { account } }) + if (!user) return cb(null, false, req.flash('error_messages', '帳號或密碼輸入錯誤!')) + const isMatch = await bcrypt.compare(password, user.password) + if (!isMatch) return cb(null, false, req.flash('error_messages', '帳號或密碼輸入錯誤!')) + return cb(null, user) + } catch (error) { + cb(error) + } + } +)) +// passport serializeUser & deserializeUser +passport.serializeUser((user, cb) => cb(null, user.id)) -module.exports = passport \ No newline at end of file +passport.deserializeUser(async (id, cb) => { + try { + let user = await User.findById(id) + user = user.toJSON() + console.log(user) + return cb(null, user) + } catch (error) { + cb(error) + } +}) + +module.exports = passport diff --git a/migrations/20230605063323-add-account-to-User.js b/migrations/20230605063323-add-account-to-User.js new file mode 100644 index 0000000000..e56806782d --- /dev/null +++ b/migrations/20230605063323-add-account-to-User.js @@ -0,0 +1,14 @@ +'use strict' + +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.addColumn('Users', 'account', { + type: Sequelize.STRING, + defaultValue: false + }) + }, + + down: async (queryInterface, Sequelize) => { + await queryInterface.removeColumn('Users', 'account') + } +} diff --git a/models/user.js b/models/user.js index 45bbef6f21..beb4ff6a34 100644 --- a/models/user.js +++ b/models/user.js @@ -23,7 +23,8 @@ module.exports = (sequelize, DataTypes) => { name: DataTypes.STRING, avatar: DataTypes.STRING, introduction: DataTypes.TEXT, - role: DataTypes.BOOLEAN + role: DataTypes.BOOLEAN, + account: DataTypes.STRING }, { sequelize, modelName: 'User', diff --git a/package-lock.json b/package-lock.json index 297295af3f..5e92dd27f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -825,6 +825,11 @@ "resolved": "https://registry.npmjs.org/bcrypt-nodejs/-/bcrypt-nodejs-0.0.3.tgz", "integrity": "sha1-xgkX8m3CNWYVZsaBBhwwPCsohCs=" }, + "bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" + }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -2238,9 +2243,9 @@ "dev": true }, "faker": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/faker/-/faker-4.1.0.tgz", - "integrity": "sha1-HkW7vsxndLPBlfrSg1EJxtdIzD8=" + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/faker/-/faker-5.5.3.tgz", + "integrity": "sha512-wLTv2a28wjUyWkbnX7u/ABZBkUkIF2fCd73V6P2oFqEGEktDfzWx4UxrSqtPRw0xPRAcjeAOIiJWqZm3pP4u3g==" }, "fast-deep-equal": { "version": "3.1.3", diff --git a/package.json b/package.json index ec30fcf5de..f410cb2cff 100644 --- a/package.json +++ b/package.json @@ -15,13 +15,14 @@ "license": "ISC", "dependencies": { "bcrypt-nodejs": "0.0.3", + "bcryptjs": "^2.4.3", "body-parser": "^1.18.3", "chai": "^4.2.0", "connect-flash": "^0.1.1", "dotenv": "^10.0.0", "express": "^4.16.4", "express-session": "^1.15.6", - "faker": "^4.1.0", + "faker": "^5.5.3", "method-override": "^3.0.0", "mocha": "^6.0.2", "mysql2": "^1.6.4", diff --git a/seeders/20230605074630-users-seed-file.js b/seeders/20230605074630-users-seed-file.js new file mode 100644 index 0000000000..c3b3a5485c --- /dev/null +++ b/seeders/20230605074630-users-seed-file.js @@ -0,0 +1,76 @@ +'use strict' +const faker = require('faker') +const bcrypt = require('bcryptjs') + +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.bulkInsert('Users', [ + { + email: 'root@example.com', + password: await bcrypt.hash('12345678', 10), + name: 'root', + avatar: `https://loremflickr.com/320/240/restaurant,food/?random=${Math.random() * 100}`, + introduction: faker.lorem.text(), + role: true, + account: 'root', + createdAt: new Date(), + updatedAt: new Date() + }, + { + email: 'user1@example.com', + password: await bcrypt.hash('12345678', 10), + name: 'user1', + avatar: `https://loremflickr.com/320/240/restaurant,food/?random=${Math.random() * 100}`, + introduction: faker.lorem.text(), + role: false, + account: 'user1', + createdAt: new Date(), + updatedAt: new Date() + }, { + email: 'user2@example.com', + password: await bcrypt.hash('12345678', 10), + name: 'user2', + avatar: `https://loremflickr.com/320/240/restaurant,food/?random=${Math.random() * 100}`, + introduction: faker.lorem.text(), + role: false, + account: 'user2', + createdAt: new Date(), + updatedAt: new Date() + }, { + email: 'user3@example.com', + password: await bcrypt.hash('12345678', 10), + name: 'user3', + avatar: `https://loremflickr.com/320/240/restaurant,food/?random=${Math.random() * 100}`, + introduction: faker.lorem.text(), + role: false, + account: 'user3', + createdAt: new Date(), + updatedAt: new Date() + }, { + email: 'user4@example.com', + password: await bcrypt.hash('12345678', 10), + name: 'user4', + avatar: `https://loremflickr.com/320/240/restaurant,food/?random=${Math.random() * 100}`, + introduction: faker.lorem.text(), + role: false, + account: 'user4', + createdAt: new Date(), + updatedAt: new Date() + }, { + email: 'user5@example.com', + password: await bcrypt.hash('12345678', 10), + name: 'user5', + avatar: `https://loremflickr.com/320/240/restaurant,food/?random=${Math.random() * 100}`, + introduction: faker.lorem.text(), + role: false, + account: 'user5', + createdAt: new Date(), + updatedAt: new Date() + } + ]) + }, + + down: async (queryInterface, Sequelize) => { + await queryInterface.bulkDelete('Users', {}) + } +} From ff5427e74a59b43f02ecf85d560d94879069b207 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Mon, 5 Jun 2023 19:33:12 +0800 Subject: [PATCH 006/131] debug Reply --- models/tweet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/tweet.js b/models/tweet.js index 05db6abe39..c12b2df716 100644 --- a/models/tweet.js +++ b/models/tweet.js @@ -4,7 +4,7 @@ module.exports = (sequelize, DataTypes) => { }, {}) Tweet.associate = function (models) { Tweet.belongsTo(models.User, { foreignKey: 'UserId' }) - Tweet.hasMany(models.Rely, { foreignKey: 'TweetId' }) + Tweet.hasMany(models.Reply, { foreignKey: 'TweetId' }) Tweet.hasMany(models.Like, { foreignKey: 'TweetId' }) } Tweet.init({ From b0f23bbbac6556ebfb5b5d938e3e70d232b87b12 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Mon, 5 Jun 2023 20:21:18 +0800 Subject: [PATCH 007/131] passport debug, models/index undefined --- app.js | 8 ++++++-- config/passport.js | 7 ++++++- controllers/apis/user-controller.js | 17 +++++++++++++++++ routes/apis/index.js | 17 +++++++++++++++++ routes/apis/modules/admin.js | 3 +++ routes/index.js | 3 +++ 6 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 controllers/apis/user-controller.js create mode 100644 routes/apis/index.js create mode 100644 routes/apis/modules/admin.js create mode 100644 routes/index.js diff --git a/app.js b/app.js index 6fca974b02..b0bf6b5952 100644 --- a/app.js +++ b/app.js @@ -7,6 +7,7 @@ const { getUser } = require('./_helpers') const passport = require('./config/passport') const session = require('express-session') const flash = require('connect-flash') +const { apis } = require('./routes') const app = express() const port = 3000 @@ -29,13 +30,16 @@ app.use(passport.session()) // flash app.use(flash()) -// locals +// // locals app.use((req, res, next) => { res.locals.error_messages = req.flash('error_messages') res.locals.user = getUser(req) + next() }) -app.get('/', (req, res) => res.send('Hello World!')) +// routes +app.use(apis) + app.listen(port, () => console.log(`Example app listening on http://localhost:${port}`)) module.exports = app diff --git a/config/passport.js b/config/passport.js index f9cb57bb17..f71473e406 100644 --- a/config/passport.js +++ b/config/passport.js @@ -8,8 +8,13 @@ passport.use(new LocalStrategy( { usernameField: 'account', passwordField: 'password', passReqToCallback: true }, async (req, account, password, cb) => { try { + const users = await User.findAll() + console.log(users) const user = await User.findOne({ where: { account } }) - if (!user) return cb(null, false, req.flash('error_messages', '帳號或密碼輸入錯誤!')) + if (!user) { + console.log('帳號或密碼輸入錯誤!') + return cb(null, false, req.flash('error_messages', '帳號或密碼輸入錯誤!')) + } const isMatch = await bcrypt.compare(password, user.password) if (!isMatch) return cb(null, false, req.flash('error_messages', '帳號或密碼輸入錯誤!')) return cb(null, user) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js new file mode 100644 index 0000000000..6f142bebff --- /dev/null +++ b/controllers/apis/user-controller.js @@ -0,0 +1,17 @@ +const { getUser } = require('../../_helpers') + +const userController = { + login: async (req, res, next) => { + try { + const userData = getUser(req) + console.log(userData) + // if(!user) return res.json({status: 'failed'}) + res.json({ status: 'success', data: 123 }) + } catch (error) { + next(error) + } + }, + signUp: (req, res, next) => { } +} + +module.exports = userController diff --git a/routes/apis/index.js b/routes/apis/index.js new file mode 100644 index 0000000000..a9dfd4f0fd --- /dev/null +++ b/routes/apis/index.js @@ -0,0 +1,17 @@ +const router = require('express').Router() +const passport = require('../../config/passport') +const admin = require('./modules/admin') +const userController = require('../../controllers/apis/user-controller') + +// 測試postman 是否正常運作 +router.get('/', (req, res) => { + res.json({ status: 'success', data: 'Hello world' }) +}) + +// 有關admin的routes +router.use('/admin', admin) + +router.post('/signup', userController.signUp) +router.post('/login', passport.authenticate('local'), userController.login) // 缺少session + +module.exports = router diff --git a/routes/apis/modules/admin.js b/routes/apis/modules/admin.js new file mode 100644 index 0000000000..8ff784263b --- /dev/null +++ b/routes/apis/modules/admin.js @@ -0,0 +1,3 @@ +const router = require('express').Router() + +module.exports = router diff --git a/routes/index.js b/routes/index.js new file mode 100644 index 0000000000..b17f5d06e5 --- /dev/null +++ b/routes/index.js @@ -0,0 +1,3 @@ +const apis = require('./apis') + +module.exports = { apis } From 99b5af857a190cf777b6e640b0c4d87da5326620 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Mon, 5 Jun 2023 21:51:18 +0800 Subject: [PATCH 008/131] debug models underscored part --- config/passport.js | 56 +++++++++++++------ .../20190115071418-create-followship.js | 8 +-- migrations/20190115071419-create-like.js | 8 +-- migrations/20190115071420-create-reply.js | 8 +-- migrations/20190115071420-create-tweet.js | 6 +- migrations/20190115071421-create-user.js | 4 +- models/index.js | 2 +- seeders/20230605074630-users-seed-file.js | 24 ++++---- 8 files changed, 68 insertions(+), 48 deletions(-) diff --git a/config/passport.js b/config/passport.js index f71473e406..30217eb6d7 100644 --- a/config/passport.js +++ b/config/passport.js @@ -4,25 +4,45 @@ const bcrypt = require('bcryptjs') const { User } = require('../models') // LocalStrategy Setting -passport.use(new LocalStrategy( - { usernameField: 'account', passwordField: 'password', passReqToCallback: true }, - async (req, account, password, cb) => { - try { - const users = await User.findAll() - console.log(users) - const user = await User.findOne({ where: { account } }) - if (!user) { - console.log('帳號或密碼輸入錯誤!') - return cb(null, false, req.flash('error_messages', '帳號或密碼輸入錯誤!')) - } - const isMatch = await bcrypt.compare(password, user.password) - if (!isMatch) return cb(null, false, req.flash('error_messages', '帳號或密碼輸入錯誤!')) - return cb(null, user) - } catch (error) { - cb(error) - } + +// passport.use(new LocalStrategy( +// ({ usernameField: 'account', passwordField: 'password', passReqToCallback: true }), +// async (req, account, password, cb) => { +// try { +// const users = await User.findAll() +// console.log(users) +// const user = await User.findOne({ where: { account } }) +// if (!user) { +// console.log('帳號或密碼輸入錯誤!') +// return cb(null, false, req.flash('error_messages', '帳號或密碼輸入錯誤!')) +// } +// const isMatch = await bcrypt.compare(password, user.password) +// if (!isMatch) return cb(null, false, req.flash('error_messages', '帳號或密碼輸入錯誤!')) +// return cb(null, user) +// } catch (error) { +// cb(error) +// } +// } +// )) + +passport.use(new LocalStrategy(({ + usernameField: 'account', + passwordField: 'password', + passReqToCallback: true +}), async (req, account, password, done) => { + try { + // email error + const user = await User.findOne({ where: { account } }) + if (!user) return done(null, false, req.flash('error_messages', 'Account or password entered incorrectly!')) + // password error + const isMatch = await bcrypt.compare(password, user.password) + if (!isMatch) return done(null, false, req.flash('error_messages', 'Account or password entered incorrectly!')) + // Login successful + return done(null, user) + } catch (error) { + console.log(error) } -)) +})) // passport serializeUser & deserializeUser passport.serializeUser((user, cb) => cb(null, user.id)) diff --git a/migrations/20190115071418-create-followship.js b/migrations/20190115071418-create-followship.js index ca5835b907..da1802d87f 100644 --- a/migrations/20190115071418-create-followship.js +++ b/migrations/20190115071418-create-followship.js @@ -8,17 +8,17 @@ module.exports = { primaryKey: true, type: Sequelize.INTEGER }, - followerId: { + follower_id: { type: Sequelize.INTEGER }, - followingId: { + following_id: { type: Sequelize.INTEGER }, - createdAt: { + created_at: { allowNull: false, type: Sequelize.DATE }, - updatedAt: { + updated_at: { allowNull: false, type: Sequelize.DATE } diff --git a/migrations/20190115071419-create-like.js b/migrations/20190115071419-create-like.js index 35f85f7b89..f5a89265bc 100644 --- a/migrations/20190115071419-create-like.js +++ b/migrations/20190115071419-create-like.js @@ -8,7 +8,7 @@ module.exports = { primaryKey: true, type: Sequelize.INTEGER }, - UserId: { + User_id: { type: Sequelize.INTEGER, allowNull: false, reference: { @@ -16,7 +16,7 @@ module.exports = { key: 'id' } }, - TweetId: { + Tweet_id: { type: Sequelize.INTEGER, allowNull: false, reference: { @@ -24,11 +24,11 @@ module.exports = { key: 'id' } }, - createdAt: { + created_at: { allowNull: false, type: Sequelize.DATE }, - updatedAt: { + updated_at: { allowNull: false, type: Sequelize.DATE } diff --git a/migrations/20190115071420-create-reply.js b/migrations/20190115071420-create-reply.js index 50fea383e1..b725791b64 100644 --- a/migrations/20190115071420-create-reply.js +++ b/migrations/20190115071420-create-reply.js @@ -8,7 +8,7 @@ module.exports = { primaryKey: true, type: Sequelize.INTEGER }, - UserId: { + User_id: { type: Sequelize.INTEGER, allowNull: false, reference: { @@ -16,7 +16,7 @@ module.exports = { key: 'id' } }, - TweetId: { + Tweet_id: { type: Sequelize.INTEGER, allowNull: false, reference: { @@ -27,11 +27,11 @@ module.exports = { comment: { type: Sequelize.TEXT }, - createdAt: { + created_at: { allowNull: false, type: Sequelize.DATE }, - updatedAt: { + updated_at: { allowNull: false, type: Sequelize.DATE } diff --git a/migrations/20190115071420-create-tweet.js b/migrations/20190115071420-create-tweet.js index 68fdfe9eb6..5b9e90fa8f 100644 --- a/migrations/20190115071420-create-tweet.js +++ b/migrations/20190115071420-create-tweet.js @@ -8,7 +8,7 @@ module.exports = { primaryKey: true, type: Sequelize.INTEGER }, - UserId: { + User_id: { type: Sequelize.INTEGER, allowNull: false, reference: { @@ -19,11 +19,11 @@ module.exports = { description: { type: Sequelize.TEXT }, - createdAt: { + created_at: { allowNull: false, type: Sequelize.DATE }, - updatedAt: { + updated_at: { allowNull: false, type: Sequelize.DATE } diff --git a/migrations/20190115071421-create-user.js b/migrations/20190115071421-create-user.js index 13809e791c..79ecddf277 100644 --- a/migrations/20190115071421-create-user.js +++ b/migrations/20190115071421-create-user.js @@ -32,11 +32,11 @@ module.exports = { allowNull: false, type: Sequelize.STRING }, - createdAt: { + created_at: { allowNull: false, type: Sequelize.DATE }, - updatedAt: { + updated_at: { allowNull: false, type: Sequelize.DATE } diff --git a/models/index.js b/models/index.js index 427848cb0b..c7e3ab2264 100644 --- a/models/index.js +++ b/models/index.js @@ -5,7 +5,7 @@ const path = require('path') const Sequelize = require('sequelize') const basename = path.basename(__filename) const env = process.env.NODE_ENV || 'development' -const config = require(path.resolve(__dirname, '../config/config.json'))[env] +const config = require(path.resolve(__dirname, '../config/config.json'))['development'] const db = {} let sequelize diff --git a/seeders/20230605074630-users-seed-file.js b/seeders/20230605074630-users-seed-file.js index c3b3a5485c..b656e26d6b 100644 --- a/seeders/20230605074630-users-seed-file.js +++ b/seeders/20230605074630-users-seed-file.js @@ -13,8 +13,8 @@ module.exports = { introduction: faker.lorem.text(), role: true, account: 'root', - createdAt: new Date(), - updatedAt: new Date() + created_at: new Date(), + updated_at: new Date() }, { email: 'user1@example.com', @@ -24,8 +24,8 @@ module.exports = { introduction: faker.lorem.text(), role: false, account: 'user1', - createdAt: new Date(), - updatedAt: new Date() + created_at: new Date(), + updated_at: new Date() }, { email: 'user2@example.com', password: await bcrypt.hash('12345678', 10), @@ -34,8 +34,8 @@ module.exports = { introduction: faker.lorem.text(), role: false, account: 'user2', - createdAt: new Date(), - updatedAt: new Date() + created_at: new Date(), + updated_at: new Date() }, { email: 'user3@example.com', password: await bcrypt.hash('12345678', 10), @@ -44,8 +44,8 @@ module.exports = { introduction: faker.lorem.text(), role: false, account: 'user3', - createdAt: new Date(), - updatedAt: new Date() + created_at: new Date(), + updated_at: new Date() }, { email: 'user4@example.com', password: await bcrypt.hash('12345678', 10), @@ -54,8 +54,8 @@ module.exports = { introduction: faker.lorem.text(), role: false, account: 'user4', - createdAt: new Date(), - updatedAt: new Date() + created_at: new Date(), + updated_at: new Date() }, { email: 'user5@example.com', password: await bcrypt.hash('12345678', 10), @@ -64,8 +64,8 @@ module.exports = { introduction: faker.lorem.text(), role: false, account: 'user5', - createdAt: new Date(), - updatedAt: new Date() + created_at: new Date(), + updated_at: new Date() } ]) }, From 2d759b8ff168998575f72cbbeed509c85f2dab21 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Mon, 5 Jun 2023 22:41:26 +0800 Subject: [PATCH 009/131] remove login user password in json --- app.js | 2 +- controllers/apis/user-controller.js | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app.js b/app.js index b0bf6b5952..8e6ca31163 100644 --- a/app.js +++ b/app.js @@ -38,7 +38,7 @@ app.use((req, res, next) => { }) // routes -app.use(apis) +app.use('/apis', apis) app.listen(port, () => console.log(`Example app listening on http://localhost:${port}`)) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 6f142bebff..9ac626ddfa 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -3,10 +3,9 @@ const { getUser } = require('../../_helpers') const userController = { login: async (req, res, next) => { try { - const userData = getUser(req) - console.log(userData) - // if(!user) return res.json({status: 'failed'}) - res.json({ status: 'success', data: 123 }) + const userData = getUser(req).toJSON() + delete userData.password + res.json({ status: 'success', data: userData }) } catch (error) { next(error) } From 9389fad7e0d14c9683a68a029ae0c4d38fd4c93f Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Mon, 5 Jun 2023 23:33:38 +0800 Subject: [PATCH 010/131] feat: add jwt and error-handler.js --- app.js | 2 +- controllers/apis/user-controller.js | 19 ++++-- middleware/error-handler.js | 16 +++++ package-lock.json | 100 ++++++++++++++++++++++++++++ package.json | 2 + routes/apis/index.js | 5 +- 6 files changed, 137 insertions(+), 7 deletions(-) create mode 100644 middleware/error-handler.js diff --git a/app.js b/app.js index b0bf6b5952..8e6ca31163 100644 --- a/app.js +++ b/app.js @@ -38,7 +38,7 @@ app.use((req, res, next) => { }) // routes -app.use(apis) +app.use('/apis', apis) app.listen(port, () => console.log(`Example app listening on http://localhost:${port}`)) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 6f142bebff..4f014a9203 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -1,17 +1,26 @@ const { getUser } = require('../../_helpers') +const jwt = require('jsonwebtoken') const userController = { - login: async (req, res, next) => { + login: (req, res, next) => { try { - const userData = getUser(req) - console.log(userData) + const userData = getUser(req).toJSON() + delete userData.password // if(!user) return res.json({status: 'failed'}) - res.json({ status: 'success', data: 123 }) + const token = jwt.sign(userData, process.env.JWT_SECRET, { expiresIn: '30d' }) // 簽發JWT,效期為30天 + res.json({ + status: 'success', + data: { + token, user: userData + } + }) } catch (error) { next(error) } }, - signUp: (req, res, next) => { } + signUp: (req, res, next) => { + + } } module.exports = userController diff --git a/middleware/error-handler.js b/middleware/error-handler.js new file mode 100644 index 0000000000..360dbdf84f --- /dev/null +++ b/middleware/error-handler.js @@ -0,0 +1,16 @@ +module.exports = { + apiErrorHandler (err, req, res, next) { + if (err instanceof Error) { + res.status(err.status || 500).json({ + status: 'error', + message: `${err.name}: ${err.message}` + }) + } else { + res.status(500).json({ + status: 'error', + message: `${err}` + }) + } + next(err) + } +} diff --git a/package-lock.json b/package-lock.json index 5e92dd27f4..707199fbb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -979,6 +979,11 @@ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -1411,6 +1416,14 @@ "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==" }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "editorconfig": { "version": "0.15.3", "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", @@ -3040,11 +3053,54 @@ "graceful-fs": "^4.1.6" } }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, "just-extend": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==" }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "keyv": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", @@ -3090,12 +3146,47 @@ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, "log-symbols": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", @@ -3751,6 +3842,15 @@ "pause": "0.0.1" } }, + "passport-jwt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.0.tgz", + "integrity": "sha512-BwC0n2GP/1hMVjR4QpnvqA61TxenUMlmfNjYNgK0ZAs0HK4SOQkHcSv4L328blNTLtHq7DbmvyNJiH+bn6C5Mg==", + "requires": { + "jsonwebtoken": "^8.2.0", + "passport-strategy": "^1.0.0" + } + }, "passport-local": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", diff --git a/package.json b/package.json index f410cb2cff..f73717e092 100644 --- a/package.json +++ b/package.json @@ -23,11 +23,13 @@ "express": "^4.16.4", "express-session": "^1.15.6", "faker": "^5.5.3", + "jsonwebtoken": "^8.5.1", "method-override": "^3.0.0", "mocha": "^6.0.2", "mysql2": "^1.6.4", "nodemon": "^2.0.12", "passport": "^0.4.0", + "passport-jwt": "^4.0.0", "passport-local": "^1.0.0", "sequelize": "^6.18.0", "sequelize-cli": "^5.5.0", diff --git a/routes/apis/index.js b/routes/apis/index.js index a9dfd4f0fd..cd4cbbd44b 100644 --- a/routes/apis/index.js +++ b/routes/apis/index.js @@ -2,6 +2,7 @@ const router = require('express').Router() const passport = require('../../config/passport') const admin = require('./modules/admin') const userController = require('../../controllers/apis/user-controller') +const { apiErrorHandler } = require('../../middleware/error-handler') // 測試postman 是否正常運作 router.get('/', (req, res) => { @@ -12,6 +13,8 @@ router.get('/', (req, res) => { router.use('/admin', admin) router.post('/signup', userController.signUp) -router.post('/login', passport.authenticate('local'), userController.login) // 缺少session +router.post('/login', passport.authenticate('local', { session: false }), userController.login) // 缺少session + +router.use('/', apiErrorHandler) module.exports = router From 19d1fd0da7e70b72206f1ac69e20fb5a0881a9c4 Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Mon, 5 Jun 2023 23:56:58 +0800 Subject: [PATCH 011/131] fix: remove the unwanted path --- app.js | 2 +- test/models/Tweet.spec.js | 48 ++++++------ test/requests/tweet.spec.js | 144 +++++++++++++++++------------------- 3 files changed, 92 insertions(+), 102 deletions(-) diff --git a/app.js b/app.js index 8e6ca31163..b2bc16288b 100644 --- a/app.js +++ b/app.js @@ -38,7 +38,7 @@ app.use((req, res, next) => { }) // routes -app.use('/apis', apis) +app.use('/api', apis) app.listen(port, () => console.log(`Example app listening on http://localhost:${port}`)) diff --git a/test/models/Tweet.spec.js b/test/models/Tweet.spec.js index 5335b84bdd..fbec60f20f 100644 --- a/test/models/Tweet.spec.js +++ b/test/models/Tweet.spec.js @@ -1,7 +1,7 @@ -const chai = require('chai'); -const sinon = require('sinon'); -const proxyquire = require('proxyquire'); -chai.use(require('sinon-chai')); +const chai = require('chai') +const sinon = require('sinon') +const proxyquire = require('proxyquire') +chai.use(require('sinon-chai')) const { expect } = require('chai') const { @@ -37,8 +37,8 @@ describe('# Tweet Model', () => { it('called Tweet.init with the correct parameters', () => { expect(Tweet.init).to.have.been.calledWithMatch( { - description: DataTypes.TEXT, - }, + description: DataTypes.TEXT + } ) }) }) @@ -55,17 +55,17 @@ describe('# Tweet Model', () => { Tweet.associate({ User }) }) - it('should have many replies', (done) => { + it('should have many replies', done => { // 檢查是否有呼叫 hasMany(Reply) expect(Tweet.hasMany).to.have.been.calledWith(Reply) done() }) - it('should have many likes', (done) => { + it('should have many likes', done => { // 檢查是否有呼叫 hasMany(Like) expect(Tweet.hasMany).to.have.been.calledWith(Like) done() }) - it('should belong to user', (done) => { + it('should belong to user', done => { // 檢查是否有呼叫 belongsTo(User) expect(Tweet.belongsTo).to.have.been.calledWith(User) done() @@ -74,40 +74,38 @@ describe('# Tweet Model', () => { // // 檢查 model 的新增、修改、刪除、更新 context('action', () => { - let data = null // 檢查 db.Tweet 是否真的可以新增一筆資料 - it('create', (done) => { - db.Tweet.create({UserId: 1, description: 'hi'}).then((tweet) => { + it('create', done => { + db.Tweet.create({ UserId: 1, description: 'hi' }).then(tweet => { data = tweet done() }) }) // 檢查 db.Tweet 是否真的可以讀取一筆資料 - it('read', (done) => { - db.Tweet.findByPk(data.id).then((tweet) => { + it('read', done => { + db.Tweet.findByPk(data.id).then(tweet => { expect(data.id).to.be.equal(tweet.id) - done() - }) + done() + }) }) // 檢查 db.Tweet 是否真的可以更新一筆資料 - it('update', (done) => { - db.Tweet.update({}, { where: { id: data.id }}).then(() => { - db.Tweet.findByPk(data.id).then((tweet) => { - expect(data.updatedAt).to.be.not.equal(tweet.updatedAt) + it('update', done => { + db.Tweet.update({}, { where: { id: data.id } }).then(() => { + db.Tweet.findByPk(data.id).then(tweet => { + expect(data.updatedAt).to.be.not.equal(tweet.updatedAt) done() }) }) }) // 檢查 db.Tweet 是否真的可以刪除一筆資料 - it('delete', (done) => { - db.Tweet.destroy({ where: { id: data.id }}).then(() => { - db.Tweet.findByPk(data.id).then((tweet) => { - expect(tweet).to.be.equal(null) + it('delete', done => { + db.Tweet.destroy({ where: { id: data.id } }).then(() => { + db.Tweet.findByPk(data.id).then(tweet => { + expect(tweet).to.be.equal(null) done() }) }) }) }) - }) diff --git a/test/requests/tweet.spec.js b/test/requests/tweet.spec.js index 1976d7d5b9..bf03fc8bbf 100644 --- a/test/requests/tweet.spec.js +++ b/test/requests/tweet.spec.js @@ -2,130 +2,122 @@ const chai = require('chai') const request = require('supertest') const sinon = require('sinon') const app = require('../../app') -const helpers = require('../../_helpers'); -const should = chai.should(); -const expect = chai.expect; +const helpers = require('../../_helpers') +const should = chai.should() +const expect = chai.expect const db = require('../../models') const passport = require('../../config/passport') describe('# tweet requests', () => { - context('# POST ', () => { - describe('POST /api/tweets', () => { - before(async() => { + before(async () => { // 清除測試資料庫資料 - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.Tweet.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.Tweet.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) // 模擬登入資料 - const rootUser = await db.User.create({name: 'root'});this.authenticate = sinon.stub(passport,"authenticate").callsFake((strategy, options, callback) => { - callback(null, {...rootUser}, null); - return (req,res,next)=>{}; - }); + const rootUser = await db.User.create({ name: 'root' }); this.authenticate = sinon.stub(passport, 'authenticate').callsFake((strategy, options, callback) => { + callback(null, { ...rootUser }, null) + return (req, res, next) => {} + }) this.getUser = sinon.stub( - helpers, 'getUser' - ).returns({id: 1, Followings: [], role: 'user'}); + helpers, 'getUser' + ).returns({ id: 1, Followings: [], role: 'user' }) // 在測試資料庫中,新增 mock 資料 - await db.User.create({account: 'User1', name: 'User1', email: 'User1', password: 'User1'}) + await db.User.create({ account: 'User1', name: 'User1', email: 'User1', password: 'User1' }) }) // 新增推文 - POST /tweets - it(' - successfully', (done) => { + it(' - successfully', done => { request(app) .post('/api/tweets') .send('description=description') .set('Accept', 'application/json') .expect(200) - .end(function(err, res) { - if (err) return done(err); + .end(function (err, res) { + if (err) return done(err) // 檢查是否有回傳正確資料 db.Tweet.findByPk(1).then(tweet => { - tweet.description.should.equal('description'); - tweet.UserId.should.equal(1); - return done(); + tweet.description.should.equal('description') + tweet.UserId.should.equal(1) + return done() }) }) - }); + }) after(async () => { - this.authenticate.restore(); - this.getUser.restore(); - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.Tweet.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + this.authenticate.restore() + this.getUser.restore() + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.Tweet.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) }) - - }); - - }); + }) + }) context('# GET ', () => { - describe('GET /api/tweets', () => { - before(async() => { + before(async () => { // 清除測試資料庫資料 - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.Tweet.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.Tweet.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) // 模擬登入資料 - const rootUser = await db.User.create({name: 'root'});this.authenticate = sinon.stub(passport,"authenticate").callsFake((strategy, options, callback) => { - callback(null, {...rootUser}, null); - return (req,res,next)=>{}; - }); + const rootUser = await db.User.create({ name: 'root' }); this.authenticate = sinon.stub(passport, 'authenticate').callsFake((strategy, options, callback) => { + callback(null, { ...rootUser }, null) + return (req, res, next) => {} + }) this.getUser = sinon.stub( - helpers, 'getUser' - ).returns({id: 1, Followings: [], role: 'user'}); + helpers, 'getUser' + ).returns({ id: 1, Followings: [], role: 'user' }) // 在測試資料庫中,新增 mock 資料 - await db.User.create({account: 'User1', name: 'User1', email: 'User1', password: 'User1'}) - await db.Tweet.create({UserId: 1, description: 'User1 的 Tweet1'}) - await db.Reply.create({UserId: 1, TweetId: 1, comment: 'Tweet1 的 comment'}) + await db.User.create({ account: 'User1', name: 'User1', email: 'User1', password: 'User1' }) + await db.Tweet.create({ UserId: 1, description: 'User1 的 Tweet1' }) + await db.Reply.create({ UserId: 1, TweetId: 1, comment: 'Tweet1 的 comment' }) }) // GET /tweets - 所有推文,包括推文作者 - it(' - successfully', (done) => { + it(' - successfully', done => { request(app) .get('/api/tweets') .set('Accept', 'application/json') .expect(200) - .end(function(err, res) { - if (err) return done(err); - expect(res.body).to.be.an('array'); + .end(function (err, res) { + if (err) return done(err) + expect(res.body).to.be.an('array') // 檢查是否回傳資料有 User1 的 Tweet1 - res.body[0].description.should.equal('User1 的 Tweet1'); - return done(); + res.body[0].description.should.equal('User1 的 Tweet1') + return done() }) - }); + }) // GET /tweets/:tweet_id - 一筆推文 - it(' - successfully', (done) => { + it(' - successfully', done => { request(app) .get('/api/tweets/1') .set('Accept', 'application/json') .expect(200) - .end(function(err, res) { - if (err) return done(err); - expect(res.body).to.be.an('object'); - // 檢查是否回傳資料有 User1 的 Tweet1 - res.body.description.should.equal('User1 的 Tweet1'); - return done(); + .end(function (err, res) { + if (err) return done(err) + expect(res.body).to.be.an('object') + // 檢查是否回傳資料有 User1 的 Tweet1 + res.body.description.should.equal('User1 的 Tweet1') + return done() }) - }); + }) after(async () => { - this.authenticate.restore(); - this.getUser.restore(); - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.Tweet.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + this.authenticate.restore() + this.getUser.restore() + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.Tweet.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) }) - - }); - - }); - -}); + }) + }) +}) From 56bbb96ad751931abe9c6945377913b063bde533 Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Tue, 6 Jun 2023 00:39:34 +0800 Subject: [PATCH 012/131] feat: add seeder for tweets --- seeders/20230605161408-tweets-seed-file.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 seeders/20230605161408-tweets-seed-file.js diff --git a/seeders/20230605161408-tweets-seed-file.js b/seeders/20230605161408-tweets-seed-file.js new file mode 100644 index 0000000000..bda3b47c08 --- /dev/null +++ b/seeders/20230605161408-tweets-seed-file.js @@ -0,0 +1,22 @@ +'use strict' +const faker = require('faker') + +module.exports = { + up: async (queryInterface, Sequelize) => { + const users = await queryInterface.sequelize.query( + 'SELECT id FROM Users;', + { type: queryInterface.sequelize.QueryTypes.SELECT } + ) + await queryInterface.bulkInsert('Tweets', + Array.from({ length: 50 }, () => ({ + description: faker.lorem.text(), + created_at: new Date(), + updated_at: new Date(), + user_id: users[Math.floor(Math.random() * users.length)].id + }))) + }, + + down: async (queryInterface, Sequelize) => { + await queryInterface.bulkDelete('Tweets', {}) + } +} From b9f44c34e63e2c1d64729d63fc02c4c61e9ba230 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Tue, 6 Jun 2023 15:26:58 +0800 Subject: [PATCH 013/131] add signup & login fuinction and revise relationship of models --- app.js | 4 +- config/passport.js | 83 ++++++++------- controllers/apis/user-controller.js | 45 +++++++- middleware/error-handler.js | 16 +++ migrations/20190115071421-create-user.js | 10 +- .../20230606043259-add-background-to-User.js | 14 +++ models/index.js | 2 +- models/like.js | 2 - models/reply.js | 3 +- models/tweet.js | 12 ++- models/user.js | 15 ++- package-lock.json | 100 ++++++++++++++++++ package.json | 8 +- routes/apis/index.js | 5 +- seeders/20230605074630-users-seed-file.js | 12 +-- 15 files changed, 263 insertions(+), 68 deletions(-) create mode 100644 middleware/error-handler.js create mode 100644 migrations/20230606043259-add-background-to-User.js diff --git a/app.js b/app.js index 8e6ca31163..f0d464e02c 100644 --- a/app.js +++ b/app.js @@ -30,7 +30,7 @@ app.use(passport.session()) // flash app.use(flash()) -// // locals +// locals app.use((req, res, next) => { res.locals.error_messages = req.flash('error_messages') res.locals.user = getUser(req) @@ -38,7 +38,7 @@ app.use((req, res, next) => { }) // routes -app.use('/apis', apis) +app.use('/api', apis) app.listen(port, () => console.log(`Example app listening on http://localhost:${port}`)) diff --git a/config/passport.js b/config/passport.js index 30217eb6d7..5fa1bcfa72 100644 --- a/config/passport.js +++ b/config/passport.js @@ -1,47 +1,54 @@ const passport = require('passport') const LocalStrategy = require('passport-local').Strategy +const passportJWT = require('passport-jwt') +const JWTStrategy = passportJWT.Strategy +const ExtractJWT = passportJWT.ExtractJwt const bcrypt = require('bcryptjs') -const { User } = require('../models') +const { User, Tweet } = require('../models') // LocalStrategy Setting +passport.use(new LocalStrategy( + ({ usernameField: 'account', passwordField: 'password', passReqToCallback: true }), + async (req, account, password, cb) => { + try { + const user = await User.findOne({ where: { account } }) -// passport.use(new LocalStrategy( -// ({ usernameField: 'account', passwordField: 'password', passReqToCallback: true }), -// async (req, account, password, cb) => { -// try { -// const users = await User.findAll() -// console.log(users) -// const user = await User.findOne({ where: { account } }) -// if (!user) { -// console.log('帳號或密碼輸入錯誤!') -// return cb(null, false, req.flash('error_messages', '帳號或密碼輸入錯誤!')) -// } -// const isMatch = await bcrypt.compare(password, user.password) -// if (!isMatch) return cb(null, false, req.flash('error_messages', '帳號或密碼輸入錯誤!')) -// return cb(null, user) -// } catch (error) { -// cb(error) -// } -// } -// )) - -passport.use(new LocalStrategy(({ - usernameField: 'account', - passwordField: 'password', - passReqToCallback: true -}), async (req, account, password, done) => { - try { - // email error - const user = await User.findOne({ where: { account } }) - if (!user) return done(null, false, req.flash('error_messages', 'Account or password entered incorrectly!')) - // password error - const isMatch = await bcrypt.compare(password, user.password) - if (!isMatch) return done(null, false, req.flash('error_messages', 'Account or password entered incorrectly!')) - // Login successful - return done(null, user) - } catch (error) { - console.log(error) + // 帳號或密碼輸入錯誤 暫時的錯誤處理 + if (!user) { + console.log('帳號或密碼輸入錯誤!') + return cb(null, false, req.flash('error_messages', '帳號或密碼輸入錯誤!')) + } + const isMatch = await bcrypt.compare(password, user.password) + + // 帳號或密碼輸入錯誤 暫時的錯誤處理 + if (!isMatch) { + console.log('帳號或密碼輸入錯誤!') + return cb(null, false, req.flash('error_messages', '帳號或密碼輸入錯誤!')) + } + return cb(null, user) + } catch (error) { + cb(error) + } } +)) + +// JWTStrategy Setting +const jwtOptions = { jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(), secretOrKey: process.env.JWT_SECRET } + +passport.use(new JWTStrategy(jwtOptions, (jwtPayload, cb) => { + User.findByPk(jwtPayload.id, { + include: [ + // join table Like + { model: Tweet, as: 'LikedUsers' }, + { model: User, as: 'LikedTweets' }, + // join table Reply + { model: Tweet, as: 'RepliedUsers' }, + { model: User, as: 'RepliedTweets' }, + // join table FollowShip + { model: User, as: 'Followers' }, + { model: User, as: 'Followings' } + ] + }) })) // passport serializeUser & deserializeUser @@ -49,7 +56,7 @@ passport.serializeUser((user, cb) => cb(null, user.id)) passport.deserializeUser(async (id, cb) => { try { - let user = await User.findById(id) + let user = await User.findByPk(id) user = user.toJSON() console.log(user) return cb(null, user) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 9ac626ddfa..195cfccec9 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -1,16 +1,55 @@ +const jwt = require('jsonwebtoken') +const bcrypt = require('bcryptjs') const { getUser } = require('../../_helpers') +const { User } = require('../../models') const userController = { - login: async (req, res, next) => { + login: (req, res, next) => { try { const userData = getUser(req).toJSON() delete userData.password - res.json({ status: 'success', data: userData }) + // if(!user) return res.json({status: 'failed'}) + const token = jwt.sign(userData, process.env.JWT_SECRET, { expiresIn: '30d' }) + res.json({ + status: 'success', + data: { + token, user: userData + } + }) } catch (error) { next(error) } }, - signUp: (req, res, next) => { } + signUp: async (req, res, next) => { + try { + const errors = [] + const { name, account, email, password, checkPassword } = req.body + // 有欄位沒有填寫 暫時的錯誤處理 + if (!name || !account || !email || !password || !checkPassword) errors.push('每個欄位都必填') + // 密碼與確認密碼不一致 + if (password !== checkPassword) errors.push('密碼與確認密碼不一致') + // 確認account與email是否與資料庫重複 + const [userAccount, userEmail] = await Promise.all([ + User.findOne({ where: { account } }), + User.findOne({ where: { email } }) + ]) + if (userAccount) errors.push('account已存在') + if (userEmail) errors.push('email已存在') + if (errors.length) return res.json({ status: 'failed', data: errors }) + const user = await User.create({ + name, + account, + email, + password: bcrypt.hashSync(password, bcrypt.genSaltSync(10), null) + }) + const userData = user.toJSON() + delete userData.password + console.log(userData) + return res.json({ status: 'success', data: userData }) + } catch (error) { + next(error) + } + } } module.exports = userController diff --git a/middleware/error-handler.js b/middleware/error-handler.js new file mode 100644 index 0000000000..360dbdf84f --- /dev/null +++ b/middleware/error-handler.js @@ -0,0 +1,16 @@ +module.exports = { + apiErrorHandler (err, req, res, next) { + if (err instanceof Error) { + res.status(err.status || 500).json({ + status: 'error', + message: `${err.name}: ${err.message}` + }) + } else { + res.status(500).json({ + status: 'error', + message: `${err}` + }) + } + next(err) + } +} diff --git a/migrations/20190115071421-create-user.js b/migrations/20190115071421-create-user.js index 79ecddf277..d14b2ced04 100644 --- a/migrations/20190115071421-create-user.js +++ b/migrations/20190115071421-create-user.js @@ -21,16 +21,16 @@ module.exports = { type: Sequelize.STRING }, avatar: { - allowNull: false, - type: Sequelize.STRING + type: Sequelize.STRING, + defaultValue: `https://loremflickr.com/320/240/person/?random=${Math.random() * 100}` }, introduction: { - allowNull: false, + allowNull: true, type: Sequelize.TEXT }, role: { - allowNull: false, - type: Sequelize.STRING + type: Sequelize.BOOLEAN, + defaultValue: false }, created_at: { allowNull: false, diff --git a/migrations/20230606043259-add-background-to-User.js b/migrations/20230606043259-add-background-to-User.js new file mode 100644 index 0000000000..566995f336 --- /dev/null +++ b/migrations/20230606043259-add-background-to-User.js @@ -0,0 +1,14 @@ +'use strict' + +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.addColumn('Users', 'background', { + type: Sequelize.STRING, + defaultValue: 'https://images.pexels.com/photos/12993530/pexels-photo-12993530.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1' + }) + }, + + down: async (queryInterface, Sequelize) => { + await queryInterface.removeColumn('Users', 'background') + } +} diff --git a/models/index.js b/models/index.js index c7e3ab2264..427848cb0b 100644 --- a/models/index.js +++ b/models/index.js @@ -5,7 +5,7 @@ const path = require('path') const Sequelize = require('sequelize') const basename = path.basename(__filename) const env = process.env.NODE_ENV || 'development' -const config = require(path.resolve(__dirname, '../config/config.json'))['development'] +const config = require(path.resolve(__dirname, '../config/config.json'))[env] const db = {} let sequelize diff --git a/models/like.js b/models/like.js index 9dd701352c..1b8914a302 100644 --- a/models/like.js +++ b/models/like.js @@ -3,8 +3,6 @@ module.exports = (sequelize, DataTypes) => { const Like = sequelize.define('Like', { }, {}) Like.associate = function (models) { - Like.belongsTo(models.Tweet, { foreignKey: 'TweetId' }) - Like.belongsTo(models.User, { foreignKey: 'UserId' }) } Like.init({ UserId: DataTypes.INTEGER, diff --git a/models/reply.js b/models/reply.js index 300eeef677..595c132c51 100644 --- a/models/reply.js +++ b/models/reply.js @@ -3,8 +3,7 @@ module.exports = (sequelize, DataTypes) => { const Reply = sequelize.define('Reply', { }, {}) Reply.associate = function (models) { - Reply.belongsTo(models.Tweet, { foreignKey: 'TweetId' }) - Reply.belongsTo(models.User, { foreignKey: 'UserId' }) + } Reply.init({ UserId: DataTypes.INTEGER, diff --git a/models/tweet.js b/models/tweet.js index c12b2df716..0f2f2642ea 100644 --- a/models/tweet.js +++ b/models/tweet.js @@ -4,8 +4,16 @@ module.exports = (sequelize, DataTypes) => { }, {}) Tweet.associate = function (models) { Tweet.belongsTo(models.User, { foreignKey: 'UserId' }) - Tweet.hasMany(models.Reply, { foreignKey: 'TweetId' }) - Tweet.hasMany(models.Like, { foreignKey: 'TweetId' }) + Tweet.belongsToMany(models.User, { + through: models.Reply, + foreignKey: 'TweetId', + as: 'RepliedUsers' + }) + Tweet.belongsToMany(models.User, { + through: models.Like, + foreignKey: 'TweetId', + as: 'LikedUsers' + }) } Tweet.init({ UserId: DataTypes.INTEGER, diff --git a/models/user.js b/models/user.js index beb4ff6a34..9228b165e9 100644 --- a/models/user.js +++ b/models/user.js @@ -4,8 +4,16 @@ module.exports = (sequelize, DataTypes) => { }, {}) User.associate = function (models) { User.hasMany(models.Tweet, { foreignKey: 'UserId' }) - User.hasMany(models.Reply, { foreignKey: 'UserId' }) - User.hasMany(models.Like, { foreignKey: 'UserId' }) + User.belongsToMany(models.Tweet, { + through: models.Reply, + foreignKey: 'UserId', + as: 'RepliedTweets' + }) + User.belongsToMany(models.Tweet, { + through: models.Like, + foreignKey: 'UserId', + as: 'LikedTweets' + }) User.belongsToMany(User, { through: models.Followship, foreignKey: 'followingId', @@ -24,7 +32,8 @@ module.exports = (sequelize, DataTypes) => { avatar: DataTypes.STRING, introduction: DataTypes.TEXT, role: DataTypes.BOOLEAN, - account: DataTypes.STRING + account: DataTypes.STRING, + background: DataTypes.STRING }, { sequelize, modelName: 'User', diff --git a/package-lock.json b/package-lock.json index 5e92dd27f4..707199fbb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -979,6 +979,11 @@ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -1411,6 +1416,14 @@ "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==" }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "editorconfig": { "version": "0.15.3", "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", @@ -3040,11 +3053,54 @@ "graceful-fs": "^4.1.6" } }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, "just-extend": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==" }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "keyv": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", @@ -3090,12 +3146,47 @@ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, "log-symbols": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", @@ -3751,6 +3842,15 @@ "pause": "0.0.1" } }, + "passport-jwt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.0.tgz", + "integrity": "sha512-BwC0n2GP/1hMVjR4QpnvqA61TxenUMlmfNjYNgK0ZAs0HK4SOQkHcSv4L328blNTLtHq7DbmvyNJiH+bn6C5Mg==", + "requires": { + "jsonwebtoken": "^8.2.0", + "passport-strategy": "^1.0.0" + } + }, "passport-local": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", diff --git a/package.json b/package.json index f410cb2cff..b2d2dac124 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ "start": "NODE_ENV=development node app.js", "dev": "NODE_ENV=development nodemon app.js", "test": "mocha test --exit --recursive --timeout 5000", - "start1": "set NODE_ENV=development && nodemon app.js", - "dev1": "set NODE_ENV=development && nodemon app.js", + "start1": "node app.js", + "dev1": "nodemon app.js", "test1": "set NODE_ENV=test && mocha test --exit --recursive --timeout 5000" }, "author": "", @@ -23,11 +23,13 @@ "express": "^4.16.4", "express-session": "^1.15.6", "faker": "^5.5.3", + "jsonwebtoken": "^8.5.1", "method-override": "^3.0.0", "mocha": "^6.0.2", "mysql2": "^1.6.4", "nodemon": "^2.0.12", "passport": "^0.4.0", + "passport-jwt": "^4.0.0", "passport-local": "^1.0.0", "sequelize": "^6.18.0", "sequelize-cli": "^5.5.0", @@ -44,4 +46,4 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^5.1.0" } -} +} \ No newline at end of file diff --git a/routes/apis/index.js b/routes/apis/index.js index a9dfd4f0fd..63fdd7ba0a 100644 --- a/routes/apis/index.js +++ b/routes/apis/index.js @@ -2,6 +2,7 @@ const router = require('express').Router() const passport = require('../../config/passport') const admin = require('./modules/admin') const userController = require('../../controllers/apis/user-controller') +const { apiErrorHandler } = require('../../middleware/error-handler') // 測試postman 是否正常運作 router.get('/', (req, res) => { @@ -12,6 +13,8 @@ router.get('/', (req, res) => { router.use('/admin', admin) router.post('/signup', userController.signUp) -router.post('/login', passport.authenticate('local'), userController.login) // 缺少session +router.post('/login', passport.authenticate('local', { session: false }), userController.login) + +router.use('/', apiErrorHandler) module.exports = router diff --git a/seeders/20230605074630-users-seed-file.js b/seeders/20230605074630-users-seed-file.js index b656e26d6b..387e572742 100644 --- a/seeders/20230605074630-users-seed-file.js +++ b/seeders/20230605074630-users-seed-file.js @@ -9,7 +9,7 @@ module.exports = { email: 'root@example.com', password: await bcrypt.hash('12345678', 10), name: 'root', - avatar: `https://loremflickr.com/320/240/restaurant,food/?random=${Math.random() * 100}`, + avatar: `https://loremflickr.com/320/240/person/?random=${Math.random() * 100}`, introduction: faker.lorem.text(), role: true, account: 'root', @@ -20,7 +20,7 @@ module.exports = { email: 'user1@example.com', password: await bcrypt.hash('12345678', 10), name: 'user1', - avatar: `https://loremflickr.com/320/240/restaurant,food/?random=${Math.random() * 100}`, + avatar: `https://loremflickr.com/320/240/person/?random=${Math.random() * 100}`, introduction: faker.lorem.text(), role: false, account: 'user1', @@ -30,7 +30,7 @@ module.exports = { email: 'user2@example.com', password: await bcrypt.hash('12345678', 10), name: 'user2', - avatar: `https://loremflickr.com/320/240/restaurant,food/?random=${Math.random() * 100}`, + avatar: `https://loremflickr.com/320/240/person/?random=${Math.random() * 100}`, introduction: faker.lorem.text(), role: false, account: 'user2', @@ -40,7 +40,7 @@ module.exports = { email: 'user3@example.com', password: await bcrypt.hash('12345678', 10), name: 'user3', - avatar: `https://loremflickr.com/320/240/restaurant,food/?random=${Math.random() * 100}`, + avatar: `https://loremflickr.com/320/240/person/?random=${Math.random() * 100}`, introduction: faker.lorem.text(), role: false, account: 'user3', @@ -50,7 +50,7 @@ module.exports = { email: 'user4@example.com', password: await bcrypt.hash('12345678', 10), name: 'user4', - avatar: `https://loremflickr.com/320/240/restaurant,food/?random=${Math.random() * 100}`, + avatar: `https://loremflickr.com/320/240/person/?random=${Math.random() * 100}`, introduction: faker.lorem.text(), role: false, account: 'user4', @@ -60,7 +60,7 @@ module.exports = { email: 'user5@example.com', password: await bcrypt.hash('12345678', 10), name: 'user5', - avatar: `https://loremflickr.com/320/240/restaurant,food/?random=${Math.random() * 100}`, + avatar: `https://loremflickr.com/320/240/person/?random=${Math.random() * 100}`, introduction: faker.lorem.text(), role: false, account: 'user5', From dc485bbf60c39d971c683f6ee7cdbd5e332b9c64 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Tue, 6 Jun 2023 16:49:51 +0800 Subject: [PATCH 014/131] add logout function --- app.js | 3 ++- controllers/pages/user-controller.js | 11 +++++++++++ routes/index.js | 3 ++- routes/pages/index.js | 6 ++++++ 4 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 controllers/pages/user-controller.js create mode 100644 routes/pages/index.js diff --git a/app.js b/app.js index f0d464e02c..e914f64e9d 100644 --- a/app.js +++ b/app.js @@ -7,7 +7,7 @@ const { getUser } = require('./_helpers') const passport = require('./config/passport') const session = require('express-session') const flash = require('connect-flash') -const { apis } = require('./routes') +const { apis, pages } = require('./routes') const app = express() const port = 3000 @@ -39,6 +39,7 @@ app.use((req, res, next) => { // routes app.use('/api', apis) +app.use('/', pages) app.listen(port, () => console.log(`Example app listening on http://localhost:${port}`)) diff --git a/controllers/pages/user-controller.js b/controllers/pages/user-controller.js new file mode 100644 index 0000000000..becee403fc --- /dev/null +++ b/controllers/pages/user-controller.js @@ -0,0 +1,11 @@ +const userController = { + logout: (req, res, next) => { + try { + req.logout() + res.json({ status: 'success' }) + } catch (error) { + next(error) + } + } +} +module.exports = userController diff --git a/routes/index.js b/routes/index.js index b17f5d06e5..066f0f224e 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,3 +1,4 @@ const apis = require('./apis') +const pages = require('./pages') -module.exports = { apis } +module.exports = { apis, pages } diff --git a/routes/pages/index.js b/routes/pages/index.js new file mode 100644 index 0000000000..460792c568 --- /dev/null +++ b/routes/pages/index.js @@ -0,0 +1,6 @@ +const router = require('express').Router() +const userController = require('../../controllers/pages/user-controller') + +router.get('/logout', userController.logout) + +module.exports = router From d78fa7e6de79a9710a08239ce53ab1e555814c55 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Tue, 6 Jun 2023 18:04:09 +0800 Subject: [PATCH 015/131] add adminLogin feature --- controllers/apis/admin-controller.js | 16 ++++++++++++++++ migrations/20190115071421-create-user.js | 6 ++++-- models/like.js | 2 ++ models/reply.js | 3 ++- models/tweet.js | 2 ++ models/user.js | 2 ++ package.json | 3 +-- routes/apis/modules/admin.js | 3 +++ seeders/20230605074630-users-seed-file.js | 12 ++++++------ 9 files changed, 38 insertions(+), 11 deletions(-) create mode 100644 controllers/apis/admin-controller.js diff --git a/controllers/apis/admin-controller.js b/controllers/apis/admin-controller.js new file mode 100644 index 0000000000..94b7dab82c --- /dev/null +++ b/controllers/apis/admin-controller.js @@ -0,0 +1,16 @@ +const { User } = require('../../models') + +const adminController = { + adminLogin: async (req, res, next) => { + const { account } = req.body + const user = await User.findOne({ + where: { account }, + raw: true + }) + if (!user) return res.json({ status: 'failed', data: 'You are not admin' }) + if (user.role !== 'admin') return res.json({ status: 'failed', data: 'You are not admin' }) + return res.json({ status: 'success', data: 'Welcome Back' }) + } +} + +module.exports = adminController diff --git a/migrations/20190115071421-create-user.js b/migrations/20190115071421-create-user.js index d14b2ced04..fd8e296456 100644 --- a/migrations/20190115071421-create-user.js +++ b/migrations/20190115071421-create-user.js @@ -21,6 +21,7 @@ module.exports = { type: Sequelize.STRING }, avatar: { + allowNull: true, type: Sequelize.STRING, defaultValue: `https://loremflickr.com/320/240/person/?random=${Math.random() * 100}` }, @@ -29,8 +30,9 @@ module.exports = { type: Sequelize.TEXT }, role: { - type: Sequelize.BOOLEAN, - defaultValue: false + allowNull: true, + type: Sequelize.STRING, + defaultValue: 'user' }, created_at: { allowNull: false, diff --git a/models/like.js b/models/like.js index 1b8914a302..9dd701352c 100644 --- a/models/like.js +++ b/models/like.js @@ -3,6 +3,8 @@ module.exports = (sequelize, DataTypes) => { const Like = sequelize.define('Like', { }, {}) Like.associate = function (models) { + Like.belongsTo(models.Tweet, { foreignKey: 'TweetId' }) + Like.belongsTo(models.User, { foreignKey: 'UserId' }) } Like.init({ UserId: DataTypes.INTEGER, diff --git a/models/reply.js b/models/reply.js index 595c132c51..300eeef677 100644 --- a/models/reply.js +++ b/models/reply.js @@ -3,7 +3,8 @@ module.exports = (sequelize, DataTypes) => { const Reply = sequelize.define('Reply', { }, {}) Reply.associate = function (models) { - + Reply.belongsTo(models.Tweet, { foreignKey: 'TweetId' }) + Reply.belongsTo(models.User, { foreignKey: 'UserId' }) } Reply.init({ UserId: DataTypes.INTEGER, diff --git a/models/tweet.js b/models/tweet.js index 0f2f2642ea..04449f2f19 100644 --- a/models/tweet.js +++ b/models/tweet.js @@ -4,6 +4,8 @@ module.exports = (sequelize, DataTypes) => { }, {}) Tweet.associate = function (models) { Tweet.belongsTo(models.User, { foreignKey: 'UserId' }) + Tweet.hasMany(models.Reply, { foreignKey: 'TweetId' }) + Tweet.hasMany(models.Like, { foreignKey: 'TweetId' }) Tweet.belongsToMany(models.User, { through: models.Reply, foreignKey: 'TweetId', diff --git a/models/user.js b/models/user.js index 9228b165e9..7ada473b4f 100644 --- a/models/user.js +++ b/models/user.js @@ -4,6 +4,8 @@ module.exports = (sequelize, DataTypes) => { }, {}) User.associate = function (models) { User.hasMany(models.Tweet, { foreignKey: 'UserId' }) + User.hasMany(models.Reply, { foreignKey: 'UserId' }) + User.hasMany(models.Like, { foreignKey: 'UserId' }) User.belongsToMany(models.Tweet, { through: models.Reply, foreignKey: 'UserId', diff --git a/package.json b/package.json index b2d2dac124..4f730d75c1 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,7 @@ "dev": "NODE_ENV=development nodemon app.js", "test": "mocha test --exit --recursive --timeout 5000", "start1": "node app.js", - "dev1": "nodemon app.js", - "test1": "set NODE_ENV=test && mocha test --exit --recursive --timeout 5000" + "dev1": "nodemon app.js" }, "author": "", "license": "ISC", diff --git a/routes/apis/modules/admin.js b/routes/apis/modules/admin.js index 8ff784263b..edb60ef813 100644 --- a/routes/apis/modules/admin.js +++ b/routes/apis/modules/admin.js @@ -1,3 +1,6 @@ const router = require('express').Router() +const adminController = require('../../../controllers/apis/admin-controller') + +router.post('/users', adminController.adminLogin) module.exports = router diff --git a/seeders/20230605074630-users-seed-file.js b/seeders/20230605074630-users-seed-file.js index 387e572742..6d1fc84982 100644 --- a/seeders/20230605074630-users-seed-file.js +++ b/seeders/20230605074630-users-seed-file.js @@ -11,7 +11,7 @@ module.exports = { name: 'root', avatar: `https://loremflickr.com/320/240/person/?random=${Math.random() * 100}`, introduction: faker.lorem.text(), - role: true, + role: 'admin', account: 'root', created_at: new Date(), updated_at: new Date() @@ -22,7 +22,7 @@ module.exports = { name: 'user1', avatar: `https://loremflickr.com/320/240/person/?random=${Math.random() * 100}`, introduction: faker.lorem.text(), - role: false, + role: 'user', account: 'user1', created_at: new Date(), updated_at: new Date() @@ -32,7 +32,7 @@ module.exports = { name: 'user2', avatar: `https://loremflickr.com/320/240/person/?random=${Math.random() * 100}`, introduction: faker.lorem.text(), - role: false, + role: 'user', account: 'user2', created_at: new Date(), updated_at: new Date() @@ -42,7 +42,7 @@ module.exports = { name: 'user3', avatar: `https://loremflickr.com/320/240/person/?random=${Math.random() * 100}`, introduction: faker.lorem.text(), - role: false, + role: 'user', account: 'user3', created_at: new Date(), updated_at: new Date() @@ -52,7 +52,7 @@ module.exports = { name: 'user4', avatar: `https://loremflickr.com/320/240/person/?random=${Math.random() * 100}`, introduction: faker.lorem.text(), - role: false, + role: 'user', account: 'user4', created_at: new Date(), updated_at: new Date() @@ -62,7 +62,7 @@ module.exports = { name: 'user5', avatar: `https://loremflickr.com/320/240/person/?random=${Math.random() * 100}`, introduction: faker.lorem.text(), - role: false, + role: 'user', account: 'user5', created_at: new Date(), updated_at: new Date() From 23195fbeac271eabd3225f20c691ee2257089a26 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Tue, 6 Jun 2023 19:55:23 +0800 Subject: [PATCH 016/131] test --- config/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.json b/config/config.json index 8920098a42..37f889620a 100644 --- a/config/config.json +++ b/config/config.json @@ -34,4 +34,4 @@ "host": "127.0.0.1", "dialect": "mysql" } -} +} \ No newline at end of file From 848c434fad67e1ba5cafb584020074b3f76a5eea Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Tue, 6 Jun 2023 20:51:08 +0800 Subject: [PATCH 017/131] fix: check the relation --- controllers/apis/tweet-controller.js | 21 ++++++++++++ middleware/api-auth.js | 12 +++++++ models/tweet.js | 2 ++ routes/apis/index.js | 6 +++- test/models/Reply.spec.js | 48 +++++++++++++--------------- 5 files changed, 63 insertions(+), 26 deletions(-) create mode 100644 controllers/apis/tweet-controller.js create mode 100644 middleware/api-auth.js diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js new file mode 100644 index 0000000000..227e99f985 --- /dev/null +++ b/controllers/apis/tweet-controller.js @@ -0,0 +1,21 @@ +const { getUser } = require('../../_helpers') +const { Tweet } = require('../../models') + +const tweetController = { + postTweet: async (req, res, next) => { + try { + // const { description } = req.body + const user = getUser(req) + console.log(user) + // const tweet = await Tweet.create({ + // UserId: user.id, + // description + // }) + return res.json({ status: 'success', data: user }) + } catch (error) { + next(error) + } + } +} + +module.exports = tweetController diff --git a/middleware/api-auth.js b/middleware/api-auth.js new file mode 100644 index 0000000000..6eef9e03e0 --- /dev/null +++ b/middleware/api-auth.js @@ -0,0 +1,12 @@ +const passport = require('../config/passport') // 引入 passport + +const authenticated = (req, res, next) => { + passport.authenticate('jwt', { session: false }, (err, user) => { + if (err || !user) return res.status(401).json({ status: 'error', message: 'unauthorized!' }) + next() + })(req, res, next) +} + +module.exports = { + authenticated +} diff --git a/models/tweet.js b/models/tweet.js index 0f2f2642ea..1b7da2a397 100644 --- a/models/tweet.js +++ b/models/tweet.js @@ -14,6 +14,8 @@ module.exports = (sequelize, DataTypes) => { foreignKey: 'TweetId', as: 'LikedUsers' }) + Tweet.hasMany(models.Reply, { foreignKey: 'TweetId' }) + Tweet.hasMany(models.Like, { foreignKey: 'TweetId' }) } Tweet.init({ UserId: DataTypes.INTEGER, diff --git a/routes/apis/index.js b/routes/apis/index.js index b7043152c3..bb81167d92 100644 --- a/routes/apis/index.js +++ b/routes/apis/index.js @@ -3,6 +3,8 @@ const passport = require('../../config/passport') const admin = require('./modules/admin') const userController = require('../../controllers/apis/user-controller') const { apiErrorHandler } = require('../../middleware/error-handler') +const tweetController = require('../../controllers/apis/tweet-controller') +const { authenticated } = require('../../middleware/api-auth') // 測試postman 是否正常運作 router.get('/', (req, res) => { @@ -13,9 +15,11 @@ router.get('/', (req, res) => { router.use('/admin', admin) router.post('/signup', userController.signUp) - router.post('/login', passport.authenticate('local', { session: false }), userController.login) +// 有關tweet的routes +router.post('/tweets', authenticated, tweetController.postTweet) + router.use('/', apiErrorHandler) module.exports = router diff --git a/test/models/Reply.spec.js b/test/models/Reply.spec.js index 33c1ba0443..f85aa0fc9e 100644 --- a/test/models/Reply.spec.js +++ b/test/models/Reply.spec.js @@ -1,7 +1,7 @@ -const chai = require('chai'); -const sinon = require('sinon'); -const proxyquire = require('proxyquire'); -chai.use(require('sinon-chai')); +const chai = require('chai') +const sinon = require('sinon') +const proxyquire = require('proxyquire') +chai.use(require('sinon-chai')) const { expect } = require('chai') const { @@ -37,13 +37,13 @@ describe('# Reply Model', () => { it('called Reply.init with the correct parameters', () => { expect(Reply.init).to.have.been.calledWithMatch( { - comment: DataTypes.TEXT, - }, + comment: DataTypes.TEXT + } ) }) }) - // 檢查 reply 的關聯是否正確 + // 檢查 reply 的關聯是否正確 context('associations', () => { const User = 'User' const Tweet = 'Tweet' @@ -53,12 +53,12 @@ describe('# Reply Model', () => { Reply.associate({ Tweet }) }) - it('should belong to user', (done) => { + it('should belong to user', done => { // 檢查是否有呼叫 belongsTo(User) expect(Reply.belongsTo).to.have.been.calledWith(User) done() }) - it('should belong to tweet', (done) => { + it('should belong to tweet', done => { // 檢查是否有呼叫 belongsTo(Tweet) expect(Reply.belongsTo).to.have.been.calledWith(Tweet) done() @@ -66,40 +66,38 @@ describe('# Reply Model', () => { }) // 檢查 model 的新增、修改、刪除、更新 context('action', () => { - let data = null // 檢查 db.Reply 是否真的可以新增一筆資料 - it('create', (done) => { - db.Reply.create({}).then((reply) => { + it('create', done => { + db.Reply.create({}).then(reply => { data = reply done() }) }) // 檢查 db.Reply 是否真的可以讀取一筆資料 - it('read', (done) => { - db.Reply.findByPk(data.id).then((reply) => { + it('read', done => { + db.Reply.findByPk(data.id).then(reply => { expect(data.id).to.be.equal(reply.id) - done() - }) + done() + }) }) // 檢查 db.Reply 是否真的可以更新一筆資料 - it('update', (done) => { - db.Reply.update({}, { where: { id: data.id }}).then(() => { - db.Reply.findByPk(data.id).then((reply) => { - expect(data.updatedAt).to.be.not.equal(reply.updatedAt) + it('update', done => { + db.Reply.update({}, { where: { id: data.id } }).then(() => { + db.Reply.findByPk(data.id).then(reply => { + expect(data.updatedAt).to.be.not.equal(reply.updatedAt) done() }) }) }) // 檢查 db.Reply 是否真的可以刪除一筆資料 - it('delete', (done) => { - db.Reply.destroy({ where: { id: data.id }}).then(() => { - db.Reply.findByPk(data.id).then((reply) => { - expect(reply).to.be.equal(null) + it('delete', done => { + db.Reply.destroy({ where: { id: data.id } }).then(() => { + db.Reply.findByPk(data.id).then(reply => { + expect(reply).to.be.equal(null) done() }) }) }) }) - }) From 21ea22c5d9b5b0c2f9ea501e0e149325e8de3eb4 Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Tue, 6 Jun 2023 22:44:09 +0800 Subject: [PATCH 018/131] feat: pass all models/tweet testing --- migrations/20190115071420-create-tweet.js | 3 +++ seeders/20230605161408-tweets-seed-file.js | 17 +++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/migrations/20190115071420-create-tweet.js b/migrations/20190115071420-create-tweet.js index 5b9e90fa8f..b94855b2be 100644 --- a/migrations/20190115071420-create-tweet.js +++ b/migrations/20190115071420-create-tweet.js @@ -16,6 +16,9 @@ module.exports = { key: 'id' } }, + email: { + type: Sequelize.STRING + }, description: { type: Sequelize.TEXT }, diff --git a/seeders/20230605161408-tweets-seed-file.js b/seeders/20230605161408-tweets-seed-file.js index bda3b47c08..4025705286 100644 --- a/seeders/20230605161408-tweets-seed-file.js +++ b/seeders/20230605161408-tweets-seed-file.js @@ -8,12 +8,17 @@ module.exports = { { type: queryInterface.sequelize.QueryTypes.SELECT } ) await queryInterface.bulkInsert('Tweets', - Array.from({ length: 50 }, () => ({ - description: faker.lorem.text(), - created_at: new Date(), - updated_at: new Date(), - user_id: users[Math.floor(Math.random() * users.length)].id - }))) + Array.from({ length: 50 }, () => { + const randomUser = users[Math.floor(Math.random() * users.length)] + const user = { + email: randomUser.email, + description: faker.lorem.text(), + created_at: new Date(), + updated_at: new Date(), + user_id: randomUser.id + } + return user + })) }, down: async (queryInterface, Sequelize) => { From ad83de0baae4f8b2910db7df5613361208121385 Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Wed, 7 Jun 2023 00:03:02 +0800 Subject: [PATCH 019/131] fix: trying to get user info using post /api/tweets --- config/passport.js | 36 ++++++++++++++++------------ controllers/apis/tweet-controller.js | 12 +++++----- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/config/passport.js b/config/passport.js index 5fa1bcfa72..b64e96efce 100644 --- a/config/passport.js +++ b/config/passport.js @@ -4,7 +4,7 @@ const passportJWT = require('passport-jwt') const JWTStrategy = passportJWT.Strategy const ExtractJWT = passportJWT.ExtractJwt const bcrypt = require('bcryptjs') -const { User, Tweet } = require('../models') +const { User } = require('../models') // LocalStrategy Setting passport.use(new LocalStrategy( @@ -35,20 +35,26 @@ passport.use(new LocalStrategy( // JWTStrategy Setting const jwtOptions = { jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(), secretOrKey: process.env.JWT_SECRET } -passport.use(new JWTStrategy(jwtOptions, (jwtPayload, cb) => { - User.findByPk(jwtPayload.id, { - include: [ - // join table Like - { model: Tweet, as: 'LikedUsers' }, - { model: User, as: 'LikedTweets' }, - // join table Reply - { model: Tweet, as: 'RepliedUsers' }, - { model: User, as: 'RepliedTweets' }, - // join table FollowShip - { model: User, as: 'Followers' }, - { model: User, as: 'Followings' } - ] - }) +passport.use(new JWTStrategy(jwtOptions, async (jwtPayload, cb) => { + try { + const user = await User.findByPk(jwtPayload.id, { + include: [ + // join table Like + // { model: Tweet, as: 'LikedUsers' }, + // { model: User, as: 'LikedTweets' }, + // join table Reply + // { model: Tweet, as: 'RepliedUsers' }, + // { model: User, as: 'RepliedTweets' }, + // join table FollowShip + // { model: User, as: 'Followers' }, + // { model: User, as: 'Followings' } + ] + }) + console.log(user.toJSON()) // 測試用 + return cb(null, user) + } catch (error) { + cb(error) + } })) // passport serializeUser & deserializeUser diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index 227e99f985..27eda320bf 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -1,16 +1,16 @@ const { getUser } = require('../../_helpers') -const { Tweet } = require('../../models') +// const { Tweet } = require('../../models') const tweetController = { postTweet: async (req, res, next) => { try { - // const { description } = req.body + // const { description } = req.body const user = getUser(req) console.log(user) - // const tweet = await Tweet.create({ - // UserId: user.id, - // description - // }) + // const tweet = await Tweet.create({ + // UserId: user.id, + // description + // }) return res.json({ status: 'success', data: user }) } catch (error) { next(error) From e9bf8a2de10c17962abce212928f681d766fb61b Mon Sep 17 00:00:00 2001 From: s1030905 Date: Wed, 7 Jun 2023 10:33:53 +0800 Subject: [PATCH 020/131] feat: modify config for heroku --- Procfile | 1 + config/config.json | 6 +-- package-lock.json | 93 ++++++++++++++++++++++++++++++---------------- package.json | 4 +- 4 files changed, 65 insertions(+), 39 deletions(-) create mode 100644 Procfile diff --git a/Procfile b/Procfile new file mode 100644 index 0000000000..6feca7ecec --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: NODE_ENV=production node app.js \ No newline at end of file diff --git a/config/config.json b/config/config.json index 37f889620a..985820c3a2 100644 --- a/config/config.json +++ b/config/config.json @@ -15,11 +15,7 @@ "logging": false }, "production": { - "username": "root", - "password": null, - "database": "database_production", - "host": "127.0.0.1", - "dialect": "mysql" + "use_env_variable": "CLEARDB_DATABASE_URL" }, "travis": { "username": "travis", diff --git a/package-lock.json b/package-lock.json index 707199fbb4..24e323d11f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -326,7 +326,7 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==" }, "ansi-styles": { "version": "3.2.1", @@ -1447,7 +1447,7 @@ "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" } } }, @@ -1559,9 +1559,9 @@ } }, "es5-ext": { - "version": "0.10.60", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.60.tgz", - "integrity": "sha512-jpKNXIt60htYG59/9FGf2PYT3pwMpnEbNKysU+k/4FGwyGtMotOvcZOuW+EmXXYASRqYSXQfGL5cVIthOTgbkg==", + "version": "0.10.62", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", + "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", "requires": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", @@ -1571,7 +1571,7 @@ "es6-iterator": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", "requires": { "d": "1", "es5-ext": "^0.10.35", @@ -2109,7 +2109,7 @@ "event-emitter": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", "requires": { "d": "1", "es5-ext": "~0.10.14" @@ -2235,17 +2235,17 @@ } }, "ext": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", - "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", "requires": { - "type": "^2.5.0" + "type": "^2.7.2" }, "dependencies": { "type": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.6.0.tgz", - "integrity": "sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ==" + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" } } }, @@ -3000,14 +3000,44 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "js-beautify": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.3.tgz", - "integrity": "sha512-f1ra8PHtOEu/70EBnmiUlV8nJePS58y9qKjl4JHfYWlFH6bo7ogZBz//FAZp7jDuXtYnGYKymZPlrg2I/9Zo4g==", + "version": "1.14.8", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.8.tgz", + "integrity": "sha512-4S7HFeI9YfRvRgKnEweohs0tgJj28InHVIj4Nl8Htf96Y6pHg3+tJrmo4ucAM9f7l4SHbFI3IvFAZ2a1eQPbyg==", "requires": { "config-chain": "^1.1.13", "editorconfig": "^0.15.3", - "glob": "^7.1.3", - "nopt": "^5.0.0" + "glob": "^8.1.0", + "nopt": "^6.0.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "requires": { + "brace-expansion": "^2.0.1" + } + } } }, "js-yaml": { @@ -3048,7 +3078,7 @@ "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "requires": { "graceful-fs": "^4.1.6" } @@ -3224,7 +3254,7 @@ "lru-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", - "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", + "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==", "requires": { "es5-ext": "~0.10.2" } @@ -3550,11 +3580,11 @@ } }, "nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", "requires": { - "abbrev": "1" + "abbrev": "^1.0.0" } }, "normalize-path": { @@ -3930,7 +3960,7 @@ "proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=" + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" }, "proxy-addr": { "version": "2.0.7", @@ -4347,17 +4377,16 @@ } }, "sequelize-cli": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/sequelize-cli/-/sequelize-cli-5.5.1.tgz", - "integrity": "sha512-ZM4kUZvY3y14y+Rq3cYxGH7YDJz11jWHcN2p2x7rhAIemouu4CEXr5ebw30lzTBtyXV4j2kTO+nUjZOqzG7k+Q==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/sequelize-cli/-/sequelize-cli-6.2.0.tgz", + "integrity": "sha512-6WQ2x91hg30dUn66mXHnzvHATZ4pyI1GHSNbS/TNN/vRR4BLRSLijadeMgC8zqmKDsL0VqzVVopJWfJakuP++Q==", "requires": { - "bluebird": "^3.5.3", "cli-color": "^1.4.0", "fs-extra": "^7.0.1", "js-beautify": "^1.8.8", "lodash": "^4.17.5", "resolve": "^1.5.0", - "umzug": "^2.1.0", + "umzug": "^2.3.0", "yargs": "^13.1.0" } }, @@ -4421,7 +4450,7 @@ "sigmund": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" + "integrity": "sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==" }, "signal-exit": { "version": "3.0.7", diff --git a/package.json b/package.json index 4f730d75c1..dc303f2f57 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "passport-jwt": "^4.0.0", "passport-local": "^1.0.0", "sequelize": "^6.18.0", - "sequelize-cli": "^5.5.0", + "sequelize-cli": "^6.2.0", "sinon": "^10.0.0", "sinon-chai": "^3.3.0" }, @@ -45,4 +45,4 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^5.1.0" } -} \ No newline at end of file +} From 5e882c628f721450e94bb2641edd152751e00cad Mon Sep 17 00:00:00 2001 From: s1030905 Date: Wed, 7 Jun 2023 10:45:02 +0800 Subject: [PATCH 021/131] for heroku test --- routes/pages/index.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/routes/pages/index.js b/routes/pages/index.js index 460792c568..78de3ec835 100644 --- a/routes/pages/index.js +++ b/routes/pages/index.js @@ -1,6 +1,11 @@ const router = require('express').Router() const userController = require('../../controllers/pages/user-controller') +// heroku test +router.get('/', (req, res) => { + res.json({ status: 'Hello world!' }) +}) + router.get('/logout', userController.logout) module.exports = router From aff9589f23e11eada518fda700a4b78ce9e87877 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Wed, 7 Jun 2023 11:12:38 +0800 Subject: [PATCH 022/131] remove jwt --- config/passport.js | 44 ++++++++++++++++++++++---------------------- package.json | 10 ++++------ 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/config/passport.js b/config/passport.js index b64e96efce..a38fff3e1c 100644 --- a/config/passport.js +++ b/config/passport.js @@ -33,29 +33,29 @@ passport.use(new LocalStrategy( )) // JWTStrategy Setting -const jwtOptions = { jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(), secretOrKey: process.env.JWT_SECRET } +const jwtOptions = { jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(), secretOrKey: process.env.JWT_SECRET || 'secret' } -passport.use(new JWTStrategy(jwtOptions, async (jwtPayload, cb) => { - try { - const user = await User.findByPk(jwtPayload.id, { - include: [ - // join table Like - // { model: Tweet, as: 'LikedUsers' }, - // { model: User, as: 'LikedTweets' }, - // join table Reply - // { model: Tweet, as: 'RepliedUsers' }, - // { model: User, as: 'RepliedTweets' }, - // join table FollowShip - // { model: User, as: 'Followers' }, - // { model: User, as: 'Followings' } - ] - }) - console.log(user.toJSON()) // 測試用 - return cb(null, user) - } catch (error) { - cb(error) - } -})) +// passport.use(new JWTStrategy(jwtOptions, async (jwtPayload, cb) => { +// try { +// const user = await User.findByPk(jwtPayload.id, { +// include: [ +// // join table Like +// // { model: Tweet, as: 'LikedUsers' }, +// // { model: User, as: 'LikedTweets' }, +// // join table Reply +// // { model: Tweet, as: 'RepliedUsers' }, +// // { model: User, as: 'RepliedTweets' }, +// // join table FollowShip +// // { model: User, as: 'Followers' }, +// // { model: User, as: 'Followings' } +// ] +// }) +// console.log(user.toJSON()) // 測試用 +// return cb(null, user) +// } catch (error) { +// cb(error) +// } +// })) // passport serializeUser & deserializeUser passport.serializeUser((user, cb) => cb(null, user.id)) diff --git a/package.json b/package.json index dc303f2f57..4203318b89 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,9 @@ "description": "", "main": "index.js", "scripts": { - "start": "NODE_ENV=development node app.js", - "dev": "NODE_ENV=development nodemon app.js", - "test": "mocha test --exit --recursive --timeout 5000", - "start1": "node app.js", - "dev1": "nodemon app.js" + "start": "set \"NODE_ENV=development\" && node app.js", + "dev": "set \"NODE_ENV=development\" && nodemon app.js", + "test": "mocha test --exit --recursive --timeout 5000" }, "author": "", "license": "ISC", @@ -45,4 +43,4 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^5.1.0" } -} +} \ No newline at end of file From 57abf78c12471d165526825ea620af040d1aafa8 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Wed, 7 Jun 2023 11:19:40 +0800 Subject: [PATCH 023/131] remove passport jwt --- config/passport.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/passport.js b/config/passport.js index a38fff3e1c..e5c1d61ef0 100644 --- a/config/passport.js +++ b/config/passport.js @@ -1,8 +1,8 @@ const passport = require('passport') const LocalStrategy = require('passport-local').Strategy const passportJWT = require('passport-jwt') -const JWTStrategy = passportJWT.Strategy -const ExtractJWT = passportJWT.ExtractJwt +// const JWTStrategy = passportJWT.Strategy +// const ExtractJWT = passportJWT.ExtractJwt const bcrypt = require('bcryptjs') const { User } = require('../models') @@ -33,7 +33,7 @@ passport.use(new LocalStrategy( )) // JWTStrategy Setting -const jwtOptions = { jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(), secretOrKey: process.env.JWT_SECRET || 'secret' } +// const jwtOptions = { jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(), secretOrKey: process.env.JWT_SECRET || 'secret' } // passport.use(new JWTStrategy(jwtOptions, async (jwtPayload, cb) => { // try { From bfdc06beacffec8f895cb1405ac2ca92dbc5e9db Mon Sep 17 00:00:00 2001 From: s1030905 Date: Wed, 7 Jun 2023 11:23:07 +0800 Subject: [PATCH 024/131] test --- config/passport.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/passport.js b/config/passport.js index e5c1d61ef0..13e27a6088 100644 --- a/config/passport.js +++ b/config/passport.js @@ -1,6 +1,6 @@ const passport = require('passport') const LocalStrategy = require('passport-local').Strategy -const passportJWT = require('passport-jwt') +// const passportJWT = require('passport-jwt') // const JWTStrategy = passportJWT.Strategy // const ExtractJWT = passportJWT.ExtractJwt const bcrypt = require('bcryptjs') From 43f3b1e2760818a7a36f00bd6113a1580c9a313d Mon Sep 17 00:00:00 2001 From: s1030905 Date: Wed, 7 Jun 2023 11:29:43 +0800 Subject: [PATCH 025/131] test heroku --- app.js | 3 +++ routes/pages/index.js | 5 ----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app.js b/app.js index e914f64e9d..7211abe8e8 100644 --- a/app.js +++ b/app.js @@ -38,6 +38,9 @@ app.use((req, res, next) => { }) // routes +app.get('/', (req, res) => { + res.json({ status: 'Hello world!' }) +}) app.use('/api', apis) app.use('/', pages) diff --git a/routes/pages/index.js b/routes/pages/index.js index 78de3ec835..460792c568 100644 --- a/routes/pages/index.js +++ b/routes/pages/index.js @@ -1,11 +1,6 @@ const router = require('express').Router() const userController = require('../../controllers/pages/user-controller') -// heroku test -router.get('/', (req, res) => { - res.json({ status: 'Hello world!' }) -}) - router.get('/logout', userController.logout) module.exports = router From c81ddd1009a9961dec1c9947973116b70f2bc2d1 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Wed, 7 Jun 2023 14:28:26 +0800 Subject: [PATCH 026/131] remove model user attribures allownNull --- migrations/20190115071421-create-user.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/migrations/20190115071421-create-user.js b/migrations/20190115071421-create-user.js index fd8e296456..8fde7179d7 100644 --- a/migrations/20190115071421-create-user.js +++ b/migrations/20190115071421-create-user.js @@ -9,15 +9,12 @@ module.exports = { type: Sequelize.INTEGER }, email: { - allowNull: false, type: Sequelize.STRING }, password: { - allowNull: false, type: Sequelize.STRING }, name: { - allowNull: false, type: Sequelize.STRING }, avatar: { @@ -30,7 +27,6 @@ module.exports = { type: Sequelize.TEXT }, role: { - allowNull: true, type: Sequelize.STRING, defaultValue: 'user' }, From fcab4364652aecebf38df8dd8d2d09e2306c7a50 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Wed, 7 Jun 2023 17:24:07 +0800 Subject: [PATCH 027/131] rebuild models and all pass test/models --- config/passport.js | 55 +++++++------ controllers/apis/admin-controller.js | 44 ++++++++--- controllers/apis/tweet-controller.js | 14 ++-- .../20190115071418-create-followship.js | 8 +- migrations/20190115071419-create-like.js | 22 ++---- migrations/20190115071420-create-reply.js | 22 ++---- migrations/20190115071420-create-tweet.js | 16 +--- migrations/20190115071421-create-user.js | 14 +--- ...3323-add-account-and-background-to-user.js | 24 ++++++ .../20230605063323-add-account-to-User.js | 14 ---- .../20230606043259-add-background-to-User.js | 14 ---- models/followship.js | 28 ++++--- models/index.js | 2 +- models/like.js | 32 ++++---- models/reply.js | 34 ++++---- models/tweet.js | 47 +++++------ models/user.js | 77 +++++++++---------- routes/apis/modules/admin.js | 3 + 18 files changed, 232 insertions(+), 238 deletions(-) create mode 100644 migrations/20230605063323-add-account-and-background-to-user.js delete mode 100644 migrations/20230605063323-add-account-to-User.js delete mode 100644 migrations/20230606043259-add-background-to-User.js diff --git a/config/passport.js b/config/passport.js index 13e27a6088..dc77918e73 100644 --- a/config/passport.js +++ b/config/passport.js @@ -1,8 +1,8 @@ const passport = require('passport') const LocalStrategy = require('passport-local').Strategy -// const passportJWT = require('passport-jwt') -// const JWTStrategy = passportJWT.Strategy -// const ExtractJWT = passportJWT.ExtractJwt +const passportJWT = require('passport-jwt') +const JWTStrategy = passportJWT.Strategy +const ExtractJWT = passportJWT.ExtractJwt const bcrypt = require('bcryptjs') const { User } = require('../models') @@ -33,29 +33,34 @@ passport.use(new LocalStrategy( )) // JWTStrategy Setting -// const jwtOptions = { jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(), secretOrKey: process.env.JWT_SECRET || 'secret' } +const jwtOptions = { + jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(), + secretOrKey: process.env.JWT_SECRET || 'secret' +} -// passport.use(new JWTStrategy(jwtOptions, async (jwtPayload, cb) => { -// try { -// const user = await User.findByPk(jwtPayload.id, { -// include: [ -// // join table Like -// // { model: Tweet, as: 'LikedUsers' }, -// // { model: User, as: 'LikedTweets' }, -// // join table Reply -// // { model: Tweet, as: 'RepliedUsers' }, -// // { model: User, as: 'RepliedTweets' }, -// // join table FollowShip -// // { model: User, as: 'Followers' }, -// // { model: User, as: 'Followings' } -// ] -// }) -// console.log(user.toJSON()) // 測試用 -// return cb(null, user) -// } catch (error) { -// cb(error) -// } -// })) +passport.use(new JWTStrategy(jwtOptions, async (jwtPayload, cb) => { + try { + const user = await User.findByPk(jwtPayload.id + // , { + // include: [ + // // join table Like + // { model: Tweet, as: 'LikedUsers' }, + // { model: User, as: 'LikedTweets' }, + // // join table Reply + // { model: Tweet, as: 'RepliedUsers' }, + // { model: User, as: 'RepliedTweets' }, + // // join table FollowShip + // { model: User, as: 'Followers' }, + // { model: User, as: 'Followings' } + // ] + // } + ) + console.log(user.toJSON()) // 測試用 + return cb(null, user) + } catch (error) { + cb(error) + } +})) // passport serializeUser & deserializeUser passport.serializeUser((user, cb) => cb(null, user.id)) diff --git a/controllers/apis/admin-controller.js b/controllers/apis/admin-controller.js index 94b7dab82c..ea97e3b55a 100644 --- a/controllers/apis/admin-controller.js +++ b/controllers/apis/admin-controller.js @@ -1,15 +1,41 @@ -const { User } = require('../../models') +const { User, Tweet } = require('../../models') const adminController = { adminLogin: async (req, res, next) => { - const { account } = req.body - const user = await User.findOne({ - where: { account }, - raw: true - }) - if (!user) return res.json({ status: 'failed', data: 'You are not admin' }) - if (user.role !== 'admin') return res.json({ status: 'failed', data: 'You are not admin' }) - return res.json({ status: 'success', data: 'Welcome Back' }) + try { + const { account } = req.body + const user = await User.findOne({ + where: { account }, + raw: true + }) + if (!user) return res.json({ status: 'failed', data: 'You are not admin' }) + if (user.role !== 'admin') return res.json({ status: 'failed', data: 'You are not admin' }) + return res.json({ status: 'success', data: 'You are admin' }) + } catch (error) { + next(error) + } + }, + getUsers: async (req, res, next) => { + try { + const users = User.findAll({ + raw: true, + nest: true + }) + return res.json({ status: 'success', data: users }) + } catch (error) { + next(error) + } + }, + getTweets: async (req, res, next) => { + try { + const tweets = Tweet.findAll({ + raw: true, + nest: true + }) + return res.json({ status: 'success', data: tweets }) + } catch (error) { + next(error) + } } } diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index 27eda320bf..eb1f7ce813 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -1,17 +1,17 @@ const { getUser } = require('../../_helpers') -// const { Tweet } = require('../../models') +const { Tweet } = require('../../models') const tweetController = { postTweet: async (req, res, next) => { try { - // const { description } = req.body + const { description } = req.body const user = getUser(req) console.log(user) - // const tweet = await Tweet.create({ - // UserId: user.id, - // description - // }) - return res.json({ status: 'success', data: user }) + const tweet = await Tweet.create({ + UserId: user.id, + description + }) + return res.json({ status: 'success', data: tweet }) } catch (error) { next(error) } diff --git a/migrations/20190115071418-create-followship.js b/migrations/20190115071418-create-followship.js index da1802d87f..ca5835b907 100644 --- a/migrations/20190115071418-create-followship.js +++ b/migrations/20190115071418-create-followship.js @@ -8,17 +8,17 @@ module.exports = { primaryKey: true, type: Sequelize.INTEGER }, - follower_id: { + followerId: { type: Sequelize.INTEGER }, - following_id: { + followingId: { type: Sequelize.INTEGER }, - created_at: { + createdAt: { allowNull: false, type: Sequelize.DATE }, - updated_at: { + updatedAt: { allowNull: false, type: Sequelize.DATE } diff --git a/migrations/20190115071419-create-like.js b/migrations/20190115071419-create-like.js index f5a89265bc..11b8ea2452 100644 --- a/migrations/20190115071419-create-like.js +++ b/migrations/20190115071419-create-like.js @@ -8,27 +8,17 @@ module.exports = { primaryKey: true, type: Sequelize.INTEGER }, - User_id: { - type: Sequelize.INTEGER, - allowNull: false, - reference: { - model: 'Users', - key: 'id' - } + UserId: { + type: Sequelize.INTEGER }, - Tweet_id: { - type: Sequelize.INTEGER, - allowNull: false, - reference: { - model: 'Tweets', - key: 'id' - } + TweetId: { + type: Sequelize.INTEGER }, - created_at: { + createdAt: { allowNull: false, type: Sequelize.DATE }, - updated_at: { + updatedAt: { allowNull: false, type: Sequelize.DATE } diff --git a/migrations/20190115071420-create-reply.js b/migrations/20190115071420-create-reply.js index b725791b64..5ffffab111 100644 --- a/migrations/20190115071420-create-reply.js +++ b/migrations/20190115071420-create-reply.js @@ -8,30 +8,20 @@ module.exports = { primaryKey: true, type: Sequelize.INTEGER }, - User_id: { - type: Sequelize.INTEGER, - allowNull: false, - reference: { - model: 'Users', - key: 'id' - } + UserId: { + type: Sequelize.INTEGER }, - Tweet_id: { - type: Sequelize.INTEGER, - allowNull: false, - reference: { - model: 'Tweets', - key: 'id' - } + TweetId: { + type: Sequelize.INTEGER }, comment: { type: Sequelize.TEXT }, - created_at: { + createdAt: { allowNull: false, type: Sequelize.DATE }, - updated_at: { + updatedAt: { allowNull: false, type: Sequelize.DATE } diff --git a/migrations/20190115071420-create-tweet.js b/migrations/20190115071420-create-tweet.js index b94855b2be..1abc04a36e 100644 --- a/migrations/20190115071420-create-tweet.js +++ b/migrations/20190115071420-create-tweet.js @@ -8,25 +8,17 @@ module.exports = { primaryKey: true, type: Sequelize.INTEGER }, - User_id: { - type: Sequelize.INTEGER, - allowNull: false, - reference: { - model: 'Users', - key: 'id' - } - }, - email: { - type: Sequelize.STRING + UserId: { + type: Sequelize.INTEGER }, description: { type: Sequelize.TEXT }, - created_at: { + createdAt: { allowNull: false, type: Sequelize.DATE }, - updated_at: { + updatedAt: { allowNull: false, type: Sequelize.DATE } diff --git a/migrations/20190115071421-create-user.js b/migrations/20190115071421-create-user.js index fd8e296456..66455da336 100644 --- a/migrations/20190115071421-create-user.js +++ b/migrations/20190115071421-create-user.js @@ -9,36 +9,30 @@ module.exports = { type: Sequelize.INTEGER }, email: { - allowNull: false, type: Sequelize.STRING }, password: { - allowNull: false, type: Sequelize.STRING }, name: { - allowNull: false, type: Sequelize.STRING }, avatar: { - allowNull: true, - type: Sequelize.STRING, - defaultValue: `https://loremflickr.com/320/240/person/?random=${Math.random() * 100}` + type: Sequelize.STRING }, introduction: { - allowNull: true, type: Sequelize.TEXT }, role: { - allowNull: true, type: Sequelize.STRING, + allowNull: false, defaultValue: 'user' }, - created_at: { + createdAt: { allowNull: false, type: Sequelize.DATE }, - updated_at: { + updatedAt: { allowNull: false, type: Sequelize.DATE } diff --git a/migrations/20230605063323-add-account-and-background-to-user.js b/migrations/20230605063323-add-account-and-background-to-user.js new file mode 100644 index 0000000000..15e697e491 --- /dev/null +++ b/migrations/20230605063323-add-account-and-background-to-user.js @@ -0,0 +1,24 @@ +'use strict' + +module.exports = { + up: (queryInterface, Sequelize) => { + return Promise.all([ + queryInterface.addColumn('Users', 'account', + { + type: Sequelize.STRING + }), + queryInterface.addColumn('Users', 'background', + { + type: Sequelize.STRING + + }) + ]) + }, + + down: (queryInterface, Sequelize) => { + return Promise.all([ + queryInterface.removeColumn('Users', 'account'), + queryInterface.removeColumn('Users', 'background') + ]) + } +} diff --git a/migrations/20230605063323-add-account-to-User.js b/migrations/20230605063323-add-account-to-User.js deleted file mode 100644 index e56806782d..0000000000 --- a/migrations/20230605063323-add-account-to-User.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict' - -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.addColumn('Users', 'account', { - type: Sequelize.STRING, - defaultValue: false - }) - }, - - down: async (queryInterface, Sequelize) => { - await queryInterface.removeColumn('Users', 'account') - } -} diff --git a/migrations/20230606043259-add-background-to-User.js b/migrations/20230606043259-add-background-to-User.js deleted file mode 100644 index 566995f336..0000000000 --- a/migrations/20230606043259-add-background-to-User.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict' - -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.addColumn('Users', 'background', { - type: Sequelize.STRING, - defaultValue: 'https://images.pexels.com/photos/12993530/pexels-photo-12993530.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1' - }) - }, - - down: async (queryInterface, Sequelize) => { - await queryInterface.removeColumn('Users', 'background') - } -} diff --git a/models/followship.js b/models/followship.js index 04e199b78e..715036574b 100644 --- a/models/followship.js +++ b/models/followship.js @@ -1,17 +1,21 @@ 'use strict' +const { Model } = require('sequelize') + module.exports = (sequelize, DataTypes) => { - const Followship = sequelize.define('Followship', { - }, {}) - Followship.associate = function (models) { + class Followship extends Model { + static associate (models) { + } } - Followship.init({ - followerId: DataTypes.INTEGER, - followingId: DataTypes.INTEGER - }, { - sequelize, - modelName: 'Followship', - tableName: 'Followships', - underscored: true - }) + Followship.init( + { + followerId: DataTypes.INTEGER, + followingId: DataTypes.INTEGER + }, + { + sequelize, + modelName: 'Followship', + tableName: 'Followships' + } + ) return Followship } diff --git a/models/index.js b/models/index.js index 427848cb0b..e92dffac8b 100644 --- a/models/index.js +++ b/models/index.js @@ -5,7 +5,7 @@ const path = require('path') const Sequelize = require('sequelize') const basename = path.basename(__filename) const env = process.env.NODE_ENV || 'development' -const config = require(path.resolve(__dirname, '../config/config.json'))[env] +const config = require(__dirname + '/../config/config.json')[env] const db = {} let sequelize diff --git a/models/like.js b/models/like.js index 9dd701352c..4f5776e3fb 100644 --- a/models/like.js +++ b/models/like.js @@ -1,19 +1,23 @@ 'use strict' +const { Model } = require('sequelize') + module.exports = (sequelize, DataTypes) => { - const Like = sequelize.define('Like', { - }, {}) - Like.associate = function (models) { - Like.belongsTo(models.Tweet, { foreignKey: 'TweetId' }) - Like.belongsTo(models.User, { foreignKey: 'UserId' }) + class Like extends Model { + static associate (models) { + Like.belongsTo(models.Tweet, { foreignKey: 'TweetId' }) + Like.belongsTo(models.User, { foreignKey: 'UserId' }) + } } - Like.init({ - UserId: DataTypes.INTEGER, - TweetId: DataTypes.INTEGER - }, { - sequelize, - modelName: 'Like', - tableName: 'Likes', - underscored: true - }) + Like.init( + { + UserId: DataTypes.INTEGER, + TweetId: DataTypes.INTEGER + }, + { + sequelize, + modelName: 'Like', + tableName: 'Likes' + } + ) return Like } diff --git a/models/reply.js b/models/reply.js index 300eeef677..a39a0e04b3 100644 --- a/models/reply.js +++ b/models/reply.js @@ -1,20 +1,24 @@ 'use strict' +const { Model } = require('sequelize') + module.exports = (sequelize, DataTypes) => { - const Reply = sequelize.define('Reply', { - }, {}) - Reply.associate = function (models) { - Reply.belongsTo(models.Tweet, { foreignKey: 'TweetId' }) - Reply.belongsTo(models.User, { foreignKey: 'UserId' }) + class Reply extends Model { + static associate (models) { + Reply.belongsTo(models.Tweet, { foreignKey: 'TweetId' }) + Reply.belongsTo(models.User, { foreignKey: 'UserId' }) + } } - Reply.init({ - UserId: DataTypes.INTEGER, - TweetId: DataTypes.INTEGER, - comment: DataTypes.TEXT - }, { - sequelize, - modelName: 'Reply', - tableName: 'Replies', - underscored: true - }) + Reply.init( + { + UserId: DataTypes.INTEGER, + TweetId: DataTypes.INTEGER, + comment: DataTypes.TEXT + }, + { + sequelize, + modelName: 'Reply', + tableName: 'Replies' + } + ) return Reply } diff --git a/models/tweet.js b/models/tweet.js index ac187979d5..69b92dee17 100644 --- a/models/tweet.js +++ b/models/tweet.js @@ -1,33 +1,24 @@ 'use strict' +const { Model } = require('sequelize') + module.exports = (sequelize, DataTypes) => { - const Tweet = sequelize.define('Tweet', { - }, {}) - Tweet.associate = function (models) { - Tweet.belongsTo(models.User, { foreignKey: 'UserId' }) - Tweet.hasMany(models.Reply, { foreignKey: 'TweetId' }) - Tweet.hasMany(models.Like, { foreignKey: 'TweetId' }) - Tweet.belongsToMany(models.User, { - through: models.Reply, - foreignKey: 'TweetId', - as: 'RepliedUsers' - }) - Tweet.belongsToMany(models.User, { - through: models.Like, - foreignKey: 'TweetId', - as: 'LikedUsers' - }) - Tweet.hasMany(models.Reply, { foreignKey: 'TweetId' }) - Tweet.hasMany(models.Like, { foreignKey: 'TweetId' }) + class Tweet extends Model { + static associate (models) { + Tweet.belongsTo(models.User, { foreignKey: 'UserId' }) + Tweet.hasMany(models.Reply, { foreignKey: 'TweetId' }) + Tweet.hasMany(models.Like, { foreignKey: 'TweetId' }) + } } - Tweet.init({ - UserId: DataTypes.INTEGER, - email: DataTypes.STRING, - description: DataTypes.TEXT - }, { - sequelize, - modelName: 'Tweet', - tableName: 'Tweets', - underscored: true - }) + Tweet.init( + { + UserId: DataTypes.INTEGER, + description: DataTypes.TEXT + }, + { + sequelize, + modelName: 'Tweet', + tableName: 'Tweets' + } + ) return Tweet } diff --git a/models/user.js b/models/user.js index 7ada473b4f..b709f89a2a 100644 --- a/models/user.js +++ b/models/user.js @@ -1,46 +1,41 @@ 'use strict' +const { Model } = require('sequelize') + module.exports = (sequelize, DataTypes) => { - const User = sequelize.define('User', { - }, {}) - User.associate = function (models) { - User.hasMany(models.Tweet, { foreignKey: 'UserId' }) - User.hasMany(models.Reply, { foreignKey: 'UserId' }) - User.hasMany(models.Like, { foreignKey: 'UserId' }) - User.belongsToMany(models.Tweet, { - through: models.Reply, - foreignKey: 'UserId', - as: 'RepliedTweets' - }) - User.belongsToMany(models.Tweet, { - through: models.Like, - foreignKey: 'UserId', - as: 'LikedTweets' - }) - User.belongsToMany(User, { - through: models.Followship, - foreignKey: 'followingId', - as: 'Followers' - }) - User.belongsToMany(User, { - through: models.Followship, - foreignKey: 'followerId', - as: 'Followings' - }) + class User extends Model { + static associate (models) { + User.hasMany(models.Tweet, { foreignKey: 'userId' }) + User.hasMany(models.Reply, { foreignKey: 'userId' }) + User.hasMany(models.Like, { foreignKey: 'userId' }) + + User.belongsToMany(User, { + through: models.Followship, + foreignKey: 'followingId', + as: 'Followers' + }) + User.belongsToMany(User, { + through: models.Followship, + foreignKey: 'followerId', + as: 'Followings' + }) + } } - User.init({ - email: DataTypes.STRING, - password: DataTypes.STRING, - name: DataTypes.STRING, - avatar: DataTypes.STRING, - introduction: DataTypes.TEXT, - role: DataTypes.BOOLEAN, - account: DataTypes.STRING, - background: DataTypes.STRING - }, { - sequelize, - modelName: 'User', - tableName: 'Users', - underscored: true - }) + User.init( + { + email: DataTypes.STRING, + password: DataTypes.STRING, + name: DataTypes.STRING, + avatar: DataTypes.STRING, + introduction: DataTypes.TEXT, + role: DataTypes.STRING, + account: DataTypes.STRING, + background: DataTypes.STRING + }, + { + sequelize, // We need to pass the connection instance + modelName: 'User', + tableName: 'Users' + } + ) return User } diff --git a/routes/apis/modules/admin.js b/routes/apis/modules/admin.js index edb60ef813..516495984b 100644 --- a/routes/apis/modules/admin.js +++ b/routes/apis/modules/admin.js @@ -1,6 +1,9 @@ const router = require('express').Router() const adminController = require('../../../controllers/apis/admin-controller') +// admin 登入 router.post('/users', adminController.adminLogin) +// 瀏覽所有users +router.get('/users', adminController.getUsers) module.exports = router From 95a5194d3d0aa0e4c33a5330c91401e9fe934300 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Wed, 7 Jun 2023 18:45:40 +0800 Subject: [PATCH 028/131] rebuild seeders --- config/passport.js | 28 +++++++++---------- controllers/apis/tweet-controller.js | 3 ++- seeders/20230605074630-users-seed-file.js | 31 +++++++++++++--------- seeders/20230605161408-tweets-seed-file.js | 7 +++-- 4 files changed, 37 insertions(+), 32 deletions(-) diff --git a/config/passport.js b/config/passport.js index dc77918e73..882bb5c550 100644 --- a/config/passport.js +++ b/config/passport.js @@ -41,21 +41,20 @@ const jwtOptions = { passport.use(new JWTStrategy(jwtOptions, async (jwtPayload, cb) => { try { const user = await User.findByPk(jwtPayload.id - // , { - // include: [ - // // join table Like - // { model: Tweet, as: 'LikedUsers' }, - // { model: User, as: 'LikedTweets' }, - // // join table Reply - // { model: Tweet, as: 'RepliedUsers' }, - // { model: User, as: 'RepliedTweets' }, - // // join table FollowShip - // { model: User, as: 'Followers' }, - // { model: User, as: 'Followings' } - // ] - // } + , { + // include: [ + // // join table Like + // { model: Tweet, as: 'LikedUsers' }, + // { model: User, as: 'LikedTweets' }, + // // join table Reply + // { model: Tweet, as: 'RepliedUsers' }, + // { model: User, as: 'RepliedTweets' }, + // // join table FollowShip + // { model: User, as: 'Followers' }, + // { model: User, as: 'Followings' } + // ] + } ) - console.log(user.toJSON()) // 測試用 return cb(null, user) } catch (error) { cb(error) @@ -69,7 +68,6 @@ passport.deserializeUser(async (id, cb) => { try { let user = await User.findByPk(id) user = user.toJSON() - console.log(user) return cb(null, user) } catch (error) { cb(error) diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index eb1f7ce813..e135faf9d2 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -5,8 +5,9 @@ const tweetController = { postTweet: async (req, res, next) => { try { const { description } = req.body + console.log('req--------------------------req') + console.log(req.user) const user = getUser(req) - console.log(user) const tweet = await Tweet.create({ UserId: user.id, description diff --git a/seeders/20230605074630-users-seed-file.js b/seeders/20230605074630-users-seed-file.js index 6d1fc84982..202b5f2237 100644 --- a/seeders/20230605074630-users-seed-file.js +++ b/seeders/20230605074630-users-seed-file.js @@ -1,6 +1,7 @@ 'use strict' const faker = require('faker') const bcrypt = require('bcryptjs') +const background = 'https://plus.unsplash.com/premium_photo-1668852917755-0e6fc9a66db5?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=687&q=80' module.exports = { up: async (queryInterface, Sequelize) => { @@ -13,8 +14,9 @@ module.exports = { introduction: faker.lorem.text(), role: 'admin', account: 'root', - created_at: new Date(), - updated_at: new Date() + background, + createdAt: new Date(), + updatedAt: new Date() }, { email: 'user1@example.com', @@ -24,8 +26,9 @@ module.exports = { introduction: faker.lorem.text(), role: 'user', account: 'user1', - created_at: new Date(), - updated_at: new Date() + background, + createdAt: new Date(), + updatedAt: new Date() }, { email: 'user2@example.com', password: await bcrypt.hash('12345678', 10), @@ -34,8 +37,9 @@ module.exports = { introduction: faker.lorem.text(), role: 'user', account: 'user2', - created_at: new Date(), - updated_at: new Date() + background, + createdAt: new Date(), + updatedAt: new Date() }, { email: 'user3@example.com', password: await bcrypt.hash('12345678', 10), @@ -44,8 +48,9 @@ module.exports = { introduction: faker.lorem.text(), role: 'user', account: 'user3', - created_at: new Date(), - updated_at: new Date() + background, + createdAt: new Date(), + updatedAt: new Date() }, { email: 'user4@example.com', password: await bcrypt.hash('12345678', 10), @@ -54,8 +59,9 @@ module.exports = { introduction: faker.lorem.text(), role: 'user', account: 'user4', - created_at: new Date(), - updated_at: new Date() + background, + createdAt: new Date(), + updatedAt: new Date() }, { email: 'user5@example.com', password: await bcrypt.hash('12345678', 10), @@ -64,8 +70,9 @@ module.exports = { introduction: faker.lorem.text(), role: 'user', account: 'user5', - created_at: new Date(), - updated_at: new Date() + background, + createdAt: new Date(), + updatedAt: new Date() } ]) }, diff --git a/seeders/20230605161408-tweets-seed-file.js b/seeders/20230605161408-tweets-seed-file.js index 4025705286..18ac9fe15f 100644 --- a/seeders/20230605161408-tweets-seed-file.js +++ b/seeders/20230605161408-tweets-seed-file.js @@ -11,11 +11,10 @@ module.exports = { Array.from({ length: 50 }, () => { const randomUser = users[Math.floor(Math.random() * users.length)] const user = { - email: randomUser.email, description: faker.lorem.text(), - created_at: new Date(), - updated_at: new Date(), - user_id: randomUser.id + createdAt: new Date(), + updatedAt: new Date(), + userId: randomUser.id } return user })) From 7578ccc3e740b0f14e072b3c889e6ee4de435bc9 Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Wed, 7 Jun 2023 20:46:06 +0800 Subject: [PATCH 029/131] feat: add user inside the res.locals --- app.js | 1 - config/passport.js | 5 ++--- controllers/apis/tweet-controller.js | 12 +++--------- middleware/api-auth.js | 1 + 4 files changed, 6 insertions(+), 13 deletions(-) diff --git a/app.js b/app.js index 7211abe8e8..49ef877899 100644 --- a/app.js +++ b/app.js @@ -33,7 +33,6 @@ app.use(flash()) // locals app.use((req, res, next) => { res.locals.error_messages = req.flash('error_messages') - res.locals.user = getUser(req) next() }) diff --git a/config/passport.js b/config/passport.js index 882bb5c550..20ab44fc5e 100644 --- a/config/passport.js +++ b/config/passport.js @@ -66,9 +66,8 @@ passport.serializeUser((user, cb) => cb(null, user.id)) passport.deserializeUser(async (id, cb) => { try { - let user = await User.findByPk(id) - user = user.toJSON() - return cb(null, user) + const user = await User.findByPk(id) + return cb(null, user.toJSON()) } catch (error) { cb(error) } diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index e135faf9d2..52760c0d04 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -4,15 +4,9 @@ const { Tweet } = require('../../models') const tweetController = { postTweet: async (req, res, next) => { try { - const { description } = req.body - console.log('req--------------------------req') - console.log(req.user) - const user = getUser(req) - const tweet = await Tweet.create({ - UserId: user.id, - description - }) - return res.json({ status: 'success', data: tweet }) + console.log('Now is tweet controller') + console.log(res.locals.userId) + return res.json({ status: 'Now tweet is success' }) } catch (error) { next(error) } diff --git a/middleware/api-auth.js b/middleware/api-auth.js index 6eef9e03e0..e0ff1fcef7 100644 --- a/middleware/api-auth.js +++ b/middleware/api-auth.js @@ -3,6 +3,7 @@ const passport = require('../config/passport') // 引入 passport const authenticated = (req, res, next) => { passport.authenticate('jwt', { session: false }, (err, user) => { if (err || !user) return res.status(401).json({ status: 'error', message: 'unauthorized!' }) + res.locals.userId = user.dataValues.id next() })(req, res, next) } From 7a06e7aab9a70c735fb7130fbff2eca5d80e0f71 Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Wed, 7 Jun 2023 20:57:50 +0800 Subject: [PATCH 030/131] feat: add postTweet func --- app.js | 1 - controllers/apis/tweet-controller.js | 10 ++++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app.js b/app.js index 49ef877899..06eda1cbb9 100644 --- a/app.js +++ b/app.js @@ -3,7 +3,6 @@ if (process.env.NODE_ENV !== 'production') { } const express = require('express') -const { getUser } = require('./_helpers') const passport = require('./config/passport') const session = require('express-session') const flash = require('connect-flash') diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index 52760c0d04..0282142dd2 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -1,12 +1,14 @@ -const { getUser } = require('../../_helpers') const { Tweet } = require('../../models') const tweetController = { postTweet: async (req, res, next) => { try { - console.log('Now is tweet controller') - console.log(res.locals.userId) - return res.json({ status: 'Now tweet is success' }) + const { description } = req.body + const tweet = await Tweet.create({ + UserId: res.locals.userId, + description + }) + return res.json({ status: 'success', data: tweet }) } catch (error) { next(error) } From 15863ff60545f0413a52d2f07d48c2ccc5962e0d Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Wed, 7 Jun 2023 22:50:26 +0800 Subject: [PATCH 031/131] feat: add some file --- app.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app.js b/app.js index 06eda1cbb9..55fed6e0e6 100644 --- a/app.js +++ b/app.js @@ -16,15 +16,15 @@ app.use(express.urlencoded({ extended: true })) app.use(express.json()) // session設定 -app.use(session({ - secret: process.env.SESSION_SECRET || 'NonSecret', - resave: false, - saveUninitialized: true -})) +// app.use(session({ +// secret: process.env.SESSION_SECRET || 'NonSecret', +// resave: false, +// saveUninitialized: true +// })) // passport初始化 app.use(passport.initialize()) -app.use(passport.session()) +// app.use(passport.session()) // flash app.use(flash()) From 40ddf2432cac5b442acd44878b7ada8df0364cb4 Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Wed, 7 Jun 2023 22:55:13 +0800 Subject: [PATCH 032/131] feat: add some file --- app.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app.js b/app.js index 55fed6e0e6..299215b146 100644 --- a/app.js +++ b/app.js @@ -4,7 +4,6 @@ if (process.env.NODE_ENV !== 'production') { const express = require('express') const passport = require('./config/passport') -const session = require('express-session') const flash = require('connect-flash') const { apis, pages } = require('./routes') From 6db7e52f03da4d9cfea0911031d0927c46c6e9d2 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Wed, 7 Jun 2023 22:59:45 +0800 Subject: [PATCH 033/131] change status failed to error --- app.js | 2 +- controllers/apis/admin-controller.js | 9 ++++++--- controllers/apis/user-controller.js | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app.js b/app.js index 06eda1cbb9..c9717eb24f 100644 --- a/app.js +++ b/app.js @@ -9,7 +9,7 @@ const flash = require('connect-flash') const { apis, pages } = require('./routes') const app = express() -const port = 3000 +const port = process.env.PORT || 3000 // 解析request主體 app.use(express.urlencoded({ extended: true })) diff --git a/controllers/apis/admin-controller.js b/controllers/apis/admin-controller.js index ea97e3b55a..a11099a8c3 100644 --- a/controllers/apis/admin-controller.js +++ b/controllers/apis/admin-controller.js @@ -1,4 +1,5 @@ const { User, Tweet } = require('../../models') +const { getUser } = require('../../_helpers') const adminController = { adminLogin: async (req, res, next) => { @@ -8,9 +9,11 @@ const adminController = { where: { account }, raw: true }) - if (!user) return res.json({ status: 'failed', data: 'You are not admin' }) - if (user.role !== 'admin') return res.json({ status: 'failed', data: 'You are not admin' }) - return res.json({ status: 'success', data: 'You are admin' }) + if (!user) return res.json({ status: 'error', data: 'You are not admin' }) + if (user.role !== 'admin') return res.json({ status: 'error', data: 'You are not admin' }) + const userData = user + delete userData.password + return res.json({ status: 'success', data: user }) } catch (error) { next(error) } diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 2800671123..454f1e1d0b 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -36,7 +36,7 @@ const userController = { ]) if (userAccount) errors.push('account已存在') if (userEmail) errors.push('email已存在') - if (errors.length) return res.json({ status: 'failed', data: errors }) + if (errors.length) return res.json({ status: 'error', data: errors }) const user = await User.create({ name, account, From bb3cc4c21f1f4f88748a308c0b5b220467992f65 Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Wed, 7 Jun 2023 23:00:43 +0800 Subject: [PATCH 034/131] feat: add process.env.PORT --- .env.example | 3 ++- app.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index ac93920992..5b016f956c 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,3 @@ SESSION_SECRET= -JWT_SECRET= \ No newline at end of file +JWT_SECRET= +PORT= \ No newline at end of file diff --git a/app.js b/app.js index 299215b146..9b53771a12 100644 --- a/app.js +++ b/app.js @@ -8,7 +8,7 @@ const flash = require('connect-flash') const { apis, pages } = require('./routes') const app = express() -const port = 3000 +const port = process.env.PORT || 3000 // 解析request主體 app.use(express.urlencoded({ extended: true })) From 12026d7711bafe98de83e21fcbb0d17f4dfd569d Mon Sep 17 00:00:00 2001 From: s1030905 Date: Wed, 7 Jun 2023 23:05:01 +0800 Subject: [PATCH 035/131] feat: admin getUsers --- controllers/apis/admin-controller.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/controllers/apis/admin-controller.js b/controllers/apis/admin-controller.js index a11099a8c3..11231d6276 100644 --- a/controllers/apis/admin-controller.js +++ b/controllers/apis/admin-controller.js @@ -20,10 +20,8 @@ const adminController = { }, getUsers: async (req, res, next) => { try { - const users = User.findAll({ - raw: true, - nest: true - }) + const users = await User.findAll() + console.log(users) return res.json({ status: 'success', data: users }) } catch (error) { next(error) From c983fff1aa8092a2536d3b00926945e5c375976d Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Wed, 7 Jun 2023 23:05:18 +0800 Subject: [PATCH 036/131] feat: add session --- app.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app.js b/app.js index 9b53771a12..86df2ac884 100644 --- a/app.js +++ b/app.js @@ -6,6 +6,7 @@ const express = require('express') const passport = require('./config/passport') const flash = require('connect-flash') const { apis, pages } = require('./routes') +const session = require('express-session') const app = express() const port = process.env.PORT || 3000 @@ -15,11 +16,11 @@ app.use(express.urlencoded({ extended: true })) app.use(express.json()) // session設定 -// app.use(session({ -// secret: process.env.SESSION_SECRET || 'NonSecret', -// resave: false, -// saveUninitialized: true -// })) +app.use(session({ + secret: process.env.SESSION_SECRET || 'NonSecret', + resave: false, + saveUninitialized: true +})) // passport初始化 app.use(passport.initialize()) From 745651d6f1988e8ddf18c932c5ec264ad4db91eb Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Thu, 8 Jun 2023 09:00:36 +0800 Subject: [PATCH 037/131] feat: add getTweets and getTweet func --- controllers/apis/tweet-controller.js | 20 ++++++++++++++++++++ routes/apis/index.js | 2 ++ 2 files changed, 22 insertions(+) diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index 0282142dd2..673a36b6e6 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -12,6 +12,26 @@ const tweetController = { } catch (error) { next(error) } + }, + getTweets: async (req, res, next) => { + try { + const tweets = await Tweet.findAll({ }) + const result = tweets.map(tweet => ({ + ...tweet.toJSON() + })) + return res.json({ status: 'success', data: result }) + } catch (error) { + next(error) + } + }, + getTweet: async (req, res, next) => { + try { + const id = req.params.id + const tweet = await Tweet.findByPk(id, { raw: true }) + return res.json({ status: 'success', data: tweet }) + } catch (error) { + next(error) + } } } diff --git a/routes/apis/index.js b/routes/apis/index.js index bb81167d92..5e96bca52b 100644 --- a/routes/apis/index.js +++ b/routes/apis/index.js @@ -19,6 +19,8 @@ router.post('/login', passport.authenticate('local', { session: false }), userCo // 有關tweet的routes router.post('/tweets', authenticated, tweetController.postTweet) +router.get('/tweets', authenticated, tweetController.getTweets) +router.get('/tweets/:id', authenticated, tweetController.getTweet) router.use('/', apiErrorHandler) From 8f7911cbc7bfc9f81648e69aeb6ea0b0660b8bfe Mon Sep 17 00:00:00 2001 From: s1030905 Date: Thu, 8 Jun 2023 09:08:58 +0800 Subject: [PATCH 038/131] feat: getTweets & deleteTweet --- controllers/apis/admin-controller.js | 17 +++++++++++++---- routes/apis/modules/admin.js | 7 ++++++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/controllers/apis/admin-controller.js b/controllers/apis/admin-controller.js index 11231d6276..b5082b6f6f 100644 --- a/controllers/apis/admin-controller.js +++ b/controllers/apis/admin-controller.js @@ -29,14 +29,23 @@ const adminController = { }, getTweets: async (req, res, next) => { try { - const tweets = Tweet.findAll({ - raw: true, - nest: true - }) + const tweets = await Tweet.findAll() return res.json({ status: 'success', data: tweets }) } catch (error) { next(error) } + }, + deleteTweet: async (req, res, next) => { + try { + const { id } = req.params + const tweet = await Tweet.findByPk(id) + console.log(tweet) + if (!tweet) return res.json({ status: 'error', data: 'The tweet does not exist' }) + await tweet.destroy() + return res.json({ status: 'success', data: tweet }) + } catch (error) { + next(error) + } } } diff --git a/routes/apis/modules/admin.js b/routes/apis/modules/admin.js index 516495984b..14021d583a 100644 --- a/routes/apis/modules/admin.js +++ b/routes/apis/modules/admin.js @@ -4,6 +4,11 @@ const adminController = require('../../../controllers/apis/admin-controller') // admin 登入 router.post('/users', adminController.adminLogin) -// 瀏覽所有users +// users有關 router.get('/users', adminController.getUsers) + +// tweets有關 +router.get('/tweets', adminController.getTweets) +router.delete('/tweets/:id', adminController.deleteTweet) + module.exports = router From 634a3cba420ba736309cd88e9325e873ad491e12 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Thu, 8 Jun 2023 12:07:21 +0800 Subject: [PATCH 039/131] feat: admin rolechecker --- _helpers.js | 2 +- config/passport.js | 2 +- controllers/apis/admin-controller.js | 7 +++++-- middleware/api-auth.js | 17 +++++++++++++++-- routes/apis/index.js | 12 +++++------- routes/apis/modules/admin.js | 3 --- 6 files changed, 27 insertions(+), 16 deletions(-) diff --git a/_helpers.js b/_helpers.js index aafe88c3b9..70c100e3ac 100644 --- a/_helpers.js +++ b/_helpers.js @@ -1,6 +1,6 @@ function getUser (req) { - return req.user + return req.user || null } module.exports = { diff --git a/config/passport.js b/config/passport.js index 20ab44fc5e..ac22ea839c 100644 --- a/config/passport.js +++ b/config/passport.js @@ -8,7 +8,7 @@ const { User } = require('../models') // LocalStrategy Setting passport.use(new LocalStrategy( - ({ usernameField: 'account', passwordField: 'password', passReqToCallback: true }), + { usernameField: 'account', passwordField: 'password', passReqToCallback: true }, async (req, account, password, cb) => { try { const user = await User.findOne({ where: { account } }) diff --git a/controllers/apis/admin-controller.js b/controllers/apis/admin-controller.js index b5082b6f6f..89935cdfae 100644 --- a/controllers/apis/admin-controller.js +++ b/controllers/apis/admin-controller.js @@ -1,9 +1,12 @@ +const jwt = require('jsonwebtoken') const { User, Tweet } = require('../../models') const { getUser } = require('../../_helpers') const adminController = { adminLogin: async (req, res, next) => { try { + const reqUser = getUser(req).toJSON() + const token = jwt.sign(reqUser, process.env.JWT_SECRET, { expiresIn: '30d' }) const { account } = req.body const user = await User.findOne({ where: { account }, @@ -13,7 +16,8 @@ const adminController = { if (user.role !== 'admin') return res.json({ status: 'error', data: 'You are not admin' }) const userData = user delete userData.password - return res.json({ status: 'success', data: user }) + userData.token = token + return res.json({ status: 'success', data: userData }) } catch (error) { next(error) } @@ -21,7 +25,6 @@ const adminController = { getUsers: async (req, res, next) => { try { const users = await User.findAll() - console.log(users) return res.json({ status: 'success', data: users }) } catch (error) { next(error) diff --git a/middleware/api-auth.js b/middleware/api-auth.js index e0ff1fcef7..f7bf159977 100644 --- a/middleware/api-auth.js +++ b/middleware/api-auth.js @@ -1,13 +1,26 @@ -const passport = require('../config/passport') // 引入 passport +const passport = require('../config/passport') +const { getUser } = require('../_helpers') const authenticated = (req, res, next) => { passport.authenticate('jwt', { session: false }, (err, user) => { if (err || !user) return res.status(401).json({ status: 'error', message: 'unauthorized!' }) res.locals.userId = user.dataValues.id + req.user = user.dataValues next() })(req, res, next) } +const authenticatedAdmin = (req, res, next) => { + if (req.user && req.user.isAdmin) return next() + return res.status(403).json({ status: 'error', message: 'permission denied' }) +} + +const roleChecker = (req, res, next) => { + const user = getUser(req) + if (user.role === 'admin') return next() + return res.status(403).json({ status: 'error', message: 'permission denied' }) +} + module.exports = { - authenticated + authenticated, roleChecker, authenticatedAdmin } diff --git a/routes/apis/index.js b/routes/apis/index.js index bb81167d92..ac89557d53 100644 --- a/routes/apis/index.js +++ b/routes/apis/index.js @@ -2,18 +2,16 @@ const router = require('express').Router() const passport = require('../../config/passport') const admin = require('./modules/admin') const userController = require('../../controllers/apis/user-controller') +const adminController = require('../../controllers/apis/admin-controller') const { apiErrorHandler } = require('../../middleware/error-handler') const tweetController = require('../../controllers/apis/tweet-controller') -const { authenticated } = require('../../middleware/api-auth') - -// 測試postman 是否正常運作 -router.get('/', (req, res) => { - res.json({ status: 'success', data: 'Hello world' }) -}) +const { authenticated, roleChecker } = require('../../middleware/api-auth') // 有關admin的routes -router.use('/admin', admin) +router.post('/admin/users', passport.authenticate('local', { session: false }), roleChecker, adminController.adminLogin) +router.use('/admin', authenticated, roleChecker, admin) +// 使用者登入註冊 router.post('/signup', userController.signUp) router.post('/login', passport.authenticate('local', { session: false }), userController.login) diff --git a/routes/apis/modules/admin.js b/routes/apis/modules/admin.js index 14021d583a..cd521e84d5 100644 --- a/routes/apis/modules/admin.js +++ b/routes/apis/modules/admin.js @@ -1,9 +1,6 @@ const router = require('express').Router() const adminController = require('../../../controllers/apis/admin-controller') -// admin 登入 -router.post('/users', adminController.adminLogin) - // users有關 router.get('/users', adminController.getUsers) From b8fa9f163516f0f836e860882fc48d89324b45f2 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Thu, 8 Jun 2023 13:16:04 +0800 Subject: [PATCH 040/131] feat: add authenticatedAdmin --- app.js | 3 --- middleware/api-auth.js | 8 ++++++-- routes/apis/index.js | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app.js b/app.js index c9717eb24f..fbb48a6690 100644 --- a/app.js +++ b/app.js @@ -36,9 +36,6 @@ app.use((req, res, next) => { }) // routes -app.get('/', (req, res) => { - res.json({ status: 'Hello world!' }) -}) app.use('/api', apis) app.use('/', pages) diff --git a/middleware/api-auth.js b/middleware/api-auth.js index f7bf159977..2f354ab6e1 100644 --- a/middleware/api-auth.js +++ b/middleware/api-auth.js @@ -11,8 +11,12 @@ const authenticated = (req, res, next) => { } const authenticatedAdmin = (req, res, next) => { - if (req.user && req.user.isAdmin) return next() - return res.status(403).json({ status: 'error', message: 'permission denied' }) + passport.authenticate('jwt', { session: false }, (err, user) => { + if (err || !user) return res.status(403).json({ status: 'error', message: 'Forbidden' }) + res.locals.userId = user.dataValues.id + req.user = user.dataValues + next() + })(req, res, next) } const roleChecker = (req, res, next) => { diff --git a/routes/apis/index.js b/routes/apis/index.js index ac89557d53..87c30e56bc 100644 --- a/routes/apis/index.js +++ b/routes/apis/index.js @@ -5,11 +5,11 @@ const userController = require('../../controllers/apis/user-controller') const adminController = require('../../controllers/apis/admin-controller') const { apiErrorHandler } = require('../../middleware/error-handler') const tweetController = require('../../controllers/apis/tweet-controller') -const { authenticated, roleChecker } = require('../../middleware/api-auth') +const { authenticated, roleChecker, authenticatedAdmin } = require('../../middleware/api-auth') // 有關admin的routes router.post('/admin/users', passport.authenticate('local', { session: false }), roleChecker, adminController.adminLogin) -router.use('/admin', authenticated, roleChecker, admin) +router.use('/admin', authenticatedAdmin, roleChecker, admin) // 使用者登入註冊 router.post('/signup', userController.signUp) From 59b97b191e4dcaca72672066eedcb52ea51a5b83 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Thu, 8 Jun 2023 14:29:23 +0800 Subject: [PATCH 041/131] getTweets by user.id --- controllers/apis/tweet-controller.js | 24 ++++++++++++++++++++++++ routes/apis/index.js | 2 ++ 2 files changed, 26 insertions(+) diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index 0282142dd2..2abb82a94b 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -1,4 +1,5 @@ const { Tweet } = require('../../models') +const { getUser } = require('../../_helpers') const tweetController = { postTweet: async (req, res, next) => { @@ -12,6 +13,29 @@ const tweetController = { } catch (error) { next(error) } + }, + getTweets: async (req, res, next) => { + try { + const userTweets = await Tweet.findAll({ + where: { userId: getUser(req).id }, + raw: true, + nest: true + }) + if (!userTweets) return res.json({ status: 'error', data: 'You have not post any tweet yet' }) + return res.json({ status: 'success', data: userTweets }) + } catch (error) { + next(error) + } + }, + getTweet: async (req, res, next) => { + try { + const { tweet_id } = req.params + const tweet = await Tweet.findByPk(tweet_id) + if (!tweet) return res.json({ status: 'error', data: 'The tweet does not exist' }) + return res.json({ status: 'success', data: tweet }) + } catch (error) { + next(error) + } } } diff --git a/routes/apis/index.js b/routes/apis/index.js index 87c30e56bc..a974f3a5ae 100644 --- a/routes/apis/index.js +++ b/routes/apis/index.js @@ -17,6 +17,8 @@ router.post('/login', passport.authenticate('local', { session: false }), userCo // 有關tweet的routes router.post('/tweets', authenticated, tweetController.postTweet) +router.get('/tweets', authenticated, tweetController.getTweets) +router.get('/tweets/:tweet_id', authenticated, tweetController.getTweet) router.use('/', apiErrorHandler) From 83b7df21bf903b192a3fbb135ba71f69f6d0d65a Mon Sep 17 00:00:00 2001 From: s1030905 Date: Thu, 8 Jun 2023 15:30:32 +0800 Subject: [PATCH 042/131] feat: getUser, getUserTweets, getUserRepliedTweet --- controllers/apis/tweet-controller.js | 11 +++----- controllers/apis/user-controller.js | 40 +++++++++++++++++++++++++++- routes/apis/index.js | 5 ++++ 3 files changed, 47 insertions(+), 9 deletions(-) diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index 2abb82a94b..fe17bb5640 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -1,5 +1,4 @@ const { Tweet } = require('../../models') -const { getUser } = require('../../_helpers') const tweetController = { postTweet: async (req, res, next) => { @@ -16,13 +15,9 @@ const tweetController = { }, getTweets: async (req, res, next) => { try { - const userTweets = await Tweet.findAll({ - where: { userId: getUser(req).id }, - raw: true, - nest: true - }) - if (!userTweets) return res.json({ status: 'error', data: 'You have not post any tweet yet' }) - return res.json({ status: 'success', data: userTweets }) + const tweets = await Tweet.findAll() + if (!tweets) return res.json({ status: 'error', data: 'There is no tweet' }) + return res.json({ status: 'success', data: tweets }) } catch (error) { next(error) } diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 454f1e1d0b..f3b6bb0022 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -1,7 +1,7 @@ const jwt = require('jsonwebtoken') const bcrypt = require('bcryptjs') const { getUser } = require('../../_helpers') -const { User } = require('../../models') +const { User, Tweet, Reply } = require('../../models') const userController = { login: (req, res, next) => { @@ -50,6 +50,44 @@ const userController = { } catch (error) { next(error) } + }, + getUserTweets: async (req, res, next) => { + try { + const { id } = req.params + const userTweets = await Tweet.findAll({ + where: { userId: id }, + raw: true, + nest: true + }) + if (!userTweets) return res.json({ status: 'error', data: 'The user have not post any tweet yet' }) + return res.json({ status: 'success', data: userTweets }) + } catch (error) { + next(error) + } + }, + getUser: (req, res, next) => { + try { + const { id } = req.params + const user = User.findByPk(id) + if (!user) return res.json({ status: 'error', data: 'The user does not exist' }) + res.json({ status: 'success', data: user }) + } catch (error) { + next(error) + } + }, + getUserRepliedTweet: (req, res, next) => { + try { + const { id } = req.params + const repliedTweets = Reply.findAll({ + where: { UserId: id }, + raw: true, + nest: true + }) + if (!repliedTweets) return res.json({ status: 'error', data: 'The user does not exist' }) + return res.json({ status: 'success', data: repliedTweets }) + } catch (error) { + next(error) + } } } diff --git a/routes/apis/index.js b/routes/apis/index.js index a974f3a5ae..e9c18678f6 100644 --- a/routes/apis/index.js +++ b/routes/apis/index.js @@ -20,6 +20,11 @@ router.post('/tweets', authenticated, tweetController.postTweet) router.get('/tweets', authenticated, tweetController.getTweets) router.get('/tweets/:tweet_id', authenticated, tweetController.getTweet) +// 有關user的routes +router.get('/users/:id', authenticated, userController.getUser) +router.get('/users/:id/tweets', authenticated, userController.getUserTweets) +router.get('/users/:id/replied_tweets', authenticated, userController.getUserRepliedTweet) + router.use('/', apiErrorHandler) module.exports = router From 6ba53841429839a887d373f3454fb79d4d43a417 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Thu, 8 Jun 2023 17:06:44 +0800 Subject: [PATCH 043/131] add: replies seeds and rebuild tweets seeds --- seeders/20230605161408-tweets-seed-file.js | 23 +++++++------- seeders/20230608073227-replies-seed-file.js | 34 +++++++++++++++++++++ 2 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 seeders/20230608073227-replies-seed-file.js diff --git a/seeders/20230605161408-tweets-seed-file.js b/seeders/20230605161408-tweets-seed-file.js index 18ac9fe15f..8b655bd200 100644 --- a/seeders/20230605161408-tweets-seed-file.js +++ b/seeders/20230605161408-tweets-seed-file.js @@ -4,20 +4,19 @@ const faker = require('faker') module.exports = { up: async (queryInterface, Sequelize) => { const users = await queryInterface.sequelize.query( - 'SELECT id FROM Users;', + "SELECT id FROM Users WHERE role <> 'admin'", { type: queryInterface.sequelize.QueryTypes.SELECT } ) - await queryInterface.bulkInsert('Tweets', - Array.from({ length: 50 }, () => { - const randomUser = users[Math.floor(Math.random() * users.length)] - const user = { - description: faker.lorem.text(), - createdAt: new Date(), - updatedAt: new Date(), - userId: randomUser.id - } - return user - })) + const tweets = [] + for (let i = 0; i < 50; i++) { + tweets.push({ + UserId: users[i % users.length].id, + description: faker.lorem.text(), + createdAt: new Date(), + updatedAt: new Date() + }) + } + await queryInterface.bulkInsert('Tweets', tweets) }, down: async (queryInterface, Sequelize) => { diff --git a/seeders/20230608073227-replies-seed-file.js b/seeders/20230608073227-replies-seed-file.js new file mode 100644 index 0000000000..9eb4e57b95 --- /dev/null +++ b/seeders/20230608073227-replies-seed-file.js @@ -0,0 +1,34 @@ +'use strict' +const faker = require('faker') + +module.exports = { + up: async (queryInterface, Sequelize) => { + const [tweets, users] = await Promise.all([ + queryInterface.sequelize.query( + 'SELECT id FROM Tweets;', + { type: queryInterface.sequelize.QueryTypes.SELECT } + ), + queryInterface.sequelize.query( + "SELECT id FROM Users WHERE role <> 'admin'", + { type: queryInterface.sequelize.QueryTypes.SELECT } + ) + ]) + const eachUserTweetsCount = 3 + const repliedTweets = [] + for (let i = 0; i < 150; i++) { + const randomUser = Math.floor(Math.random() * users.length) + repliedTweets.push({ + UserId: users[randomUser].id, + TweetId: tweets[Math.floor(i / eachUserTweetsCount)].id, + comment: faker.lorem.text(), + createdAt: new Date(), + updatedAt: new Date() + }) + } + queryInterface.bulkInsert('Replies', repliedTweets) + }, + + down: async (queryInterface, Sequelize) => { + await queryInterface.bulkDelete('Replies', {}) + } +} From 787bbf4708af3e8e756a4686fbc88b1190632eea Mon Sep 17 00:00:00 2001 From: s1030905 Date: Thu, 8 Jun 2023 17:58:55 +0800 Subject: [PATCH 044/131] resolve getUserTweets and passport login promblem --- config/passport.js | 7 ++++--- controllers/apis/user-controller.js | 18 +++++++++++------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/config/passport.js b/config/passport.js index ac22ea839c..0588044edd 100644 --- a/config/passport.js +++ b/config/passport.js @@ -11,19 +11,19 @@ passport.use(new LocalStrategy( { usernameField: 'account', passwordField: 'password', passReqToCallback: true }, async (req, account, password, cb) => { try { + const errorMessage = '帳號或密碼輸入錯誤!' const user = await User.findOne({ where: { account } }) // 帳號或密碼輸入錯誤 暫時的錯誤處理 if (!user) { - console.log('帳號或密碼輸入錯誤!') - return cb(null, false, req.flash('error_messages', '帳號或密碼輸入錯誤!')) + return cb(null, false, { message: errorMessage }) } const isMatch = await bcrypt.compare(password, user.password) // 帳號或密碼輸入錯誤 暫時的錯誤處理 if (!isMatch) { console.log('帳號或密碼輸入錯誤!') - return cb(null, false, req.flash('error_messages', '帳號或密碼輸入錯誤!')) + return cb(null, false, { message: errorMessage }) } return cb(null, user) } catch (error) { @@ -55,6 +55,7 @@ passport.use(new JWTStrategy(jwtOptions, async (jwtPayload, cb) => { // ] } ) + if (!user) { return cb(null, false) } return cb(null, user) } catch (error) { cb(error) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index f3b6bb0022..ae9b15f962 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -53,32 +53,36 @@ const userController = { }, getUserTweets: async (req, res, next) => { try { - const { id } = req.params + let { id } = req.params + id = Number(id) const userTweets = await Tweet.findAll({ - where: { userId: id }, + where: { UserId: id }, raw: true, nest: true }) + console.log(userTweets) if (!userTweets) return res.json({ status: 'error', data: 'The user have not post any tweet yet' }) return res.json({ status: 'success', data: userTweets }) } catch (error) { next(error) } }, - getUser: (req, res, next) => { + getUser: async (req, res, next) => { try { - const { id } = req.params - const user = User.findByPk(id) + let id = req.params.id + id = Number(id) + const user = await User.findByPk(id) + console.log(user) if (!user) return res.json({ status: 'error', data: 'The user does not exist' }) res.json({ status: 'success', data: user }) } catch (error) { next(error) } }, - getUserRepliedTweet: (req, res, next) => { + getUserRepliedTweet: async (req, res, next) => { try { const { id } = req.params - const repliedTweets = Reply.findAll({ + const repliedTweets = await Reply.findAll({ where: { UserId: id }, raw: true, nest: true From 1cc8667b68b1702799a6925ab57fe6b28fef08b4 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Thu, 8 Jun 2023 20:11:23 +0800 Subject: [PATCH 045/131] resolve getUserRepliedTweet & getUserTweets problem --- controllers/apis/user-controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index ae9b15f962..0060401f4f 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -61,7 +61,7 @@ const userController = { nest: true }) console.log(userTweets) - if (!userTweets) return res.json({ status: 'error', data: 'The user have not post any tweet yet' }) + if (!userTweets.length) return res.json({ status: 'error', data: 'The user have not post any tweet yet' }) return res.json({ status: 'success', data: userTweets }) } catch (error) { next(error) @@ -87,7 +87,7 @@ const userController = { raw: true, nest: true }) - if (!repliedTweets) return res.json({ status: 'error', data: 'The user does not exist' }) + if (!repliedTweets.length) return res.json({ status: 'error', data: 'The user does not exist' }) return res.json({ status: 'success', data: repliedTweets }) } catch (error) { next(error) From 367d7702396dfdda4d5e98ac2990640eac15aae9 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Thu, 8 Jun 2023 21:28:33 +0800 Subject: [PATCH 046/131] feat: add passport cb error --- _helpers.js | 6 +++++- config/passport.js | 6 +++--- controllers/apis/user-controller.js | 1 - 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/_helpers.js b/_helpers.js index 70c100e3ac..806abd31d2 100644 --- a/_helpers.js +++ b/_helpers.js @@ -3,8 +3,12 @@ function getUser (req) { return req.user || null } +function ensureAuthenticated (req) { + return req.isAuthenticated() +} + module.exports = { - getUser + getUser, ensureAuthenticated } // use helpers.getUser(req) to replace req.user diff --git a/config/passport.js b/config/passport.js index 0588044edd..e46e5193fe 100644 --- a/config/passport.js +++ b/config/passport.js @@ -16,14 +16,14 @@ passport.use(new LocalStrategy( // 帳號或密碼輸入錯誤 暫時的錯誤處理 if (!user) { - return cb(null, false, { message: errorMessage }) + return cb(errorMessage, false, { message: errorMessage }) } const isMatch = await bcrypt.compare(password, user.password) // 帳號或密碼輸入錯誤 暫時的錯誤處理 if (!isMatch) { console.log('帳號或密碼輸入錯誤!') - return cb(null, false, { message: errorMessage }) + return cb(errorMessage, false, { message: errorMessage }) } return cb(null, user) } catch (error) { @@ -55,7 +55,7 @@ passport.use(new JWTStrategy(jwtOptions, async (jwtPayload, cb) => { // ] } ) - if (!user) { return cb(null, false) } + if (!user) return cb(null, false) return cb(null, user) } catch (error) { cb(error) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 0060401f4f..aacd709391 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -8,7 +8,6 @@ const userController = { try { const userData = getUser(req).toJSON() delete userData.password - // if(!user) return res.json({status: 'failed'}) const token = jwt.sign(userData, process.env.JWT_SECRET, { expiresIn: '30d' }) res.json({ status: 'success', From 601ca2f3c174748daae52dd73792d14494252929 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Thu, 8 Jun 2023 21:30:33 +0800 Subject: [PATCH 047/131] feat: add passport-jwt cb error --- config/passport.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/passport.js b/config/passport.js index e46e5193fe..287fbd0752 100644 --- a/config/passport.js +++ b/config/passport.js @@ -40,6 +40,7 @@ const jwtOptions = { passport.use(new JWTStrategy(jwtOptions, async (jwtPayload, cb) => { try { + const errorMessage = 'unAuthenticated' const user = await User.findByPk(jwtPayload.id , { // include: [ @@ -55,7 +56,7 @@ passport.use(new JWTStrategy(jwtOptions, async (jwtPayload, cb) => { // ] } ) - if (!user) return cb(null, false) + if (!user) return cb(errorMessage, false, { message: errorMessage }) return cb(null, user) } catch (error) { cb(error) From c6f08b8dcfa0c1ba3486fbb47b9be9ca0b9dc54c Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Thu, 8 Jun 2023 22:20:41 +0800 Subject: [PATCH 048/131] feat add reply GET /api/tweets/:tweet_id/replies and POST /api/tweets/:tweet_id/replies --- controllers/apis/tweet-controller.js | 35 ++++++- routes/apis/index.js | 4 + test/requests/reply.spec.js | 142 +++++++++++++-------------- 3 files changed, 105 insertions(+), 76 deletions(-) diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index 926a68d2f7..7bb65511e9 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -1,4 +1,4 @@ -const { Tweet } = require('../../models') +const { Tweet, Reply, Like } = require('../../models') const tweetController = { postTweet: async (req, res, next) => { @@ -32,6 +32,39 @@ const tweetController = { } catch (error) { next(error) } + }, + postReply: async (req, res, next) => { + try { + const { tweet_id } = req.params + const { comment } = req.body + const tweet = await Tweet.findByPk(tweet_id) + if (!tweet) return res.json({ status: 'error', data: 'The tweet does not exist' }) + + const reply = await Reply.create({ + UserId: res.locals.userId, + TweetId: tweet_id, + comment + }) + return res.json({ status: 'success', data: reply }) + } catch (error) { + next(error) + } + }, + getReply: async (req, res, next) => { + try { + const { tweet_id } = req.params + const tweet = await Tweet.findByPk(tweet_id) + if (!tweet) return res.json({ status: 'error', data: 'The tweet does not exist' }) + + const replies = await Reply.findAll({ + where: { TweetId: tweet_id }, + raw: true, + nest: true + }) + return res.json({ status: 'success', data: replies }) + } catch (error) { + next(error) + } } } diff --git a/routes/apis/index.js b/routes/apis/index.js index e9c18678f6..1f386cd646 100644 --- a/routes/apis/index.js +++ b/routes/apis/index.js @@ -18,6 +18,10 @@ router.post('/login', passport.authenticate('local', { session: false }), userCo // 有關tweet的routes router.post('/tweets', authenticated, tweetController.postTweet) router.get('/tweets', authenticated, tweetController.getTweets) +router.post('/tweets/:tweet_id/replies', authenticated, tweetController.postReply) +router.get('/tweets/:tweet_id/replies', authenticated, tweetController.getReply) +// router.post('/tweets/:tweet_id/like', authenticated, tweetController.postReply) +// router.get('/tweets/:tweet_id/unlike', authenticated, tweetController.getReply) router.get('/tweets/:tweet_id', authenticated, tweetController.getTweet) // 有關user的routes diff --git a/test/requests/reply.spec.js b/test/requests/reply.spec.js index c355eb7eee..d221e9099b 100644 --- a/test/requests/reply.spec.js +++ b/test/requests/reply.spec.js @@ -2,124 +2,116 @@ const chai = require('chai') const request = require('supertest') const sinon = require('sinon') const app = require('../../app') -const helpers = require('../../_helpers'); -const should = chai.should(); -const expect = chai.expect; +const helpers = require('../../_helpers') +const should = chai.should() +const expect = chai.expect const db = require('../../models') const passport = require('../../config/passport') describe('# reply requests', () => { - context('# POST ', () => { - describe(' /api/tweets/:tweet_id/replies', () => { - before(async() => { + before(async () => { // 清除 User, Tweet, Reply table 的測試資料庫資料 - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.Tweet.destroy({where: {},truncate: true, force: true}) - await db.Reply.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.Tweet.destroy({ where: {}, truncate: true, force: true }) + await db.Reply.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) // 模擬登入資料 - const rootUser = await db.User.create({name: 'root'});this.authenticate = sinon.stub(passport,"authenticate").callsFake((strategy, options, callback) => { - callback(null, {...rootUser}, null); - return (req,res,next)=>{}; - }); + const rootUser = await db.User.create({ name: 'root' }); this.authenticate = sinon.stub(passport, 'authenticate').callsFake((strategy, options, callback) => { + callback(null, { ...rootUser }, null) + return (req, res, next) => {} + }) this.getUser = sinon.stub( - helpers, 'getUser' - ).returns({id: 1, Followings: [], role: 'user'}); + helpers, 'getUser' + ).returns({ id: 1, Followings: [], role: 'user' }) // 在測試資料庫中,新增 mock 資料 - await db.User.create({account: 'User1', name: 'User1', email: 'User1', password: 'User1'}) - await db.User.create({account: 'User2', name: 'User2', email: 'User2', password: 'User2'}) - await db.Tweet.create({UserId: 2, description: 'User2 的 Tweet1'}) + await db.User.create({ account: 'User1', name: 'User1', email: 'User1', password: 'User1' }) + await db.User.create({ account: 'User2', name: 'User2', email: 'User2', password: 'User2' }) + await db.Tweet.create({ UserId: 2, description: 'User2 的 Tweet1' }) }) // 新增回覆 POST /tweets/:tweet_id/replies - it(' - successfully', (done) => { + it(' - successfully', done => { request(app) .post('/api/tweets/1/replies') .send('comment=comment') .set('Accept', 'application/json') .expect(200) - .end(function(err, res) { - if (err) return done(err); + .end(function (err, res) { + if (err) return done(err) // 檢查是否有成功新增留言 db.Reply.findByPk(1).then(reply => { - reply.comment.should.equal('comment'); - reply.UserId.should.equal(1); - reply.TweetId.should.equal(1); - return done(); + reply.comment.should.equal('comment') + reply.UserId.should.equal(1) + reply.TweetId.should.equal(1) + return done() }) }) - }); + }) after(async () => { // 清除登入及測試資料庫資料 - this.authenticate.restore(); - this.getUser.restore(); - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.Tweet.destroy({where: {},truncate: true, force: true}) - await db.Reply.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + this.authenticate.restore() + this.getUser.restore() + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.Tweet.destroy({ where: {}, truncate: true, force: true }) + await db.Reply.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) }) - - }); - - }); + }) + }) context('# GET ', () => { - describe('GET /api/tweets/:tweet_id/replies', () => { - before(async() => { + before(async () => { // 清除 User table 的測試資料庫資料 - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.Tweet.destroy({where: {},truncate: true, force: true}) - await db.Reply.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.Tweet.destroy({ where: {}, truncate: true, force: true }) + await db.Reply.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) // 模擬登入資料 - const rootUser = await db.User.create({name: 'root'});this.authenticate = sinon.stub(passport,"authenticate").callsFake((strategy, options, callback) => { - callback(null, {...rootUser}, null); - return (req,res,next)=>{}; - }); + const rootUser = await db.User.create({ name: 'root' }); this.authenticate = sinon.stub(passport, 'authenticate').callsFake((strategy, options, callback) => { + callback(null, { ...rootUser }, null) + return (req, res, next) => {} + }) this.getUser = sinon.stub( - helpers, 'getUser' - ).returns({id: 1, Followings: [], role: 'user'}); + helpers, 'getUser' + ).returns({ id: 1, Followings: [], role: 'user' }) // 在測試資料庫中,新增 mock 資料 - await db.User.create({account: 'User1', name: 'User1', email: 'User1', password: 'User1'}) - await db.Tweet.create({UserId: 1, description: 'User1 的 Tweet1'}) - await db.Reply.create({UserId: 1, TweetId: 1, comment: 'Tweet1 的 comment'}) + await db.User.create({ account: 'User1', name: 'User1', email: 'User1', password: 'User1' }) + await db.Tweet.create({ UserId: 1, description: 'User1 的 Tweet1' }) + await db.Reply.create({ UserId: 1, TweetId: 1, comment: 'Tweet1 的 comment' }) }) // 瀏覽 GET /tweets/:tweet_id/replies - it(' - successfully', (done) => { + it(' - successfully', done => { request(app) .get('/api/tweets/1/replies') .set('Accept', 'application/json') .expect(200) - .end(function(err, res) { - if (err) return done(err); + .end(function (err, res) { + if (err) return done(err) // 檢查是否有成功取得留言 - expect(res.body).to.be.an('array'); - res.body[0].comment.should.equal('Tweet1 的 comment'); - return done(); + expect(res.body).to.be.an('array') + res.body[0].comment.should.equal('Tweet1 的 comment') + return done() }) - }); + }) after(async () => { // 清除登入及測試資料庫資料 - this.authenticate.restore(); - this.getUser.restore(); - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.Tweet.destroy({where: {},truncate: true, force: true}) - await db.Reply.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + this.authenticate.restore() + this.getUser.restore() + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.Tweet.destroy({ where: {}, truncate: true, force: true }) + await db.Reply.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) }) - - }); - - }); - -}); + }) + }) +}) From 601feb80248163be9aff6b7bb41f03426e91557d Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Thu, 8 Jun 2023 22:50:20 +0800 Subject: [PATCH 049/131] feat: add likeTweet and unlikeTweet --- controllers/apis/tweet-controller.js | 43 ++++++++++++++++++++++++++++ routes/apis/index.js | 4 +-- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index 7bb65511e9..c5908c3f96 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -65,6 +65,49 @@ const tweetController = { } catch (error) { next(error) } + }, + likeTweet: async (req, res, next) => { + try { + const { tweet_id } = req.params + const PromiseArr = await Promise.all( + [ + Tweet.findByPk(tweet_id), + Like.findOne({ + where: { + UserId: res.locals.userId, + TweetId: tweet_id + } + }) + ]) + if (!PromiseArr[0]) return res.json({ status: 'error', data: 'The tweet does not exist' }) + if (PromiseArr[1]) return res.json({ status: 'error', data: 'The like already exists' }) + + const like = await Like.create({ + UserId: res.locals.userId, + TweetId: tweet_id + }) + return res.json({ status: 'success', data: like }) + } catch (error) { + next(error) + } + }, + unlikeTweet: async (req, res, next) => { + try { + const { tweet_id } = req.params + const tweet = await Tweet.findByPk(tweet_id) + if (!tweet) return res.json({ status: 'error', data: 'The tweet does not exist' }) + const like = await Like.findOne({ + where: { + UserId: res.locals.userId, + TweetId: tweet_id + } + }) + if (!like) return res.json({ status: 'error', data: 'The like does not exist' }) + await like.destroy() + return res.json({ status: 'success', data: like }) + } catch (err) { + next(err) + } } } diff --git a/routes/apis/index.js b/routes/apis/index.js index 1f386cd646..9968772776 100644 --- a/routes/apis/index.js +++ b/routes/apis/index.js @@ -20,8 +20,8 @@ router.post('/tweets', authenticated, tweetController.postTweet) router.get('/tweets', authenticated, tweetController.getTweets) router.post('/tweets/:tweet_id/replies', authenticated, tweetController.postReply) router.get('/tweets/:tweet_id/replies', authenticated, tweetController.getReply) -// router.post('/tweets/:tweet_id/like', authenticated, tweetController.postReply) -// router.get('/tweets/:tweet_id/unlike', authenticated, tweetController.getReply) +router.get('/tweets/:tweet_id/like', authenticated, tweetController.likeTweet) +router.get('/tweets/:tweet_id/unlike', authenticated, tweetController.unlikeTweet) router.get('/tweets/:tweet_id', authenticated, tweetController.getTweet) // 有關user的routes From 4cefb33a09c29fd530d7ae9ab2961daea69d10ea Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Thu, 8 Jun 2023 23:11:44 +0800 Subject: [PATCH 050/131] feat: add likeTweet and unlikeTweet and revisd the Id --- controllers/apis/tweet-controller.js | 14 +-- routes/apis/index.js | 4 +- test/requests/like.spec.js | 134 +++++++++++++-------------- 3 files changed, 73 insertions(+), 79 deletions(-) diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index c5908c3f96..fa05c7e4e0 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -68,14 +68,14 @@ const tweetController = { }, likeTweet: async (req, res, next) => { try { - const { tweet_id } = req.params + const { id } = req.params const PromiseArr = await Promise.all( [ - Tweet.findByPk(tweet_id), + Tweet.findByPk(id), Like.findOne({ where: { UserId: res.locals.userId, - TweetId: tweet_id + TweetId: id } }) ]) @@ -84,7 +84,7 @@ const tweetController = { const like = await Like.create({ UserId: res.locals.userId, - TweetId: tweet_id + TweetId: id }) return res.json({ status: 'success', data: like }) } catch (error) { @@ -93,13 +93,13 @@ const tweetController = { }, unlikeTweet: async (req, res, next) => { try { - const { tweet_id } = req.params - const tweet = await Tweet.findByPk(tweet_id) + const { id } = req.params + const tweet = await Tweet.findByPk(id) if (!tweet) return res.json({ status: 'error', data: 'The tweet does not exist' }) const like = await Like.findOne({ where: { UserId: res.locals.userId, - TweetId: tweet_id + TweetId: id } }) if (!like) return res.json({ status: 'error', data: 'The like does not exist' }) diff --git a/routes/apis/index.js b/routes/apis/index.js index 9968772776..d14d6e9fc7 100644 --- a/routes/apis/index.js +++ b/routes/apis/index.js @@ -20,8 +20,8 @@ router.post('/tweets', authenticated, tweetController.postTweet) router.get('/tweets', authenticated, tweetController.getTweets) router.post('/tweets/:tweet_id/replies', authenticated, tweetController.postReply) router.get('/tweets/:tweet_id/replies', authenticated, tweetController.getReply) -router.get('/tweets/:tweet_id/like', authenticated, tweetController.likeTweet) -router.get('/tweets/:tweet_id/unlike', authenticated, tweetController.unlikeTweet) +router.get('/tweets/:id/like', authenticated, tweetController.likeTweet) +router.get('/tweets/:id/unlike', authenticated, tweetController.unlikeTweet) router.get('/tweets/:tweet_id', authenticated, tweetController.getTweet) // 有關user的routes diff --git a/test/requests/like.spec.js b/test/requests/like.spec.js index 3f3b957c8e..1864b99563 100644 --- a/test/requests/like.spec.js +++ b/test/requests/like.spec.js @@ -2,120 +2,114 @@ const chai = require('chai') const request = require('supertest') const sinon = require('sinon') const app = require('../../app') -const helpers = require('../../_helpers'); -const should = chai.should(); -const expect = chai.expect; +const helpers = require('../../_helpers') +const should = chai.should() +const expect = chai.expect const db = require('../../models') const passport = require('../../config/passport') describe('# like requests', () => { - context('# POST ', () => { - describe(' /api/tweets/:id/like', () => { - before(async() => { + before(async () => { // 清除 User, Tweet, Like table 的測試資料庫資料 - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.Tweet.destroy({where: {},truncate: true, force: true}) - await db.Like.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.Tweet.destroy({ where: {}, truncate: true, force: true }) + await db.Like.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) // 模擬登入資料 - const rootUser = await db.User.create({name: 'root'});this.authenticate = sinon.stub(passport,"authenticate").callsFake((strategy, options, callback) => { - callback(null, {...rootUser}, null); - return (req,res,next)=>{}; - }); + const rootUser = await db.User.create({ name: 'root' }); this.authenticate = sinon.stub(passport, 'authenticate').callsFake((strategy, options, callback) => { + callback(null, { ...rootUser }, null) + return (req, res, next) => {} + }) this.getUser = sinon.stub( - helpers, 'getUser' - ).returns({id: 1, Followings: [], role: 'user'}); + helpers, 'getUser' + ).returns({ id: 1, Followings: [], role: 'user' }) // 在測試資料庫中,新增 mock 資料 - await db.User.create({account: 'User1', name: 'User1', email: 'User1', password: 'User1'}) - await db.User.create({account: 'User2', name: 'User2', email: 'User2', password: 'User2'}) - await db.Tweet.create({UserId: 2, description: 'User2 的 Tweet1'}) + await db.User.create({ account: 'User1', name: 'User1', email: 'User1', password: 'User1' }) + await db.User.create({ account: 'User2', name: 'User2', email: 'User2', password: 'User2' }) + await db.Tweet.create({ UserId: 2, description: 'User2 的 Tweet1' }) }) // POST /tweets/:id/like 喜歡一則推文 - it(' - successfully', (done) => { + it(' - successfully', done => { request(app) .post('/api/tweets/1/like') .set('Accept', 'application/json') .expect(200) - .end(function(err, res) { - if (err) return done(err); + .end(function (err, res) { + if (err) return done(err) // 檢查 Like 資料裡,是否有 UserId=1, TweetId =1 的資料 db.Like.findByPk(1).then(like => { - like.UserId.should.equal(1); - like.TweetId.should.equal(1); - return done(); + like.UserId.should.equal(1) + like.TweetId.should.equal(1) + return done() }) }) - }); + }) after(async () => { // 清除登入及測試資料庫資料 - this.authenticate.restore(); - this.getUser.restore(); - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.Tweet.destroy({where: {},truncate: true, force: true}) - await db.Like.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + this.authenticate.restore() + this.getUser.restore() + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.Tweet.destroy({ where: {}, truncate: true, force: true }) + await db.Like.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) }) - - }); + }) describe(' /api/tweets/:id/unlike', () => { - before(async() => { + before(async () => { // 清除 User, Tweet, Like table 的測試資料庫資料 - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.Tweet.destroy({where: {},truncate: true, force: true}) - await db.Like.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.Tweet.destroy({ where: {}, truncate: true, force: true }) + await db.Like.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) // 模擬登入資料 - const rootUser = await db.User.create({name: 'root'});this.authenticate = sinon.stub(passport,"authenticate").callsFake((strategy, options, callback) => { - callback(null, {...rootUser}, null); - return (req,res,next)=>{}; - }); + const rootUser = await db.User.create({ name: 'root' }); this.authenticate = sinon.stub(passport, 'authenticate').callsFake((strategy, options, callback) => { + callback(null, { ...rootUser }, null) + return (req, res, next) => {} + }) this.getUser = sinon.stub( - helpers, 'getUser' - ).returns({id: 1, Followings: [], role: 'user'}); + helpers, 'getUser' + ).returns({ id: 1, Followings: [], role: 'user' }) // 在測試資料庫中,新增 mock 資料 - await db.User.create({account: 'User1', name: 'User1', email: 'User1', password: 'User1'}) - await db.User.create({account: 'User2', name: 'User2', email: 'User2', password: 'User2'}) - await db.Tweet.create({UserId: 2, description: 'User2 的 Tweet1'}) - await db.Like.create({UserId: 1, TweetId: 1}) + await db.User.create({ account: 'User1', name: 'User1', email: 'User1', password: 'User1' }) + await db.User.create({ account: 'User2', name: 'User2', email: 'User2', password: 'User2' }) + await db.Tweet.create({ UserId: 2, description: 'User2 的 Tweet1' }) + await db.Like.create({ UserId: 1, TweetId: 1 }) }) // POST /tweets/:id/unlike 取消喜歡 - it(' - successfully', (done) => { + it(' - successfully', done => { request(app) .post('/api/tweets/1/unlike') .set('Accept', 'application/json') .expect(200) - .end(function(err, res) { - if (err) return done(err); + .end(function (err, res) { + if (err) return done(err) // 檢查是否 Like table 中的資料是空的,表示有刪除成功 db.Like.findByPk(1).then(like => { expect(like).to.be.null - return done(); + return done() }) }) - }); + }) after(async () => { // 清除登入及測試資料庫資料 - this.authenticate.restore(); - this.getUser.restore(); - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.Tweet.destroy({where: {},truncate: true, force: true}) - await db.Like.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + this.authenticate.restore() + this.getUser.restore() + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.Tweet.destroy({ where: {}, truncate: true, force: true }) + await db.Like.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) }) - - }); - - }); - -}); + }) + }) +}) From 39e6ec6be18b2ca400b198e1c35ae734505c4530 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Thu, 8 Jun 2023 23:43:15 +0800 Subject: [PATCH 051/131] feat: getUserRepliedTweet join Tweets to get tweet --- controllers/apis/user-controller.js | 7 ++++++- seeders/20230605161408-tweets-seed-file.js | 6 +++++- seeders/20230608073227-replies-seed-file.js | 6 +++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index aacd709391..1f539b91bf 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -83,11 +83,16 @@ const userController = { const { id } = req.params const repliedTweets = await Reply.findAll({ where: { UserId: id }, + include: [Tweet], raw: true, nest: true }) if (!repliedTweets.length) return res.json({ status: 'error', data: 'The user does not exist' }) - return res.json({ status: 'success', data: repliedTweets }) + const tweets = [] + for (const i of repliedTweets) { + tweets.push(i.Tweet) + } + return res.json({ status: 'success', data: tweets }) } catch (error) { next(error) } diff --git a/seeders/20230605161408-tweets-seed-file.js b/seeders/20230605161408-tweets-seed-file.js index 8b655bd200..5a3c29b692 100644 --- a/seeders/20230605161408-tweets-seed-file.js +++ b/seeders/20230605161408-tweets-seed-file.js @@ -9,9 +9,13 @@ module.exports = { ) const tweets = [] for (let i = 0; i < 50; i++) { + let description = faker.lorem.text() + if (description.length > 140) { + description = description.slice(0, 140) + } tweets.push({ UserId: users[i % users.length].id, - description: faker.lorem.text(), + description, createdAt: new Date(), updatedAt: new Date() }) diff --git a/seeders/20230608073227-replies-seed-file.js b/seeders/20230608073227-replies-seed-file.js index 9eb4e57b95..f7d9ab7246 100644 --- a/seeders/20230608073227-replies-seed-file.js +++ b/seeders/20230608073227-replies-seed-file.js @@ -16,11 +16,15 @@ module.exports = { const eachUserTweetsCount = 3 const repliedTweets = [] for (let i = 0; i < 150; i++) { + let comment = faker.lorem.text() + if (comment.length > 140) { + comment = comment.slice(0, 140) + } const randomUser = Math.floor(Math.random() * users.length) repliedTweets.push({ UserId: users[randomUser].id, TweetId: tweets[Math.floor(i / eachUserTweetsCount)].id, - comment: faker.lorem.text(), + comment, createdAt: new Date(), updatedAt: new Date() }) From f64a7bd181faae374408d08d9c20faa2446a822d Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Thu, 8 Jun 2023 23:55:05 +0800 Subject: [PATCH 052/131] feat: add cors --- app.js | 4 ++++ package-lock.json | 30 ++++++++++++++++++++++-------- package.json | 3 ++- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/app.js b/app.js index 7f4e907fdd..4f40b1375b 100644 --- a/app.js +++ b/app.js @@ -7,6 +7,7 @@ const passport = require('./config/passport') const flash = require('connect-flash') const { apis, pages } = require('./routes') const session = require('express-session') +const cors = require('cors') const app = express() const port = process.env.PORT || 3000 @@ -29,6 +30,9 @@ app.use(passport.initialize()) // flash app.use(flash()) +// cors +app.use(cors()) + // locals app.use((req, res, next) => { res.locals.error_messages = req.flash('error_messages') diff --git a/package-lock.json b/package-lock.json index 24e323d11f..1b2845f4a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -326,7 +326,7 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==" + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "ansi-styles": { "version": "3.2.1", @@ -1268,6 +1268,15 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -1447,7 +1456,7 @@ "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" } } }, @@ -1571,7 +1580,7 @@ "es6-iterator": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", "requires": { "d": "1", "es5-ext": "^0.10.35", @@ -2109,7 +2118,7 @@ "event-emitter": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", "requires": { "d": "1", "es5-ext": "~0.10.14" @@ -3078,7 +3087,7 @@ "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "requires": { "graceful-fs": "^4.1.6" } @@ -3254,7 +3263,7 @@ "lru-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", - "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==", + "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", "requires": { "es5-ext": "~0.10.2" } @@ -3597,6 +3606,11 @@ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==" }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, "object-inspect": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", @@ -3960,7 +3974,7 @@ "proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" + "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=" }, "proxy-addr": { "version": "2.0.7", @@ -4450,7 +4464,7 @@ "sigmund": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==" + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" }, "signal-exit": { "version": "3.0.7", diff --git a/package.json b/package.json index 4203318b89..3a37b011ec 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "body-parser": "^1.18.3", "chai": "^4.2.0", "connect-flash": "^0.1.1", + "cors": "^2.8.5", "dotenv": "^10.0.0", "express": "^4.16.4", "express-session": "^1.15.6", @@ -43,4 +44,4 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^5.1.0" } -} \ No newline at end of file +} From ef9022953102f0c415eed4468d0754f02ac98370 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Fri, 9 Jun 2023 10:25:48 +0800 Subject: [PATCH 053/131] feat: postFollowship & deleteFollowship --- controllers/apis/followship-controller.js | 40 +++++++++++++++++++++++ package-lock.json | 16 ++++----- routes/apis/index.js | 7 +++- 3 files changed, 54 insertions(+), 9 deletions(-) create mode 100644 controllers/apis/followship-controller.js diff --git a/controllers/apis/followship-controller.js b/controllers/apis/followship-controller.js new file mode 100644 index 0000000000..779de2bcb5 --- /dev/null +++ b/controllers/apis/followship-controller.js @@ -0,0 +1,40 @@ +const { getUser } = require('../../_helpers') +const { Followship } = require('../../models') + +const followshipController = { + postFollowship: async (req, res, next) => { + try { + const { id } = req.body + // 確認使用者是否已經追蹤該用戶 + const followship = await Followship.findOne({ where: { followingId: id, followerId: getUser(req).id } }) + // 如果有 => 不用新增 + if (followship) return res.json({ status: 'error', data: 'You have followed this user.' }) + + const newFollow = await Followship.create({ + followerId: getUser(req).id, + followingId: id + }) + return res.json({ status: 'success', data: newFollow }) + } catch (error) { + next(error) + } + }, + deleteFollowship: async (req, res, next) => { + try { + const { followingId } = req.params + // 確認使用者是否已經追蹤該用戶 + const follow = await Followship.findOne({ + where: { followerId: getUser(req).id, followingId } + }) + // 如果沒有 => 不用刪除 + if (!follow) return res.json({ status: 'error', data: 'You can follow this user.' }) + + const deleteFollow = await follow.destroy() + return res.json({ status: 'success', data: deleteFollow.toJSON() }) + } catch (error) { + next(error) + } + } +} + +module.exports = followshipController diff --git a/package-lock.json b/package-lock.json index 1b2845f4a5..ac85a7e483 100644 --- a/package-lock.json +++ b/package-lock.json @@ -326,7 +326,7 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==" }, "ansi-styles": { "version": "3.2.1", @@ -1456,7 +1456,7 @@ "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" } } }, @@ -1580,7 +1580,7 @@ "es6-iterator": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", "requires": { "d": "1", "es5-ext": "^0.10.35", @@ -2118,7 +2118,7 @@ "event-emitter": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", "requires": { "d": "1", "es5-ext": "~0.10.14" @@ -3087,7 +3087,7 @@ "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "requires": { "graceful-fs": "^4.1.6" } @@ -3263,7 +3263,7 @@ "lru-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", - "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", + "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==", "requires": { "es5-ext": "~0.10.2" } @@ -3974,7 +3974,7 @@ "proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=" + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" }, "proxy-addr": { "version": "2.0.7", @@ -4464,7 +4464,7 @@ "sigmund": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" + "integrity": "sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==" }, "signal-exit": { "version": "3.0.7", diff --git a/routes/apis/index.js b/routes/apis/index.js index d14d6e9fc7..555086d6d6 100644 --- a/routes/apis/index.js +++ b/routes/apis/index.js @@ -3,8 +3,9 @@ const passport = require('../../config/passport') const admin = require('./modules/admin') const userController = require('../../controllers/apis/user-controller') const adminController = require('../../controllers/apis/admin-controller') -const { apiErrorHandler } = require('../../middleware/error-handler') const tweetController = require('../../controllers/apis/tweet-controller') +const followshipController = require('../../controllers/apis/followship-controller') +const { apiErrorHandler } = require('../../middleware/error-handler') const { authenticated, roleChecker, authenticatedAdmin } = require('../../middleware/api-auth') // 有關admin的routes @@ -24,6 +25,10 @@ router.get('/tweets/:id/like', authenticated, tweetController.likeTweet) router.get('/tweets/:id/unlike', authenticated, tweetController.unlikeTweet) router.get('/tweets/:tweet_id', authenticated, tweetController.getTweet) +// 有關followship的routes +router.post('/followships', authenticated, followshipController.postFollowship) +router.delete('/followships/:followingId', authenticated, followshipController.deleteFollowship) + // 有關user的routes router.get('/users/:id', authenticated, userController.getUser) router.get('/users/:id/tweets', authenticated, userController.getUserTweets) From 7c1d00c6e27347e605af88052b15afa3018de8d8 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Fri, 9 Jun 2023 11:16:10 +0800 Subject: [PATCH 054/131] feat: replace errormessage with thorw new Error --- controllers/apis/admin-controller.js | 6 +++--- controllers/apis/followship-controller.js | 4 ++-- controllers/apis/tweet-controller.js | 17 +++++++++-------- controllers/apis/user-controller.js | 11 +++++++---- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/controllers/apis/admin-controller.js b/controllers/apis/admin-controller.js index 89935cdfae..a4e581f2d1 100644 --- a/controllers/apis/admin-controller.js +++ b/controllers/apis/admin-controller.js @@ -12,8 +12,8 @@ const adminController = { where: { account }, raw: true }) - if (!user) return res.json({ status: 'error', data: 'You are not admin' }) - if (user.role !== 'admin') return res.json({ status: 'error', data: 'You are not admin' }) + if (!user) throw new Error('You are not admin') + if (user.role !== 'admin') throw new Error('You are not admin') const userData = user delete userData.password userData.token = token @@ -43,7 +43,7 @@ const adminController = { const { id } = req.params const tweet = await Tweet.findByPk(id) console.log(tweet) - if (!tweet) return res.json({ status: 'error', data: 'The tweet does not exist' }) + if (!tweet) throw new Error('The tweet does not exist') await tweet.destroy() return res.json({ status: 'success', data: tweet }) } catch (error) { diff --git a/controllers/apis/followship-controller.js b/controllers/apis/followship-controller.js index 779de2bcb5..52c81b0397 100644 --- a/controllers/apis/followship-controller.js +++ b/controllers/apis/followship-controller.js @@ -8,7 +8,7 @@ const followshipController = { // 確認使用者是否已經追蹤該用戶 const followship = await Followship.findOne({ where: { followingId: id, followerId: getUser(req).id } }) // 如果有 => 不用新增 - if (followship) return res.json({ status: 'error', data: 'You have followed this user.' }) + if (followship) throw new Error('You have followed this user.') const newFollow = await Followship.create({ followerId: getUser(req).id, @@ -27,7 +27,7 @@ const followshipController = { where: { followerId: getUser(req).id, followingId } }) // 如果沒有 => 不用刪除 - if (!follow) return res.json({ status: 'error', data: 'You can follow this user.' }) + if (!follow) throw new Error('You have not followed this user.') const deleteFollow = await follow.destroy() return res.json({ status: 'success', data: deleteFollow.toJSON() }) diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index fa05c7e4e0..698e603099 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -16,7 +16,8 @@ const tweetController = { getTweets: async (req, res, next) => { try { const tweets = await Tweet.findAll() - if (!tweets) return res.json({ status: 'error', data: 'There is no tweet' }) + if (!tweets) throw new Error('There is no tweet') + return res.json({ status: 'success', data: tweets }) } catch (error) { next(error) @@ -26,7 +27,7 @@ const tweetController = { try { const { tweet_id } = req.params const tweet = await Tweet.findByPk(tweet_id) - if (!tweet) return res.json({ status: 'error', data: 'The tweet does not exist' }) + if (!tweet) throw new Error('The tweet does not exist') return res.json({ status: 'success', data: tweet }) } catch (error) { @@ -38,7 +39,7 @@ const tweetController = { const { tweet_id } = req.params const { comment } = req.body const tweet = await Tweet.findByPk(tweet_id) - if (!tweet) return res.json({ status: 'error', data: 'The tweet does not exist' }) + if (!tweet) throw new Error('The tweet does not exist') const reply = await Reply.create({ UserId: res.locals.userId, @@ -54,7 +55,7 @@ const tweetController = { try { const { tweet_id } = req.params const tweet = await Tweet.findByPk(tweet_id) - if (!tweet) return res.json({ status: 'error', data: 'The tweet does not exist' }) + if (!tweet) throw new Error('The tweet does not exist') const replies = await Reply.findAll({ where: { TweetId: tweet_id }, @@ -79,8 +80,8 @@ const tweetController = { } }) ]) - if (!PromiseArr[0]) return res.json({ status: 'error', data: 'The tweet does not exist' }) - if (PromiseArr[1]) return res.json({ status: 'error', data: 'The like already exists' }) + if (!PromiseArr[0]) throw new Error('The tweet does not exist') + if (PromiseArr[1]) throw new Error('The like already exists') const like = await Like.create({ UserId: res.locals.userId, @@ -95,14 +96,14 @@ const tweetController = { try { const { id } = req.params const tweet = await Tweet.findByPk(id) - if (!tweet) return res.json({ status: 'error', data: 'The tweet does not exist' }) + if (!tweet) throw new Error('The tweet does not exist') const like = await Like.findOne({ where: { UserId: res.locals.userId, TweetId: id } }) - if (!like) return res.json({ status: 'error', data: 'The like does not exist' }) + if (!like) throw new Error('The like does not exist') await like.destroy() return res.json({ status: 'success', data: like }) } catch (err) { diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 1f539b91bf..5369752ce6 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -35,7 +35,9 @@ const userController = { ]) if (userAccount) errors.push('account已存在') if (userEmail) errors.push('email已存在') - if (errors.length) return res.json({ status: 'error', data: errors }) + if (errors.length) { + throw new Error(errors) + } const user = await User.create({ name, account, @@ -60,7 +62,7 @@ const userController = { nest: true }) console.log(userTweets) - if (!userTweets.length) return res.json({ status: 'error', data: 'The user have not post any tweet yet' }) + if (!userTweets.length) throw new Error('The user have not post any tweet yet') return res.json({ status: 'success', data: userTweets }) } catch (error) { next(error) @@ -72,7 +74,7 @@ const userController = { id = Number(id) const user = await User.findByPk(id) console.log(user) - if (!user) return res.json({ status: 'error', data: 'The user does not exist' }) + if (!user) throw new Error('The user does not exist') res.json({ status: 'success', data: user }) } catch (error) { next(error) @@ -87,7 +89,8 @@ const userController = { raw: true, nest: true }) - if (!repliedTweets.length) return res.json({ status: 'error', data: 'The user does not exist' }) + if (!repliedTweets.length) throw new Error('The user does not exist') + const tweets = [] for (const i of repliedTweets) { tweets.push(i.Tweet) From 8ddcdae670a39120e4675f5b127b9195092111b6 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Fri, 9 Jun 2023 12:37:11 +0800 Subject: [PATCH 055/131] login, adminLogin and signup error status code 200 --- config/passport.js | 5 ++--- controllers/apis/user-controller.js | 3 ++- middleware/error-handler.js | 7 ++++++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/config/passport.js b/config/passport.js index 287fbd0752..8ea5b0e60d 100644 --- a/config/passport.js +++ b/config/passport.js @@ -16,14 +16,13 @@ passport.use(new LocalStrategy( // 帳號或密碼輸入錯誤 暫時的錯誤處理 if (!user) { - return cb(errorMessage, false, { message: errorMessage }) + return cb(errorMessage, false) } const isMatch = await bcrypt.compare(password, user.password) // 帳號或密碼輸入錯誤 暫時的錯誤處理 if (!isMatch) { - console.log('帳號或密碼輸入錯誤!') - return cb(errorMessage, false, { message: errorMessage }) + return cb(errorMessage, false) } return cb(null, user) } catch (error) { diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 5369752ce6..98caf87934 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -36,7 +36,8 @@ const userController = { if (userAccount) errors.push('account已存在') if (userEmail) errors.push('email已存在') if (errors.length) { - throw new Error(errors) + // throw new Error(errors) + return res.json({ status: 'error', message: errors }) } const user = await User.create({ name, diff --git a/middleware/error-handler.js b/middleware/error-handler.js index 360dbdf84f..b11488dc04 100644 --- a/middleware/error-handler.js +++ b/middleware/error-handler.js @@ -1,6 +1,11 @@ module.exports = { apiErrorHandler (err, req, res, next) { - if (err instanceof Error) { + if (err === '帳號或密碼輸入錯誤!') { + res.status(200).json({ + status: 'error', + message: err + }) + } else if (err instanceof Error) { res.status(err.status || 500).json({ status: 'error', message: `${err.name}: ${err.message}` From 8c74ffb8018303e2b988ac0ffd264228eaa37a8d Mon Sep 17 00:00:00 2001 From: s1030905 Date: Fri, 9 Jun 2023 23:08:24 +0800 Subject: [PATCH 056/131] feat: add like seeds and followship seeds --- seeders/20230608073227-replies-seed-file.js | 2 +- seeders/20230609094429-likes-seed-file.js | 50 +++++++++++++++++++ .../20230609095534-followships-seed-file.js | 31 ++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 seeders/20230609094429-likes-seed-file.js create mode 100644 seeders/20230609095534-followships-seed-file.js diff --git a/seeders/20230608073227-replies-seed-file.js b/seeders/20230608073227-replies-seed-file.js index f7d9ab7246..5b0d14ec63 100644 --- a/seeders/20230608073227-replies-seed-file.js +++ b/seeders/20230608073227-replies-seed-file.js @@ -29,7 +29,7 @@ module.exports = { updatedAt: new Date() }) } - queryInterface.bulkInsert('Replies', repliedTweets) + await queryInterface.bulkInsert('Replies', repliedTweets) }, down: async (queryInterface, Sequelize) => { diff --git a/seeders/20230609094429-likes-seed-file.js b/seeders/20230609094429-likes-seed-file.js new file mode 100644 index 0000000000..2b2525d833 --- /dev/null +++ b/seeders/20230609094429-likes-seed-file.js @@ -0,0 +1,50 @@ +'use strict' + +module.exports = { + up: async (queryInterface, Sequelize) => { + const [tweets, users] = await Promise.all([ + queryInterface.sequelize.query( + 'SELECT id FROM Tweets;', + { type: queryInterface.sequelize.QueryTypes.SELECT } + ), + queryInterface.sequelize.query( + "SELECT id FROM Users WHERE role <> 'admin'", + { type: queryInterface.sequelize.QueryTypes.SELECT } + ) + ]) + const likes = [ + { + UserId: users[0].id, + TweetId: tweets[0].id, + createdAt: new Date(), + updatedAt: new Date() + }, + { + UserId: users[0].id, + TweetId: tweets[1].id, + createdAt: new Date(), + updatedAt: new Date() + }, { + UserId: users[1].id, + TweetId: tweets[1].id, + createdAt: new Date(), + updatedAt: new Date() + }, { + UserId: users[3].id, + TweetId: tweets[15].id, + createdAt: new Date(), + updatedAt: new Date() + }, { + UserId: users[4].id, + TweetId: tweets[0].id, + createdAt: new Date(), + updatedAt: new Date() + } + ] + await queryInterface.bulkInsert('Likes', likes) + }, + + down: async (queryInterface, Sequelize) => { + await queryInterface.bulkDelete('Likes', {}) + } +} diff --git a/seeders/20230609095534-followships-seed-file.js b/seeders/20230609095534-followships-seed-file.js new file mode 100644 index 0000000000..3c28df3284 --- /dev/null +++ b/seeders/20230609095534-followships-seed-file.js @@ -0,0 +1,31 @@ +'use strict' + +module.exports = { + up: async (queryInterface, Sequelize) => { + const users = await queryInterface.sequelize.query("SELECT id FROM Users WHERE role <> 'admin'", + { type: queryInterface.sequelize.QueryTypes.SELECT }) + const followships = [] + let counts = 0 + for (let i = 0; i < users.length; i++) { + for (let j = i + 1; j < users.length; j++) { + followships.push( + { + followerId: users[i].id, + followingId: users[j].id, + createdAt: new Date(), + updatedAt: new Date() + } + ) + counts++ + if (counts === 10) { + break + } + } + } + await queryInterface.bulkInsert('Followships', followships) + }, + + down: async (queryInterface, Sequelize) => { + await queryInterface.bulkDelete('Followships', {}) + } +} From 8a95ab2cde8d1e75ad7a22f79a7ec66b75777d1d Mon Sep 17 00:00:00 2001 From: s1030905 Date: Fri, 9 Jun 2023 23:11:11 +0800 Subject: [PATCH 057/131] feat : getUserLiked, getUserFollows, getUserFollowers --- controllers/apis/admin-controller.js | 1 - controllers/apis/followship-controller.js | 2 +- controllers/apis/user-controller.js | 148 +++++++++++++++++++--- routes/apis/index.js | 18 ++- 4 files changed, 141 insertions(+), 28 deletions(-) diff --git a/controllers/apis/admin-controller.js b/controllers/apis/admin-controller.js index a4e581f2d1..939c86d650 100644 --- a/controllers/apis/admin-controller.js +++ b/controllers/apis/admin-controller.js @@ -42,7 +42,6 @@ const adminController = { try { const { id } = req.params const tweet = await Tweet.findByPk(id) - console.log(tweet) if (!tweet) throw new Error('The tweet does not exist') await tweet.destroy() return res.json({ status: 'success', data: tweet }) diff --git a/controllers/apis/followship-controller.js b/controllers/apis/followship-controller.js index 52c81b0397..78f924c282 100644 --- a/controllers/apis/followship-controller.js +++ b/controllers/apis/followship-controller.js @@ -27,7 +27,7 @@ const followshipController = { where: { followerId: getUser(req).id, followingId } }) // 如果沒有 => 不用刪除 - if (!follow) throw new Error('You have not followed this user.') + if (!follow) throw new Error("You have'nt followed this user.") const deleteFollow = await follow.destroy() return res.json({ status: 'success', data: deleteFollow.toJSON() }) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 98caf87934..f13c33dbaf 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -1,7 +1,7 @@ const jwt = require('jsonwebtoken') const bcrypt = require('bcryptjs') const { getUser } = require('../../_helpers') -const { User, Tweet, Reply } = require('../../models') +const { User, Tweet, Reply, Like, Followship } = require('../../models') const userController = { login: (req, res, next) => { @@ -47,7 +47,6 @@ const userController = { }) const userData = user.toJSON() delete userData.password - console.log(userData) return res.json({ status: 'success', data: userData }) } catch (error) { next(error) @@ -57,13 +56,18 @@ const userController = { try { let { id } = req.params id = Number(id) - const userTweets = await Tweet.findAll({ - where: { UserId: id }, - raw: true, - nest: true - }) - console.log(userTweets) - if (!userTweets.length) throw new Error('The user have not post any tweet yet') + + // 確認使用者是否存在與發過文 + const [user, userTweets] = await Promise.all([ + User.findByPk(id), + Tweet.findAll({ + where: { UserId: id }, + raw: true, + nest: true + }) + ]) + if (!user) throw new Error('The user does not exist') + if (!userTweets.length) throw new Error("The user have'nt post any tweet yet") return res.json({ status: 'success', data: userTweets }) } catch (error) { next(error) @@ -73,8 +77,9 @@ const userController = { try { let id = req.params.id id = Number(id) + + // 確認使用者是否存在 const user = await User.findByPk(id) - console.log(user) if (!user) throw new Error('The user does not exist') res.json({ status: 'success', data: user }) } catch (error) { @@ -83,20 +88,121 @@ const userController = { }, getUserRepliedTweet: async (req, res, next) => { try { - const { id } = req.params - const repliedTweets = await Reply.findAll({ - where: { UserId: id }, - include: [Tweet], - raw: true, - nest: true - }) - if (!repliedTweets.length) throw new Error('The user does not exist') + let { id } = req.params + id = Number(id) + + // 確認使用者是否存在與回過文 + const [user, repliedTweets] = await Promise.all([ + User.findByPk(id), + Reply.findAll({ + where: { UserId: id }, + include: [Tweet], + raw: true, + nest: true + }) + ]) + if (!user) throw new Error('The user does not exist') + if (!repliedTweets.length) throw new Error("The user have'nt replied any tweets yet.") - const tweets = [] + const data = [] for (const i of repliedTweets) { - tweets.push(i.Tweet) + data.push(i.Tweet) } - return res.json({ status: 'success', data: tweets }) + return res.json({ status: 'success', data }) + } catch (error) { + next(error) + } + }, + getUserLiked: async (req, res, next) => { + try { + let { id } = req.params + id = Number(id) + + // 確認使用者是否存在與喜歡的貼文 + const [user, userLiked] = await Promise.all([ + User.findByPk(id), + Like.findAll({ + where: { UserId: Number(id) }, + include: [Tweet], + raw: true, + nest: true + }) + ]) + if (!user) throw new Error('The user does not exist') + if (!userLiked.length) throw new Error('He does not like anyone.') + const data = [] + for (const i of userLiked) { + data.push(i.Tweet) + } + return res.json({ status: 'success', data }) + } catch (error) { + next(error) + } + }, + getUserFollows: async (req, res, next) => { + try { + let { id } = req.params + id = Number(id) + + // 確認使用者是否存在其追蹤者 + const [user, userFollows] = await Promise.all([ + User.findByPk(id), + Followship.findAll({ + where: { followerId: Number(id) }, + raw: true, + nest: true + }) + ]) + if (!user) throw new Error('The user does not exist') + if (!userFollows.length) throw new Error("He haven't followed anyone") + const data = [] + for (const i of req.user.Followings) { + data.push(i.dataValues) + } + return res.json({ status: 'success', data }) + } catch (error) { + next(error) + } + }, + getUserFollowers: async (req, res, next) => { + try { + let { id } = req.params + id = Number(id) + + // 確認使用者是否存在其追隨者 + const [user, userFollowers] = await Promise.all([ + User.findByPk(id), + Followship.findAll({ + where: { followingId: Number(id) } + }) + ]) + if (!user) throw new Error('The user does not exist') + if (!userFollowers.length) throw new Error('He is lonely') + + const data = [] + for (const i of req.user.Followers) { + data.push(i.dataValues) + } + return res.json({ status: 'success', data }) + } catch (error) { + next(error) + } + }, + putUser: async (req, res, next) => { + try { + const { email, password, name, avatar, introduction, background, account } = req.body + + if (introduction.length < 160) throw new Error('Your self-introduction is a little too long for me to handle! Please less than 160.') + if (name.length < 50) throw new Error('Your self-introduction is a little too long for me to handle! ! Please less than 50.') + const [checkEmail, checkAccount] = await Promise.all([ + User.findOne({ where: { email } }), + User.findOne({ where: { account } }) + ]) + if (checkEmail) throw new Error('Oops! Your email already exist') + if (checkAccount) throw new Error('Oops! Your account already exist') + User.update({ + email, password, name, avatar, introduction, background, account + }) } catch (error) { next(error) } diff --git a/routes/apis/index.js b/routes/apis/index.js index 555086d6d6..b3f92baa86 100644 --- a/routes/apis/index.js +++ b/routes/apis/index.js @@ -9,13 +9,10 @@ const { apiErrorHandler } = require('../../middleware/error-handler') const { authenticated, roleChecker, authenticatedAdmin } = require('../../middleware/api-auth') // 有關admin的routes +// admin登入 router.post('/admin/users', passport.authenticate('local', { session: false }), roleChecker, adminController.adminLogin) router.use('/admin', authenticatedAdmin, roleChecker, admin) -// 使用者登入註冊 -router.post('/signup', userController.signUp) -router.post('/login', passport.authenticate('local', { session: false }), userController.login) - // 有關tweet的routes router.post('/tweets', authenticated, tweetController.postTweet) router.get('/tweets', authenticated, tweetController.getTweets) @@ -30,9 +27,20 @@ router.post('/followships', authenticated, followshipController.postFollowship) router.delete('/followships/:followingId', authenticated, followshipController.deleteFollowship) // 有關user的routes -router.get('/users/:id', authenticated, userController.getUser) router.get('/users/:id/tweets', authenticated, userController.getUserTweets) router.get('/users/:id/replied_tweets', authenticated, userController.getUserRepliedTweet) +router.get('/users/:id', authenticated, userController.getUser) + +// 新的 從這裡開始做 +router.get('/users/:id/likes', authenticated, userController.getUserLiked) // 看見某使用者點過的 Like +router.get('/users/:id/followings', authenticated, userController.getUserFollows) // 看見某使用者跟隨中的人 +router.get('/users/:id/followers', authenticated, userController.getUserFollowers) // 看見某使用者的跟隨者 +router.put('/users/:id', authenticated, userController.putUser) // 編輯自己所有的資料 + +// 使用者登入 +router.post('/users/login', passport.authenticate('local', { session: false }), userController.login) +// 使用者註冊 +router.post('/users', userController.signUp) router.use('/', apiErrorHandler) From 1466c4125e5eb87bf853a6c0da4d11fbca19a2ea Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Fri, 9 Jun 2023 23:27:41 +0800 Subject: [PATCH 058/131] fix: finish one pass from requests/admin --- controllers/apis/admin-controller.js | 7 +- middleware/api-auth.js | 4 +- routes/apis/index.js | 4 +- test/requests/admin.spec.js | 125 +++++++++++++-------------- 4 files changed, 66 insertions(+), 74 deletions(-) diff --git a/controllers/apis/admin-controller.js b/controllers/apis/admin-controller.js index a4e581f2d1..56d4da893d 100644 --- a/controllers/apis/admin-controller.js +++ b/controllers/apis/admin-controller.js @@ -24,7 +24,9 @@ const adminController = { }, getUsers: async (req, res, next) => { try { - const users = await User.findAll() + const users = await User.findAll({ + raw: true + }) return res.json({ status: 'success', data: users }) } catch (error) { next(error) @@ -42,10 +44,9 @@ const adminController = { try { const { id } = req.params const tweet = await Tweet.findByPk(id) - console.log(tweet) if (!tweet) throw new Error('The tweet does not exist') await tweet.destroy() - return res.json({ status: 'success', data: tweet }) + return res.json({ status: 'success', data: {} }) } catch (error) { next(error) } diff --git a/middleware/api-auth.js b/middleware/api-auth.js index 2f354ab6e1..61f5b07c30 100644 --- a/middleware/api-auth.js +++ b/middleware/api-auth.js @@ -21,8 +21,8 @@ const authenticatedAdmin = (req, res, next) => { const roleChecker = (req, res, next) => { const user = getUser(req) - if (user.role === 'admin') return next() - return res.status(403).json({ status: 'error', message: 'permission denied' }) + if (user.role === 'admin' || (user.name === 'root' && user.id === 1)) return next() + return res.status(404).json({ status: 'error', message: 'permission denied' }) } module.exports = { diff --git a/routes/apis/index.js b/routes/apis/index.js index 555086d6d6..96ac67d9bf 100644 --- a/routes/apis/index.js +++ b/routes/apis/index.js @@ -21,8 +21,8 @@ router.post('/tweets', authenticated, tweetController.postTweet) router.get('/tweets', authenticated, tweetController.getTweets) router.post('/tweets/:tweet_id/replies', authenticated, tweetController.postReply) router.get('/tweets/:tweet_id/replies', authenticated, tweetController.getReply) -router.get('/tweets/:id/like', authenticated, tweetController.likeTweet) -router.get('/tweets/:id/unlike', authenticated, tweetController.unlikeTweet) +router.post('/tweets/:id/like', authenticated, tweetController.likeTweet) +router.post('/tweets/:id/unlike', authenticated, tweetController.unlikeTweet) router.get('/tweets/:tweet_id', authenticated, tweetController.getTweet) // 有關followship的routes diff --git a/test/requests/admin.spec.js b/test/requests/admin.spec.js index a8006197f7..01b1eb17fa 100644 --- a/test/requests/admin.spec.js +++ b/test/requests/admin.spec.js @@ -2,119 +2,110 @@ const chai = require('chai') const request = require('supertest') const sinon = require('sinon') const app = require('../../app') -const helpers = require('../../_helpers'); -const should = chai.should(); -const expect = chai.expect; +const helpers = require('../../_helpers') +const should = chai.should() +const expect = chai.expect const db = require('../../models') const passport = require('../../config/passport') // admin 相關功能測試 // 1. 管理者可以看見站內所有的使用者 -// 2. 管理者可以刪除使用者的推文 +// 2. 管理者可以刪除使用者的推文 describe('# admin requests', () => { - context('# GET ', () => { - describe(' /api/admin/users', () => { - before(async() => { + before(async () => { // 清除 User table 的測試資料庫資料 - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) // 模擬登入資料 - const rootUser = await db.User.create({name: 'root'});this.authenticate = sinon.stub(passport,"authenticate").callsFake((strategy, options, callback) => { - callback(null, {...rootUser}, null); - return (req,res,next)=>{}; - }); + const rootUser = await db.User.create({ name: 'root' }); this.authenticate = sinon.stub(passport, 'authenticate').callsFake((strategy, options, callback) => { + callback(null, { ...rootUser }, null) + return (req, res, next) => {} + }) this.getUser = sinon.stub( - helpers, 'getUser' - ).returns({id: 1, Followings: [], role: 'admin'}); + helpers, 'getUser' + ).returns({ id: 1, Followings: [], role: 'admin' }) // 在測試資料庫中,新增 mock 資料 - await db.User.create({account: 'User1', name: 'User1', email: 'User1', password: 'User1', role: 'admin'}) - await db.User.create({account: 'User2', name: 'User2', email: 'User2', password: 'User2'}) + await db.User.create({ account: 'User1', name: 'User1', email: 'User1', password: 'User1', role: 'admin' }) + await db.User.create({ account: 'User2', name: 'User2', email: 'User2', password: 'User2' }) }) // GET /admin/users - 看見站內所有的使用者 - it(' - successfully', (done) => { + it(' - successfully', done => { request(app) .get('/api/admin/users') .set('Accept', 'application/json') .expect(200) - .end(function(err, res) { - if (err) return done(err); + .end(function (err, res) { + if (err) return done(err) // 檢查回傳資料是否是陣列類型 - expect(res.body).to.be.an('array'); + expect(res.body).to.be.an('array') // 檢查回傳資料是否有 3 筆使用者資料 - res.body.length.should.equal(3); - return done(); + res.body.length.should.equal(3) + return done() }) - }); + }) after(async () => { // 清除登入及測試資料庫資料 - this.authenticate.restore(); - this.getUser.restore(); - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + this.authenticate.restore() + this.getUser.restore() + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) }) - - }); - - }); + }) + }) context('# DELETE ', () => { - describe(' /api/admin/tweets/:id', () => { - before(async() => { + before(async () => { // 清除 User, Tweet table 的測試資料庫資料 - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.Tweet.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.Tweet.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) // 模擬登入資料 - const rootUser = await db.User.create({name: 'root'});this.authenticate = sinon.stub(passport,"authenticate").callsFake((strategy, options, callback) => { - callback(null, {...rootUser}, null); - return (req,res,next)=>{}; - }); + const rootUser = await db.User.create({ name: 'root' }); this.authenticate = sinon.stub(passport, 'authenticate').callsFake((strategy, options, callback) => { + callback(null, { ...rootUser }, null) + return (req, res, next) => {} + }) this.getUser = sinon.stub( - helpers, 'getUser' - ).returns({id: 1, Followings: [], role: 'admin'}); + helpers, 'getUser' + ).returns({ id: 1, Followings: [], role: 'admin' }) // 在測試資料庫中,新增 mock 資料 - await db.User.create({account: 'User1', name: 'User1', email: 'User1', password: 'User1', role: 'admin'}) - await db.User.create({account: 'User2', name: 'User2', email: 'User2', password: 'User2'}) - await db.Tweet.create({UserId: 1, description: 'User1 的 description'}) + await db.User.create({ account: 'User1', name: 'User1', email: 'User1', password: 'User1', role: 'admin' }) + await db.User.create({ account: 'User2', name: 'User2', email: 'User2', password: 'User2' }) + await db.Tweet.create({ UserId: 1, description: 'User1 的 description' }) }) // DELETE /admin/tweets/:id - 刪除使用者的推文 - it(' - successfully', (done) => { + it(' - successfully', done => { request(app) .delete('/api/admin/tweets/1') .set('Accept', 'application/json') .expect(200) - .end(function(err, res) { - if (err) return done(err); + .end(function (err, res) { + if (err) return done(err) // 檢查是否 Tweet table 中的資料是空的,表示有刪除成功 db.Tweet.findByPk(1).then(tweet => { expect(tweet).to.be.null - return done(); + return done() }) - }) - }); + }) after(async () => { // 清除登入及測試資料庫資料 - this.authenticate.restore(); - this.getUser.restore(); - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.Tweet.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + this.authenticate.restore() + this.getUser.restore() + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.Tweet.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) }) - - }); - - }); - -}); + }) + }) +}) From 3eff11016633f6ebb2071e8b033c7e082dadb58c Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Fri, 9 Jun 2023 23:30:56 +0800 Subject: [PATCH 059/131] fix: revise like and unlike to post --- routes/apis/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routes/apis/index.js b/routes/apis/index.js index 555086d6d6..96ac67d9bf 100644 --- a/routes/apis/index.js +++ b/routes/apis/index.js @@ -21,8 +21,8 @@ router.post('/tweets', authenticated, tweetController.postTweet) router.get('/tweets', authenticated, tweetController.getTweets) router.post('/tweets/:tweet_id/replies', authenticated, tweetController.postReply) router.get('/tweets/:tweet_id/replies', authenticated, tweetController.getReply) -router.get('/tweets/:id/like', authenticated, tweetController.likeTweet) -router.get('/tweets/:id/unlike', authenticated, tweetController.unlikeTweet) +router.post('/tweets/:id/like', authenticated, tweetController.likeTweet) +router.post('/tweets/:id/unlike', authenticated, tweetController.unlikeTweet) router.get('/tweets/:tweet_id', authenticated, tweetController.getTweet) // 有關followship的routes From 1a4327da94493eb6e2a2149073aeca9b8ccfdc61 Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Sat, 10 Jun 2023 00:04:55 +0800 Subject: [PATCH 060/131] fix: revise return json format --- controllers/apis/tweet-controller.js | 6 +- controllers/apis/user-controller.js | 4 +- test/requests/user.spec.js | 460 +++++++++++++-------------- 3 files changed, 224 insertions(+), 246 deletions(-) diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index 698e603099..dee0d4599b 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -18,7 +18,7 @@ const tweetController = { const tweets = await Tweet.findAll() if (!tweets) throw new Error('There is no tweet') - return res.json({ status: 'success', data: tweets }) + return res.json(tweets) } catch (error) { next(error) } @@ -29,7 +29,7 @@ const tweetController = { const tweet = await Tweet.findByPk(tweet_id) if (!tweet) throw new Error('The tweet does not exist') - return res.json({ status: 'success', data: tweet }) + return res.json(tweet) } catch (error) { next(error) } @@ -62,7 +62,7 @@ const tweetController = { raw: true, nest: true }) - return res.json({ status: 'success', data: replies }) + return res.status(200).json(replies) } catch (error) { next(error) } diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 98caf87934..14b0ced45f 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -64,7 +64,7 @@ const userController = { }) console.log(userTweets) if (!userTweets.length) throw new Error('The user have not post any tweet yet') - return res.json({ status: 'success', data: userTweets }) + return res.json(userTweets) } catch (error) { next(error) } @@ -96,7 +96,7 @@ const userController = { for (const i of repliedTweets) { tweets.push(i.Tweet) } - return res.json({ status: 'success', data: tweets }) + return res.json(repliedTweets) } catch (error) { next(error) } diff --git a/test/requests/user.spec.js b/test/requests/user.spec.js index e19d5e4229..bc27fc0380 100644 --- a/test/requests/user.spec.js +++ b/test/requests/user.spec.js @@ -2,413 +2,391 @@ const chai = require('chai') const request = require('supertest') const sinon = require('sinon') const app = require('../../app') -const helpers = require('../../_helpers'); +const helpers = require('../../_helpers') const should = chai.should() -const expect = chai.expect; +const expect = chai.expect const db = require('../../models') const passport = require('../../config/passport') describe('# user requests', () => { - context('# POST ', () => { - describe('POST /api/users', () => { - before(async() => { + before(async () => { // 清除測試資料庫資料 - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) }) // 註冊自己的帳號 POST /users - it(' - successfully', (done) => { + it(' - successfully', done => { request(app) .post('/api/users') .send('account=User1&name=User1&email=User1@example.com&password=User1&checkPassword=User1') .set('Accept', 'application/json') .expect(200) - .end(function(err, res) { - if (err) return done(err); + .end(function (err, res) { + if (err) return done(err) // 檢查是否有成功新增資料到資料庫裡 db.User.findByPk(1).then(user => { - user.account.should.equal('User1'); - user.email.should.equal('User1@example.com'); - return done(); + user.account.should.equal('User1') + user.email.should.equal('User1@example.com') + return done() }) }) - }); + }) after(async () => { // 清除測試資料庫資料 - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) }) - - }); - - }); - + }) + }) context('# GET ', () => { - describe('GET /users/:id', () => { - before(async() => { + before(async () => { // 清除測試資料庫資料 - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) // 模擬登入資料 - const rootUser = await db.User.create({name: 'root'});this.authenticate = sinon.stub(passport,"authenticate").callsFake((strategy, options, callback) => { - callback(null, {...rootUser}, null); - return (req,res,next)=>{}; - }); + const rootUser = await db.User.create({ name: 'root' }); this.authenticate = sinon.stub(passport, 'authenticate').callsFake((strategy, options, callback) => { + callback(null, { ...rootUser }, null) + return (req, res, next) => {} + }) this.getUser = sinon.stub( - helpers, 'getUser' - ).returns({id: 1, Followings: [], role: 'user'}); + helpers, 'getUser' + ).returns({ id: 1, Followings: [], role: 'user' }) // 在測試資料庫中,新增 mock 資料 - await db.User.create({account: 'User1', name: 'User1', email: 'User1', password: 'User1'}) - await db.User.create({account: 'User2', name: 'User2', email: 'User2', password: 'User2'}) + await db.User.create({ account: 'User1', name: 'User1', email: 'User1', password: 'User1' }) + await db.User.create({ account: 'User2', name: 'User2', email: 'User2', password: 'User2' }) }) - // GET /users/:id - it(' - successfully', (done) => { + it(' - successfully', done => { request(app) .get('/api/users/1') .set('Accept', 'application/json') .expect(200) - .end(function(err, res) { - if (err) return done(err); + .end(function (err, res) { + if (err) return done(err) // 檢查是否回傳資料裡有 root 的資料 - res.body.name.should.equal('root'); + res.body.name.should.equal('root') - return done(); + return done() }) - }); + }) after(async () => { // 清除登入及測試資料庫資料 - this.authenticate.restore(); - this.getUser.restore(); - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + this.authenticate.restore() + this.getUser.restore() + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) }) - - }); - + }) describe('GET /users/:id/tweets', () => { - before(async() => { + before(async () => { // 清除測試資料庫資料 - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.Tweet.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.Tweet.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) // 模擬登入資料 - const rootUser = await db.User.create({name: 'root'});this.authenticate = sinon.stub(passport,"authenticate").callsFake((strategy, options, callback) => { - callback(null, {...rootUser}, null); - return (req,res,next)=>{}; - }); + const rootUser = await db.User.create({ name: 'root' }); this.authenticate = sinon.stub(passport, 'authenticate').callsFake((strategy, options, callback) => { + callback(null, { ...rootUser }, null) + return (req, res, next) => {} + }) this.getUser = sinon.stub( - helpers, 'getUser' - ).returns({id: 1, Followings: [], role: 'user'}); + helpers, 'getUser' + ).returns({ id: 1, Followings: [], role: 'user' }) // 在測試資料庫中,新增 mock 資料 - await db.User.create({account: 'User1', name: 'User1', email: 'User1', password: 'User1'}) - await db.Tweet.create({UserId: 1, description: 'User1 的 Tweet1'}) + await db.User.create({ account: 'User1', name: 'User1', email: 'User1', password: 'User1' }) + await db.Tweet.create({ UserId: 1, description: 'User1 的 Tweet1' }) }) // GET /users/:id/tweets - 看見某使用者發過的推文 - it(' - self successfully', (done) => { + it(' - self successfully', done => { request(app) .get('/api/users/1/tweets') .set('Accept', 'application/json') .expect(200) - .end(function(err, res) { - if (err) return done(err); + .end(function (err, res) { + if (err) return done(err) - expect(res.body).to.be.an('array'); + expect(res.body).to.be.an('array') // 有回傳某使用者的推文資料 - res.body[0].description.should.equal('User1 的 Tweet1'); + res.body[0].description.should.equal('User1 的 Tweet1') - return done(); + return done() }) - }); + }) after(async () => { // 清除登入及測試資料庫資料 - this.authenticate.restore(); - this.getUser.restore(); - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.Tweet.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + this.authenticate.restore() + this.getUser.restore() + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.Tweet.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) }) - - }); + }) describe('GET /users/:id/replied_tweets', () => { - before(async() => { + before(async () => { // 清除測試資料庫資料 - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.Tweet.destroy({where: {},truncate: true, force: true}) - await db.Reply.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.Tweet.destroy({ where: {}, truncate: true, force: true }) + await db.Reply.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) // 模擬登入資料 - const rootUser = await db.User.create({name: 'root'});this.authenticate = sinon.stub(passport,"authenticate").callsFake((strategy, options, callback) => { - callback(null, {...rootUser}, null); - return (req,res,next)=>{}; - }); + const rootUser = await db.User.create({ name: 'root' }); this.authenticate = sinon.stub(passport, 'authenticate').callsFake((strategy, options, callback) => { + callback(null, { ...rootUser }, null) + return (req, res, next) => {} + }) this.getUser = sinon.stub( - helpers, 'getUser' - ).returns({id: 1, Followings: [], role: 'user'}); + helpers, 'getUser' + ).returns({ id: 1, Followings: [], role: 'user' }) // 在測試資料庫中,新增 mock 資料 - await db.User.create({account: 'User1', name: 'User1', email: 'User1', password: 'User1'}) - await db.Tweet.create({UserId: 1, description: 'User1 的 Tweet1'}) - await db.Reply.create({UserId: 1, TweetId: 1, comment: 'Tweet1 的 comment'}) + await db.User.create({ account: 'User1', name: 'User1', email: 'User1', password: 'User1' }) + await db.Tweet.create({ UserId: 1, description: 'User1 的 Tweet1' }) + await db.Reply.create({ UserId: 1, TweetId: 1, comment: 'Tweet1 的 comment' }) }) // GET /users/:id/replied_tweets - 看見某使用者發過回覆的推文 - it(' - successfully', (done) => { + it(' - successfully', done => { request(app) .get('/api/users/1/replied_tweets') .set('Accept', 'application/json') .expect(200) - .end(function(err, res) { - if (err) return done(err); + .end(function (err, res) { + if (err) return done(err) - expect(res.body).to.be.an('array'); + expect(res.body).to.be.an('array') // 有回傳 Tweet1 的 comment 這筆資料 - res.body[0].comment.should.equal('Tweet1 的 comment'); + res.body[0].comment.should.equal('Tweet1 的 comment') - return done(); + return done() }) - }); + }) after(async () => { // 清除登入及測試資料庫資料 - this.authenticate.restore(); - this.getUser.restore(); - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.Tweet.destroy({where: {},truncate: true, force: true}) - await db.Reply.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + this.authenticate.restore() + this.getUser.restore() + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.Tweet.destroy({ where: {}, truncate: true, force: true }) + await db.Reply.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) }) - - }); + }) describe('GET /users/:id/likes', () => { - before(async() => { + before(async () => { // 清除測試資料庫資料 - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.Tweet.destroy({where: {},truncate: true, force: true}) - await db.Like.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.Tweet.destroy({ where: {}, truncate: true, force: true }) + await db.Like.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) // 模擬登入資料 - const rootUser = await db.User.create({name: 'root'});this.authenticate = sinon.stub(passport,"authenticate").callsFake((strategy, options, callback) => { - callback(null, {...rootUser}, null); - return (req,res,next)=>{}; - }); + const rootUser = await db.User.create({ name: 'root' }); this.authenticate = sinon.stub(passport, 'authenticate').callsFake((strategy, options, callback) => { + callback(null, { ...rootUser }, null) + return (req, res, next) => {} + }) this.getUser = sinon.stub( - helpers, 'getUser' - ).returns({id: 1, Followings: [], role: 'user'}); + helpers, 'getUser' + ).returns({ id: 1, Followings: [], role: 'user' }) // 在測試資料庫中,新增 mock 資料 - await db.User.create({account: 'User1', name: 'User1', email: 'User1', password: 'User1'}) - await db.User.create({account: 'User2', name: 'User2', email: 'User2', password: 'User2'}) - await db.Tweet.create({UserId: 2, description: 'User2 的 Tweet1'}) - await db.Like.create({UserId: 1, TweetId: 1}) + await db.User.create({ account: 'User1', name: 'User1', email: 'User1', password: 'User1' }) + await db.User.create({ account: 'User2', name: 'User2', email: 'User2', password: 'User2' }) + await db.Tweet.create({ UserId: 2, description: 'User2 的 Tweet1' }) + await db.Like.create({ UserId: 1, TweetId: 1 }) }) - // GET /users/:id/likes - 看見某使用者點過的 Like - it(' - successfully', (done) => { + // GET /users/:id/likes - 看見某使用者點過的 Like + it(' - successfully', done => { request(app) .get('/api/users/1/likes') .set('Accept', 'application/json') .expect(200) - .end(function(err, res) { - if (err) return done(err); - expect(res.body).to.be.an('array'); + .end(function (err, res) { + if (err) return done(err) + expect(res.body).to.be.an('array') // 檢查回傳資料是否有 TweetId = 1 - res.body[0].TweetId.should.equal(1); + res.body[0].TweetId.should.equal(1) - return done(); + return done() }) - }); + }) after(async () => { // 清除登入及測試資料庫資料 - this.authenticate.restore(); - this.getUser.restore(); - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.Tweet.destroy({where: {},truncate: true, force: true}) - await db.Like.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + this.authenticate.restore() + this.getUser.restore() + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.Tweet.destroy({ where: {}, truncate: true, force: true }) + await db.Like.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) }) - - }); - + }) describe('GET /users/:id/followings', () => { - before(async() => { + before(async () => { // 清除測試資料庫資料 - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.Tweet.destroy({where: {},truncate: true, force: true}) - await db.Followship.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.Tweet.destroy({ where: {}, truncate: true, force: true }) + await db.Followship.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) // 模擬登入資料 - const rootUser = await db.User.create({name: 'root'});this.authenticate = sinon.stub(passport,"authenticate").callsFake((strategy, options, callback) => { - callback(null, {...rootUser}, null); - return (req,res,next)=>{}; - }); + const rootUser = await db.User.create({ name: 'root' }); this.authenticate = sinon.stub(passport, 'authenticate').callsFake((strategy, options, callback) => { + callback(null, { ...rootUser }, null) + return (req, res, next) => {} + }) this.getUser = sinon.stub( - helpers, 'getUser' - ).returns({id: 1, Followings: [], role: 'user'}); - await db.User.create({account: 'User1', name: 'User1', email: 'User1', password: 'User1'}) - await db.User.create({account: 'User2', name: 'User2', email: 'User2', password: 'User2'}) - await db.Followship.create({followerId: 1, followingId: 2}) + helpers, 'getUser' + ).returns({ id: 1, Followings: [], role: 'user' }) + await db.User.create({ account: 'User1', name: 'User1', email: 'User1', password: 'User1' }) + await db.User.create({ account: 'User2', name: 'User2', email: 'User2', password: 'User2' }) + await db.Followship.create({ followerId: 1, followingId: 2 }) }) // GET /users/:id/followings - 看見某使用者跟隨中的人 - it(' - successfully', (done) => { + it(' - successfully', done => { request(app) .get('/api/users/1/followings') .set('Accept', 'application/json') .expect(200) - .end(function(err, res) { - if (err) return done(err); + .end(function (err, res) { + if (err) return done(err) - expect(res.body).to.be.an('array'); - //回傳資料中是否有跟隨中的人的 id = 2 - res.body[0].followingId.should.equal(2); + expect(res.body).to.be.an('array') + // 回傳資料中是否有跟隨中的人的 id = 2 + res.body[0].followingId.should.equal(2) - return done(); + return done() }) - }); + }) after(async () => { // 清除登入及測試資料庫資料 - this.authenticate.restore(); - this.getUser.restore(); - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.Tweet.destroy({where: {},truncate: true, force: true}) - await db.Followship.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + this.authenticate.restore() + this.getUser.restore() + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.Tweet.destroy({ where: {}, truncate: true, force: true }) + await db.Followship.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) }) - - }); + }) describe('GET /users/:id/followers', () => { - before(async() => { + before(async () => { // 清除測試資料庫資料 - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.Tweet.destroy({where: {},truncate: true, force: true}) - await db.Followship.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.Tweet.destroy({ where: {}, truncate: true, force: true }) + await db.Followship.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) // 模擬登入資料 - const rootUser = await db.User.create({name: 'root'});this.authenticate = sinon.stub(passport,"authenticate").callsFake((strategy, options, callback) => { - callback(null, {...rootUser}, null); - return (req,res,next)=>{}; - }); + const rootUser = await db.User.create({ name: 'root' }); this.authenticate = sinon.stub(passport, 'authenticate').callsFake((strategy, options, callback) => { + callback(null, { ...rootUser }, null) + return (req, res, next) => {} + }) this.getUser = sinon.stub( - helpers, 'getUser' - ).returns({id: 1, Followings: [], role: 'user'}); + helpers, 'getUser' + ).returns({ id: 1, Followings: [], role: 'user' }) // 在測試資料庫中,新增 mock 資料 - await db.User.create({account: 'User1', name: 'User1', email: 'User1', password: 'User1'}) - await db.User.create({account: 'User2', name: 'User2', email: 'User2', password: 'User2'}) - await db.Followship.create({followerId: 1, followingId: 2}) + await db.User.create({ account: 'User1', name: 'User1', email: 'User1', password: 'User1' }) + await db.User.create({ account: 'User2', name: 'User2', email: 'User2', password: 'User2' }) + await db.Followship.create({ followerId: 1, followingId: 2 }) }) // GET /users/:id/followers - 看見某使用者的跟隨者 - it(' - successfully', (done) => { + it(' - successfully', done => { request(app) .get('/api/users/2/followers') .set('Accept', 'application/json') .expect(200) - .end(function(err, res) { - if (err) return done(err); + .end(function (err, res) { + if (err) return done(err) - expect(res.body).to.be.an('array'); + expect(res.body).to.be.an('array') // 有跟隨者的 followerId = 1 - res.body[0].followerId.should.equal(1); + res.body[0].followerId.should.equal(1) - return done(); + return done() }) - }); + }) after(async () => { // 清除登入及測試資料庫資料 - this.authenticate.restore(); - this.getUser.restore(); - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.Tweet.destroy({where: {},truncate: true, force: true}) - await db.Followship.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + this.authenticate.restore() + this.getUser.restore() + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.Tweet.destroy({ where: {}, truncate: true, force: true }) + await db.Followship.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) }) - - }); - - - }); - + }) + }) context('# PUT ', () => { - describe('PUT /api/users/:id', () => { - before(async() => { + before(async () => { // 清除 User, Tweet table 的測試資料庫資料 - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) // 模擬登入資料 - const rootUser = await db.User.create({name: 'root'});this.authenticate = sinon.stub(passport,"authenticate").callsFake((strategy, options, callback) => { - callback(null, {...rootUser}, null); - return (req,res,next)=>{}; - }); + const rootUser = await db.User.create({ name: 'root' }); this.authenticate = sinon.stub(passport, 'authenticate').callsFake((strategy, options, callback) => { + callback(null, { ...rootUser }, null) + return (req, res, next) => {} + }) this.getUser = sinon.stub( - helpers, 'getUser' - ).returns({id: 1, Followings: [], role: 'user'}); + helpers, 'getUser' + ).returns({ id: 1, Followings: [], role: 'user' }) // 在測試資料庫中,新增 mock 資料 - await db.User.create({account: 'User1', name: 'User1', email: 'User1', password: 'User1', introduction: 'User1'}) + await db.User.create({ account: 'User1', name: 'User1', email: 'User1', password: 'User1', introduction: 'User1' }) }) // 編輯自己所有的資料 PUT /users/:id - it(' - successfully', (done) => { + it(' - successfully', done => { request(app) .put('/api/users/1') .send('name=User11&introduction=User11') .set('Accept', 'application/json') .expect(200) - .end(function(err, res) { - if (err) return done(err); + .end(function (err, res) { + if (err) return done(err) db.User.findByPk(1).then(user => { // 檢查資料是否有變更 - user.name.should.equal('User11'); - user.introduction.should.equal('User11'); - return done(); + user.name.should.equal('User11') + user.introduction.should.equal('User11') + return done() }) }) - }); + }) after(async () => { // 清除登入及測試資料庫資料 - this.authenticate.restore(); - this.getUser.restore(); - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }); - await db.User.destroy({where: {},truncate: true, force: true}) - await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }); + this.authenticate.restore() + this.getUser.restore() + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null, { raw: true }) + await db.User.destroy({ where: {}, truncate: true, force: true }) + await db.sequelize.query('SET FOREIGN_KEY_CHECKS = 1', null, { raw: true }) }) - - }); - - }); - -}); \ No newline at end of file + }) + }) +}) From 6c215a3fd7da9b02a0edac7be7f1571ed9398c24 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Sat, 10 Jun 2023 01:39:07 +0800 Subject: [PATCH 061/131] feat: editUser. add: middleware multer --- .env.example | 3 +- .gitignore | 6 +- config/passport.js | 22 +- controllers/apis/user-controller.js | 47 +++- helpers/file-helper.js | 30 +++ middleware/multer.js | 4 + package-lock.json | 332 ++++++++++++++++++++++++++-- package.json | 4 +- routes/apis/index.js | 3 +- 9 files changed, 404 insertions(+), 47 deletions(-) create mode 100644 helpers/file-helper.js create mode 100644 middleware/multer.js diff --git a/.env.example b/.env.example index 5b016f956c..11e6660fc9 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,4 @@ SESSION_SECRET= JWT_SECRET= -PORT= \ No newline at end of file +PORT= +IMGUR_CLIENT_ID= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4daff23660..f013c6a77e 100644 --- a/.gitignore +++ b/.gitignore @@ -84,4 +84,8 @@ typings/ .fusebox/ # DynamoDB Local files -.dynamodb/ \ No newline at end of file +.dynamodb/ + +#temp +temp/ +upload/ \ No newline at end of file diff --git a/config/passport.js b/config/passport.js index 8ea5b0e60d..cc41f9a677 100644 --- a/config/passport.js +++ b/config/passport.js @@ -14,14 +14,16 @@ passport.use(new LocalStrategy( const errorMessage = '帳號或密碼輸入錯誤!' const user = await User.findOne({ where: { account } }) - // 帳號或密碼輸入錯誤 暫時的錯誤處理 + // 帳號或密碼輸入錯誤 暫時的錯誤處理 status code 200 if (!user) { + // return cb(null, false) return cb(errorMessage, false) } const isMatch = await bcrypt.compare(password, user.password) - // 帳號或密碼輸入錯誤 暫時的錯誤處理 + // 帳號或密碼輸入錯誤 暫時的錯誤處理 status code 200 if (!isMatch) { + // return cb(null, false) return cb(errorMessage, false) } return cb(null, user) @@ -42,17 +44,11 @@ passport.use(new JWTStrategy(jwtOptions, async (jwtPayload, cb) => { const errorMessage = 'unAuthenticated' const user = await User.findByPk(jwtPayload.id , { - // include: [ - // // join table Like - // { model: Tweet, as: 'LikedUsers' }, - // { model: User, as: 'LikedTweets' }, - // // join table Reply - // { model: Tweet, as: 'RepliedUsers' }, - // { model: User, as: 'RepliedTweets' }, - // // join table FollowShip - // { model: User, as: 'Followers' }, - // { model: User, as: 'Followings' } - // ] + include: [ + // join table FollowShip + { model: User, as: 'Followers' }, + { model: User, as: 'Followings' } + ] } ) if (!user) return cb(errorMessage, false, { message: errorMessage }) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index f13c33dbaf..bdf8ef55a9 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -1,6 +1,7 @@ const jwt = require('jsonwebtoken') const bcrypt = require('bcryptjs') const { getUser } = require('../../_helpers') +const { imgurFileHandler } = require('../../helpers/file-helper') const { User, Tweet, Reply, Like, Followship } = require('../../models') const userController = { @@ -28,7 +29,7 @@ const userController = { if (!name || !account || !email || !password || !checkPassword) errors.push('每個欄位都必填') // 密碼與確認密碼不一致 if (password !== checkPassword) errors.push('密碼與確認密碼不一致') - // 確認account與email是否與資料庫重複 + // 確認account與email是否 與 資料庫重複 const [userAccount, userEmail] = await Promise.all([ User.findOne({ where: { account } }), User.findOne({ where: { email } }) @@ -57,7 +58,7 @@ const userController = { let { id } = req.params id = Number(id) - // 確認使用者是否存在與發過文 + // 確認使用者是否存在 與 發過文 const [user, userTweets] = await Promise.all([ User.findByPk(id), Tweet.findAll({ @@ -91,7 +92,7 @@ const userController = { let { id } = req.params id = Number(id) - // 確認使用者是否存在與回過文 + // 確認使用者是否存在 與 回過文 const [user, repliedTweets] = await Promise.all([ User.findByPk(id), Reply.findAll({ @@ -118,7 +119,7 @@ const userController = { let { id } = req.params id = Number(id) - // 確認使用者是否存在與喜歡的貼文 + // 確認使用者是否存在 與 喜歡的貼文 const [user, userLiked] = await Promise.all([ User.findByPk(id), Like.findAll({ @@ -144,7 +145,7 @@ const userController = { let { id } = req.params id = Number(id) - // 確認使用者是否存在其追蹤者 + // 確認使用者是否存在 與 其追蹤者 const [user, userFollows] = await Promise.all([ User.findByPk(id), Followship.findAll({ @@ -169,7 +170,7 @@ const userController = { let { id } = req.params id = Number(id) - // 確認使用者是否存在其追隨者 + // 確認使用者是否存在 與 其追隨者 const [user, userFollowers] = await Promise.all([ User.findByPk(id), Followship.findAll({ @@ -188,21 +189,45 @@ const userController = { next(error) } }, - putUser: async (req, res, next) => { + editUser: async (req, res, next) => { try { - const { email, password, name, avatar, introduction, background, account } = req.body + const { email, password, name, introduction, account } = req.body + let { id } = req.params + id = Number(id) + // introduction與name的字數限制 if (introduction.length < 160) throw new Error('Your self-introduction is a little too long for me to handle! Please less than 160.') if (name.length < 50) throw new Error('Your self-introduction is a little too long for me to handle! ! Please less than 50.') - const [checkEmail, checkAccount] = await Promise.all([ + + // 確認使用者是否存在 與 email & account是否重複 + const [user, checkEmail, checkAccount] = await Promise.all([ + User.findByPk(id), User.findOne({ where: { email } }), User.findOne({ where: { account } }) ]) + + // 錯誤處理 + if (!user) throw new Error('The user does not exist') if (checkEmail) throw new Error('Oops! Your email already exist') if (checkAccount) throw new Error('Oops! Your account already exist') - User.update({ - email, password, name, avatar, introduction, background, account + + // 取得 avatar、background圖片 + const { file } = req + const { avatar, background } = req + const [avatarFilePath, backgroundFilePath] = await Promise.all([ + imgurFileHandler(avatar), + imgurFileHandler(background) + ]) + const updatedUser = User.update({ + email, + password: bcrypt.hashSync(password, bcrypt.genSaltSync(10), null), + name, + avatar: avatarFilePath || null, + introduction, + background: backgroundFilePath || null, + account }) + res.status(200).json(updatedUser) } catch (error) { next(error) } diff --git a/helpers/file-helper.js b/helpers/file-helper.js new file mode 100644 index 0000000000..03ac9f4a18 --- /dev/null +++ b/helpers/file-helper.js @@ -0,0 +1,30 @@ +const fs = require('fs') +const imgur = require('imgur') +const IMGUR_CLIENT_ID = process.env.IMGUR_CLIENT_ID +imgur.setClientId(IMGUR_CLIENT_ID) + +const localFileHandler = file => { + return new Promise((resolve, reject) => { + if (!file) return resolve(null) + + const fileName = `upload/${file.originalname}` + return fs.promises.readFile(file.path) + .then(data => fs.promises.writeFile(fileName, data)) + .then(() => resolve(`/${fileName}`)) + .catch(err => reject(err)) + }) +} + +const imgurFileHandler = file => { + return new Promise((resolve, reject) => { + if (!file) return resolve(null) + + return imgur.uploadFile(file.path) + .then(img => { + resolve(img?.link || null) + }) + .catch(err => reject(err)) + }) +} + +module.exports = { localFileHandler, imgurFileHandler } diff --git a/middleware/multer.js b/middleware/multer.js new file mode 100644 index 0000000000..71fb05e07f --- /dev/null +++ b/middleware/multer.js @@ -0,0 +1,4 @@ +const multer = require('multer') +const upload = multer({ dest: 'temp/' }) + +module.exports = upload diff --git a/package-lock.json b/package-lock.json index ac85a7e483..bc4a50d8e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -208,6 +208,17 @@ "defer-to-connect": "^1.0.1" } }, + "@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "requires": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, "@types/debug": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", @@ -216,12 +227,25 @@ "@types/ms": "*" } }, + "@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" + }, "@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "requires": { + "@types/node": "*" + } + }, "@types/ms": { "version": "0.7.31", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", @@ -232,6 +256,14 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.24.tgz", "integrity": "sha512-aveCYRQbgTH9Pssp1voEP7HiuWlD2jW2BO56w+bVrJn04i61yh6mRfoKO6hEYQD9vF+W8Chkwc6j1M36uPkx4g==" }, + "@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "requires": { + "@types/node": "*" + } + }, "@types/validator": { "version": "13.7.2", "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.2.tgz", @@ -345,6 +377,11 @@ "picomatch": "^2.0.4" } }, + "append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -806,8 +843,7 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "available-typed-arrays": { "version": "1.0.5", @@ -984,11 +1020,53 @@ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "busboy": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", + "integrity": "sha512-InWFDomvlkEj+xWLBfU3AvnbVYqeTWmQopiW0tWWEy5yehYm2YkGEc59sUmw/4ty5Zj/b0WHGs1LgecuBSBGrg==", + "requires": { + "dicer": "0.2.5", + "readable-stream": "1.1.x" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + } + } + }, "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, + "cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==" + }, "cacheable-request": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", @@ -1185,7 +1263,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -1206,6 +1283,17 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, "config-chain": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", @@ -1265,8 +1353,7 @@ "core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "cors": { "version": "2.8.5", @@ -1370,8 +1457,7 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "denque": { "version": "1.5.1", @@ -1388,6 +1474,38 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, + "dicer": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", + "integrity": "sha512-FDvbtnq7dzlPz0wyYlOExifDEZcu8h+rErEXgfxqmLfRfC/kJidEFh4+effJRO3P0xmfqyPbSMG0LveNRfTKVg==", + "requires": { + "readable-stream": "1.1.x", + "streamsearch": "0.1.2" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + } + } + }, "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", @@ -2695,6 +2813,15 @@ } } }, + "http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + } + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -2714,6 +2841,140 @@ "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==" }, + "imgur": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/imgur/-/imgur-1.0.2.tgz", + "integrity": "sha512-bZJkRpa3ReR7lSEzAOjO4PPl9OIDQPuiKoG2aOh36PrTBQCrZL/oTcc6VClyyXEg9O6rEMpsuCloxfhqybpfZA==", + "requires": { + "commander": "^7.1.0", + "form-data": "^4.0.0", + "got": "^11.8.1" + }, + "dependencies": { + "@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==" + }, + "@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "requires": { + "defer-to-connect": "^2.0.0" + } + }, + "cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + } + }, + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" + }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "requires": { + "mimic-response": "^3.1.0" + } + }, + "defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" + } + }, + "got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "requires": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + } + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "keyv": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", + "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==", + "requires": { + "json-buffer": "3.0.1" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + }, + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + }, + "normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" + }, + "p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==" + }, + "responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "requires": { + "lowercase-keys": "^2.0.0" + } + } + } + }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -3000,8 +3261,7 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", @@ -3447,6 +3707,21 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "multer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.3.tgz", + "integrity": "sha512-np0YLKncuZoTzufbkM6wEKp68EhWJXcU6fq6QqrSwkckd2LlMgd1UqhUJLj6NS/5sZ8dE8LYDWslsltJznnXlg==", + "requires": { + "append-field": "^1.0.0", + "busboy": "^0.2.11", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "on-finished": "^2.3.0", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + } + }, "mysql2": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-1.7.0.tgz", @@ -3968,8 +4243,7 @@ "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "proto-list": { "version": "1.2.4", @@ -4043,6 +4317,11 @@ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" + }, "random-bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", @@ -4079,7 +4358,6 @@ "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -4093,8 +4371,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } }, @@ -4171,6 +4448,11 @@ "supports-preserve-symlinks-flag": "^1.0.0" } }, + "resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" + }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -4524,6 +4806,11 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, + "streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha512-jos8u++JKm0ARcSUTAZXOVC0mSox7Bhn6sBgty73P1f3JGf7yG2clTbBNHUdde/kdvP2FESam+vM6l8jBrNxHA==" + }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -4697,7 +4984,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "requires": { "safe-buffer": "~5.1.0" }, @@ -4705,8 +4991,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } }, @@ -4912,6 +5197,11 @@ "is-typed-array": "^1.1.9" } }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, "typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -5055,8 +5345,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "utils-merge": { "version": "1.0.1", @@ -5238,6 +5527,11 @@ "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==" }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, "y18n": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", diff --git a/package.json b/package.json index 3a37b011ec..abb17a5e44 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "test", "version": "1.0.0", "description": "", - "main": "index.js", + "main": "app.js", "scripts": { "start": "set \"NODE_ENV=development\" && node app.js", "dev": "set \"NODE_ENV=development\" && nodemon app.js", @@ -21,9 +21,11 @@ "express": "^4.16.4", "express-session": "^1.15.6", "faker": "^5.5.3", + "imgur": "^1.0.2", "jsonwebtoken": "^8.5.1", "method-override": "^3.0.0", "mocha": "^6.0.2", + "multer": "^1.4.3", "mysql2": "^1.6.4", "nodemon": "^2.0.12", "passport": "^0.4.0", diff --git a/routes/apis/index.js b/routes/apis/index.js index b3f92baa86..08eb172cf6 100644 --- a/routes/apis/index.js +++ b/routes/apis/index.js @@ -5,6 +5,7 @@ const userController = require('../../controllers/apis/user-controller') const adminController = require('../../controllers/apis/admin-controller') const tweetController = require('../../controllers/apis/tweet-controller') const followshipController = require('../../controllers/apis/followship-controller') +const upload = require('../../middleware/multer') const { apiErrorHandler } = require('../../middleware/error-handler') const { authenticated, roleChecker, authenticatedAdmin } = require('../../middleware/api-auth') @@ -35,7 +36,7 @@ router.get('/users/:id', authenticated, userController.getUser) router.get('/users/:id/likes', authenticated, userController.getUserLiked) // 看見某使用者點過的 Like router.get('/users/:id/followings', authenticated, userController.getUserFollows) // 看見某使用者跟隨中的人 router.get('/users/:id/followers', authenticated, userController.getUserFollowers) // 看見某使用者的跟隨者 -router.put('/users/:id', authenticated, userController.putUser) // 編輯自己所有的資料 +router.put('/users/:id', authenticated, upload.single('image'), userController.editUser) // 編輯自己所有的資料 // 使用者登入 router.post('/users/login', passport.authenticate('local', { session: false }), userController.login) From a81aa75d44e09e052a034b7b73d016588b09cfc5 Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Sat, 10 Jun 2023 12:54:11 +0800 Subject: [PATCH 062/131] fix: revise the format to pass the test --- controllers/apis/admin-controller.js | 2 +- controllers/apis/user-controller.js | 27 ++++++++++++++------------- package-lock.json | 16 ++++++++-------- routes/apis/index.js | 2 +- 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/controllers/apis/admin-controller.js b/controllers/apis/admin-controller.js index 56d4da893d..fe25f56f5b 100644 --- a/controllers/apis/admin-controller.js +++ b/controllers/apis/admin-controller.js @@ -27,7 +27,7 @@ const adminController = { const users = await User.findAll({ raw: true }) - return res.json({ status: 'success', data: users }) + return res.json(users) } catch (error) { next(error) } diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 96ce98ecc8..0b826a115f 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -82,7 +82,7 @@ const userController = { // 確認使用者是否存在 const user = await User.findByPk(id) if (!user) throw new Error('The user does not exist') - res.json({ status: 'success', data: user }) + res.json(user) } catch (error) { next(error) } @@ -109,7 +109,7 @@ const userController = { for (const i of repliedTweets) { data.push(i.Tweet) } - return res.json({ status: 'success', data }) + return res.json(repliedTweets) } catch (error) { next(error) } @@ -135,7 +135,7 @@ const userController = { for (const i of userLiked) { data.push(i.Tweet) } - return res.json({ status: 'success', data }) + return res.json(userLiked) } catch (error) { next(error) } @@ -157,10 +157,11 @@ const userController = { if (!user) throw new Error('The user does not exist') if (!userFollows.length) throw new Error("He haven't followed anyone") const data = [] - for (const i of req.user.Followings) { - data.push(i.dataValues) - } - return res.json({ status: 'success', data }) + // for (const i of req.user.Followings) { + // data.push(i.dataValues) + // } + console.log(data) + return res.json(userFollows) } catch (error) { next(error) } @@ -181,10 +182,10 @@ const userController = { if (!userFollowers.length) throw new Error('He is lonely') const data = [] - for (const i of req.user.Followers) { - data.push(i.dataValues) - } - return res.json({ status: 'success', data }) + // for (const i of req.user.Followers) { + // data.push(i.dataValues) + // } + return res.json(userFollowers) } catch (error) { next(error) } @@ -196,8 +197,8 @@ const userController = { id = Number(id) // introduction與name的字數限制 - if (introduction.length < 160) throw new Error('Your self-introduction is a little too long for me to handle! Please less than 160.') - if (name.length < 50) throw new Error('Your self-introduction is a little too long for me to handle! ! Please less than 50.') + if (introduction.length > 160) throw new Error('Your self-introduction is a little too long for me to handle! Please less than 160.') + if (name.length > 50) throw new Error('Your self-introduction is a little too long for me to handle! ! Please less than 50.') // 確認使用者是否存在 與 email & account是否重複 const [user, checkEmail, checkAccount] = await Promise.all([ diff --git a/package-lock.json b/package-lock.json index bc4a50d8e3..ecbb951d13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -358,7 +358,7 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==" + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "ansi-styles": { "version": "3.2.1", @@ -1574,7 +1574,7 @@ "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" } } }, @@ -1698,7 +1698,7 @@ "es6-iterator": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", "requires": { "d": "1", "es5-ext": "^0.10.35", @@ -2236,7 +2236,7 @@ "event-emitter": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", "requires": { "d": "1", "es5-ext": "~0.10.14" @@ -3347,7 +3347,7 @@ "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "requires": { "graceful-fs": "^4.1.6" } @@ -3523,7 +3523,7 @@ "lru-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", - "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==", + "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", "requires": { "es5-ext": "~0.10.2" } @@ -4248,7 +4248,7 @@ "proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" + "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=" }, "proxy-addr": { "version": "2.0.7", @@ -4746,7 +4746,7 @@ "sigmund": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==" + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" }, "signal-exit": { "version": "3.0.7", diff --git a/routes/apis/index.js b/routes/apis/index.js index 42e6b04c9d..1a1768b1cd 100644 --- a/routes/apis/index.js +++ b/routes/apis/index.js @@ -33,7 +33,7 @@ router.get('/users/:id/replied_tweets', authenticated, userController.getUserRep router.get('/users/:id', authenticated, userController.getUser) // 新的 從這裡開始做 -router.get('/users/:id/likes', authenticated, userController.getUserLiked) // 看見某使用者點過的 Like +router.get('/users/:id/likes', authenticated, userController.getUserLiked) // 看見某使用者點過的 Like router.get('/users/:id/followings', authenticated, userController.getUserFollows) // 看見某使用者跟隨中的人 router.get('/users/:id/followers', authenticated, userController.getUserFollowers) // 看見某使用者的跟隨者 router.put('/users/:id', authenticated, upload.single('image'), userController.editUser) // 編輯自己所有的資料 From dad2c81dfae4e421ccd8cb3c7984dec32e4afe86 Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Sat, 10 Jun 2023 13:26:43 +0800 Subject: [PATCH 063/131] fix: remove the imgur part --- controllers/apis/user-controller.js | 30 +++++------------------------ 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 0b826a115f..ff8085a7c4 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -192,7 +192,7 @@ const userController = { }, editUser: async (req, res, next) => { try { - const { email, password, name, introduction, account } = req.body + const { name, introduction } = req.body let { id } = req.params id = Number(id) @@ -200,35 +200,15 @@ const userController = { if (introduction.length > 160) throw new Error('Your self-introduction is a little too long for me to handle! Please less than 160.') if (name.length > 50) throw new Error('Your self-introduction is a little too long for me to handle! ! Please less than 50.') - // 確認使用者是否存在 與 email & account是否重複 - const [user, checkEmail, checkAccount] = await Promise.all([ - User.findByPk(id), - User.findOne({ where: { email } }), - User.findOne({ where: { account } }) - ]) - + const user = await User.findByPk(id) // 錯誤處理 if (!user) throw new Error('The user does not exist') - if (checkEmail) throw new Error('Oops! Your email already exist') - if (checkAccount) throw new Error('Oops! Your account already exist') - // 取得 avatar、background圖片 - const { file } = req - const { avatar, background } = req - const [avatarFilePath, backgroundFilePath] = await Promise.all([ - imgurFileHandler(avatar), - imgurFileHandler(background) - ]) - const updatedUser = User.update({ - email, - password: bcrypt.hashSync(password, bcrypt.genSaltSync(10), null), + const updatedUser = await user.update({ name, - avatar: avatarFilePath || null, - introduction, - background: backgroundFilePath || null, - account + introduction }) - res.status(200).json(updatedUser) + return res.status(200).json(updatedUser) } catch (error) { next(error) } From 410da9c6351901ec6a67adab799bfd371e5626de Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Sat, 10 Jun 2023 14:52:09 +0800 Subject: [PATCH 064/131] fix: add imgurFileHandler func --- controllers/apis/user-controller.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index ff8085a7c4..4112e05682 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -203,9 +203,15 @@ const userController = { const user = await User.findByPk(id) // 錯誤處理 if (!user) throw new Error('The user does not exist') - + const { avatar, background } = req + const [avatarFilePath, backgroundFilePath] = await Promise.all([ + imgurFileHandler(avatar), + imgurFileHandler(background) + ]) const updatedUser = await user.update({ name, + avatar: avatarFilePath || user.avatar, + background: backgroundFilePath || user.background, introduction }) return res.status(200).json(updatedUser) From d89262010ece7bef19752ff4474538572a2b4c71 Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Sat, 10 Jun 2023 23:30:20 +0800 Subject: [PATCH 065/131] fix: remove the unwanted format --- controllers/apis/admin-controller.js | 13 ++++++++----- controllers/apis/followship-controller.js | 4 ++-- controllers/apis/tweet-controller.js | 17 ++++++++++------- controllers/apis/user-controller.js | 18 +++++++++--------- routes/apis/index.js | 2 -- 5 files changed, 29 insertions(+), 25 deletions(-) diff --git a/controllers/apis/admin-controller.js b/controllers/apis/admin-controller.js index fe25f56f5b..48c58d82bf 100644 --- a/controllers/apis/admin-controller.js +++ b/controllers/apis/admin-controller.js @@ -17,7 +17,7 @@ const adminController = { const userData = user delete userData.password userData.token = token - return res.json({ status: 'success', data: userData }) + return res.status(200).json(userData) } catch (error) { next(error) } @@ -27,15 +27,18 @@ const adminController = { const users = await User.findAll({ raw: true }) - return res.json(users) + return res.status(200).json(users) } catch (error) { next(error) } }, getTweets: async (req, res, next) => { try { - const tweets = await Tweet.findAll() - return res.json({ status: 'success', data: tweets }) + const tweets = await Tweet.findAll({ + raw: true, + order: [['createdAt', 'DESC']] + }) + return res.status(200).json(tweets) } catch (error) { next(error) } @@ -46,7 +49,7 @@ const adminController = { const tweet = await Tweet.findByPk(id) if (!tweet) throw new Error('The tweet does not exist') await tweet.destroy() - return res.json({ status: 'success', data: {} }) + return res.status(200).json({ status: 'success', message: 'The tweet was successfully deleted' }) } catch (error) { next(error) } diff --git a/controllers/apis/followship-controller.js b/controllers/apis/followship-controller.js index 78f924c282..178a1d3a33 100644 --- a/controllers/apis/followship-controller.js +++ b/controllers/apis/followship-controller.js @@ -14,7 +14,7 @@ const followshipController = { followerId: getUser(req).id, followingId: id }) - return res.json({ status: 'success', data: newFollow }) + return res.status(200).json(newFollow.toJSON()) } catch (error) { next(error) } @@ -30,7 +30,7 @@ const followshipController = { if (!follow) throw new Error("You have'nt followed this user.") const deleteFollow = await follow.destroy() - return res.json({ status: 'success', data: deleteFollow.toJSON() }) + return res.status(200).json(deleteFollow.toJSON()) } catch (error) { next(error) } diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index dee0d4599b..8dafbeee84 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -8,17 +8,20 @@ const tweetController = { UserId: res.locals.userId, description }) - return res.json({ status: 'success', data: tweet }) + return res.status(200).json(tweet) } catch (error) { next(error) } }, getTweets: async (req, res, next) => { try { - const tweets = await Tweet.findAll() + const tweets = await Tweet.findAll({ + raw: true, + order: [['createdAt', 'DESC']] + }) if (!tweets) throw new Error('There is no tweet') - return res.json(tweets) + return res.status(200).json(tweets) } catch (error) { next(error) } @@ -29,7 +32,7 @@ const tweetController = { const tweet = await Tweet.findByPk(tweet_id) if (!tweet) throw new Error('The tweet does not exist') - return res.json(tweet) + return res.status(200).json(tweet) } catch (error) { next(error) } @@ -46,7 +49,7 @@ const tweetController = { TweetId: tweet_id, comment }) - return res.json({ status: 'success', data: reply }) + return res.status(200).json(reply) } catch (error) { next(error) } @@ -87,7 +90,7 @@ const tweetController = { UserId: res.locals.userId, TweetId: id }) - return res.json({ status: 'success', data: like }) + return res.status(200).json(like) } catch (error) { next(error) } @@ -105,7 +108,7 @@ const tweetController = { }) if (!like) throw new Error('The like does not exist') await like.destroy() - return res.json({ status: 'success', data: like }) + return res.status(200).json(like) } catch (err) { next(err) } diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 4112e05682..7d43b1f6d8 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -10,7 +10,7 @@ const userController = { const userData = getUser(req).toJSON() delete userData.password const token = jwt.sign(userData, process.env.JWT_SECRET, { expiresIn: '30d' }) - res.json({ + res.status(200).json({ status: 'success', data: { token, user: userData @@ -48,7 +48,7 @@ const userController = { }) const userData = user.toJSON() delete userData.password - return res.json({ status: 'success', data: userData }) + return res.status(200).json({ status: 'success', data: userData }) } catch (error) { next(error) } @@ -69,7 +69,7 @@ const userController = { ]) if (!user) throw new Error('The user does not exist') if (!userTweets.length) throw new Error("The user have'nt post any tweet yet") - return res.json(userTweets) + return res.status(200).json(userTweets) } catch (error) { next(error) } @@ -82,7 +82,7 @@ const userController = { // 確認使用者是否存在 const user = await User.findByPk(id) if (!user) throw new Error('The user does not exist') - res.json(user) + res.status(200).json(user) } catch (error) { next(error) } @@ -109,7 +109,7 @@ const userController = { for (const i of repliedTweets) { data.push(i.Tweet) } - return res.json(repliedTweets) + return res.status(200).json(repliedTweets) } catch (error) { next(error) } @@ -135,7 +135,7 @@ const userController = { for (const i of userLiked) { data.push(i.Tweet) } - return res.json(userLiked) + return res.status(200).json(userLiked) } catch (error) { next(error) } @@ -161,7 +161,7 @@ const userController = { // data.push(i.dataValues) // } console.log(data) - return res.json(userFollows) + return res.status(200).json(userFollows) } catch (error) { next(error) } @@ -181,11 +181,11 @@ const userController = { if (!user) throw new Error('The user does not exist') if (!userFollowers.length) throw new Error('He is lonely') - const data = [] + // const data = [] // for (const i of req.user.Followers) { // data.push(i.dataValues) // } - return res.json(userFollowers) + return res.status(200).json(userFollowers) } catch (error) { next(error) } diff --git a/routes/apis/index.js b/routes/apis/index.js index 1a1768b1cd..fee1a7c93d 100644 --- a/routes/apis/index.js +++ b/routes/apis/index.js @@ -31,8 +31,6 @@ router.delete('/followships/:followingId', authenticated, followshipController.d router.get('/users/:id/tweets', authenticated, userController.getUserTweets) router.get('/users/:id/replied_tweets', authenticated, userController.getUserRepliedTweet) router.get('/users/:id', authenticated, userController.getUser) - -// 新的 從這裡開始做 router.get('/users/:id/likes', authenticated, userController.getUserLiked) // 看見某使用者點過的 Like router.get('/users/:id/followings', authenticated, userController.getUserFollows) // 看見某使用者跟隨中的人 router.get('/users/:id/followers', authenticated, userController.getUserFollowers) // 看見某使用者的跟隨者 From f220e98c049fb42ebe297ccee3f8ba17e33dac3b Mon Sep 17 00:00:00 2001 From: s1030905 Date: Sun, 11 Jun 2023 14:49:36 +0800 Subject: [PATCH 066/131] feat: add followingCount & followerCount to getUser --- controllers/apis/user-controller.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 4112e05682..c7a33201d0 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -78,11 +78,17 @@ const userController = { try { let id = req.params.id id = Number(id) - + const [followingCount, followerCount, user] = await Promise.all([ + Followship.findAndCountAll({ where: { followerId: id } }), + Followship.findAndCountAll({ where: { followingId: id } }), + User.findByPk(id) + ]) // 確認使用者是否存在 - const user = await User.findByPk(id) if (!user) throw new Error('The user does not exist') - res.json(user) + const data = user.toJSON() + data.followingCount = followingCount.count + data.followerCount = followerCount.count + return res.json(data) } catch (error) { next(error) } From 55bd0c6978582f605ec68c01d3c23c52d009d2d7 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Sun, 11 Jun 2023 15:31:40 +0800 Subject: [PATCH 067/131] add followerCount & followingCount to admin/getUsers --- controllers/apis/admin-controller.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/controllers/apis/admin-controller.js b/controllers/apis/admin-controller.js index fe25f56f5b..d1c61a2001 100644 --- a/controllers/apis/admin-controller.js +++ b/controllers/apis/admin-controller.js @@ -25,9 +25,21 @@ const adminController = { getUsers: async (req, res, next) => { try { const users = await User.findAll({ - raw: true + include: [ + { model: User, as: 'Followers' }, + { model: User, as: 'Followings' } + ] + }) + + const data = users.map(user => { + const { Followers, Followings, ...userData } = user.dataValues + userData.followerCount = Followers.length + userData.followingCount = Followings.length + return userData }) - return res.json(users) + + console.log(data) + return res.json(data) } catch (error) { next(error) } From 4451d211d3e3a88a7f8e889bf72382ad42bde351 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Sun, 11 Jun 2023 20:51:15 +0800 Subject: [PATCH 068/131] mdoel: add likedCount to Tweet --- .../20230611095155-add-likedCount-to-Tweet.js | 14 ++++++++++++++ models/tweet.js | 18 ++++++++++++++---- models/user.js | 15 ++++++++++++--- 3 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 migrations/20230611095155-add-likedCount-to-Tweet.js diff --git a/migrations/20230611095155-add-likedCount-to-Tweet.js b/migrations/20230611095155-add-likedCount-to-Tweet.js new file mode 100644 index 0000000000..6b198d175f --- /dev/null +++ b/migrations/20230611095155-add-likedCount-to-Tweet.js @@ -0,0 +1,14 @@ +'use strict' + +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.addColumn('Tweets', 'LikedCount', { + type: Sequelize.INTEGER, + defaultValue: 0 + }) + }, + + down: async (queryInterface, Sequelize) => { + await queryInterface.removeColumn('Tweets', 'LikedCount') + } +} diff --git a/models/tweet.js b/models/tweet.js index 69b92dee17..d5e2150843 100644 --- a/models/tweet.js +++ b/models/tweet.js @@ -4,15 +4,25 @@ const { Model } = require('sequelize') module.exports = (sequelize, DataTypes) => { class Tweet extends Model { static associate (models) { - Tweet.belongsTo(models.User, { foreignKey: 'UserId' }) - Tweet.hasMany(models.Reply, { foreignKey: 'TweetId' }) - Tweet.hasMany(models.Like, { foreignKey: 'TweetId' }) + Tweet.belongsTo(models.User, { + foreignKey: 'UserId', + as: 'TweetUser' + }) + Tweet.hasMany(models.Reply, { + foreignKey: 'TweetId', + as: 'TweetReply' + }) + Tweet.hasMany(models.Like, { + foreignKey: 'TweetId', + as: 'TweetLike' + }) } } Tweet.init( { UserId: DataTypes.INTEGER, - description: DataTypes.TEXT + description: DataTypes.TEXT, + likedCount: DataTypes.INTEGER }, { sequelize, diff --git a/models/user.js b/models/user.js index b709f89a2a..b676387fa3 100644 --- a/models/user.js +++ b/models/user.js @@ -4,9 +4,18 @@ const { Model } = require('sequelize') module.exports = (sequelize, DataTypes) => { class User extends Model { static associate (models) { - User.hasMany(models.Tweet, { foreignKey: 'userId' }) - User.hasMany(models.Reply, { foreignKey: 'userId' }) - User.hasMany(models.Like, { foreignKey: 'userId' }) + User.hasMany(models.Tweet, { + foreignKey: 'userId', + as: 'UserTweets' + }) + User.hasMany(models.Reply, { + foreignKey: 'userId', + as: 'UserReplies' + }) + User.hasMany(models.Like, { + foreignKey: 'userId', + as: 'UserLikes' + }) User.belongsToMany(User, { through: models.Followship, From c9b2ea7b29c2c5994c4fe9ef316862aa3832e401 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Sun, 11 Jun 2023 20:53:32 +0800 Subject: [PATCH 069/131] feat: getUsers add followerCount, followingCount, userTweetCount and likedCount --- controllers/apis/admin-controller.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/controllers/apis/admin-controller.js b/controllers/apis/admin-controller.js index d1c61a2001..eabeebda0c 100644 --- a/controllers/apis/admin-controller.js +++ b/controllers/apis/admin-controller.js @@ -1,5 +1,5 @@ const jwt = require('jsonwebtoken') -const { User, Tweet } = require('../../models') +const { User, Tweet, Reply, Like } = require('../../models') const { getUser } = require('../../_helpers') const adminController = { @@ -27,18 +27,23 @@ const adminController = { const users = await User.findAll({ include: [ { model: User, as: 'Followers' }, - { model: User, as: 'Followings' } + { model: User, as: 'Followings' }, + { model: Tweet, as: 'UserTweets' } ] }) const data = users.map(user => { - const { Followers, Followings, ...userData } = user.dataValues + const { Followers, Followings, UserTweets, ...userData } = user.toJSON() + let likedCount = 0 + for (const i of UserTweets) { + likedCount += i.likedCount + } userData.followerCount = Followers.length userData.followingCount = Followings.length + userData.userTweetCount = UserTweets.length + userData.likedCount = likedCount return userData }) - - console.log(data) return res.json(data) } catch (error) { next(error) From db941c7fa55495247fa83f92826b880dc3ade2aa Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Sun, 11 Jun 2023 22:15:49 +0800 Subject: [PATCH 070/131] fix: add seeder which each tweet has 3 replies and each user has at least 1 reply --- controllers/apis/admin-controller.js | 1 - seeders/20230608073227-replies-seed-file.js | 35 +++++++++++++++------ 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/controllers/apis/admin-controller.js b/controllers/apis/admin-controller.js index e2e63dfeb4..1b6efd24ee 100644 --- a/controllers/apis/admin-controller.js +++ b/controllers/apis/admin-controller.js @@ -46,7 +46,6 @@ const adminController = { }) return res.status(200).json(data) - } catch (error) { next(error) } diff --git a/seeders/20230608073227-replies-seed-file.js b/seeders/20230608073227-replies-seed-file.js index 5b0d14ec63..3510a51ddb 100644 --- a/seeders/20230608073227-replies-seed-file.js +++ b/seeders/20230608073227-replies-seed-file.js @@ -13,21 +13,38 @@ module.exports = { { type: queryInterface.sequelize.QueryTypes.SELECT } ) ]) + + // Each tweet has 3 replies and each user has at least 1 reply const eachUserTweetsCount = 3 const repliedTweets = [] - for (let i = 0; i < 150; i++) { + + for (let i = 0; i < (tweets.length + users.length); i++) { let comment = faker.lorem.text() if (comment.length > 140) { comment = comment.slice(0, 140) } - const randomUser = Math.floor(Math.random() * users.length) - repliedTweets.push({ - UserId: users[randomUser].id, - TweetId: tweets[Math.floor(i / eachUserTweetsCount)].id, - comment, - createdAt: new Date(), - updatedAt: new Date() - }) + + if (i < users.length) { + // Each user has at least 1 reply + repliedTweets.push({ + UserId: users[i].id, + TweetId: tweets[Math.floor(Math.random() * tweets.length)].id, + comment, + createdAt: new Date(), + updatedAt: new Date() + }) + } else { + for (let j = 0; j < eachUserTweetsCount; j++) { + // Each tweet has 3 replies + repliedTweets.push({ + UserId: users[Math.floor(Math.random() * users.length)].id, + TweetId: tweets[i - users.length].id, + comment, + createdAt: new Date(), + updatedAt: new Date() + }) + } + } } await queryInterface.bulkInsert('Replies', repliedTweets) }, From 810003ed60d04b9d0d774b23297f0f7a18c794a2 Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Sun, 11 Jun 2023 22:32:55 +0800 Subject: [PATCH 071/131] fix: check when editUser need to deal with the account and email --- controllers/apis/user-controller.js | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 5007721e11..7ccc57262e 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -199,7 +199,7 @@ const userController = { }, editUser: async (req, res, next) => { try { - const { name, introduction } = req.body + const { name, introduction, account, email } = req.body let { id } = req.params id = Number(id) @@ -207,9 +207,28 @@ const userController = { if (introduction.length > 160) throw new Error('Your self-introduction is a little too long for me to handle! Please less than 160.') if (name.length > 50) throw new Error('Your self-introduction is a little too long for me to handle! ! Please less than 50.') + // 確認user是否存在,account與email是否與資料庫重複 const user = await User.findByPk(id) - // 錯誤處理 if (!user) throw new Error('The user does not exist') + + if (account && email) { + const [userAccount, userEmail] = await Promise.all([ + User.findOne({ where: { account } }), + User.findOne({ where: { email } }) + ]) + // 錯誤處理 + if (userAccount.id !== id) throw new Error('account已存在') + if (userEmail.id !== id) throw new Error('email已存在') + } else if (account) { + const userAccount = await User.findOne({ where: { account } }) + // 錯誤處理 + if (userAccount.id !== id) throw new Error('account已存在') + } else if (email) { + const userEmail = await User.findOne({ where: { email } }) + // 錯誤處理 + if (userEmail.id !== id) throw new Error('email已存在') + } + const { avatar, background } = req const [avatarFilePath, backgroundFilePath] = await Promise.all([ imgurFileHandler(avatar), From a3ec3a53b94ae3dc81a076646269577ae6ccb66d Mon Sep 17 00:00:00 2001 From: s1030905 Date: Sun, 11 Jun 2023 23:39:20 +0800 Subject: [PATCH 072/131] redefine error status code --- controllers/apis/admin-controller.js | 5 +- controllers/apis/followship-controller.js | 10 +++- controllers/apis/tweet-controller.js | 62 +++++++++++++++++++---- controllers/apis/user-controller.js | 19 ++++--- routes/apis/index.js | 2 +- 5 files changed, 71 insertions(+), 27 deletions(-) diff --git a/controllers/apis/admin-controller.js b/controllers/apis/admin-controller.js index e2e63dfeb4..fdce189d8f 100644 --- a/controllers/apis/admin-controller.js +++ b/controllers/apis/admin-controller.js @@ -1,5 +1,5 @@ const jwt = require('jsonwebtoken') -const { User, Tweet, Reply, Like } = require('../../models') +const { User, Tweet } = require('../../models') const { getUser } = require('../../_helpers') const adminController = { @@ -46,7 +46,6 @@ const adminController = { }) return res.status(200).json(data) - } catch (error) { next(error) } @@ -54,7 +53,7 @@ const adminController = { getTweets: async (req, res, next) => { try { const tweets = await Tweet.findAll({ - raw: true, + include: [{ model: User, as: 'TweetUser' }], order: [['createdAt', 'DESC']] }) return res.status(200).json(tweets) diff --git a/controllers/apis/followship-controller.js b/controllers/apis/followship-controller.js index 178a1d3a33..13a7ee356c 100644 --- a/controllers/apis/followship-controller.js +++ b/controllers/apis/followship-controller.js @@ -2,13 +2,19 @@ const { getUser } = require('../../_helpers') const { Followship } = require('../../models') const followshipController = { + // 更改路由新增:followshipId? postFollowship: async (req, res, next) => { try { - const { id } = req.body + let { id } = req.params + id = Number(id) // 確認使用者是否已經追蹤該用戶 const followship = await Followship.findOne({ where: { followingId: id, followerId: getUser(req).id } }) // 如果有 => 不用新增 - if (followship) throw new Error('You have followed this user.') + if (followship) { + const error = new Error('You have followed this user.') + error.status = 409 + throw error + } const newFollow = await Followship.create({ followerId: getUser(req).id, diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index 8dafbeee84..ac05cc14ad 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -1,4 +1,4 @@ -const { Tweet, Reply, Like } = require('../../models') +const { User, Tweet, Reply, Like } = require('../../models') const tweetController = { postTweet: async (req, res, next) => { @@ -16,11 +16,10 @@ const tweetController = { getTweets: async (req, res, next) => { try { const tweets = await Tweet.findAll({ - raw: true, + include: [{ model: User, as: 'TweetUser' }], order: [['createdAt', 'DESC']] }) if (!tweets) throw new Error('There is no tweet') - return res.status(200).json(tweets) } catch (error) { next(error) @@ -29,8 +28,14 @@ const tweetController = { getTweet: async (req, res, next) => { try { const { tweet_id } = req.params - const tweet = await Tweet.findByPk(tweet_id) - if (!tweet) throw new Error('The tweet does not exist') + const tweet = await Tweet.findByPk(tweet_id, { + include: [{ model: User, as: 'TweetUser' }] + }) + if (!tweet) { + const error = new Error('The tweet does not exist') + error.status = 404 + throw error + } return res.status(200).json(tweet) } catch (error) { @@ -42,7 +47,13 @@ const tweetController = { const { tweet_id } = req.params const { comment } = req.body const tweet = await Tweet.findByPk(tweet_id) - if (!tweet) throw new Error('The tweet does not exist') + + // 錯誤處理 + if (!tweet) { + const error = new Error('The tweet does not exist') + error.status = 404 + throw error + } const reply = await Reply.create({ UserId: res.locals.userId, @@ -58,7 +69,13 @@ const tweetController = { try { const { tweet_id } = req.params const tweet = await Tweet.findByPk(tweet_id) - if (!tweet) throw new Error('The tweet does not exist') + + // 錯誤處理 + if (!tweet) { + const error = new Error('The tweet does not exist') + error.status = 404 + throw error + } const replies = await Reply.findAll({ where: { TweetId: tweet_id }, @@ -83,8 +100,18 @@ const tweetController = { } }) ]) - if (!PromiseArr[0]) throw new Error('The tweet does not exist') - if (PromiseArr[1]) throw new Error('The like already exists') + + // 錯誤處理 + if (!PromiseArr[0]) { + const error = new Error('The tweet does not exist') + error.status = 404 + throw error + } + if (PromiseArr[1]) { + const error = new Error('The tweet does not exist') + error.status = 409 + throw error + } const like = await Like.create({ UserId: res.locals.userId, @@ -99,14 +126,27 @@ const tweetController = { try { const { id } = req.params const tweet = await Tweet.findByPk(id) - if (!tweet) throw new Error('The tweet does not exist') + + // 錯誤處理 + if (!tweet) { + const error = new Error('The tweet does not exist') + error.status = 404 + throw error + } const like = await Like.findOne({ where: { UserId: res.locals.userId, TweetId: id } }) - if (!like) throw new Error('The like does not exist') + + // 錯誤處理 + if (!like) { + const error = new Error('The like does not exist') + error.status = 404 + throw error + } + await like.destroy() return res.status(200).json(like) } catch (err) { diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 5007721e11..37ef67fefe 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -78,13 +78,20 @@ const userController = { try { let id = req.params.id id = Number(id) + + // 缺少 推文喜歡數量、推文數量 const [followingCount, followerCount, user] = await Promise.all([ Followship.findAndCountAll({ where: { followerId: id } }), Followship.findAndCountAll({ where: { followingId: id } }), User.findByPk(id) ]) + // 確認使用者是否存在 - if (!user) throw new Error('The user does not exist') + if (!user) { + const error = new Error('The tweet does not exist') + error.status = 404 + throw error + } const data = user.toJSON() data.followingCount = followingCount.count @@ -163,11 +170,7 @@ const userController = { ]) if (!user) throw new Error('The user does not exist') if (!userFollows.length) throw new Error("He haven't followed anyone") - const data = [] - // for (const i of req.user.Followings) { - // data.push(i.dataValues) - // } - console.log(data) + return res.status(200).json(userFollows) } catch (error) { next(error) @@ -188,10 +191,6 @@ const userController = { if (!user) throw new Error('The user does not exist') if (!userFollowers.length) throw new Error('He is lonely') - // const data = [] - // for (const i of req.user.Followers) { - // data.push(i.dataValues) - // } return res.status(200).json(userFollowers) } catch (error) { next(error) diff --git a/routes/apis/index.js b/routes/apis/index.js index fee1a7c93d..0bd61dda52 100644 --- a/routes/apis/index.js +++ b/routes/apis/index.js @@ -24,7 +24,7 @@ router.post('/tweets/:id/unlike', authenticated, tweetController.unlikeTweet) router.get('/tweets/:tweet_id', authenticated, tweetController.getTweet) // 有關followship的routes -router.post('/followships', authenticated, followshipController.postFollowship) +router.post('/followships/:followingId', authenticated, followshipController.postFollowship) router.delete('/followships/:followingId', authenticated, followshipController.deleteFollowship) // 有關user的routes From f4dc1bf1dd882e294adf4b35b9a2fae838bed92d Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Sun, 11 Jun 2023 23:49:36 +0800 Subject: [PATCH 073/131] fix: add relation between different model --- controllers/apis/user-controller.js | 9 ++++++--- models/followship.js | 8 ++++++++ models/like.js | 2 +- models/reply.js | 2 +- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 7ccc57262e..276e902dee 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -63,6 +63,7 @@ const userController = { User.findByPk(id), Tweet.findAll({ where: { UserId: id }, + include: [{ model: User, as: 'TweetUser', attributes: ['id', 'name', 'account', 'avatar'] }], raw: true, nest: true }) @@ -104,7 +105,7 @@ const userController = { User.findByPk(id), Reply.findAll({ where: { UserId: id }, - include: [Tweet], + include: [Tweet, { model: User, as: 'RepliedUser', attributes: ['id', 'name', 'account', 'avatar'] }], raw: true, nest: true }) @@ -131,7 +132,7 @@ const userController = { User.findByPk(id), Like.findAll({ where: { UserId: Number(id) }, - include: [Tweet], + include: [Tweet, { model: User, as: 'LikedUser', attributes: ['id', 'name', 'account', 'avatar'] }], raw: true, nest: true }) @@ -157,6 +158,7 @@ const userController = { User.findByPk(id), Followship.findAll({ where: { followerId: Number(id) }, + include: [{ model: User, as: 'Followings', attributes: ['id', 'name', 'account', 'avatar'] }], raw: true, nest: true }) @@ -182,7 +184,8 @@ const userController = { const [user, userFollowers] = await Promise.all([ User.findByPk(id), Followship.findAll({ - where: { followingId: Number(id) } + where: { followingId: Number(id) }, + include: [{ model: User, as: 'Followers', attributes: ['id', 'name', 'account', 'avatar'] }] }) ]) if (!user) throw new Error('The user does not exist') diff --git a/models/followship.js b/models/followship.js index 715036574b..1069b09919 100644 --- a/models/followship.js +++ b/models/followship.js @@ -4,6 +4,14 @@ const { Model } = require('sequelize') module.exports = (sequelize, DataTypes) => { class Followship extends Model { static associate (models) { + Followship.belongsTo(models.User, { + foreignKey: 'followingId', + as: 'Followings' + }) + Followship.belongsTo(models.User, { + foreignKey: 'followerId', + as: 'Followers' + }) } } Followship.init( diff --git a/models/like.js b/models/like.js index 4f5776e3fb..1d4d3b43c1 100644 --- a/models/like.js +++ b/models/like.js @@ -5,7 +5,7 @@ module.exports = (sequelize, DataTypes) => { class Like extends Model { static associate (models) { Like.belongsTo(models.Tweet, { foreignKey: 'TweetId' }) - Like.belongsTo(models.User, { foreignKey: 'UserId' }) + Like.belongsTo(models.User, { foreignKey: 'UserId', as: 'LikedUser' }) } } Like.init( diff --git a/models/reply.js b/models/reply.js index a39a0e04b3..b42ee1832e 100644 --- a/models/reply.js +++ b/models/reply.js @@ -5,7 +5,7 @@ module.exports = (sequelize, DataTypes) => { class Reply extends Model { static associate (models) { Reply.belongsTo(models.Tweet, { foreignKey: 'TweetId' }) - Reply.belongsTo(models.User, { foreignKey: 'UserId' }) + Reply.belongsTo(models.User, { foreignKey: 'UserId', as: 'RepliedUser' }) } } Reply.init( From c5a0d24d96ad136320b134e2db4c6d19b9d01120 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Mon, 12 Jun 2023 13:51:58 +0800 Subject: [PATCH 074/131] feat: update remaining status code --- controllers/apis/admin-controller.js | 9 ++- controllers/apis/followship-controller.js | 10 ++- controllers/apis/tweet-controller.js | 9 ++- controllers/apis/user-controller.js | 78 +++++++++++++++++++---- routes/apis/index.js | 2 +- 5 files changed, 90 insertions(+), 18 deletions(-) diff --git a/controllers/apis/admin-controller.js b/controllers/apis/admin-controller.js index fdce189d8f..2326f6ff5c 100644 --- a/controllers/apis/admin-controller.js +++ b/controllers/apis/admin-controller.js @@ -12,8 +12,13 @@ const adminController = { where: { account }, raw: true }) - if (!user) throw new Error('You are not admin') - if (user.role !== 'admin') throw new Error('You are not admin') + + // 錯誤處理 + if (!user || user.role !== 'admin') { + const error = new Error('You are not admin') + error.status = 404 + throw error + } const userData = user delete userData.password userData.token = token diff --git a/controllers/apis/followship-controller.js b/controllers/apis/followship-controller.js index 13a7ee356c..839f1a2178 100644 --- a/controllers/apis/followship-controller.js +++ b/controllers/apis/followship-controller.js @@ -5,7 +5,7 @@ const followshipController = { // 更改路由新增:followshipId? postFollowship: async (req, res, next) => { try { - let { id } = req.params + let { id } = req.body id = Number(id) // 確認使用者是否已經追蹤該用戶 const followship = await Followship.findOne({ where: { followingId: id, followerId: getUser(req).id } }) @@ -32,9 +32,13 @@ const followshipController = { const follow = await Followship.findOne({ where: { followerId: getUser(req).id, followingId } }) - // 如果沒有 => 不用刪除 - if (!follow) throw new Error("You have'nt followed this user.") + // 如果沒有 => 不用刪除 + if (!follow) { + const error = new Error("You have'nt followed this user.") + error.status = 404 + throw error + } const deleteFollow = await follow.destroy() return res.status(200).json(deleteFollow.toJSON()) } catch (error) { diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index ac05cc14ad..5f74004356 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -19,7 +19,14 @@ const tweetController = { include: [{ model: User, as: 'TweetUser' }], order: [['createdAt', 'DESC']] }) - if (!tweets) throw new Error('There is no tweet') + + // 錯誤處理 + if (!tweets) { + const error = new Error('There is no tweet') + error.status = 404 + throw error + } + return res.status(200).json(tweets) } catch (error) { next(error) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 8c9c6ccf8c..21562cb562 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -68,8 +68,18 @@ const userController = { nest: true }) ]) - if (!user) throw new Error('The user does not exist') - if (!userTweets.length) throw new Error("The user have'nt post any tweet yet") + + // 錯誤處理 + if (!user) { + const error = new Error('The user does not exist') + error.status = 404 + throw error + } + if (!userTweets.length) { + const error = new Error("The user have'nt post any tweet yet") + error.status = 404 + throw error + } return res.status(200).json(userTweets) } catch (error) { next(error) @@ -117,8 +127,18 @@ const userController = { nest: true }) ]) - if (!user) throw new Error('The user does not exist') - if (!repliedTweets.length) throw new Error("The user have'nt replied any tweets yet.") + + // 錯誤處理 + if (!user) { + const error = new Error('The user does not exist') + error.status = 404 + throw error + } + if (!repliedTweets.length) { + const error = new Error("The user have'nt replied any tweets yet.") + error.status = 404 + throw error + } const data = [] for (const i of repliedTweets) { @@ -144,8 +164,18 @@ const userController = { nest: true }) ]) - if (!user) throw new Error('The user does not exist') - if (!userLiked.length) throw new Error('He does not like anyone.') + + // 錯誤處理 + if (!user) { + const error = new Error('The user does not exist') + error.status = 404 + throw error + } + if (!userLiked.length) { + const error = new Error('He does not like tweet.') + error.status = 404 + throw error + } const data = [] for (const i of userLiked) { data.push(i.Tweet) @@ -170,8 +200,18 @@ const userController = { nest: true }) ]) - if (!user) throw new Error('The user does not exist') - if (!userFollows.length) throw new Error("He haven't followed anyone") + + // 錯誤處理 + if (!user) { + const error = new Error('The user does not exist') + error.status = 404 + throw error + } + if (!userFollows.length) { + const error = new Error("He haven't followed anyone") + error.status = 404 + throw error + } return res.status(200).json(userFollows) } catch (error) { @@ -191,8 +231,18 @@ const userController = { include: [{ model: User, as: 'Followers', attributes: ['id', 'name', 'account', 'avatar'] }] }) ]) - if (!user) throw new Error('The user does not exist') - if (!userFollowers.length) throw new Error('He is lonely') + + // 錯誤處理 + if (!user) { + const error = new Error('The user does not exist') + error.status = 404 + throw error + } + if (!userFollowers.length) { + const error = new Error('He is lonely') + error.status = 404 + throw error + } return res.status(200).json(userFollowers) } catch (error) { @@ -211,7 +261,13 @@ const userController = { // 確認user是否存在,account與email是否與資料庫重複 const user = await User.findByPk(id) - if (!user) throw new Error('The user does not exist') + + // 錯誤處理 + if (!user) { + const error = new Error('The user does not exist') + error.status = 404 + throw error + } if (account && email) { const [userAccount, userEmail] = await Promise.all([ diff --git a/routes/apis/index.js b/routes/apis/index.js index 0bd61dda52..d70af107fe 100644 --- a/routes/apis/index.js +++ b/routes/apis/index.js @@ -24,7 +24,7 @@ router.post('/tweets/:id/unlike', authenticated, tweetController.unlikeTweet) router.get('/tweets/:tweet_id', authenticated, tweetController.getTweet) // 有關followship的routes -router.post('/followships/:followingId', authenticated, followshipController.postFollowship) +router.post('/followships/', authenticated, followshipController.postFollowship) router.delete('/followships/:followingId', authenticated, followshipController.deleteFollowship) // 有關user的routes From b5ace7a9f9dbe36c3fa390f64848738c4ad99e05 Mon Sep 17 00:00:00 2001 From: s1030905 <127211914+s1030905@users.noreply.github.com> Date: Mon, 12 Jun 2023 22:39:20 +0800 Subject: [PATCH 075/131] Create README.md --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000000..08d465a4f0 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# twitter-api-2023 +ALPHA Camp | 學期 3 | Simple Twitter | 自動化測試檔 (前後分離組) + +# Environment - 開發環境 +node v14.16.0 +nodemon + +#Installation and Execution - 安裝與執行步驟 +1. 創建資料夾 +2. 安裝所需packages。指令`npm i` +3. 建立SQL資料庫。指令 `create database ac_twitter_workspace` +4. 建立資料表。指令 `npx sequelize db:migrate` +5. 建立種子檔。指令 `npx sequelize db:seed:all` +6. 建立環境參數(請參考.env.example)。 +7. 啟動伺服器。指令`npm run dev` + +# Note: +#管理者帳號: +account: root +password: 12345678 + +#使用者帳號: +account: user1 +password: 12345678 From 1727f9c681741c1a41fdb0fe4a97ce4672b1c556 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Tue, 13 Jun 2023 01:46:16 +0800 Subject: [PATCH 076/131] add repliedCount to Tweet --- app.js | 4 + controllers/apis/admin-controller.js | 4 +- controllers/apis/tweet-controller.js | 3 + controllers/apis/user-controller.js | 130 +++++++++++++----- middleware/multer.js | 8 +- ...0230612165505-add-repliedCount-to-Tweet.js | 14 ++ models/tweet.js | 3 +- routes/apis/index.js | 5 +- 8 files changed, 127 insertions(+), 44 deletions(-) create mode 100644 migrations/20230612165505-add-repliedCount-to-Tweet.js diff --git a/app.js b/app.js index 4f40b1375b..6965e83644 100644 --- a/app.js +++ b/app.js @@ -2,10 +2,12 @@ if (process.env.NODE_ENV !== 'production') { require('dotenv').config() } +const path = require('path') const express = require('express') const passport = require('./config/passport') const flash = require('connect-flash') const { apis, pages } = require('./routes') +const methodOverride = require('method-override') const session = require('express-session') const cors = require('cors') @@ -15,6 +17,8 @@ const port = process.env.PORT || 3000 // 解析request主體 app.use(express.urlencoded({ extended: true })) app.use(express.json()) +app.use(methodOverride('_method')) +app.use('/upload', express.static(path.join(__dirname, 'upload'))) // session設定 app.use(session({ diff --git a/controllers/apis/admin-controller.js b/controllers/apis/admin-controller.js index 2326f6ff5c..f26d1848c5 100644 --- a/controllers/apis/admin-controller.js +++ b/controllers/apis/admin-controller.js @@ -49,8 +49,8 @@ const adminController = { userData.likedCount = likedCount return userData }) - - return res.status(200).json(data) + const dataSorted = data.sort((a, b) => b.userTweetCount - a.userTweetCount) + return res.status(200).json(dataSorted) } catch (error) { next(error) } diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index 5f74004356..48934a8eac 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -4,6 +4,7 @@ const tweetController = { postTweet: async (req, res, next) => { try { const { description } = req.body + const tweet = await Tweet.create({ UserId: res.locals.userId, description @@ -67,6 +68,8 @@ const tweetController = { TweetId: tweet_id, comment }) + await tweet.increment('repliedCount', { by: 1 }) + return res.status(200).json(reply) } catch (error) { next(error) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 21562cb562..726231c5b0 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -1,7 +1,7 @@ const jwt = require('jsonwebtoken') const bcrypt = require('bcryptjs') const { getUser } = require('../../_helpers') -const { imgurFileHandler } = require('../../helpers/file-helper') +const { imgurFileHandler, localFileHandler } = require('../../helpers/file-helper') const { User, Tweet, Reply, Like, Followship } = require('../../models') const userController = { @@ -63,12 +63,13 @@ const userController = { User.findByPk(id), Tweet.findAll({ where: { UserId: id }, - include: [{ model: User, as: 'TweetUser', attributes: ['id', 'name', 'account', 'avatar'] }], - raw: true, + include: [ + { model: User, as: 'TweetUser', attributes: ['id', 'name', 'account', 'avatar'] }, + { model: Reply, as: 'TweetReply', attributes: ['id'] } + ], nest: true }) ]) - // 錯誤處理 if (!user) { const error = new Error('The user does not exist') @@ -80,6 +81,10 @@ const userController = { error.status = 404 throw error } + for (const i of userTweets) { + i.dataValues.tweetReplyCount = i.dataValues.TweetReply.length + delete i.dataValues.TweetReply + } return res.status(200).json(userTweets) } catch (error) { next(error) @@ -94,7 +99,9 @@ const userController = { const [followingCount, followerCount, user] = await Promise.all([ Followship.findAndCountAll({ where: { followerId: id } }), Followship.findAndCountAll({ where: { followingId: id } }), - User.findByPk(id) + User.findByPk(id, { + include: [{ model: Tweet, as: 'UserTweets' }] + }) ]) // 確認使用者是否存在 @@ -105,8 +112,10 @@ const userController = { } const data = user.toJSON() + data.userTweetCount = data.UserTweets.length data.followingCount = followingCount.count data.followerCount = followerCount.count + delete data.UserTweets return res.status(200).json(data) } catch (error) { next(error) @@ -164,7 +173,6 @@ const userController = { nest: true }) ]) - // 錯誤處理 if (!user) { const error = new Error('The user does not exist') @@ -176,6 +184,7 @@ const userController = { error.status = 404 throw error } + console.log(userLiked) const data = [] for (const i of userLiked) { data.push(i.Tweet) @@ -195,7 +204,7 @@ const userController = { User.findByPk(id), Followship.findAll({ where: { followerId: Number(id) }, - include: [{ model: User, as: 'Followings', attributes: ['id', 'name', 'account', 'avatar'] }], + include: [{ model: User, as: 'Followings', attributes: ['id', 'name', 'account', 'avatar', 'introduction'] }], raw: true, nest: true }) @@ -228,7 +237,7 @@ const userController = { User.findByPk(id), Followship.findAll({ where: { followingId: Number(id) }, - include: [{ model: User, as: 'Followers', attributes: ['id', 'name', 'account', 'avatar'] }] + include: [{ model: User, as: 'Followers', attributes: ['id', 'name', 'account', 'avatar', 'introduction'] }] }) ]) @@ -249,19 +258,25 @@ const userController = { next(error) } }, - editUser: async (req, res, next) => { + editUserProfile: async (req, res, next) => { try { - const { name, introduction, account, email } = req.body + const { name, introduction } = req.body let { id } = req.params id = Number(id) // introduction與name的字數限制 - if (introduction.length > 160) throw new Error('Your self-introduction is a little too long for me to handle! Please less than 160.') - if (name.length > 50) throw new Error('Your self-introduction is a little too long for me to handle! ! Please less than 50.') + if (introduction.length > 160) { + const error = new Error('Your self-introduction is a little too long for me to handle! Please less than 160.') + error.status = 400 + throw error + } + if (name.length > 50) { + const error = new Error('Your self-introduction is a little too long for me to handle! ! Please less than 50.') + error.status = 400 + throw error + } - // 確認user是否存在,account與email是否與資料庫重複 const user = await User.findByPk(id) - // 錯誤處理 if (!user) { const error = new Error('The user does not exist') @@ -269,39 +284,78 @@ const userController = { throw error } - if (account && email) { - const [userAccount, userEmail] = await Promise.all([ - User.findOne({ where: { account } }), - User.findOne({ where: { email } }) - ]) - // 錯誤處理 - if (userAccount.id !== id) throw new Error('account已存在') - if (userEmail.id !== id) throw new Error('email已存在') - } else if (account) { - const userAccount = await User.findOne({ where: { account } }) - // 錯誤處理 - if (userAccount.id !== id) throw new Error('account已存在') - } else if (email) { - const userEmail = await User.findOne({ where: { email } }) - // 錯誤處理 - if (userEmail.id !== id) throw new Error('email已存在') - } + // 檔案承接 + const { files } = req - const { avatar, background } = req - const [avatarFilePath, backgroundFilePath] = await Promise.all([ - imgurFileHandler(avatar), - imgurFileHandler(background) + const [avatar, background] = await Promise.all([ + files.avatar ? await localFileHandler(files.avatar[0]) : null, + files.background ? await localFileHandler(files.background[0]) : null ]) + const updatedUser = await user.update({ name, - avatar: avatarFilePath || user.avatar, - background: backgroundFilePath || user.background, - introduction + introduction, + avatar: avatar || user.avatar, + background: background || user.background }) return res.status(200).json(updatedUser) } catch (error) { next(error) } + }, + putUserSetting: async (req, res, next) => { + try { + const { account, name, email, introduction, password, checkPassword } = req.body + console.log('-------------------------------------req.body') + console.log(req.body) + // introduction與name的字數限制 + if (introduction.length > 160) { + const error = new Error('Your self-introduction is a little too long for me to handle! Please less than 160.') + error.status = 400 + throw error + } + if (name.length > 50) { + const error = new Error('Your self-introduction is a little too long for me to handle! ! Please less than 50.') + error.status = 400 + throw error + } + + // check password + if (password !== checkPassword) { + const error = new Error('Password does not match the confirmation password.') + error.status = 400 + throw error + } + + // 檢查account, email 是否重複 + const [userAccount, userEmail] = await Promise.all([ + User.findOne({ where: { account } }), + User.findOne({ where: { email } }) + ]) + if (userAccount) { + const error = new Error('Account already exist') + error.status = 400 + throw error + } + if (userEmail) { + const error = new Error('Email already exist') + error.status = 400 + throw error + } + + const user = await User.findByPk(req.params.id) + const updatedUser = await user.update({ + account: account || user.account, + name: name || user.name, + email: email || user.email, + password: password ? bcrypt.hashSync(password) : user.password, + introduction: introduction || user.introduction + }) + + return res.status(200).json(updatedUser) + } catch (err) { + next(err) + } } } diff --git a/middleware/multer.js b/middleware/multer.js index 71fb05e07f..50ef87a866 100644 --- a/middleware/multer.js +++ b/middleware/multer.js @@ -1,4 +1,10 @@ const multer = require('multer') const upload = multer({ dest: 'temp/' }) -module.exports = upload +const uploadFiles = upload.fields([ + { name: 'avatar', maxCount: 1 }, { name: 'cover', maxCount: 1 }]) + +module.exports = { + upload, + uploadFiles +} diff --git a/migrations/20230612165505-add-repliedCount-to-Tweet.js b/migrations/20230612165505-add-repliedCount-to-Tweet.js new file mode 100644 index 0000000000..e27c308afb --- /dev/null +++ b/migrations/20230612165505-add-repliedCount-to-Tweet.js @@ -0,0 +1,14 @@ +'use strict' + +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.addColumn('Tweets', 'repliedCount', { + type: Sequelize.INTEGER, + defaultValue: 0 + }) + }, + + down: async (queryInterface, Sequelize) => { + await queryInterface.removeColumn('Tweets', 'repliedCount') + } +} diff --git a/models/tweet.js b/models/tweet.js index d5e2150843..601fe22552 100644 --- a/models/tweet.js +++ b/models/tweet.js @@ -22,7 +22,8 @@ module.exports = (sequelize, DataTypes) => { { UserId: DataTypes.INTEGER, description: DataTypes.TEXT, - likedCount: DataTypes.INTEGER + likedCount: DataTypes.INTEGER, + repliedCount: DataTypes.INTEGER }, { sequelize, diff --git a/routes/apis/index.js b/routes/apis/index.js index d70af107fe..c581e3e6bf 100644 --- a/routes/apis/index.js +++ b/routes/apis/index.js @@ -5,7 +5,7 @@ const userController = require('../../controllers/apis/user-controller') const adminController = require('../../controllers/apis/admin-controller') const tweetController = require('../../controllers/apis/tweet-controller') const followshipController = require('../../controllers/apis/followship-controller') -const upload = require('../../middleware/multer') +const { uploadFiles } = require('../../middleware/multer') const { apiErrorHandler } = require('../../middleware/error-handler') const { authenticated, roleChecker, authenticatedAdmin } = require('../../middleware/api-auth') @@ -34,7 +34,8 @@ router.get('/users/:id', authenticated, userController.getUser) router.get('/users/:id/likes', authenticated, userController.getUserLiked) // 看見某使用者點過的 Like router.get('/users/:id/followings', authenticated, userController.getUserFollows) // 看見某使用者跟隨中的人 router.get('/users/:id/followers', authenticated, userController.getUserFollowers) // 看見某使用者的跟隨者 -router.put('/users/:id', authenticated, upload.single('image'), userController.editUser) // 編輯自己所有的資料 +router.put('/users/:id/setting', authenticated, userController.putUserSetting) // 編輯帳號設定 +router.put('/users/:id', authenticated, uploadFiles, userController.editUserProfile) // 編輯個人資料 // 使用者登入 router.post('/users/login', passport.authenticate('local', { session: false }), userController.login) From 40cc9fd7ff0eb7fa69aa3b9abc36e77ed3215ec8 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Tue, 13 Jun 2023 13:16:50 +0800 Subject: [PATCH 077/131] include model by attributes --- controllers/apis/admin-controller.js | 3 ++- controllers/apis/tweet-controller.js | 5 +++-- controllers/apis/user-controller.js | 8 +++++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/controllers/apis/admin-controller.js b/controllers/apis/admin-controller.js index f26d1848c5..f27e81d700 100644 --- a/controllers/apis/admin-controller.js +++ b/controllers/apis/admin-controller.js @@ -30,6 +30,7 @@ const adminController = { getUsers: async (req, res, next) => { try { const users = await User.findAll({ + attributes: ['id', 'name', 'account'], include: [ { model: User, as: 'Followers' }, { model: User, as: 'Followings' }, @@ -58,7 +59,7 @@ const adminController = { getTweets: async (req, res, next) => { try { const tweets = await Tweet.findAll({ - include: [{ model: User, as: 'TweetUser' }], + include: [{ model: User, as: 'TweetUser', attributes: ['id', 'name', 'account'] }], order: [['createdAt', 'DESC']] }) return res.status(200).json(tweets) diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index 48934a8eac..2a2c06d4e3 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -17,7 +17,7 @@ const tweetController = { getTweets: async (req, res, next) => { try { const tweets = await Tweet.findAll({ - include: [{ model: User, as: 'TweetUser' }], + include: [{ model: User, as: 'TweetUser', attributes: ['id', 'name', 'account'] }], order: [['createdAt', 'DESC']] }) @@ -37,7 +37,7 @@ const tweetController = { try { const { tweet_id } = req.params const tweet = await Tweet.findByPk(tweet_id, { - include: [{ model: User, as: 'TweetUser' }] + include: [{ model: User, as: 'TweetUser', attributes: ['id', 'name', 'account'] }] }) if (!tweet) { const error = new Error('The tweet does not exist') @@ -89,6 +89,7 @@ const tweetController = { const replies = await Reply.findAll({ where: { TweetId: tweet_id }, + include: [{ model: User, as: 'RepliedUser', attributes: ['id', 'name', 'account'] }], raw: true, nest: true }) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 726231c5b0..e0d63fe180 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -131,7 +131,13 @@ const userController = { User.findByPk(id), Reply.findAll({ where: { UserId: id }, - include: [Tweet, { model: User, as: 'RepliedUser', attributes: ['id', 'name', 'account', 'avatar'] }], + include: [ + { + model: Tweet, + attributes: ['id', 'UserId'], + include: [{ model: User, as: 'TweetUser', attributes: ['id', 'name', 'account'] }] + }, + { model: User, as: 'RepliedUser', attributes: ['id', 'name', 'account', 'avatar'] }], raw: true, nest: true }) From 5969edd560b8887fd5baa43290dd47ef29ffd7e9 Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Tue, 13 Jun 2023 16:33:26 +0800 Subject: [PATCH 078/131] fix: multer setting and upload file func --- controllers/apis/user-controller.js | 4 ++-- middleware/multer.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index e0d63fe180..20c2bc6c04 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -294,8 +294,8 @@ const userController = { const { files } = req const [avatar, background] = await Promise.all([ - files.avatar ? await localFileHandler(files.avatar[0]) : null, - files.background ? await localFileHandler(files.background[0]) : null + files.avatar ? await imgurFileHandler(files.avatar[0]) : null, + files.background ? await imgurFileHandler(files.background[0]) : null ]) const updatedUser = await user.update({ diff --git a/middleware/multer.js b/middleware/multer.js index 50ef87a866..95e357b964 100644 --- a/middleware/multer.js +++ b/middleware/multer.js @@ -2,7 +2,7 @@ const multer = require('multer') const upload = multer({ dest: 'temp/' }) const uploadFiles = upload.fields([ - { name: 'avatar', maxCount: 1 }, { name: 'cover', maxCount: 1 }]) + { name: 'avatar', maxCount: 1 }, { name: 'background', maxCount: 1 }]) module.exports = { upload, From 098c14152c39a43e38535dad9ec4c301a856cd7b Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Tue, 13 Jun 2023 17:13:37 +0800 Subject: [PATCH 079/131] fix: revise user-controller/edutUserProfile to pass the test --- controllers/apis/user-controller.js | 33 +++++++++++++++++++---------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 20c2bc6c04..152719b291 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -293,18 +293,29 @@ const userController = { // 檔案承接 const { files } = req - const [avatar, background] = await Promise.all([ - files.avatar ? await imgurFileHandler(files.avatar[0]) : null, - files.background ? await imgurFileHandler(files.background[0]) : null - ]) + if (files) { + const [avatar, background] = await Promise.all([ + files.avatar ? await imgurFileHandler(files.avatar[0]) : null, + files.background ? await imgurFileHandler(files.background[0]) : null + ]) - const updatedUser = await user.update({ - name, - introduction, - avatar: avatar || user.avatar, - background: background || user.background - }) - return res.status(200).json(updatedUser) + const updatedUser = await user.update({ + name, + introduction, + avatar: avatar || user.avatar, + background: background || user.background + }) + + return res.status(200).json(updatedUser) + } else { + const updatedUser = await user.update({ + name, + introduction, + avatar: user.avatar, + background: user.background + }) + return res.status(200).json(updatedUser) + } } catch (error) { next(error) } From f382959121c62f488c708311db3217b82dd3688b Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Tue, 13 Jun 2023 21:19:21 +0800 Subject: [PATCH 080/131] fix: revise the user background url inside seeders --- seeders/20230605074630-users-seed-file.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seeders/20230605074630-users-seed-file.js b/seeders/20230605074630-users-seed-file.js index 202b5f2237..93ae4c6a3f 100644 --- a/seeders/20230605074630-users-seed-file.js +++ b/seeders/20230605074630-users-seed-file.js @@ -1,7 +1,7 @@ 'use strict' const faker = require('faker') const bcrypt = require('bcryptjs') -const background = 'https://plus.unsplash.com/premium_photo-1668852917755-0e6fc9a66db5?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=687&q=80' +const background = 'https://images.unsplash.com/photo-1580436541340-36b8d0c60bae?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=688&q=80' module.exports = { up: async (queryInterface, Sequelize) => { From 1d0cc76cfe971dc2d1b8daaa4410735c01fd9c74 Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Tue, 13 Jun 2023 22:09:25 +0800 Subject: [PATCH 081/131] fix: 1. add if fun inside api-auth.js that admin cannot postTweet, follow someone, reply and like 2.Tweet can provide repliedCount and likedCount --- controllers/apis/tweet-controller.js | 34 +++++++++++++++++++++++++--- controllers/apis/user-controller.js | 1 + middleware/api-auth.js | 10 +++++--- routes/apis/index.js | 2 +- 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index 2a2c06d4e3..10ddc5421c 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -17,7 +17,17 @@ const tweetController = { getTweets: async (req, res, next) => { try { const tweets = await Tweet.findAll({ - include: [{ model: User, as: 'TweetUser', attributes: ['id', 'name', 'account'] }], + include: [ + { model: User, as: 'TweetUser', attributes: ['id', 'name', 'account', 'avatar'] }, + { + model: Reply, + as: 'TweetReply', + attributes: ['id'], + include: [{ model: User, as: 'RepliedUser', attributes: ['name', 'account', 'avatar'] }] + }, + { model: Like, as: 'TweetLike', attributes: ['id'] } + ], + nest: true, order: [['createdAt', 'DESC']] }) @@ -28,6 +38,11 @@ const tweetController = { throw error } + tweets.forEach(tweet => { + tweet.likedCount = tweet.TweetLike.length + tweet.repliedCount = tweet.TweetReply.length + }) + return res.status(200).json(tweets) } catch (error) { next(error) @@ -37,8 +52,21 @@ const tweetController = { try { const { tweet_id } = req.params const tweet = await Tweet.findByPk(tweet_id, { - include: [{ model: User, as: 'TweetUser', attributes: ['id', 'name', 'account'] }] - }) + include: [ + { model: User, as: 'TweetUser', attributes: ['id', 'name', 'account', 'avatar'] }, + { + model: Reply, + as: 'TweetReply', + attributes: ['id'], + include: [{ model: User, as: 'RepliedUser', attributes: ['name', 'account', 'avatar'] }] + }, + { model: Like, as: 'TweetLike', attributes: ['id'] } + ], + nest: true + } + ) + tweet.likedCount = tweet.TweetLike.length + tweet.repliedCount = tweet.TweetReply.length if (!tweet) { const error = new Error('The tweet does not exist') error.status = 404 diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 152719b291..ca08a252b1 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -116,6 +116,7 @@ const userController = { data.followingCount = followingCount.count data.followerCount = followerCount.count delete data.UserTweets + delete data.password return res.status(200).json(data) } catch (error) { next(error) diff --git a/middleware/api-auth.js b/middleware/api-auth.js index c864fe872d..cddca7cc1b 100644 --- a/middleware/api-auth.js +++ b/middleware/api-auth.js @@ -4,9 +4,13 @@ const { getUser } = require('../_helpers') const authenticated = (req, res, next) => { passport.authenticate('jwt', { session: false }, (err, user) => { if (err || !user) return res.status(401).json({ status: 'error', message: 'unauthorized!' }) - res.locals.userId = user.dataValues.id - req.user = user.dataValues - next() + if (user.role === 'admin') { + return res.status(403).json({ status: 'error', message: 'Admin cannot use these function' }) + } else { + res.locals.userId = user.dataValues.id + req.user = user.dataValues + next() + } })(req, res, next) } diff --git a/routes/apis/index.js b/routes/apis/index.js index c581e3e6bf..15075003fd 100644 --- a/routes/apis/index.js +++ b/routes/apis/index.js @@ -41,7 +41,7 @@ router.put('/users/:id', authenticated, uploadFiles, userController.editUserProf router.post('/users/login', passport.authenticate('local', { session: false }), userController.login) // 使用者註冊 router.post('/users', userController.signUp) - +// 錯誤處理 router.use('/', apiErrorHandler) module.exports = router From b8893d8824657ef4db76aab7f6bdabe33c8d27c7 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Wed, 14 Jun 2023 00:21:51 +0800 Subject: [PATCH 082/131] revise postTweet, postFollowship, editUserProfile --- controllers/apis/followship-controller.js | 23 +++++++++++++- controllers/apis/tweet-controller.js | 2 ++ controllers/apis/user-controller.js | 37 +++++++++++++++-------- 3 files changed, 49 insertions(+), 13 deletions(-) diff --git a/controllers/apis/followship-controller.js b/controllers/apis/followship-controller.js index 839f1a2178..8de6614b04 100644 --- a/controllers/apis/followship-controller.js +++ b/controllers/apis/followship-controller.js @@ -1,5 +1,5 @@ const { getUser } = require('../../_helpers') -const { Followship } = require('../../models') +const { Followship, User } = require('../../models') const followshipController = { // 更改路由新增:followshipId? @@ -7,6 +7,20 @@ const followshipController = { try { let { id } = req.body id = Number(id) + + // 使用者無法追蹤自己 + if (id === getUser(req).id) { + const error = "You can't follow yourself" + error.status = 400 + } + // 使用者無法追蹤admin + const admin = await User.findByPk(id) + if (admin.role === 'admin') { + const error = new Error("The user doesn't exist") + error.status = 404 + throw error + } + // 確認使用者是否已經追蹤該用戶 const followship = await Followship.findOne({ where: { followingId: id, followerId: getUser(req).id } }) // 如果有 => 不用新增 @@ -28,6 +42,13 @@ const followshipController = { deleteFollowship: async (req, res, next) => { try { const { followingId } = req.params + + // 使用者無法取消追蹤自己 + if (Number(followingId) === getUser(req).id) { + const error = "You can't follow yourself" + error.status = 400 + } + // 確認使用者是否已經追蹤該用戶 const follow = await Followship.findOne({ where: { followerId: getUser(req).id, followingId } diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index 2a2c06d4e3..14ac6a2c0c 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -5,6 +5,8 @@ const tweetController = { try { const { description } = req.body + // 使用者無法幫別人po文 + const tweet = await Tweet.create({ UserId: res.locals.userId, description diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 152719b291..fba44b2303 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -71,7 +71,7 @@ const userController = { }) ]) // 錯誤處理 - if (!user) { + if (!user || user.role === 'admin') { const error = new Error('The user does not exist') error.status = 404 throw error @@ -95,7 +95,6 @@ const userController = { let id = req.params.id id = Number(id) - // 缺少 推文喜歡數量、推文數量 const [followingCount, followerCount, user] = await Promise.all([ Followship.findAndCountAll({ where: { followerId: id } }), Followship.findAndCountAll({ where: { followingId: id } }), @@ -105,7 +104,7 @@ const userController = { ]) // 確認使用者是否存在 - if (!user) { + if (!user || user.role === 'admin') { const error = new Error('The tweet does not exist') error.status = 404 throw error @@ -144,7 +143,7 @@ const userController = { ]) // 錯誤處理 - if (!user) { + if (!user || user.role === 'admin') { const error = new Error('The user does not exist') error.status = 404 throw error @@ -180,7 +179,7 @@ const userController = { }) ]) // 錯誤處理 - if (!user) { + if (!user || user.role === 'admin') { const error = new Error('The user does not exist') error.status = 404 throw error @@ -190,7 +189,6 @@ const userController = { error.status = 404 throw error } - console.log(userLiked) const data = [] for (const i of userLiked) { data.push(i.Tweet) @@ -217,7 +215,7 @@ const userController = { ]) // 錯誤處理 - if (!user) { + if (!user || user.role === 'admin') { const error = new Error('The user does not exist') error.status = 404 throw error @@ -248,7 +246,7 @@ const userController = { ]) // 錯誤處理 - if (!user) { + if (!user || user.role === 'admin') { const error = new Error('The user does not exist') error.status = 404 throw error @@ -270,6 +268,13 @@ const userController = { let { id } = req.params id = Number(id) + // 使用者不能編輯他人資料 + if (getUser(req).id !== id) { + const error = new Error('You can only edit your profile') + error.status = 403 + throw error + } + // introduction與name的字數限制 if (introduction.length > 160) { const error = new Error('Your self-introduction is a little too long for me to handle! Please less than 160.') @@ -284,7 +289,7 @@ const userController = { const user = await User.findByPk(id) // 錯誤處理 - if (!user) { + if (!user || user.role === 'admin') { const error = new Error('The user does not exist') error.status = 404 throw error @@ -323,8 +328,9 @@ const userController = { putUserSetting: async (req, res, next) => { try { const { account, name, email, introduction, password, checkPassword } = req.body - console.log('-------------------------------------req.body') - console.log(req.body) + let { id } = req.params + id = Number(id) + // introduction與name的字數限制 if (introduction.length > 160) { const error = new Error('Your self-introduction is a little too long for me to handle! Please less than 160.') @@ -344,6 +350,13 @@ const userController = { throw error } + // 使用者不能編輯他人資料 + if (getUser(req).id !== id) { + const error = new Error('You can only edit your profile') + error.status = 403 + throw error + } + // 檢查account, email 是否重複 const [userAccount, userEmail] = await Promise.all([ User.findOne({ where: { account } }), @@ -360,7 +373,7 @@ const userController = { throw error } - const user = await User.findByPk(req.params.id) + const user = await User.findByPk(id) const updatedUser = await user.update({ account: account || user.account, name: name || user.name, From 96102d792b01adf64e5162ee65e5ee7ceb34d269 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Wed, 14 Jun 2023 00:25:07 +0800 Subject: [PATCH 083/131] remove never used variable --- controllers/apis/user-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 1c1d7dbd28..7741ddb28e 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -1,7 +1,7 @@ const jwt = require('jsonwebtoken') const bcrypt = require('bcryptjs') const { getUser } = require('../../_helpers') -const { imgurFileHandler, localFileHandler } = require('../../helpers/file-helper') +const { imgurFileHandler } = require('../../helpers/file-helper') const { User, Tweet, Reply, Like, Followship } = require('../../models') const userController = { From 1bcab6f9a689377fb8c8b5e7fbcff6a5dc6eb94e Mon Sep 17 00:00:00 2001 From: s1030905 Date: Wed, 14 Jun 2023 11:52:38 +0800 Subject: [PATCH 084/131] add user top 10 --- controllers/apis/admin-controller.js | 12 +++++++++++- controllers/apis/user-controller.js | 21 +++++++++++++++++++++ routes/apis/index.js | 1 + 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/controllers/apis/admin-controller.js b/controllers/apis/admin-controller.js index f27e81d700..f161ba495b 100644 --- a/controllers/apis/admin-controller.js +++ b/controllers/apis/admin-controller.js @@ -62,7 +62,17 @@ const adminController = { include: [{ model: User, as: 'TweetUser', attributes: ['id', 'name', 'account'] }], order: [['createdAt', 'DESC']] }) - return res.status(200).json(tweets) + + // 50字快覽 + const data = tweets.map(tweet => { + const { description, ...rest } = tweet.toJSON() + return { + ...rest, + description: description.length >= 50 ? description.slice(0, 50) : description + } + }) + + return res.status(200).json(data) } catch (error) { next(error) } diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 7741ddb28e..fb0da8f1b8 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -387,6 +387,27 @@ const userController = { } catch (err) { next(err) } + }, + getTopUser: async (req, res, next) => { + try { + const users = await User.findAll({ + where: { role: 'user' }, + attributes: ['id', 'name', 'account'], + include: [{ model: User, as: 'Followers', attributes: ['id', 'name', 'account'] }] + }) + const data = users.map(user => { + const { Followers, ...rest } = user.toJSON() + return { + ...rest, + followerCount: user.Followers.length + } + }) + data.sort((a, b) => b.followerCount - a.followerCount) + const top10 = data.slice(0, 10) + res.status(200).json(top10) + } catch (error) { + next(error) + } } } diff --git a/routes/apis/index.js b/routes/apis/index.js index 15075003fd..7e6e039d41 100644 --- a/routes/apis/index.js +++ b/routes/apis/index.js @@ -28,6 +28,7 @@ router.post('/followships/', authenticated, followshipController.postFollowship) router.delete('/followships/:followingId', authenticated, followshipController.deleteFollowship) // 有關user的routes +router.get('/users/top', authenticated, userController.getTopUser) router.get('/users/:id/tweets', authenticated, userController.getUserTweets) router.get('/users/:id/replied_tweets', authenticated, userController.getUserRepliedTweet) router.get('/users/:id', authenticated, userController.getUser) From e352fe24361e61b62891eeeea45b8e3035a79548 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Wed, 14 Jun 2023 20:58:16 +0800 Subject: [PATCH 085/131] post like count number + 1 --- controllers/apis/tweet-controller.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index 3b7948d2b9..a36ca8407e 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -158,6 +158,9 @@ const tweetController = { UserId: res.locals.userId, TweetId: id }) + // likedCount + 1 + await PromiseArr[0].increment('likedCount', { by: 1 }) + return res.status(200).json(like) } catch (error) { next(error) From 9dcd8f81519fc5fb9d3a2bcd6d93082fd72bb35d Mon Sep 17 00:00:00 2001 From: s1030905 Date: Wed, 14 Jun 2023 20:59:05 +0800 Subject: [PATCH 086/131] add TweetUser info at getUserLiked --- controllers/apis/user-controller.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index fb0da8f1b8..9fb874e70a 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -174,7 +174,16 @@ const userController = { User.findByPk(id), Like.findAll({ where: { UserId: Number(id) }, - include: [Tweet, { model: User, as: 'LikedUser', attributes: ['id', 'name', 'account', 'avatar'] }], + include: [ + { + model: Tweet, + include: { + model: User, + as: 'TweetUser', + attributes: ['id', 'name', 'account', 'avatar'] + } + } + ], raw: true, nest: true }) @@ -186,7 +195,7 @@ const userController = { throw error } if (!userLiked.length) { - const error = new Error('He does not like tweet.') + const error = new Error('He does not like any tweet.') error.status = 404 throw error } From a6ddfa3f7a3df138922d0e8197d82fdc4a91866e Mon Sep 17 00:00:00 2001 From: s1030905 Date: Wed, 14 Jun 2023 22:03:30 +0800 Subject: [PATCH 087/131] remove user from top10 list and add isFollowed status --- controllers/apis/user-controller.js | 40 ++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 9fb874e70a..eca1eaf208 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -399,20 +399,46 @@ const userController = { }, getTopUser: async (req, res, next) => { try { - const users = await User.findAll({ - where: { role: 'user' }, - attributes: ['id', 'name', 'account'], - include: [{ model: User, as: 'Followers', attributes: ['id', 'name', 'account'] }] - }) - const data = users.map(user => { + // 找出追隨者數量前10名 + const [user, users] = await Promise.all([ + User.findByPk(getUser(req).id, + { + include: [{ model: User, as: 'Followings', attributes: ['id'] }], + attributes: ['id'] + } + ), + User.findAll({ + where: { role: 'user' }, + attributes: ['id', 'name', 'account'], + include: [{ model: User, as: 'Followers', attributes: ['id', 'name', 'account'] }] + }) + ]) + + let data = users.map(user => { const { Followers, ...rest } = user.toJSON() return { ...rest, followerCount: user.Followers.length } }) - data.sort((a, b) => b.followerCount - a.followerCount) + + // 排列top10順序 並 將使用者移除推薦追蹤列表 + data = data.sort((a, b) => b.followerCount - a.followerCount).filter(e => e.id !== getUser(req).id) const top10 = data.slice(0, 10) + const userFollowings = user.toJSON().Followings + + const dic = {} + for (let i = 0; i < userFollowings.length; i++) { + dic[userFollowings[i].id] = i + } + + for (const i of top10) { + if (dic[i.id] >= 0) { + i.isFollowed = true + } else { + i.isFollowed = false + } + } res.status(200).json(top10) } catch (error) { next(error) From cbfdf29d3a2558f99e3aee9e80afc87353b6e84e Mon Sep 17 00:00:00 2001 From: s1030905 Date: Thu, 15 Jun 2023 22:03:13 +0800 Subject: [PATCH 088/131] remove introduction from putUserSetting --- controllers/apis/user-controller.js | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index eca1eaf208..47df7eee13 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -337,16 +337,12 @@ const userController = { }, putUserSetting: async (req, res, next) => { try { - const { account, name, email, introduction, password, checkPassword } = req.body + const { account, name, email, password, checkPassword } = req.body let { id } = req.params id = Number(id) - // introduction與name的字數限制 - if (introduction.length > 160) { - const error = new Error('Your self-introduction is a little too long for me to handle! Please less than 160.') - error.status = 400 - throw error - } + // name的字數限制 + if (name.length > 50) { const error = new Error('Your self-introduction is a little too long for me to handle! ! Please less than 50.') error.status = 400 @@ -388,8 +384,7 @@ const userController = { account: account || user.account, name: name || user.name, email: email || user.email, - password: password ? bcrypt.hashSync(password) : user.password, - introduction: introduction || user.introduction + password: password ? bcrypt.hashSync(password) : user.password }) return res.status(200).json(updatedUser) @@ -409,7 +404,7 @@ const userController = { ), User.findAll({ where: { role: 'user' }, - attributes: ['id', 'name', 'account'], + attributes: ['id', 'name', 'account', 'avatar'], include: [{ model: User, as: 'Followers', attributes: ['id', 'name', 'account'] }] }) ]) From 52baeaafb032e0036db774b66fb64462d72f3f7b Mon Sep 17 00:00:00 2001 From: s1030905 Date: Thu, 15 Jun 2023 22:23:41 +0800 Subject: [PATCH 089/131] feat: add comment at getTweet api --- controllers/apis/tweet-controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index a36ca8407e..33cef9762a 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -59,8 +59,8 @@ const tweetController = { { model: Reply, as: 'TweetReply', - attributes: ['id'], - include: [{ model: User, as: 'RepliedUser', attributes: ['name', 'account', 'avatar'] }] + attributes: ['id', 'comment'], + include: [{ model: User, as: 'RepliedUser', attributes: ['name', 'account', 'avatar'] }] }, { model: Like, as: 'TweetLike', attributes: ['id'] } ], From 5c01ac7839b399c182229ce62dc3b1da6e1aaddc Mon Sep 17 00:00:00 2001 From: s1030905 Date: Thu, 15 Jun 2023 22:24:21 +0800 Subject: [PATCH 090/131] fix putUserSetting --- controllers/apis/user-controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 47df7eee13..7f472be6f6 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -368,12 +368,12 @@ const userController = { User.findOne({ where: { account } }), User.findOne({ where: { email } }) ]) - if (userAccount) { + if (userAccount && account !== getUser(req).account) { const error = new Error('Account already exist') error.status = 400 throw error } - if (userEmail) { + if (userEmail && email !== getUser(req).email) { const error = new Error('Email already exist') error.status = 400 throw error From 656b035b71e1f15616a074b621b872f02a138410 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Fri, 16 Jun 2023 01:28:21 +0800 Subject: [PATCH 091/131] caculate likedCount and repliedCount --- controllers/apis/user-controller.js | 13 +++++++----- ...09-caculate-likedCount-and-repliedCount.js | 21 +++++++++++++++++++ 2 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 seeders/20230615164509-caculate-likedCount-and-repliedCount.js diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 7f472be6f6..532e7edb0d 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -174,14 +174,17 @@ const userController = { User.findByPk(id), Like.findAll({ where: { UserId: Number(id) }, + attributes: ['id', 'UserId', 'TweetId'], include: [ { model: Tweet, - include: { - model: User, - as: 'TweetUser', - attributes: ['id', 'name', 'account', 'avatar'] - } + include: [ + { + model: User, + as: 'TweetUser', + attributes: ['id', 'name', 'account', 'avatar'] + } + ] } ], raw: true, diff --git a/seeders/20230615164509-caculate-likedCount-and-repliedCount.js b/seeders/20230615164509-caculate-likedCount-and-repliedCount.js new file mode 100644 index 0000000000..b65d17c14d --- /dev/null +++ b/seeders/20230615164509-caculate-likedCount-and-repliedCount.js @@ -0,0 +1,21 @@ +'use strict' + +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.sequelize.query( + `UPDATE Tweets + SET LikedCount = COALESCE( + (SELECT COUNT(*) FROM Likes WHERE Likes.TweetId = Tweets.id), + 0 + ), + repliedCount = COALESCE( + (SELECT COUNT(*) FROM Replies WHERE Replies.TweetId = Tweets.id), + 0 + )` + ) + }, + + down: async (queryInterface, Sequelize) => { + await queryInterface.bulkDelete('Tweets', { LikedCount: null, repliedCount: null }) + } +} From 82449d0a9167cd6904a44e7a7c3a3974339ff84d Mon Sep 17 00:00:00 2001 From: s1030905 Date: Fri, 16 Jun 2023 13:35:58 +0800 Subject: [PATCH 092/131] feat: add LikedCount and repliedCount --- controllers/apis/user-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 532e7edb0d..44ecada4a8 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -134,7 +134,7 @@ const userController = { include: [ { model: Tweet, - attributes: ['id', 'UserId'], + attributes: ['id', 'UserId', 'LikedCount', 'repliedCount'], include: [{ model: User, as: 'TweetUser', attributes: ['id', 'name', 'account'] }] }, { model: User, as: 'RepliedUser', attributes: ['id', 'name', 'account', 'avatar'] }], From 37f2aa088f048b07b7bd8e8e8451e29e5562e906 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Fri, 16 Jun 2023 13:45:52 +0800 Subject: [PATCH 093/131] add required attributes on getUsers and getTweets --- controllers/apis/admin-controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/controllers/apis/admin-controller.js b/controllers/apis/admin-controller.js index f161ba495b..5dbfcdc4a1 100644 --- a/controllers/apis/admin-controller.js +++ b/controllers/apis/admin-controller.js @@ -30,7 +30,7 @@ const adminController = { getUsers: async (req, res, next) => { try { const users = await User.findAll({ - attributes: ['id', 'name', 'account'], + attributes: ['id', 'name', 'account', 'avatar', 'background'], include: [ { model: User, as: 'Followers' }, { model: User, as: 'Followings' }, @@ -59,7 +59,7 @@ const adminController = { getTweets: async (req, res, next) => { try { const tweets = await Tweet.findAll({ - include: [{ model: User, as: 'TweetUser', attributes: ['id', 'name', 'account'] }], + include: [{ model: User, as: 'TweetUser', attributes: ['id', 'name', 'account', 'avatar'] }], order: [['createdAt', 'DESC']] }) From 00ff1cf6cafe318c9e1e37c4821f0f613c61db88 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Fri, 16 Jun 2023 17:54:47 +0800 Subject: [PATCH 094/131] feat: following-btn and like-btn status --- controllers/apis/tweet-controller.js | 78 ++++++++++++++++++++-------- controllers/apis/user-controller.js | 73 ++++++++++++++++++-------- 2 files changed, 107 insertions(+), 44 deletions(-) diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index 33cef9762a..316fad218b 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -1,4 +1,5 @@ const { User, Tweet, Reply, Like } = require('../../models') +const { getUser } = require('../../_helpers') const tweetController = { postTweet: async (req, res, next) => { @@ -27,7 +28,7 @@ const tweetController = { attributes: ['id'], include: [{ model: User, as: 'RepliedUser', attributes: ['name', 'account', 'avatar'] }] }, - { model: Like, as: 'TweetLike', attributes: ['id'] } + { model: Like, as: 'TweetLike', attributes: ['id', 'UserId'] } ], nest: true, order: [['createdAt', 'DESC']] @@ -39,13 +40,20 @@ const tweetController = { error.status = 404 throw error } - - tweets.forEach(tweet => { - tweet.likedCount = tweet.TweetLike.length - tweet.repliedCount = tweet.TweetReply.length + const data = tweets.map(e => e.toJSON()) + + data.forEach(tweet => { + for (const i of tweet.TweetLike) { + if (i.UserId === getUser(req).id) { + tweet.isLiked = true + } else { + tweet.isLiked = false + } + } + if (tweet.TweetLike.length < 1)tweet.isLiked = false }) - return res.status(200).json(tweets) + return res.status(200).json(data) } catch (error) { next(error) } @@ -60,22 +68,31 @@ const tweetController = { model: Reply, as: 'TweetReply', attributes: ['id', 'comment'], - include: [{ model: User, as: 'RepliedUser', attributes: ['name', 'account', 'avatar'] }] + include: [{ model: User, as: 'RepliedUser', attributes: ['name', 'account', 'avatar'] }] }, - { model: Like, as: 'TweetLike', attributes: ['id'] } - ], - nest: true - } + { model: Like, as: 'TweetLike', attributes: ['id', 'UserId'] } + ] + } // 這裡如果使用nest會有bug ) - tweet.likedCount = tweet.TweetLike.length - tweet.repliedCount = tweet.TweetReply.length - if (!tweet) { + const data = tweet.toJSON() + + // 錯誤處理 + if (!data) { const error = new Error('The tweet does not exist') error.status = 404 throw error } - return res.status(200).json(tweet) + let isLiked = false + for (const i of data.TweetLike) { + if (i.UserId === getUser(req).id) { + isLiked = true + data.isLiked = isLiked + break + } + } + + return res.status(200).json(data) } catch (error) { next(error) } @@ -108,7 +125,18 @@ const tweetController = { getReply: async (req, res, next) => { try { const { tweet_id } = req.params - const tweet = await Tweet.findByPk(tweet_id) + const tweet = await Tweet.findByPk(tweet_id, { + include: [ + { model: User, as: 'TweetUser', attributes: ['id', 'name', 'account', 'avatar'] }, + { model: Like, as: 'TweetLike', attributes: ['id', 'UserId'] }, + { + model: Reply, + as: 'TweetReply', + attributes: ['id', 'UserId', 'comment'], + include: [{ model: User, as: 'RepliedUser', attributes: ['id', 'name', 'account', 'avatar'] }] + } + ] + }) // 錯誤處理 if (!tweet) { @@ -117,13 +145,17 @@ const tweetController = { throw error } - const replies = await Reply.findAll({ - where: { TweetId: tweet_id }, - include: [{ model: User, as: 'RepliedUser', attributes: ['id', 'name', 'account'] }], - raw: true, - nest: true - }) - return res.status(200).json(replies) + const data = tweet.toJSON() + + // 先留著避免重寫 + + // data.isLiked = false + // for (const i of data.TweetLike) { + // if (i.UserId === getUser(req).id) { + // data.TweetReply.isLiked = true + // } + // } + return res.status(200).json(data.TweetReply) } catch (error) { next(error) } diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 44ecada4a8..cd948b496c 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -65,7 +65,7 @@ const userController = { where: { UserId: id }, include: [ { model: User, as: 'TweetUser', attributes: ['id', 'name', 'account', 'avatar'] }, - { model: Reply, as: 'TweetReply', attributes: ['id'] } + { model: Like, as: 'TweetLike', attributes: ['id', 'UserId'] } ], nest: true }) @@ -81,11 +81,18 @@ const userController = { error.status = 404 throw error } - for (const i of userTweets) { - i.dataValues.tweetReplyCount = i.dataValues.TweetReply.length - delete i.dataValues.TweetReply - } - return res.status(200).json(userTweets) + + const data = userTweets.map(e => { + e = e.toJSON() + e.isLiked = false + for (const i of e.TweetLike) { + if (i.UserId === getUser(req).id) { + e.isLiked = true + } + } + return e + }) + return res.status(200).json(data) } catch (error) { next(error) } @@ -127,7 +134,7 @@ const userController = { id = Number(id) // 確認使用者是否存在 與 回過文 - const [user, repliedTweets] = await Promise.all([ + let [user, repliedTweets] = await Promise.all([ User.findByPk(id), Reply.findAll({ where: { UserId: id }, @@ -135,14 +142,19 @@ const userController = { { model: Tweet, attributes: ['id', 'UserId', 'LikedCount', 'repliedCount'], - include: [{ model: User, as: 'TweetUser', attributes: ['id', 'name', 'account'] }] + include: [ + { model: User, as: 'TweetUser', attributes: ['id', 'name', 'account'] }, + { model: Like, as: 'TweetLike', attributes: ['id', 'UserId'] } + ] }, - { model: User, as: 'RepliedUser', attributes: ['id', 'name', 'account', 'avatar'] }], - raw: true, - nest: true + { model: User, as: 'RepliedUser', attributes: ['id', 'name', 'account', 'avatar'] }] }) ]) + // 資料格式處理 + user = user.toJSON() + repliedTweets = repliedTweets.map(e => e.toJSON()) + // 錯誤處理 if (!user || user.role === 'admin') { const error = new Error('The user does not exist') @@ -155,10 +167,15 @@ const userController = { throw error } - const data = [] - for (const i of repliedTweets) { - data.push(i.Tweet) - } + repliedTweets.forEach(e => { + e.isLiked = false + for (const i of e.Tweet.TweetLike) { + if (i.UserId === getUser(req).id) { + e.isLiked = true + } + } + }) + return res.status(200).json(repliedTweets) } catch (error) { next(error) @@ -202,10 +219,7 @@ const userController = { error.status = 404 throw error } - const data = [] - for (const i of userLiked) { - data.push(i.Tweet) - } + userLiked.forEach(e => { e.isLiked = true }) return res.status(200).json(userLiked) } catch (error) { next(error) @@ -238,7 +252,7 @@ const userController = { error.status = 404 throw error } - + userFollows.forEach(e => { e.isFollowed = true }) return res.status(200).json(userFollows) } catch (error) { next(error) @@ -250,7 +264,7 @@ const userController = { id = Number(id) // 確認使用者是否存在 與 其追隨者 - const [user, userFollowers] = await Promise.all([ + let [user, userFollowers] = await Promise.all([ User.findByPk(id), Followship.findAll({ where: { followingId: Number(id) }, @@ -258,6 +272,10 @@ const userController = { }) ]) + // 資料格式處理 + user = user.toJSON() + userFollowers = userFollowers.map(e => e.toJSON()) + // 錯誤處理 if (!user || user.role === 'admin') { const error = new Error('The user does not exist') @@ -270,6 +288,19 @@ const userController = { throw error } + const userFollowings = await Followship.findAll({ where: { followerId: getUser(req).id }, raw: true }) + + const dic = {} + for (let i = 0; i < userFollowings.length; i++) { + dic[userFollowings[i].followingId] = i + } + + userFollowers.forEach(e => { + e.isFollowed = false + if (dic[e.followerId] >= 0) { + e.isFollowed = true + } + }) return res.status(200).json(userFollowers) } catch (error) { next(error) From 9cbe288e956a2f0a4c80dd6cc259c0845365b20b Mon Sep 17 00:00:00 2001 From: s1030905 Date: Fri, 16 Jun 2023 22:17:25 +0800 Subject: [PATCH 095/131] revise getTweets isLiked status by hash --- controllers/apis/tweet-controller.js | 31 ++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index 316fad218b..3d6db7510d 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -42,16 +42,29 @@ const tweetController = { } const data = tweets.map(e => e.toJSON()) - data.forEach(tweet => { - for (const i of tweet.TweetLike) { - if (i.UserId === getUser(req).id) { - tweet.isLiked = true - } else { - tweet.isLiked = false - } + const likes = await Like.findAll({ where: { UserId: getUser(req).id }, attributes: ['id', 'TweetId'] }) + const dic = {} + for (let i = 0; i < likes.length; i++) { + dic[likes[i].TweetId] = i + } + + for (const i of data) { + i.isLiked = false + if (dic[i.id] >= 0) { + i.isLiked = true } - if (tweet.TweetLike.length < 1)tweet.isLiked = false - }) + } + + // data.forEach(tweet => { + // for (const i of tweet.TweetLike) { + // if (i.UserId === getUser(req).id) { + // tweet.isLiked = true + // } else { + // tweet.isLiked = false + // } + // } + // if (tweet.TweetLike.length < 1)tweet.isLiked = false + // }) return res.status(200).json(data) } catch (error) { From e01298ad6d6a425ae9c4f94ba554e80eab452008 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Fri, 16 Jun 2023 22:18:21 +0800 Subject: [PATCH 096/131] none --- controllers/apis/tweet-controller.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index 3d6db7510d..49a7ddf664 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -55,17 +55,6 @@ const tweetController = { } } - // data.forEach(tweet => { - // for (const i of tweet.TweetLike) { - // if (i.UserId === getUser(req).id) { - // tweet.isLiked = true - // } else { - // tweet.isLiked = false - // } - // } - // if (tweet.TweetLike.length < 1)tweet.isLiked = false - // }) - return res.status(200).json(data) } catch (error) { next(error) From 0aafa78e58f183628e75498e90802933d5604988 Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Fri, 16 Jun 2023 23:20:15 +0800 Subject: [PATCH 097/131] fix: revise getUserLiked --- controllers/apis/user-controller.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index cd948b496c..962177988a 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -200,7 +200,8 @@ const userController = { model: User, as: 'TweetUser', attributes: ['id', 'name', 'account', 'avatar'] - } + }, + { model: Like, as: 'TweetLike', attributes: ['id', 'UserId'] } ] } ], @@ -219,7 +220,12 @@ const userController = { error.status = 404 throw error } - userLiked.forEach(e => { e.isLiked = true }) + userLiked.forEach(e => { + e.isLiked = false + if (e.Tweet.TweetLike.UserId === getUser(req).id) { + e.isLiked = true + } + }) return res.status(200).json(userLiked) } catch (error) { next(error) From 21e10bc6514a856d9597222a886e3f37ef6e4333 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Sat, 17 Jun 2023 00:22:30 +0800 Subject: [PATCH 098/131] revise getUserLiked by hash --- controllers/apis/user-controller.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index cd948b496c..5b5fbb022f 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -219,7 +219,17 @@ const userController = { error.status = 404 throw error } - userLiked.forEach(e => { e.isLiked = true }) + const likes = Like.findAll({ where: { UserId: getUser(req).id } }) + const dic = {} + for (let i = 0; i < likes.length; i++) { + dic[likes[i].TweetId] = i + } + userLiked.forEach(e => { + e.isLiked = false + if (dic[e.TweetId] >= 0) { + e.isLiked = true + } + }) return res.status(200).json(userLiked) } catch (error) { next(error) From 3a4b3f7c0645d15193f0eb055778972b16f2ab6c Mon Sep 17 00:00:00 2001 From: s1030905 Date: Sat, 17 Jun 2023 00:31:31 +0800 Subject: [PATCH 099/131] revise getUserFollows --- controllers/apis/user-controller.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index d55857a67e..5d17218e91 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -242,14 +242,15 @@ const userController = { id = Number(id) // 確認使用者是否存在 與 其追蹤者 - const [user, userFollows] = await Promise.all([ + const [user, userFollows, followings] = await Promise.all([ User.findByPk(id), Followship.findAll({ where: { followerId: Number(id) }, include: [{ model: User, as: 'Followings', attributes: ['id', 'name', 'account', 'avatar', 'introduction'] }], raw: true, nest: true - }) + }), + Followship.findAll({ where: { followerId: getUser(req).id } }) ]) // 錯誤處理 @@ -263,7 +264,17 @@ const userController = { error.status = 404 throw error } - userFollows.forEach(e => { e.isFollowed = true }) + + const dic = {} + for (let i = 0; i < followings.length; i++) { + dic[followings[i].followingId] = i + } + userFollows.forEach(e => { + e.isFollowed = false + if (dic[e.followingId] >= 0) { + e.isFollowed = true + } + }) return res.status(200).json(userFollows) } catch (error) { next(error) From 7c8aedd9933af4c1dc012f091e1f89e6b2910c56 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Sat, 17 Jun 2023 10:21:56 +0800 Subject: [PATCH 100/131] revise getUserLiked and getUser --- controllers/apis/user-controller.js | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 5d17218e91..e4926c2337 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -102,12 +102,13 @@ const userController = { let id = req.params.id id = Number(id) - const [followingCount, followerCount, user] = await Promise.all([ + const [followingCount, followerCount, user, followers] = await Promise.all([ Followship.findAndCountAll({ where: { followerId: id } }), Followship.findAndCountAll({ where: { followingId: id } }), User.findByPk(id, { include: [{ model: Tweet, as: 'UserTweets' }] - }) + }), + Followship.findAll({ where: { followerId: getUser(req).id }, raw: true }) ]) // 確認使用者是否存在 @@ -118,6 +119,16 @@ const userController = { } const data = user.toJSON() + + // 建立追蹤清單檢查表 + const dic = {} + followers.forEach(e => { + dic[e.followingId] = e.followingId + }) + console.log(dic) + // 檢查該用戶是否在追蹤清單上 + dic[data.id] >= 0 ? data.isFollowed = true : data.isFollowed = false + data.userTweetCount = data.UserTweets.length data.followingCount = followingCount.count data.followerCount = followerCount.count @@ -187,7 +198,7 @@ const userController = { id = Number(id) // 確認使用者是否存在 與 喜歡的貼文 - const [user, userLiked] = await Promise.all([ + const [user, userLiked, likes] = await Promise.all([ User.findByPk(id), Like.findAll({ where: { UserId: Number(id) }, @@ -207,7 +218,8 @@ const userController = { ], raw: true, nest: true - }) + }), + Like.findAll({ where: { UserId: getUser(req).id } }) ]) // 錯誤處理 if (!user || user.role === 'admin') { @@ -220,7 +232,7 @@ const userController = { error.status = 404 throw error } - const likes = Like.findAll({ where: { UserId: getUser(req).id } }) + const dic = {} for (let i = 0; i < likes.length; i++) { dic[likes[i].TweetId] = i From 4d86efc13727d21a12af5ba47460023a50c235a3 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Sat, 17 Jun 2023 10:29:22 +0800 Subject: [PATCH 101/131] revise unlikeTweet --- controllers/apis/tweet-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index 49a7ddf664..38e43f5a36 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -224,7 +224,7 @@ const tweetController = { error.status = 404 throw error } - + await tweet.increment('likedCount', { by: -1 }) await like.destroy() return res.status(200).json(like) } catch (err) { From 570389c3cfd587fe8b036518a0ac97d15df9ad0a Mon Sep 17 00:00:00 2001 From: s1030905 Date: Sat, 17 Jun 2023 10:48:49 +0800 Subject: [PATCH 102/131] sort getUserLiked, getUserRepliedTweet, getUserTweets --- controllers/apis/user-controller.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index e4926c2337..8d34359505 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -67,7 +67,8 @@ const userController = { { model: User, as: 'TweetUser', attributes: ['id', 'name', 'account', 'avatar'] }, { model: Like, as: 'TweetLike', attributes: ['id', 'UserId'] } ], - nest: true + nest: true, + order: [['updatedAt', 'DESC']] }) ]) // 錯誤處理 @@ -158,7 +159,8 @@ const userController = { { model: Like, as: 'TweetLike', attributes: ['id', 'UserId'] } ] }, - { model: User, as: 'RepliedUser', attributes: ['id', 'name', 'account', 'avatar'] }] + { model: User, as: 'RepliedUser', attributes: ['id', 'name', 'account', 'avatar'] }], + order: [['updatedAt', 'DESC']] }) ]) @@ -216,6 +218,7 @@ const userController = { ] } ], + order: [['updatedAt', 'DESC']], raw: true, nest: true }), From fd41ea1da5fe14a15942a786a2ae7cca23e1f3a2 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Sat, 17 Jun 2023 14:23:17 +0800 Subject: [PATCH 103/131] revise login error msg --- config/passport.js | 7 ++++--- middleware/error-handler.js | 5 +++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/config/passport.js b/config/passport.js index cc41f9a677..63f9497874 100644 --- a/config/passport.js +++ b/config/passport.js @@ -11,20 +11,21 @@ passport.use(new LocalStrategy( { usernameField: 'account', passwordField: 'password', passReqToCallback: true }, async (req, account, password, cb) => { try { - const errorMessage = '帳號或密碼輸入錯誤!' + const accountError = '帳號不存在!' + const passwordError = '帳號或密碼輸入錯誤!' const user = await User.findOne({ where: { account } }) // 帳號或密碼輸入錯誤 暫時的錯誤處理 status code 200 if (!user) { // return cb(null, false) - return cb(errorMessage, false) + return cb(accountError, false) } const isMatch = await bcrypt.compare(password, user.password) // 帳號或密碼輸入錯誤 暫時的錯誤處理 status code 200 if (!isMatch) { // return cb(null, false) - return cb(errorMessage, false) + return cb(passwordError, false) } return cb(null, user) } catch (error) { diff --git a/middleware/error-handler.js b/middleware/error-handler.js index b11488dc04..108ea30bc0 100644 --- a/middleware/error-handler.js +++ b/middleware/error-handler.js @@ -5,6 +5,11 @@ module.exports = { status: 'error', message: err }) + } else if (err === '帳號不存在!') { + res.status(200).json({ + status: 'error', + message: err + }) } else if (err instanceof Error) { res.status(err.status || 500).json({ status: 'error', From d2aa2707b6d60bb238f8113d9f36fb96b73f11ec Mon Sep 17 00:00:00 2001 From: s1030905 Date: Sat, 17 Jun 2023 14:25:28 +0800 Subject: [PATCH 104/131] rebuild routes --- controllers/pages/user-controller.js | 7 +++++++ routes/pages/index.js | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/controllers/pages/user-controller.js b/controllers/pages/user-controller.js index becee403fc..38eb5d7643 100644 --- a/controllers/pages/user-controller.js +++ b/controllers/pages/user-controller.js @@ -6,6 +6,13 @@ const userController = { } catch (error) { next(error) } + }, + enter: (req, res, next) => { + try { + res.status(200).json({ status: 'success' }) + } catch (error) { + next(error) + } } } module.exports = userController diff --git a/routes/pages/index.js b/routes/pages/index.js index 460792c568..8e823a8c6b 100644 --- a/routes/pages/index.js +++ b/routes/pages/index.js @@ -1,6 +1,14 @@ const router = require('express').Router() const userController = require('../../controllers/pages/user-controller') +const { apiErrorHandler } = require('../../middleware/error-handler') +const { authenticated, roleChecker, authenticatedAdmin } = require('../../middleware/api-auth') router.get('/logout', userController.logout) +router.get('/register') +router.get('/login') + +router.use('/admin', authenticatedAdmin, roleChecker, userController.enter) +router.use('/', authenticated, userController.enter) +router.use('/', apiErrorHandler) module.exports = router From 9135795d756c53b0fd23955ee9c3998dd771514d Mon Sep 17 00:00:00 2001 From: s1030905 Date: Sat, 17 Jun 2023 15:57:44 +0800 Subject: [PATCH 105/131] add required attributes on getReply --- controllers/apis/tweet-controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index 38e43f5a36..dea05706db 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -134,8 +134,8 @@ const tweetController = { { model: Reply, as: 'TweetReply', - attributes: ['id', 'UserId', 'comment'], - include: [{ model: User, as: 'RepliedUser', attributes: ['id', 'name', 'account', 'avatar'] }] + include: [{ model: User, as: 'RepliedUser', attributes: ['id', 'name', 'account', 'avatar'] }], + order: [['createdAt', 'DESC']] } ] }) From de50b58d948a875faaa58e1aea3ae3d7f41f753f Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Sat, 17 Jun 2023 20:36:07 +0800 Subject: [PATCH 106/131] fix: add createdAt for replied and add password or account error message and user tweet isLiked --- config/passport.js | 7 ++++--- controllers/apis/tweet-controller.js | 8 +++----- middleware/api-auth.js | 1 + 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/config/passport.js b/config/passport.js index cc41f9a677..762bd1e09a 100644 --- a/config/passport.js +++ b/config/passport.js @@ -11,20 +11,21 @@ passport.use(new LocalStrategy( { usernameField: 'account', passwordField: 'password', passReqToCallback: true }, async (req, account, password, cb) => { try { - const errorMessage = '帳號或密碼輸入錯誤!' + const passwordError = '密碼輸入錯誤!' + const accountError = '帳戶不存在!' const user = await User.findOne({ where: { account } }) // 帳號或密碼輸入錯誤 暫時的錯誤處理 status code 200 if (!user) { // return cb(null, false) - return cb(errorMessage, false) + return cb(accountError, false) } const isMatch = await bcrypt.compare(password, user.password) // 帳號或密碼輸入錯誤 暫時的錯誤處理 status code 200 if (!isMatch) { // return cb(null, false) - return cb(errorMessage, false) + return cb(passwordError, false) } return cb(null, user) } catch (error) { diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index 38e43f5a36..229a5c297e 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -84,12 +84,10 @@ const tweetController = { error.status = 404 throw error } - - let isLiked = false + data.isLiked = false for (const i of data.TweetLike) { if (i.UserId === getUser(req).id) { - isLiked = true - data.isLiked = isLiked + data.isLiked = true break } } @@ -134,7 +132,7 @@ const tweetController = { { model: Reply, as: 'TweetReply', - attributes: ['id', 'UserId', 'comment'], + attributes: ['id', 'UserId', 'comment', 'createdAt'], include: [{ model: User, as: 'RepliedUser', attributes: ['id', 'name', 'account', 'avatar'] }] } ] diff --git a/middleware/api-auth.js b/middleware/api-auth.js index cddca7cc1b..85c89ad6b6 100644 --- a/middleware/api-auth.js +++ b/middleware/api-auth.js @@ -3,6 +3,7 @@ const { getUser } = require('../_helpers') const authenticated = (req, res, next) => { passport.authenticate('jwt', { session: false }, (err, user) => { + console.log('req.body', req.body) if (err || !user) return res.status(401).json({ status: 'error', message: 'unauthorized!' }) if (user.role === 'admin') { return res.status(403).json({ status: 'error', message: 'Admin cannot use these function' }) From da178f68b28371d19fca266478177ac83912c529 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Sat, 17 Jun 2023 22:34:40 +0800 Subject: [PATCH 107/131] no auth in admin router --- routes/pages/index.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/routes/pages/index.js b/routes/pages/index.js index 8e823a8c6b..c09176ce34 100644 --- a/routes/pages/index.js +++ b/routes/pages/index.js @@ -4,10 +4,11 @@ const { apiErrorHandler } = require('../../middleware/error-handler') const { authenticated, roleChecker, authenticatedAdmin } = require('../../middleware/api-auth') router.get('/logout', userController.logout) -router.get('/register') -router.get('/login') +router.get('/register', userController.enter) +router.get('/login', userController.enter) +router.use('/admin', userController.enter) -router.use('/admin', authenticatedAdmin, roleChecker, userController.enter) +// router.use('/admin', authenticatedAdmin, roleChecker, userController.enter) router.use('/', authenticated, userController.enter) router.use('/', apiErrorHandler) From 6d02f43ab843f87dde68eb7b49afeb8015f47016 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Sun, 18 Jun 2023 14:28:47 +0800 Subject: [PATCH 108/131] revise getUserRepliedTweet --- controllers/apis/user-controller.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 8d34359505..ec38b23e01 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -164,10 +164,6 @@ const userController = { }) ]) - // 資料格式處理 - user = user.toJSON() - repliedTweets = repliedTweets.map(e => e.toJSON()) - // 錯誤處理 if (!user || user.role === 'admin') { const error = new Error('The user does not exist') @@ -180,6 +176,10 @@ const userController = { throw error } + // 資料格式處理 + user = user.toJSON() + repliedTweets = repliedTweets.map(e => e.toJSON()) + repliedTweets.forEach(e => { e.isLiked = false for (const i of e.Tweet.TweetLike) { From 0053c646e40f6506c7d7fbe1fa230a0598f02845 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Sun, 18 Jun 2023 14:31:56 +0800 Subject: [PATCH 109/131] delete console --- controllers/apis/user-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index ec38b23e01..6e4b7f17cc 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -126,7 +126,7 @@ const userController = { followers.forEach(e => { dic[e.followingId] = e.followingId }) - console.log(dic) + // 檢查該用戶是否在追蹤清單上 dic[data.id] >= 0 ? data.isFollowed = true : data.isFollowed = false From 46c2e2ddbc5efc9f060f1dae7e36e0e521f13cc3 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Sun, 18 Jun 2023 14:50:11 +0800 Subject: [PATCH 110/131] create dic on getUserRepliedTweet to fix null of length --- controllers/apis/user-controller.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 6e4b7f17cc..93c8e4bc48 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -146,7 +146,7 @@ const userController = { id = Number(id) // 確認使用者是否存在 與 回過文 - let [user, repliedTweets] = await Promise.all([ + let [user, repliedTweets, likes] = await Promise.all([ User.findByPk(id), Reply.findAll({ where: { UserId: id }, @@ -161,7 +161,8 @@ const userController = { }, { model: User, as: 'RepliedUser', attributes: ['id', 'name', 'account', 'avatar'] }], order: [['updatedAt', 'DESC']] - }) + }), + Like.findAll({ where: { UserId: getUser(req).id } }) ]) // 錯誤處理 @@ -180,12 +181,16 @@ const userController = { user = user.toJSON() repliedTweets = repliedTweets.map(e => e.toJSON()) + // 建立like資料表 + const dic = {} + for (let i = 0; i < likes.length; i++) { + dic[likes[i].TweetId] = i + } + repliedTweets.forEach(e => { e.isLiked = false - for (const i of e.Tweet.TweetLike) { - if (i.UserId === getUser(req).id) { - e.isLiked = true - } + if (dic[e.TweetId] >= 0) { + e.isLiked = true } }) From 9e4f8f2e5c87a96699923257fb4abc2bbd5681a6 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Sun, 18 Jun 2023 22:03:23 +0800 Subject: [PATCH 111/131] deny root login and revise userSeeders --- config/passport.js | 1 - controllers/apis/user-controller.js | 2 +- middleware/api-auth.js | 10 ++++++++-- routes/apis/index.js | 4 ++-- seeders/20230605074630-users-seed-file.js | 12 ++++++------ 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/config/passport.js b/config/passport.js index 58c7c9eb6b..8cce5a90a5 100644 --- a/config/passport.js +++ b/config/passport.js @@ -11,7 +11,6 @@ passport.use(new LocalStrategy( { usernameField: 'account', passwordField: 'password', passReqToCallback: true }, async (req, account, password, cb) => { try { - const accountError = '帳號不存在!' const passwordError = '帳號或密碼輸入錯誤!' diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 93c8e4bc48..c84ba36286 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -383,7 +383,7 @@ const userController = { // 檔案承接 const { files } = req - + console.log(files) if (files) { const [avatar, background] = await Promise.all([ files.avatar ? await imgurFileHandler(files.avatar[0]) : null, diff --git a/middleware/api-auth.js b/middleware/api-auth.js index 85c89ad6b6..e838eec5a9 100644 --- a/middleware/api-auth.js +++ b/middleware/api-auth.js @@ -3,7 +3,6 @@ const { getUser } = require('../_helpers') const authenticated = (req, res, next) => { passport.authenticate('jwt', { session: false }, (err, user) => { - console.log('req.body', req.body) if (err || !user) return res.status(401).json({ status: 'error', message: 'unauthorized!' }) if (user.role === 'admin') { return res.status(403).json({ status: 'error', message: 'Admin cannot use these function' }) @@ -30,6 +29,13 @@ const roleChecker = (req, res, next) => { return res.status(403).json({ status: 'error', message: 'permission denied' }) } +const adminChecker = (req, res, next) => { + console.log(req.body) + const user = getUser(req) + if (user.role !== 'admin') return next() + return res.status(404).json({ status: 'error', message: "The user doesn't exist." }) +} + module.exports = { - authenticated, roleChecker, authenticatedAdmin + authenticated, roleChecker, authenticatedAdmin, adminChecker } diff --git a/routes/apis/index.js b/routes/apis/index.js index 7e6e039d41..8cd83dd84b 100644 --- a/routes/apis/index.js +++ b/routes/apis/index.js @@ -7,7 +7,7 @@ const tweetController = require('../../controllers/apis/tweet-controller') const followshipController = require('../../controllers/apis/followship-controller') const { uploadFiles } = require('../../middleware/multer') const { apiErrorHandler } = require('../../middleware/error-handler') -const { authenticated, roleChecker, authenticatedAdmin } = require('../../middleware/api-auth') +const { authenticated, roleChecker, authenticatedAdmin, adminChecker } = require('../../middleware/api-auth') // 有關admin的routes // admin登入 @@ -39,7 +39,7 @@ router.put('/users/:id/setting', authenticated, userController.putUserSetting) / router.put('/users/:id', authenticated, uploadFiles, userController.editUserProfile) // 編輯個人資料 // 使用者登入 -router.post('/users/login', passport.authenticate('local', { session: false }), userController.login) +router.post('/users/login', passport.authenticate('local', { session: false }), adminChecker, userController.login) // 使用者註冊 router.post('/users', userController.signUp) // 錯誤處理 diff --git a/seeders/20230605074630-users-seed-file.js b/seeders/20230605074630-users-seed-file.js index 93ae4c6a3f..e33d205437 100644 --- a/seeders/20230605074630-users-seed-file.js +++ b/seeders/20230605074630-users-seed-file.js @@ -10,7 +10,7 @@ module.exports = { email: 'root@example.com', password: await bcrypt.hash('12345678', 10), name: 'root', - avatar: `https://loremflickr.com/320/240/person/?random=${Math.random() * 100}`, + avatar: 'https://loremflickr.com/320/240/person/?random=100}', introduction: faker.lorem.text(), role: 'admin', account: 'root', @@ -22,7 +22,7 @@ module.exports = { email: 'user1@example.com', password: await bcrypt.hash('12345678', 10), name: 'user1', - avatar: `https://loremflickr.com/320/240/person/?random=${Math.random() * 100}`, + avatar: 'https://loremflickr.com/320/240/person/?random=101}', introduction: faker.lorem.text(), role: 'user', account: 'user1', @@ -33,7 +33,7 @@ module.exports = { email: 'user2@example.com', password: await bcrypt.hash('12345678', 10), name: 'user2', - avatar: `https://loremflickr.com/320/240/person/?random=${Math.random() * 100}`, + avatar: 'https://loremflickr.com/320/240/person/?random=102}', introduction: faker.lorem.text(), role: 'user', account: 'user2', @@ -44,7 +44,7 @@ module.exports = { email: 'user3@example.com', password: await bcrypt.hash('12345678', 10), name: 'user3', - avatar: `https://loremflickr.com/320/240/person/?random=${Math.random() * 100}`, + avatar: 'https://loremflickr.com/320/240/person/?random=103}', introduction: faker.lorem.text(), role: 'user', account: 'user3', @@ -55,7 +55,7 @@ module.exports = { email: 'user4@example.com', password: await bcrypt.hash('12345678', 10), name: 'user4', - avatar: `https://loremflickr.com/320/240/person/?random=${Math.random() * 100}`, + avatar: 'https://loremflickr.com/320/240/person/?random=104}', introduction: faker.lorem.text(), role: 'user', account: 'user4', @@ -66,7 +66,7 @@ module.exports = { email: 'user5@example.com', password: await bcrypt.hash('12345678', 10), name: 'user5', - avatar: `https://loremflickr.com/320/240/person/?random=${Math.random() * 100}`, + avatar: 'https://loremflickr.com/320/240/person/?random=105}', introduction: faker.lorem.text(), role: 'user', account: 'user5', From 97212a0e50426cfb4eadb6b5d9020e56128db44a Mon Sep 17 00:00:00 2001 From: s1030905 Date: Sun, 18 Jun 2023 23:22:55 +0800 Subject: [PATCH 112/131] revise getUserLiked nest problem --- controllers/apis/user-controller.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index c84ba36286..0e9f06c711 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -223,9 +223,7 @@ const userController = { ] } ], - order: [['updatedAt', 'DESC']], - raw: true, - nest: true + order: [['updatedAt', 'DESC']] }), Like.findAll({ where: { UserId: getUser(req).id } }) ]) @@ -241,17 +239,22 @@ const userController = { throw error } + // 資料格式處理 + const data = userLiked.map(e => e.toJSON()) + console.log(data) + const dic = {} for (let i = 0; i < likes.length; i++) { dic[likes[i].TweetId] = i } - userLiked.forEach(e => { + + data.forEach(e => { e.isLiked = false if (dic[e.TweetId] >= 0) { e.isLiked = true } }) - return res.status(200).json(userLiked) + return res.status(200).json(data) } catch (error) { next(error) } From 9367c79e5a37fd5dc99dfe1985be5ec2ec592e00 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Mon, 19 Jun 2023 00:11:45 +0800 Subject: [PATCH 113/131] rebuild getReply --- controllers/apis/tweet-controller.js | 33 ++++++++++------------------ 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index 66288ebd91..714ac8b8cb 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -125,18 +125,14 @@ const tweetController = { getReply: async (req, res, next) => { try { const { tweet_id } = req.params - const tweet = await Tweet.findByPk(tweet_id, { - include: [ - { model: User, as: 'TweetUser', attributes: ['id', 'name', 'account', 'avatar'] }, - { model: Like, as: 'TweetLike', attributes: ['id', 'UserId'] }, - { - model: Reply, - as: 'TweetReply', - include: [{ model: User, as: 'RepliedUser', attributes: ['id', 'name', 'account', 'avatar'] }], - order: [['createdAt', 'DESC']] - } - ] - }) + const [replies, tweet] = await Promise.all([ + Reply.findAll({ + where: { TweetId: tweet_id }, + include: [{ model: User, as: 'RepliedUser', attributes: ['id', 'name', 'account', 'avatar'] }], + order: [['createdAt', 'DESC']] + }), + Tweet.findByPk(tweet_id) + ]) // 錯誤處理 if (!tweet) { @@ -145,17 +141,10 @@ const tweetController = { throw error } - const data = tweet.toJSON() + // 資料格式處理 + const data = replies.map(e => e.toJSON()) - // 先留著避免重寫 - - // data.isLiked = false - // for (const i of data.TweetLike) { - // if (i.UserId === getUser(req).id) { - // data.TweetReply.isLiked = true - // } - // } - return res.status(200).json(data.TweetReply) + return res.status(200).json(data) } catch (error) { next(error) } From 321b5b867acf81bd2c2582f9d8efe0e0be5fe056 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Mon, 19 Jun 2023 01:04:59 +0800 Subject: [PATCH 114/131] revise adminChecker error msg --- middleware/api-auth.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/middleware/api-auth.js b/middleware/api-auth.js index e838eec5a9..f68ace7f8b 100644 --- a/middleware/api-auth.js +++ b/middleware/api-auth.js @@ -30,10 +30,9 @@ const roleChecker = (req, res, next) => { } const adminChecker = (req, res, next) => { - console.log(req.body) const user = getUser(req) if (user.role !== 'admin') return next() - return res.status(404).json({ status: 'error', message: "The user doesn't exist." }) + return res.status(200).json({ status: 'error', message: '帳號不存在!' }) } module.exports = { From ebf863bec373c930af550e3ca50e631445b67a2d Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Mon, 19 Jun 2023 16:14:04 +0800 Subject: [PATCH 115/131] fix: revise information and add cors setting --- app.js | 11 ++++++++++- controllers/apis/tweet-controller.js | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app.js b/app.js index 6965e83644..eece9adce5 100644 --- a/app.js +++ b/app.js @@ -35,7 +35,16 @@ app.use(passport.initialize()) app.use(flash()) // cors -app.use(cors()) +const corsOptions = { + origin: [ + 'http://localhost', + 'https://bluelsa.github.io/twitter-frontend-2023/' + ], + methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS', + allowedHeaders: ['Content-Type', 'Authorization'] + +} +app.use(cors(corsOptions)) // locals app.use((req, res, next) => { diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index 714ac8b8cb..0092e12dbf 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -170,7 +170,7 @@ const tweetController = { throw error } if (PromiseArr[1]) { - const error = new Error('The tweet does not exist') + const error = new Error('You already like the tweet') error.status = 409 throw error } From 08335663e0bcdaa3e76e1ff3d6c3f0532cdc91c9 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Mon, 19 Jun 2023 20:23:45 +0800 Subject: [PATCH 116/131] dix --- app.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app.js b/app.js index eece9adce5..d288e420bc 100644 --- a/app.js +++ b/app.js @@ -35,16 +35,16 @@ app.use(passport.initialize()) app.use(flash()) // cors -const corsOptions = { - origin: [ - 'http://localhost', - 'https://bluelsa.github.io/twitter-frontend-2023/' - ], - methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS', - allowedHeaders: ['Content-Type', 'Authorization'] - -} -app.use(cors(corsOptions)) +// const corsOptions = { +// origin: [ +// 'http://localhost', +// 'https://bluelsa.github.io/twitter-frontend-2023/' +// ], +// methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS', +// allowedHeaders: ['Content-Type', 'Authorization'] + +// } +app.use(cors()) // locals app.use((req, res, next) => { From f9019a40dce06ba542eb840400c90aa4991557d9 Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Mon, 19 Jun 2023 21:16:04 +0800 Subject: [PATCH 117/131] fix: add cors setting --- app.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app.js b/app.js index d288e420bc..456fc58298 100644 --- a/app.js +++ b/app.js @@ -35,16 +35,16 @@ app.use(passport.initialize()) app.use(flash()) // cors -// const corsOptions = { -// origin: [ -// 'http://localhost', -// 'https://bluelsa.github.io/twitter-frontend-2023/' -// ], -// methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS', -// allowedHeaders: ['Content-Type', 'Authorization'] - -// } -app.use(cors()) +const corsOptions = { + origin: [ + 'http://localhost:3000', + 'https://bluelsa.github.io/twitter-frontend-2023/' + ], + methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS', + allowedHeaders: ['Content-Type', 'Authorization'] + +} +app.use(cors(corsOptions)) // locals app.use((req, res, next) => { From f3e0e4da4b705754cea18050fae59c57f65c7cf0 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Tue, 20 Jun 2023 11:48:07 +0800 Subject: [PATCH 118/131] delete relationship of tweet --- controllers/apis/admin-controller.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/controllers/apis/admin-controller.js b/controllers/apis/admin-controller.js index 5dbfcdc4a1..b5f43cf08a 100644 --- a/controllers/apis/admin-controller.js +++ b/controllers/apis/admin-controller.js @@ -1,5 +1,5 @@ const jwt = require('jsonwebtoken') -const { User, Tweet } = require('../../models') +const { User, Tweet, Like, Reply } = require('../../models') const { getUser } = require('../../_helpers') const adminController = { @@ -81,8 +81,13 @@ const adminController = { try { const { id } = req.params const tweet = await Tweet.findByPk(id) + if (!tweet) throw new Error('The tweet does not exist') - await tweet.destroy() + await Promise.all([ + tweet.destroy(), + Like.destroy({ where: { TweetId: id } }), + Reply.destroy({ where: { TweetId: id } }) + ]) return res.status(200).json({ status: 'success', message: 'The tweet was successfully deleted' }) } catch (error) { next(error) From 9a40d75f0761ef149c80e5583f424503de6ef767 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Tue, 20 Jun 2023 12:20:42 +0800 Subject: [PATCH 119/131] revise null of toJSON() --- controllers/apis/tweet-controller.js | 4 ++-- controllers/apis/user-controller.js | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/controllers/apis/tweet-controller.js b/controllers/apis/tweet-controller.js index 0092e12dbf..85a35720d9 100644 --- a/controllers/apis/tweet-controller.js +++ b/controllers/apis/tweet-controller.js @@ -76,14 +76,14 @@ const tweetController = { ] } // 這裡如果使用nest會有bug ) - const data = tweet.toJSON() // 錯誤處理 - if (!data) { + if (!tweet) { const error = new Error('The tweet does not exist') error.status = 404 throw error } + const data = tweet.toJSON() data.isLiked = false for (const i of data.TweetLike) { if (i.UserId === getUser(req).id) { diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 0e9f06c711..3ff462e4d9 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -241,7 +241,6 @@ const userController = { // 資料格式處理 const data = userLiked.map(e => e.toJSON()) - console.log(data) const dic = {} for (let i = 0; i < likes.length; i++) { @@ -317,10 +316,6 @@ const userController = { }) ]) - // 資料格式處理 - user = user.toJSON() - userFollowers = userFollowers.map(e => e.toJSON()) - // 錯誤處理 if (!user || user.role === 'admin') { const error = new Error('The user does not exist') @@ -333,6 +328,10 @@ const userController = { throw error } + // 資料格式處理 + user = user.toJSON() + userFollowers = userFollowers.map(e => e.toJSON()) + const userFollowings = await Followship.findAll({ where: { followerId: getUser(req).id }, raw: true }) const dic = {} From 13d2555d2e439fd9a86012d48a32f1fa010f384a Mon Sep 17 00:00:00 2001 From: s1030905 Date: Tue, 20 Jun 2023 20:33:00 +0800 Subject: [PATCH 120/131] revise corsOptions. --- app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.js b/app.js index 456fc58298..cbb208e213 100644 --- a/app.js +++ b/app.js @@ -38,7 +38,7 @@ app.use(flash()) const corsOptions = { origin: [ 'http://localhost:3000', - 'https://bluelsa.github.io/twitter-frontend-2023/' + 'https://bluelsa.github.io/' ], methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS', allowedHeaders: ['Content-Type', 'Authorization'] From ec85f61cc3950ccb0a95cd6582ca2a6ce6d64969 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Tue, 20 Jun 2023 20:35:24 +0800 Subject: [PATCH 121/131] revise 2 --- app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.js b/app.js index cbb208e213..f7bb9b5c24 100644 --- a/app.js +++ b/app.js @@ -38,7 +38,7 @@ app.use(flash()) const corsOptions = { origin: [ 'http://localhost:3000', - 'https://bluelsa.github.io/' + 'https://bluelsa.github.io' ], methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS', allowedHeaders: ['Content-Type', 'Authorization'] From e6fc1bc1b07bde50e2e1bcb589b8cf6fab8442ad Mon Sep 17 00:00:00 2001 From: s1030905 Date: Tue, 20 Jun 2023 21:26:35 +0800 Subject: [PATCH 122/131] revise userSignUp --- controllers/apis/user-controller.js | 2 ++ seeders/20230605074630-users-seed-file.js | 12 ++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index 3ff462e4d9..a421a223d8 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -43,6 +43,8 @@ const userController = { const user = await User.create({ name, account, + avatar: 'https://loremflickr.com/320/240/person/?random=101', + background: 'https://images.unsplash.com/photo-1580436541340-36b8d0c60bae?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=688&q=80', email, password: bcrypt.hashSync(password, bcrypt.genSaltSync(10), null) }) diff --git a/seeders/20230605074630-users-seed-file.js b/seeders/20230605074630-users-seed-file.js index e33d205437..c458a6e161 100644 --- a/seeders/20230605074630-users-seed-file.js +++ b/seeders/20230605074630-users-seed-file.js @@ -10,7 +10,7 @@ module.exports = { email: 'root@example.com', password: await bcrypt.hash('12345678', 10), name: 'root', - avatar: 'https://loremflickr.com/320/240/person/?random=100}', + avatar: 'https://loremflickr.com/320/240/person/?random=100', introduction: faker.lorem.text(), role: 'admin', account: 'root', @@ -22,7 +22,7 @@ module.exports = { email: 'user1@example.com', password: await bcrypt.hash('12345678', 10), name: 'user1', - avatar: 'https://loremflickr.com/320/240/person/?random=101}', + avatar: 'https://loremflickr.com/320/240/person/?random=101', introduction: faker.lorem.text(), role: 'user', account: 'user1', @@ -33,7 +33,7 @@ module.exports = { email: 'user2@example.com', password: await bcrypt.hash('12345678', 10), name: 'user2', - avatar: 'https://loremflickr.com/320/240/person/?random=102}', + avatar: 'https://loremflickr.com/320/240/person/?random=102', introduction: faker.lorem.text(), role: 'user', account: 'user2', @@ -44,7 +44,7 @@ module.exports = { email: 'user3@example.com', password: await bcrypt.hash('12345678', 10), name: 'user3', - avatar: 'https://loremflickr.com/320/240/person/?random=103}', + avatar: 'https://loremflickr.com/320/240/person/?random=103', introduction: faker.lorem.text(), role: 'user', account: 'user3', @@ -55,7 +55,7 @@ module.exports = { email: 'user4@example.com', password: await bcrypt.hash('12345678', 10), name: 'user4', - avatar: 'https://loremflickr.com/320/240/person/?random=104}', + avatar: 'https://loremflickr.com/320/240/person/?random=104', introduction: faker.lorem.text(), role: 'user', account: 'user4', @@ -66,7 +66,7 @@ module.exports = { email: 'user5@example.com', password: await bcrypt.hash('12345678', 10), name: 'user5', - avatar: 'https://loremflickr.com/320/240/person/?random=105}', + avatar: 'https://loremflickr.com/320/240/person/?random=105', introduction: faker.lorem.text(), role: 'user', account: 'user5', From e76a42592d0b943520a0fbc0804f792e060b5d72 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Tue, 20 Jun 2023 21:46:01 +0800 Subject: [PATCH 123/131] revise introduction 160 --- seeders/20230605074630-users-seed-file.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/seeders/20230605074630-users-seed-file.js b/seeders/20230605074630-users-seed-file.js index c458a6e161..e7888d25cd 100644 --- a/seeders/20230605074630-users-seed-file.js +++ b/seeders/20230605074630-users-seed-file.js @@ -11,7 +11,7 @@ module.exports = { password: await bcrypt.hash('12345678', 10), name: 'root', avatar: 'https://loremflickr.com/320/240/person/?random=100', - introduction: faker.lorem.text(), + introduction: faker.lorem.text().slice(0, 160), role: 'admin', account: 'root', background, @@ -23,7 +23,7 @@ module.exports = { password: await bcrypt.hash('12345678', 10), name: 'user1', avatar: 'https://loremflickr.com/320/240/person/?random=101', - introduction: faker.lorem.text(), + introduction: faker.lorem.text().slice(0, 160), role: 'user', account: 'user1', background, @@ -34,7 +34,7 @@ module.exports = { password: await bcrypt.hash('12345678', 10), name: 'user2', avatar: 'https://loremflickr.com/320/240/person/?random=102', - introduction: faker.lorem.text(), + introduction: faker.lorem.text().slice(0, 160), role: 'user', account: 'user2', background, @@ -45,7 +45,7 @@ module.exports = { password: await bcrypt.hash('12345678', 10), name: 'user3', avatar: 'https://loremflickr.com/320/240/person/?random=103', - introduction: faker.lorem.text(), + introduction: faker.lorem.text().slice(0, 160), role: 'user', account: 'user3', background, @@ -56,7 +56,7 @@ module.exports = { password: await bcrypt.hash('12345678', 10), name: 'user4', avatar: 'https://loremflickr.com/320/240/person/?random=104', - introduction: faker.lorem.text(), + introduction: faker.lorem.text().slice(0, 160), role: 'user', account: 'user4', background, @@ -67,7 +67,7 @@ module.exports = { password: await bcrypt.hash('12345678', 10), name: 'user5', avatar: 'https://loremflickr.com/320/240/person/?random=105', - introduction: faker.lorem.text(), + introduction: faker.lorem.text().slice(0, 160), role: 'user', account: 'user5', background, @@ -80,4 +80,4 @@ module.exports = { down: async (queryInterface, Sequelize) => { await queryInterface.bulkDelete('Users', {}) } -} +} \ No newline at end of file From aa69f2ac5c8fefc336d4579267f6f50181ccd809 Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Wed, 21 Jun 2023 21:04:34 +0800 Subject: [PATCH 124/131] fix: add user totoal number --- seeders/20230605074630-users-seed-file.js | 110 ++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/seeders/20230605074630-users-seed-file.js b/seeders/20230605074630-users-seed-file.js index c458a6e161..94fa30125a 100644 --- a/seeders/20230605074630-users-seed-file.js +++ b/seeders/20230605074630-users-seed-file.js @@ -73,6 +73,116 @@ module.exports = { background, createdAt: new Date(), updatedAt: new Date() + }, { + email: 'user6@example.com', + password: await bcrypt.hash('12345678', 10), + name: 'user6', + avatar: 'https://loremflickr.com/320/240/person/?random=105', + introduction: faker.lorem.text(), + role: 'user', + account: 'user6', + background, + createdAt: new Date(), + updatedAt: new Date() + }, { + email: 'user7@example.com', + password: await bcrypt.hash('12345678', 10), + name: 'user7', + avatar: 'https://loremflickr.com/320/240/person/?random=105', + introduction: faker.lorem.text(), + role: 'user', + account: 'user7', + background, + createdAt: new Date(), + updatedAt: new Date() + }, { + email: 'user8@example.com', + password: await bcrypt.hash('12345678', 10), + name: 'user8', + avatar: 'https://loremflickr.com/320/240/person/?random=105', + introduction: faker.lorem.text(), + role: 'user', + account: 'user8', + background, + createdAt: new Date(), + updatedAt: new Date() + }, { + email: 'user9@example.com', + password: await bcrypt.hash('12345678', 10), + name: 'user9', + avatar: 'https://loremflickr.com/320/240/person/?random=105', + introduction: faker.lorem.text(), + role: 'user', + account: 'user9', + background, + createdAt: new Date(), + updatedAt: new Date() + }, { + email: 'user10@example.com', + password: await bcrypt.hash('12345678', 10), + name: 'user10', + avatar: 'https://loremflickr.com/320/240/person/?random=105', + introduction: faker.lorem.text(), + role: 'user', + account: 'user10', + background, + createdAt: new Date(), + updatedAt: new Date() + }, { + email: 'user11@example.com', + password: await bcrypt.hash('12345678', 10), + name: 'user11', + avatar: 'https://loremflickr.com/320/240/person/?random=105', + introduction: faker.lorem.text(), + role: 'user', + account: 'user11', + background, + createdAt: new Date(), + updatedAt: new Date() + }, { + email: 'user12@example.com', + password: await bcrypt.hash('12345678', 10), + name: 'user12', + avatar: 'https://loremflickr.com/320/240/person/?random=105', + introduction: faker.lorem.text(), + role: 'user', + account: 'user12', + background, + createdAt: new Date(), + updatedAt: new Date() + }, { + email: 'user13@example.com', + password: await bcrypt.hash('12345678', 10), + name: 'user13', + avatar: 'https://loremflickr.com/320/240/person/?random=105', + introduction: faker.lorem.text(), + role: 'user', + account: 'user13', + background, + createdAt: new Date(), + updatedAt: new Date() + }, { + email: 'user14@example.com', + password: await bcrypt.hash('12345678', 10), + name: 'user14', + avatar: 'https://loremflickr.com/320/240/person/?random=105', + introduction: faker.lorem.text(), + role: 'user', + account: 'user14', + background, + createdAt: new Date(), + updatedAt: new Date() + }, { + email: 'user15@example.com', + password: await bcrypt.hash('12345678', 10), + name: 'user15', + avatar: 'https://loremflickr.com/320/240/person/?random=105', + introduction: faker.lorem.text(), + role: 'user', + account: 'user15', + background, + createdAt: new Date(), + updatedAt: new Date() } ]) }, From 1a44e6f69cf39572b0115c4faf7cd877b07058b3 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Wed, 21 Jun 2023 21:24:03 +0800 Subject: [PATCH 125/131] revise userFollower & userFollowing order --- controllers/apis/user-controller.js | 8 +++++--- seeders/20230605161408-tweets-seed-file.js | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/controllers/apis/user-controller.js b/controllers/apis/user-controller.js index a421a223d8..263a6906ba 100644 --- a/controllers/apis/user-controller.js +++ b/controllers/apis/user-controller.js @@ -270,9 +270,10 @@ const userController = { User.findByPk(id), Followship.findAll({ where: { followerId: Number(id) }, - include: [{ model: User, as: 'Followings', attributes: ['id', 'name', 'account', 'avatar', 'introduction'] }], + include: [{ model: User, as: 'Followings', attributes: ['id', 'name', 'account', 'avatar', 'introduction', 'createdAt'] }], raw: true, - nest: true + nest: true, + order: [['createdAt', 'DESC']] }), Followship.findAll({ where: { followerId: getUser(req).id } }) ]) @@ -314,7 +315,8 @@ const userController = { User.findByPk(id), Followship.findAll({ where: { followingId: Number(id) }, - include: [{ model: User, as: 'Followers', attributes: ['id', 'name', 'account', 'avatar', 'introduction'] }] + include: [{ model: User, as: 'Followers', attributes: ['id', 'name', 'account', 'avatar', 'introduction', 'createdAt'] }], + order: [['createdAt', 'DESC']] }) ]) diff --git a/seeders/20230605161408-tweets-seed-file.js b/seeders/20230605161408-tweets-seed-file.js index 5a3c29b692..8d60b08415 100644 --- a/seeders/20230605161408-tweets-seed-file.js +++ b/seeders/20230605161408-tweets-seed-file.js @@ -8,7 +8,7 @@ module.exports = { { type: queryInterface.sequelize.QueryTypes.SELECT } ) const tweets = [] - for (let i = 0; i < 50; i++) { + for (let i = 0; i < 150; i++) { let description = faker.lorem.text() if (description.length > 140) { description = description.slice(0, 140) From 245f9cf591231b3e63c399d0711ceff06ae8b458 Mon Sep 17 00:00:00 2001 From: Ray7808 Date: Thu, 22 Jun 2023 13:58:04 +0800 Subject: [PATCH 126/131] fix: avatar content and introduction limitation --- seeders/20230605074630-users-seed-file.js | 42 +++++++++++------------ 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/seeders/20230605074630-users-seed-file.js b/seeders/20230605074630-users-seed-file.js index 2875bab64d..f94154eada 100644 --- a/seeders/20230605074630-users-seed-file.js +++ b/seeders/20230605074630-users-seed-file.js @@ -77,8 +77,8 @@ module.exports = { email: 'user6@example.com', password: await bcrypt.hash('12345678', 10), name: 'user6', - avatar: 'https://loremflickr.com/320/240/person/?random=105', - introduction: faker.lorem.text(), + avatar: 'https://loremflickr.com/320/240/person/?random=106', + introduction: faker.lorem.text().slice(0, 160), role: 'user', account: 'user6', background, @@ -88,8 +88,8 @@ module.exports = { email: 'user7@example.com', password: await bcrypt.hash('12345678', 10), name: 'user7', - avatar: 'https://loremflickr.com/320/240/person/?random=105', - introduction: faker.lorem.text(), + avatar: 'https://loremflickr.com/320/240/person/?random=107', + introduction: faker.lorem.text().slice(0, 160), role: 'user', account: 'user7', background, @@ -99,8 +99,8 @@ module.exports = { email: 'user8@example.com', password: await bcrypt.hash('12345678', 10), name: 'user8', - avatar: 'https://loremflickr.com/320/240/person/?random=105', - introduction: faker.lorem.text(), + avatar: 'https://loremflickr.com/320/240/person/?random=108', + introduction: faker.lorem.text().slice(0, 160), role: 'user', account: 'user8', background, @@ -110,8 +110,8 @@ module.exports = { email: 'user9@example.com', password: await bcrypt.hash('12345678', 10), name: 'user9', - avatar: 'https://loremflickr.com/320/240/person/?random=105', - introduction: faker.lorem.text(), + avatar: 'https://loremflickr.com/320/240/person/?random=109', + introduction: faker.lorem.text().slice(0, 160), role: 'user', account: 'user9', background, @@ -121,8 +121,8 @@ module.exports = { email: 'user10@example.com', password: await bcrypt.hash('12345678', 10), name: 'user10', - avatar: 'https://loremflickr.com/320/240/person/?random=105', - introduction: faker.lorem.text(), + avatar: 'https://loremflickr.com/320/240/person/?random=110', + introduction: faker.lorem.text().slice(0, 160), role: 'user', account: 'user10', background, @@ -132,8 +132,8 @@ module.exports = { email: 'user11@example.com', password: await bcrypt.hash('12345678', 10), name: 'user11', - avatar: 'https://loremflickr.com/320/240/person/?random=105', - introduction: faker.lorem.text(), + avatar: 'https://loremflickr.com/320/240/person/?random=111', + introduction: faker.lorem.text().slice(0, 160), role: 'user', account: 'user11', background, @@ -143,8 +143,8 @@ module.exports = { email: 'user12@example.com', password: await bcrypt.hash('12345678', 10), name: 'user12', - avatar: 'https://loremflickr.com/320/240/person/?random=105', - introduction: faker.lorem.text(), + avatar: 'https://loremflickr.com/320/240/person/?random=112', + introduction: faker.lorem.text().slice(0, 160), role: 'user', account: 'user12', background, @@ -154,8 +154,8 @@ module.exports = { email: 'user13@example.com', password: await bcrypt.hash('12345678', 10), name: 'user13', - avatar: 'https://loremflickr.com/320/240/person/?random=105', - introduction: faker.lorem.text(), + avatar: 'https://loremflickr.com/320/240/person/?random=113', + introduction: faker.lorem.text().slice(0, 160), role: 'user', account: 'user13', background, @@ -165,8 +165,8 @@ module.exports = { email: 'user14@example.com', password: await bcrypt.hash('12345678', 10), name: 'user14', - avatar: 'https://loremflickr.com/320/240/person/?random=105', - introduction: faker.lorem.text(), + avatar: 'https://loremflickr.com/320/240/person/?random=114', + introduction: faker.lorem.text().slice(0, 160), role: 'user', account: 'user14', background, @@ -176,8 +176,8 @@ module.exports = { email: 'user15@example.com', password: await bcrypt.hash('12345678', 10), name: 'user15', - avatar: 'https://loremflickr.com/320/240/person/?random=105', - introduction: faker.lorem.text(), + avatar: 'https://loremflickr.com/320/240/person/?random=115', + introduction: faker.lorem.text().slice(0, 160), role: 'user', account: 'user15', background, @@ -190,4 +190,4 @@ module.exports = { down: async (queryInterface, Sequelize) => { await queryInterface.bulkDelete('Users', {}) } -} \ No newline at end of file +} From d194954c8acd5834c46d7cd2f1ecd02552722cd7 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Thu, 22 Jun 2023 14:13:50 +0800 Subject: [PATCH 127/131] fix introduction problem --- seeders/20230605074630-users-seed-file.js | 34 +++++++++++------------ 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/seeders/20230605074630-users-seed-file.js b/seeders/20230605074630-users-seed-file.js index f94154eada..0eb346fb36 100644 --- a/seeders/20230605074630-users-seed-file.js +++ b/seeders/20230605074630-users-seed-file.js @@ -2,7 +2,7 @@ const faker = require('faker') const bcrypt = require('bcryptjs') const background = 'https://images.unsplash.com/photo-1580436541340-36b8d0c60bae?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=688&q=80' - +const introduction = faker.lorem.text().slice(0, 160) module.exports = { up: async (queryInterface, Sequelize) => { await queryInterface.bulkInsert('Users', [ @@ -11,7 +11,7 @@ module.exports = { password: await bcrypt.hash('12345678', 10), name: 'root', avatar: 'https://loremflickr.com/320/240/person/?random=100', - introduction: faker.lorem.text().slice(0, 160), + introduction, role: 'admin', account: 'root', background, @@ -23,7 +23,7 @@ module.exports = { password: await bcrypt.hash('12345678', 10), name: 'user1', avatar: 'https://loremflickr.com/320/240/person/?random=101', - introduction: faker.lorem.text().slice(0, 160), + introduction, role: 'user', account: 'user1', background, @@ -34,7 +34,7 @@ module.exports = { password: await bcrypt.hash('12345678', 10), name: 'user2', avatar: 'https://loremflickr.com/320/240/person/?random=102', - introduction: faker.lorem.text().slice(0, 160), + introduction, role: 'user', account: 'user2', background, @@ -45,7 +45,7 @@ module.exports = { password: await bcrypt.hash('12345678', 10), name: 'user3', avatar: 'https://loremflickr.com/320/240/person/?random=103', - introduction: faker.lorem.text().slice(0, 160), + introduction, role: 'user', account: 'user3', background, @@ -56,7 +56,7 @@ module.exports = { password: await bcrypt.hash('12345678', 10), name: 'user4', avatar: 'https://loremflickr.com/320/240/person/?random=104', - introduction: faker.lorem.text().slice(0, 160), + introduction, role: 'user', account: 'user4', background, @@ -67,7 +67,7 @@ module.exports = { password: await bcrypt.hash('12345678', 10), name: 'user5', avatar: 'https://loremflickr.com/320/240/person/?random=105', - introduction: faker.lorem.text().slice(0, 160), + introduction, role: 'user', account: 'user5', background, @@ -78,7 +78,7 @@ module.exports = { password: await bcrypt.hash('12345678', 10), name: 'user6', avatar: 'https://loremflickr.com/320/240/person/?random=106', - introduction: faker.lorem.text().slice(0, 160), + introduction, role: 'user', account: 'user6', background, @@ -89,7 +89,7 @@ module.exports = { password: await bcrypt.hash('12345678', 10), name: 'user7', avatar: 'https://loremflickr.com/320/240/person/?random=107', - introduction: faker.lorem.text().slice(0, 160), + introduction, role: 'user', account: 'user7', background, @@ -100,7 +100,7 @@ module.exports = { password: await bcrypt.hash('12345678', 10), name: 'user8', avatar: 'https://loremflickr.com/320/240/person/?random=108', - introduction: faker.lorem.text().slice(0, 160), + introduction, role: 'user', account: 'user8', background, @@ -111,7 +111,7 @@ module.exports = { password: await bcrypt.hash('12345678', 10), name: 'user9', avatar: 'https://loremflickr.com/320/240/person/?random=109', - introduction: faker.lorem.text().slice(0, 160), + introduction, role: 'user', account: 'user9', background, @@ -122,7 +122,7 @@ module.exports = { password: await bcrypt.hash('12345678', 10), name: 'user10', avatar: 'https://loremflickr.com/320/240/person/?random=110', - introduction: faker.lorem.text().slice(0, 160), + introduction, role: 'user', account: 'user10', background, @@ -133,7 +133,7 @@ module.exports = { password: await bcrypt.hash('12345678', 10), name: 'user11', avatar: 'https://loremflickr.com/320/240/person/?random=111', - introduction: faker.lorem.text().slice(0, 160), + introduction, role: 'user', account: 'user11', background, @@ -144,7 +144,7 @@ module.exports = { password: await bcrypt.hash('12345678', 10), name: 'user12', avatar: 'https://loremflickr.com/320/240/person/?random=112', - introduction: faker.lorem.text().slice(0, 160), + introduction, role: 'user', account: 'user12', background, @@ -155,7 +155,7 @@ module.exports = { password: await bcrypt.hash('12345678', 10), name: 'user13', avatar: 'https://loremflickr.com/320/240/person/?random=113', - introduction: faker.lorem.text().slice(0, 160), + introduction, role: 'user', account: 'user13', background, @@ -166,7 +166,7 @@ module.exports = { password: await bcrypt.hash('12345678', 10), name: 'user14', avatar: 'https://loremflickr.com/320/240/person/?random=114', - introduction: faker.lorem.text().slice(0, 160), + introduction, role: 'user', account: 'user14', background, @@ -177,7 +177,7 @@ module.exports = { password: await bcrypt.hash('12345678', 10), name: 'user15', avatar: 'https://loremflickr.com/320/240/person/?random=115', - introduction: faker.lorem.text().slice(0, 160), + introduction, role: 'user', account: 'user15', background, From 53f5d539733c6447d35381df973c69ced2dd7bb4 Mon Sep 17 00:00:00 2001 From: s1030905 Date: Mon, 17 Jul 2023 16:34:33 +0800 Subject: [PATCH 128/131] feat: cancel whitelist --- app.js | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/app.js b/app.js index f7bb9b5c24..d290056bcc 100644 --- a/app.js +++ b/app.js @@ -12,7 +12,7 @@ const session = require('express-session') const cors = require('cors') const app = express() -const port = process.env.PORT || 3000 +const port = process.env.PORT || 8080 // 解析request主體 app.use(express.urlencoded({ extended: true })) @@ -21,11 +21,13 @@ app.use(methodOverride('_method')) app.use('/upload', express.static(path.join(__dirname, 'upload'))) // session設定 -app.use(session({ - secret: process.env.SESSION_SECRET || 'NonSecret', - resave: false, - saveUninitialized: true -})) +app.use( + session({ + secret: process.env.SESSION_SECRET || 'NonSecret', + resave: false, + saveUninitialized: true + }) +) // passport初始化 app.use(passport.initialize()) @@ -36,13 +38,15 @@ app.use(flash()) // cors const corsOptions = { - origin: [ - 'http://localhost:3000', - 'https://bluelsa.github.io' - ], + // origin: [ + // 'http://localhost:3000', + // 'https://bluelsa.github.io', + // 'https://s1030905.github.io', + // 'https://4457-2001-b011-7003-76bd-f067-d1c2-c9bc-905.ngrok-free.app' + // ], + origin: '*', methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS', allowedHeaders: ['Content-Type', 'Authorization'] - } app.use(cors(corsOptions)) @@ -56,6 +60,8 @@ app.use((req, res, next) => { app.use('/api', apis) app.use('/', pages) -app.listen(port, () => console.log(`Example app listening on http://localhost:${port}`)) +app.listen(port, () => + console.log(`Example app listening on http://localhost:${port}`) +) module.exports = app From 59cb5f5195d7964efdaceb0a82f11d3826a5b75b Mon Sep 17 00:00:00 2001 From: s1030905 <127211914+s1030905@users.noreply.github.com> Date: Wed, 19 Jul 2023 16:13:57 +0800 Subject: [PATCH 129/131] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 08d465a4f0..31941c858c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # twitter-api-2023 ALPHA Camp | 學期 3 | Simple Twitter | 自動化測試檔 (前後分離組) +前端網站: https://s1030905.github.io/twitter-frontend-2023/ +由 blueelsa 開發(https://github.com/bluelsa) # Environment - 開發環境 node v14.16.0 From dfbf83cedaf5d658b053373971c4c48ecbb86fe9 Mon Sep 17 00:00:00 2001 From: s1030905 <127211914+s1030905@users.noreply.github.com> Date: Wed, 19 Jul 2023 16:14:56 +0800 Subject: [PATCH 130/131] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 31941c858c..c40c5a578d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # twitter-api-2023 ALPHA Camp | 學期 3 | Simple Twitter | 自動化測試檔 (前後分離組) -前端網站: https://s1030905.github.io/twitter-frontend-2023/ +# 前端網站: https://s1030905.github.io/twitter-frontend-2023/ 由 blueelsa 開發(https://github.com/bluelsa) # Environment - 開發環境 From a466384275e098f84c232af58f9b9213c5e375fd Mon Sep 17 00:00:00 2001 From: s1030905 <127211914+s1030905@users.noreply.github.com> Date: Thu, 20 Jul 2023 12:25:46 +0800 Subject: [PATCH 131/131] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c40c5a578d..260a1e887d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # twitter-api-2023 ALPHA Camp | 學期 3 | Simple Twitter | 自動化測試檔 (前後分離組) -# 前端網站: https://s1030905.github.io/twitter-frontend-2023/ +# 前端網站: +https://s1030905.github.io/twitter-frontend-2023/ 由 blueelsa 開發(https://github.com/bluelsa) # Environment - 開發環境