From 4111b3229d005c02d6cb562f4465b97b5b283134 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Tue, 12 Oct 2021 14:39:08 +0800 Subject: [PATCH 001/198] add proxyquire & sequelize-mock npm lib --- package-lock.json | 44 ++++++++++++++++++++++++++++++++++++++++++++ package.json | 4 ++++ 2 files changed, 48 insertions(+) diff --git a/package-lock.json b/package-lock.json index 6cdb461e..4d773f32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -581,6 +581,16 @@ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" }, + "fill-keys": { + "version": "1.0.2", + "resolved": "http://npm.ooapi.com:4873/fill-keys/-/fill-keys-1.0.2.tgz", + "integrity": "sha1-mo+jb06K1jTjv2tPPIiCVRRS6yA=", + "dev": true, + "requires": { + "is-object": "~1.0.1", + "merge-descriptors": "~1.0.0" + } + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -822,6 +832,12 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, + "is-object": { + "version": "1.0.2", + "resolved": "http://npm.ooapi.com:4873/is-object/-/is-object-1.0.2.tgz", + "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", + "dev": true + }, "is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -1075,6 +1091,12 @@ } } }, + "module-not-found-error": { + "version": "1.0.1", + "resolved": "http://npm.ooapi.com:4873/module-not-found-error/-/module-not-found-error-1.0.1.tgz", + "integrity": "sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=", + "dev": true + }, "moment": { "version": "2.29.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", @@ -1278,6 +1300,17 @@ "ipaddr.js": "1.9.1" } }, + "proxyquire": { + "version": "2.1.3", + "resolved": "http://npm.ooapi.com:4873/proxyquire/-/proxyquire-2.1.3.tgz", + "integrity": "sha512-BQWfCqYM+QINd+yawJz23tbBM40VIGXOdDw3X344KcclI/gtBbdWF6SlQ4nK/bYhF9d27KYug9WzljHC6B9Ysg==", + "dev": true, + "requires": { + "fill-keys": "^1.0.2", + "module-not-found-error": "^1.0.1", + "resolve": "^1.11.1" + } + }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -1453,6 +1486,17 @@ "yargs": "^13.1.0" } }, + "sequelize-mock": { + "version": "0.10.2", + "resolved": "http://npm.ooapi.com:4873/sequelize-mock/-/sequelize-mock-0.10.2.tgz", + "integrity": "sha1-GdOXHM2utbhkFwwkznkqinHxRL0=", + "dev": true, + "requires": { + "bluebird": "^3.4.6", + "inflection": "^1.10.0", + "lodash": "^4.16.4" + } + }, "sequelize-pool": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-6.1.0.tgz", diff --git a/package.json b/package.json index dd99a5f1..c25bd055 100644 --- a/package.json +++ b/package.json @@ -27,5 +27,9 @@ "sequelize-cli": "^6.2.0", "sinon": "^9.2.0", "supertest": "^5.0.0" + }, + "devDependencies": { + "proxyquire": "^2.1.3", + "sequelize-mock": "^0.10.2" } } From 161a8374c8828e52923a8077fdc9d04ff669b7a4 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Tue, 12 Oct 2021 14:39:53 +0800 Subject: [PATCH 002/198] add new A17 test --- tests/A17.test.js | 142 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 tests/A17.test.js diff --git a/tests/A17.test.js b/tests/A17.test.js new file mode 100644 index 00000000..e04cface --- /dev/null +++ b/tests/A17.test.js @@ -0,0 +1,142 @@ +const chai = require('chai') +const request = require('supertest') +const sinon = require('sinon') +const should = chai.should() +const SequelizeMock = require('sequelize-mock'); +const proxyquire = require('proxyquire'); + +const dbMock = new SequelizeMock(); + +const app = require('../app'); + +const mockRequest = (query) => { + return { + ...query, + flash: sinon.spy() + }; +}; +const mockResponse = () => { + return { + redirect: sinon.spy() + } +}; + +describe('# A17', () => { + describe('登入測試: POST /signin', function(){ + it('should fail without proper username', function(done){ + request(app) + .post('/signin') + .type('urlencoded') + .send('email=root@example.com&password=123') + .expect('Location', '/signin') + .expect(302, done) + }) + + it('should fail without proper password', function(done){ + request(app) + .post('/signin') + .type('urlencoded') + .send('email=tu&password=12345678') + .expect('Location', '/signin') + .expect(302, done) + }) + + it('should succeed with proper credentials', function(done){ + request(app) + .post('/signin') + .type('urlencoded') + .send('email=root@example.com&password=12345678') + .expect('Location', '/') + .expect(302, done) + }) + }); + + describe('# A17: 使用者權限管理', function () { + before(() => { + this.UserMock = dbMock.define('User', { + id: 1, + email: 'root@example.com', + name: 'admin', + isAdmin: false, + }); + + this.adminController = proxyquire('../controllers/adminController', { + '../models': { User: this.UserMock } + }); + }) + + context('# [顯示使用者清單]', () => { + it(" GET /admin/users ", (done) => { + const req = {} + const res = { + render: (_, results) => { + results.users[0].name.should.equal("admin"); + done() + } + }; + + this.adminController.getUsers(req, res); + }); + }) + + context('# [修改使用者權限] for admin', () => { + before(() => { + this.UserMock = dbMock.define('User', { + id: 1, + email: 'root@example.com', + name: 'admin', + isAdmin: false, + }); + + this.adminController = proxyquire('../controllers/adminController', { + '../models': { User: this.UserMock } + }); + }) + + it(" PUT /admin/users/:id/toggleAdmin ", async () => { + const req = mockRequest({params: {id: 1}}); + const res = mockResponse(); + + await this.adminController.toggleAdmin(req, res); + + req.flash.calledWith('error_messages', 'core manager can not be changed').should.be.true; + res.redirect.calledWith('back').should.be.true; + }); + }) + + context('# [修改使用者權限] for user', () => { + before(() => { + this.UserMock = dbMock.define('User', { + id: 1, + email: 'user@example.com', + name: 'user', + isAdmin: false, + }, { + instanceMethods: { + update: (changes) => { + this.UserMock._defaults = {...changes}; + return Promise.resolve(); + } + } + }); + + this.adminController = proxyquire('../controllers/adminController', { + '../models': { User: this.UserMock } + }); + }) + + it(" PUT /admin/users/:id/toggleAdmin ", async () => { + const req = mockRequest({params: {id: 1}}); + const res = mockResponse(); + + await this.adminController.toggleAdmin(req, res); + + req.flash.calledWith('success_messages', 'user was successfully to update').should.be.true; + res.redirect.calledWith('/admin/users').should.be.true; + + const user = await this.UserMock.findOne({where: {id: 1}}); + user.isAdmin.should.equal(true); + }); + }) + }) +}) \ No newline at end of file From bb8ce8949d17ff04efd7fa721d3645f01c0a8988 Mon Sep 17 00:00:00 2001 From: Stanney Date: Tue, 19 Oct 2021 02:08:00 +0800 Subject: [PATCH 003/198] tests/A17.test.js added comments --- config/config.json | 2 +- package-lock.json | 2494 +++++++++++++++++++++++++++++++++++++++++++- tests/A17.test.js | 60 +- 3 files changed, 2524 insertions(+), 32 deletions(-) diff --git a/config/config.json b/config/config.json index 56636a1c..bfb32f98 100644 --- a/config/config.json +++ b/config/config.json @@ -1,7 +1,7 @@ { "development": { "username": "root", - "password": "password", + "password": "", "database": "forum", "host": "127.0.0.1", "dialect": "mysql" diff --git a/package-lock.json b/package-lock.json index 4d773f32..7ebc04a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,8 +1,2480 @@ { "name": "forum-express-workspace", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "forum-express-workspace", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "chai": "^4.2.0", + "express": "^4.17.1", + "mocha": "^8.2.0", + "mysql2": "^2.2.5", + "sequelize": "^6.3.5", + "sequelize-cli": "^6.2.0", + "sinon": "^9.2.0", + "supertest": "^5.0.0" + }, + "devDependencies": { + "proxyquire": "^2.1.3", + "sequelize-mock": "^0.10.2" + } + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", + "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", + "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/formatio": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-5.0.1.tgz", + "integrity": "sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==", + "dependencies": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^5.0.2" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.2.0.tgz", + "integrity": "sha512-CaIcyX5cDsjcW/ab7HposFWzV1kC++4HNsfnEdFJa7cP1QIuILAKV+BgfeqRXhcnSAc76r/Rh/O5C+300BwUIw==", + "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/node": { + "version": "14.14.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.5.tgz", + "integrity": "sha512-H5Wn24s/ZOukBmDn03nnGTp18A60ny9AmCwnEcgJiTgSGsCO7k+NWP7zjCCbhlcnVCoI+co52dUAt9GMhOSULw==" + }, + "node_modules/@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==" + }, + "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.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dependencies": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "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/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, + "node_modules/anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "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=" + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "node_modules/binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "engines": { + "node": ">=8" + } + }, + "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.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "dependencies": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "engines": { + "node": ">= 0.8" + } + }, + "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.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk/node_modules/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==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/chalk/node_modules/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==" + }, + "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/chokidar": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", + "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", + "dependencies": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.1.2" + } + }, + "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/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==", + "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==" + }, + "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.12", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", + "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "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.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "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.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==" + }, + "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/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/denque": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", + "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==", + "engines": { + "node": ">=0.10" + } + }, + "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/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "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/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/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/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/es5-ext": { + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "dependencies": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" + } + }, + "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": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "dependencies": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "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.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "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/ext": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", + "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "dependencies": { + "type": "^2.0.0" + } + }, + "node_modules/ext/node_modules/type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.1.0.tgz", + "integrity": "sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA==" + }, + "node_modules/fast-safe-stringify": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", + "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" + }, + "node_modules/fill-keys": { + "version": "1.0.2", + "resolved": "http://npm.ooapi.com:4873/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/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==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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/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": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/form-data": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", + "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formidable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", + "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==", + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, + "node_modules/forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "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/fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "deprecated": "\"Please update to latest v2.3 or v2.2\"", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "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/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "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": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + }, + "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-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/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": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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.12.0", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", + "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=", + "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.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "node_modules/ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "deprecated": "Please update to ini >=1.3.6 to avoid a prototype pollution issue", + "engines": { + "node": "*" + } + }, + "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-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==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.0.0.tgz", + "integrity": "sha512-jq1AH6C8MuteOoBPwkxHafmByhL9j5q4OaPGdbuD+ZtQJVzH+i6E3BJDQcBA09k57i2Hh2yQbEG8yObZ0jdlWw==", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "engines": { + "node": ">=0.10.0" + } + }, + "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-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-object": { + "version": "1.0.2", + "resolved": "http://npm.ooapi.com:4873/is-object/-/is-object-1.0.2.tgz", + "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "engines": { + "node": ">=8" + } + }, + "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/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "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.13.0", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.13.0.tgz", + "integrity": "sha512-/Tbp1OVzZjbwzwJQFIlYLm9eWQ+3aYbBXLSaqb1mEJzhcQAfrqMMQYtjb6io+U6KpD0ID4F+Id3/xcjH3l/sqA==", + "dependencies": { + "config-chain": "^1.1.12", + "editorconfig": "^0.15.3", + "glob": "^7.1.3", + "mkdirp": "^1.0.4", + "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" + } + }, + "node_modules/js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "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.1.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz", + "integrity": "sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==" + }, + "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.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + }, + "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": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "dependencies": { + "chalk": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "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/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/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.14", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", + "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.45", + "es6-weak-map": "^2.0.2", + "event-emitter": "^0.3.5", + "is-promise": "^2.1", + "lru-queue": "0.1", + "next-tick": "1", + "timers-ext": "^0.1.5" + } + }, + "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/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.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "dependencies": { + "mime-db": "1.44.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/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.0.tgz", + "integrity": "sha512-lEWEMq2LMfNJMKeuEwb5UELi+OgFDollXaytR5ggQcHpzG3NP/R7rvixAvF+9/lLsTWhWG+4yD2M70GsM06nxw==", + "dependencies": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.4.3", + "debug": "4.2.0", + "diff": "4.0.2", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.6", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.14.0", + "log-symbols": "4.0.0", + "minimatch": "3.0.4", + "ms": "2.1.2", + "nanoid": "3.1.12", + "serialize-javascript": "5.0.1", + "strip-json-comments": "3.1.1", + "supports-color": "7.2.0", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.0.2", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 10.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "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.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mocha/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/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/mocha/node_modules/p-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", + "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/module-not-found-error": { + "version": "1.0.1", + "resolved": "http://npm.ooapi.com:4873/module-not-found-error/-/module-not-found-error-1.0.1.tgz", + "integrity": "sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=", + "dev": true + }, + "node_modules/moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.31", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.31.tgz", + "integrity": "sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==", + "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": "2.2.5", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.2.5.tgz", + "integrity": "sha512-XRqPNxcZTpmFdXbJqb+/CtYVLCx14x1RTeNMD4954L331APu75IC74GDqnZMEt1kwaXy6TySo55rF2F3YJS78g==", + "dependencies": { + "denque": "^1.4.1", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.2", + "long": "^4.0.0", + "lru-cache": "^6.0.0", + "named-placeholders": "^1.1.2", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/mysql2/node_modules/iconv-lite": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", + "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "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/nanoid": { + "version": "3.1.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz", + "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || >=13.7" + } + }, + "node_modules/negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + }, + "node_modules/nise": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/nise/-/nise-4.0.4.tgz", + "integrity": "sha512-bTTRUNlemx6deJa+ZyoCUTRvH3liK5+N6VQZ4NIw90AgDXY6iPnsqplNFf6STcj+ePk0H/xqxnP75Lr0J0Fq3A==", + "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/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/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/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "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/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/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.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, + "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.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "engines": { + "node": "*" + } + }, + "node_modules/picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "dependencies": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxyquire": { + "version": "2.1.3", + "resolved": "http://npm.ooapi.com:4873/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.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "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.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "dependencies": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "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.18.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz", + "integrity": "sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA==", + "dependencies": { + "is-core-module": "^2.0.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/retry-as-promised": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-3.2.0.tgz", + "integrity": "sha512-CybGs60B7oYU/qSQ6kuaFmRd9sTZ6oXSc0toqePvV74Ac6/IFZSI1ReFQmtCN+uvW1Mtqdwpvt/LGOiCBAY2Mg==", + "dependencies": { + "any-promise": "^1.3.0" + } + }, + "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==" + }, + "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": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "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.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/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/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.3.5", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.3.5.tgz", + "integrity": "sha512-MiwiPkYSA8NWttRKAXdU9h0TxP6HAc1fl7qZmMO/VQqQOND83G4nZLXd0kWILtAoT9cxtZgFqeb/MPYgEeXwsw==", + "dependencies": { + "debug": "^4.1.1", + "dottie": "^2.0.0", + "inflection": "1.12.0", + "lodash": "^4.17.15", + "moment": "^2.26.0", + "moment-timezone": "^0.5.31", + "retry-as-promised": "^3.2.0", + "semver": "^7.3.2", + "sequelize-pool": "^6.0.0", + "toposort-class": "^1.0.1", + "uuid": "^8.1.0", + "validator": "^10.11.0", + "wkx": "^0.5.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependenciesMeta": { + "mariadb": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-hstore": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "tedious": { + "optional": true + } + } + }, + "node_modules/sequelize-cli": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/sequelize-cli/-/sequelize-cli-6.2.0.tgz", + "integrity": "sha512-6WQ2x91hg30dUn66mXHnzvHATZ4pyI1GHSNbS/TNN/vRR4BLRSLijadeMgC8zqmKDsL0VqzVVopJWfJakuP++Q==", + "dependencies": { + "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.3.0", + "yargs": "^13.1.0" + }, + "bin": { + "sequelize": "lib/sequelize", + "sequelize-cli": "lib/sequelize" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/sequelize-mock": { + "version": "0.10.2", + "resolved": "http://npm.ooapi.com:4873/sequelize-mock/-/sequelize-mock-0.10.2.tgz", + "integrity": "sha1-GdOXHM2utbhkFwwkznkqinHxRL0=", + "dev": true, + "dependencies": { + "bluebird": "^3.4.6", + "inflection": "^1.10.0", + "lodash": "^4.16.4" + } + }, + "node_modules/sequelize-pool": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-6.1.0.tgz", + "integrity": "sha512-4YwEw3ZgK/tY/so+GfnSgXkdwIJJ1I32uZJztIEgZeAO6HMgj64OzySbWLgxj+tXhZCJnzRfkY9gINw8Ft8ZMg==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/sequelize/node_modules/debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "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.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "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/serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + }, + "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.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "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": "9.2.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.0.tgz", + "integrity": "sha512-eSNXz1XMcGEMHw08NJXSyTHIu6qTCOiN8x9ODACmZpNQpr0aXTBXBnI4xTzQzR+TEpOmLiKowGf9flCuKIzsbw==", + "dependencies": { + "@sinonjs/commons": "^1.8.1", + "@sinonjs/fake-timers": "^6.0.1", + "@sinonjs/formatio": "^5.0.1", + "@sinonjs/samsam": "^5.2.0", + "diff": "^4.0.2", + "nise": "^4.0.4", + "supports-color": "^7.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "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.2", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz", + "integrity": "sha512-vF4ZbYdKS8OnoJAWBmMxCQDkiEBkGQYU7UZPtL8flbDRSNkhaXvRJ279ZtI6M+zDaQovVU4tuRgzK5fVhvFAhg==", + "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.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string_decoder/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/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/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/strip-ansi/node_modules/ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/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==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/superagent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-6.1.0.tgz", + "integrity": "sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg==", + "dependencies": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.2", + "debug": "^4.1.1", + "fast-safe-stringify": "^2.0.7", + "form-data": "^3.0.0", + "formidable": "^1.2.2", + "methods": "^1.1.2", + "mime": "^2.4.6", + "qs": "^6.9.4", + "readable-stream": "^3.6.0", + "semver": "^7.3.2" + }, + "engines": { + "node": ">= 7.0.0" + } + }, + "node_modules/superagent/node_modules/debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "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.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/superagent/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/superagent/node_modules/qs": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==", + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/supertest": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-5.0.0.tgz", + "integrity": "sha512-2JAWpPrUOZF4hHH5ZTCN2xjKXvJS3AEwPNXl0HUseHsfcXFvMy9kcsufIHCNAmQ5hlGCvgeAqaR5PBEouN3hlQ==", + "dependencies": { + "methods": "1.1.2", + "superagent": "6.1.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "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/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/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==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "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/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/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=" + }, + "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.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", + "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/validator": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", + "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==", + "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": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "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/wide-align/node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "engines": { + "node": ">=4" + } + }, + "node_modules/wide-align/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/wide-align/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/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/workerpool": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.2.tgz", + "integrity": "sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==" + }, + "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/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.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + }, + "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/yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "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" + } + }, + "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" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs-unparser/node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, "dependencies": { "@sinonjs/commons": { "version": "1.8.1", @@ -1565,16 +4037,6 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, - "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==", - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -1590,6 +4052,16 @@ } } }, + "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==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", diff --git a/tests/A17.test.js b/tests/A17.test.js index e04cface..fe0fe948 100644 --- a/tests/A17.test.js +++ b/tests/A17.test.js @@ -7,7 +7,8 @@ const proxyquire = require('proxyquire'); const dbMock = new SequelizeMock(); -const app = require('../app'); +const app = require('../../app'); +const { classToInvokable } = require('sequelize/types/lib/utils'); const mockRequest = (query) => { return { @@ -23,7 +24,7 @@ const mockResponse = () => { describe('# A17', () => { describe('登入測試: POST /signin', function(){ - it('should fail without proper username', function(done){ + it('#1 密碼錯誤', function(done){ request(app) .post('/signin') .type('urlencoded') @@ -32,7 +33,7 @@ describe('# A17', () => { .expect(302, done) }) - it('should fail without proper password', function(done){ + it('#2 帳號錯誤', function(done){ request(app) .post('/signin') .type('urlencoded') @@ -41,7 +42,7 @@ describe('# A17', () => { .expect(302, done) }) - it('should succeed with proper credentials', function(done){ + it('#3 成功登入', function(done){ request(app) .post('/signin') .type('urlencoded') @@ -53,42 +54,50 @@ describe('# A17', () => { describe('# A17: 使用者權限管理', function () { before(() => { + // 製作假 db 資料: UserMock this.UserMock = dbMock.define('User', { id: 1, email: 'root@example.com', name: 'admin', isAdmin: false, }); - - this.adminController = proxyquire('../controllers/adminController', { + // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) + this.UserMock.findByPk = (id) => this.UserMock.findOne({where: {id: id}}); + // 將 adminController 中的 User db 取代成 User mock db + this.adminController = proxyquire('../../controllers/adminController', { '../models': { User: this.UserMock } }); }) context('# [顯示使用者清單]', () => { - it(" GET /admin/users ", (done) => { - const req = {} - const res = { - render: (_, results) => { - results.users[0].name.should.equal("admin"); - done() - } - }; + it(" GET /admin/users ", async () => { + // 製作假 request & response + const req = mockRequest(); + const res = mockResponse(); - this.adminController.getUsers(req, res); + // 測試 adminController.getUsers function + await this.adminController.getUsers(req, res); + + // render 裡的第二個參數,回傳了 users, 名稱必須跟假資料一樣是 admin + res.render.getCall(0).args[1].users[0].name.should.equal('admin'); }); }) context('# [修改使用者權限] for admin', () => { before(() => { + // 製作假 db 資料: UserMock this.UserMock = dbMock.define('User', { id: 1, email: 'root@example.com', name: 'admin', - isAdmin: false, + isAdmin: true, }); - - this.adminController = proxyquire('../controllers/adminController', { + // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) + // 將 count 的 function 預設回傳假資料數目 1 + this.UserMock.count = () => 1; + this.UserMock.findByPk = (id) => this.UserMock.findOne({where: {id: id}}); + // 將 adminController 中的 User db 取代成 User mock db + this.adminController = proxyquire('../../controllers/adminController', { '../models': { User: this.UserMock } }); }) @@ -97,9 +106,12 @@ describe('# A17', () => { const req = mockRequest({params: {id: 1}}); const res = mockResponse(); + // 測試 adminController.toggleAdmin function await this.adminController.toggleAdmin(req, res); + // 執行完 toggleAdmin 後,res.flash 有被呼叫而且呼叫的訊息正確 req.flash.calledWith('error_messages', 'core manager can not be changed').should.be.true; + // 執行完 toggleAmin 後 res.redirect 有被呼叫而且 redirect 到 back res.redirect.calledWith('back').should.be.true; }); }) @@ -113,19 +125,23 @@ describe('# A17', () => { isAdmin: false, }, { instanceMethods: { + // 模擬 update 函數,並依據傳入的變數,跟著修正,如果傳入 {isAdmin: true},假資料中的 isAdmin 變會變成 true update: (changes) => { this.UserMock._defaults = {...changes}; return Promise.resolve(); } } }); + + this.UserMock.count = () => 1; + this.UserMock.findByPk = (id) => this.UserMock.findOne({where: {id: id}}); - this.adminController = proxyquire('../controllers/adminController', { + this.adminController = proxyquire('../../controllers/adminController', { '../models': { User: this.UserMock } }); }) - it(" PUT /admin/users/:id/toggleAdmin ", async () => { + it(" PUT /admin/users/:id/toggleAdmin ", async (done) => { const req = mockRequest({params: {id: 1}}); const res = mockResponse(); @@ -134,8 +150,12 @@ describe('# A17', () => { req.flash.calledWith('success_messages', 'user was successfully to update').should.be.true; res.redirect.calledWith('/admin/users').should.be.true; + // 執行完 toggleAmin 後,因為假資料中使用者是 user@example.com,因此可以修改權限 isAdmin 從 false -> true + // 所以這邊將假資料撈出,比對看是否有修改到 const user = await this.UserMock.findOne({where: {id: 1}}); user.isAdmin.should.equal(true); + + done() }); }) }) From c15fe793c388a5ad9894838b0d84ff2715400cc0 Mon Sep 17 00:00:00 2001 From: Stanney Date: Tue, 19 Oct 2021 02:09:43 +0800 Subject: [PATCH 004/198] add A19 test --- tests/A19.test.js | 157 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 tests/A19.test.js diff --git a/tests/A19.test.js b/tests/A19.test.js new file mode 100644 index 00000000..996f9939 --- /dev/null +++ b/tests/A19.test.js @@ -0,0 +1,157 @@ +const chai = require('chai') +const request = require('supertest') +const sinon = require('sinon') +const should = chai.should() + +const app = require('../../app') +const routes = require('../../routes/index') +const db = require('../../models') +const helpers = require('../../_helpers'); +const SequelizeMock = require('sequelize-mock'); +const proxyquire = require('proxyquire'); + +const dbMock = new SequelizeMock(); + +const mockRequest = (query) => { + return { + ...query, + flash: sinon.spy() + }; +}; +const mockResponse = () => { + return { + redirect: sinon.spy(), + render: sinon.spy(), + } +}; +describe('# A19', () => { + describe('# A19: 建立 User Profile', function() { + context('# [瀏覽 Profile]', () => { + before(() => { + this.ensureAuthenticated = sinon.stub( + helpers, 'ensureAuthenticated' + ).returns(true); + this.getUser = sinon.stub( + helpers, 'getUser' + ).returns({id: 1}); + this.UserMock = dbMock.define('User', { + id: 1, + email: 'root@example.com', + name: 'admin', + isAdmin: false, + }); + + this.UserMock.findByPk = (id) => this.UserMock.findOne({where: {id: id}}); + + this.userController = proxyquire('../../controllers/userController', { + '../models': { User: this.UserMock } + }); + }) + + it(" GET /users/:id ", async () => { + const req = mockRequest({params: {id: 1}}); + const res = mockResponse(); + + await this.userController.getUser(req, res); + + res.render.getCall(0).args[0].should.equal('profile'); + res.render.getCall(0).args[1].userData.name.should.equal("admin"); + }); + + after(async () => { + this.ensureAuthenticated.restore(); + this.getUser.restore(); + }) + }) + + context('# [瀏覽編輯 Profile 頁面]', () => { + before(() => { + this.ensureAuthenticated = sinon.stub( + helpers, 'ensureAuthenticated' + ).returns(true); + this.getUser = sinon.stub( + helpers, 'getUser' + ).returns({id: 1}); + this.UserMock = dbMock.define('User', { + id: 1, + email: 'root@example.com', + name: 'admin', + isAdmin: false, + }); + + this.UserMock.findByPk = (id) => this.UserMock.findOne({where: {id: id}}); + + this.userController = proxyquire('../../controllers/userController', { + '../models': { User: this.UserMock } + }); + }) + + it(" GET /users/:id/edit ", async () => { + const req = mockRequest({params: {id: 1}}); + const res = mockResponse(); + + await this.userController.editUser(req, res); + + res.render.getCall(0).args[0].should.equal('edit'); + res.render.getCall(0).args[1].user.name.should.equal("admin"); + }); + + after(async () => { + this.ensureAuthenticated.restore(); + this.getUser.restore(); + }) + }) + + context('# [編輯 Profile]', () => { + before(async() => { + this.ensureAuthenticated = sinon.stub( + helpers, 'ensureAuthenticated' + ).returns(true); + this.getUser = sinon.stub( + helpers, 'getUser' + ).returns({id: 1}); + this.UserMock = dbMock.define('User', { + id: 1, + email: 'root@example.com', + name: 'admin', + isAdmin: false, + }, { + instanceMethods: { + update: (changes) => { + this.UserMock._defaults = {...changes}; + return Promise.resolve(); + } + } + }); + + this.UserMock.findByPk = (id) => this.UserMock.findOne({where: {id: id}}); + + this.userController = proxyquire('../../controllers/userController', { + '../models': { User: this.UserMock } + }); + }) + + it(" PUT /users/:id ", async () => { + const req = mockRequest({ + params: {id: 1}, + body: {name: "admin2", email: "admin_test@gmail.com"} + }); + const res = mockResponse(); + + await this.userController.putUser(req, res); + + req.flash.calledWith('success_messages', 'user was updated successfully').should.be.true; + res.redirect.calledWith('/users/1').should.be.true; + + const user = await this.UserMock.findOne({where: {id: 1}}); + user.name.should.equal('admin2'); + user.email.should.equal('admin_test@gmail.com'); + }); + + after(async () => { + this.ensureAuthenticated.restore(); + this.getUser.restore(); + }) + }) + }) +}) \ No newline at end of file From da713e00459d4dc1894b9bed059ec863cb4a6814 Mon Sep 17 00:00:00 2001 From: Stanney Date: Tue, 19 Oct 2021 02:10:04 +0800 Subject: [PATCH 005/198] add A20 test --- tests/A20.test.js | 74 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 tests/A20.test.js diff --git a/tests/A20.test.js b/tests/A20.test.js new file mode 100644 index 00000000..5ed231a0 --- /dev/null +++ b/tests/A20.test.js @@ -0,0 +1,74 @@ +const chai = require('chai') +const request = require('supertest') +const sinon = require('sinon') +const should = chai.should() + +const helpers = require('../_helpers') +const SequelizeMock = require('sequelize-mock'); +const proxyquire = require('proxyquire'); + +const dbMock = new SequelizeMock(); + + +const mockRequest = (query) => { + return { + ...query, + flash: sinon.spy() + }; +}; +const mockResponse = () => { + return { + redirect: sinon.spy(), + render: sinon.spy(), + } +}; + +describe('# A20: 餐廳資訊整理:Dashboard', function() { + + context('# [Q1: Dashboard - 1 - controller / view / route]', () => { + before(async() => { + this.UserMock = dbMock.define('User', { + id: 1, + email: 'root@example.com', + name: 'admin', + isAdmin: false, + }); + this.RestaurantMock = dbMock.define('Restaurant', { + id: 1, + name: '銷魂麵' + }); + this.CategoryMock = dbMock.define('Category', { + id: 1, + name: '食物' + }); + this.CommentMock = dbMock.define('Comment', { + id: 1, + text: "gogogo" + }) + + this.RestaurantMock.belongsTo(this.CategoryMock); + this.RestaurantMock.findByPk = (id) => this.RestaurantMock.findOne({where: {id: id}}); + this.CommentMock.count = () => 1; + + this.restController = proxyquire('../controllers/restController', { + '../models': { + User: this.UserMock, + Category: this.CategoryMock, + Restaurant: this.RestaurantMock, + Comment: this.CommentMock, + } + }); + }) + + it(" GET /restaurants/:id/dashboard ", async () => { + const req = mockRequest({params: {id: 1}}); + const res = mockResponse() + + await this.restController.getDashBoard(req, res); + + res.render.getCall(0).args[0].should.equal('dashboard'); + res.render.getCall(0).args[1].restaurant.name.should.equal('銷魂麵'); + res.render.getCall(0).args[1].n_comments.should.equal(1); + }); + }) +}) \ No newline at end of file From 88812da23bb63023626aabd3e4705ec5afcec3ca Mon Sep 17 00:00:00 2001 From: Stanney Date: Tue, 19 Oct 2021 02:10:20 +0800 Subject: [PATCH 006/198] add A21 test --- tests/A21.test.js | 124 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 tests/A21.test.js diff --git a/tests/A21.test.js b/tests/A21.test.js new file mode 100644 index 00000000..b5f9dd0d --- /dev/null +++ b/tests/A21.test.js @@ -0,0 +1,124 @@ +const chai = require('chai') +const request = require('supertest') +const sinon = require('sinon') +const should = chai.should() + +const db = require('../models') +const helpers = require('../_helpers') +const SequelizeMock = require('sequelize-mock'); +const proxyquire = require('proxyquire'); + +const dbMock = new SequelizeMock({autoQueryFallback: false}); + +const mockRequest = (query) => { + return { + ...query, + flash: sinon.spy() + }; +}; +const mockResponse = () => { + return { + redirect: sinon.spy(), + render: sinon.spy(), + } +}; + +let mockLikeData = [{ + UserId: 1, + RestaurantId: 2, +}] + +describe('# A21: Like / Unlike', function () { + + context('# Q1: 使用者可以 Like 餐廳', () => { + before(() => { + this.ensureAuthenticated = sinon.stub( + helpers, 'ensureAuthenticated' + ).returns(true); + this.getUser = sinon.stub( + helpers, 'getUser' + ).returns({id: 1}); + this.mockLikeData = []; + this.likeMock = dbMock.define('Like'); + this.likeMock.create = this.likeMock.upsert; + + this.likeMock.$queryInterface.$useHandler((query, queryOptions,done) => { + if (query === 'upsert') { + const {UserId, RestaurantId} = queryOptions[0]; + + this.mockLikeData.push({ UserId, RestaurantId }); + + return Promise.resolve(this.likeMock.build(this.mockLikeData)); + } else if (query === 'findAll') { + return Promise.resolve(this.mockLikeData); + } + }); + + this.userController = proxyquire('../controllers/userController', { + '../models': {Like: this.likeMock} + }); + }) + + it(" POST /like/:restaurantId ", async () => { + const req = mockRequest({params: {id: 1, restaurantId: 2}}); + const res = mockResponse(); + + await this.userController.addLike(req, res); + + const likes = await this.likeMock.findAll(); + console.log(likes); + likes.should.have.lengthOf(1); + }); + + after(async () => { + this.ensureAuthenticated.restore(); + this.getUser.restore(); + }) + }) + + context('# Q1: 使用者可以 unLike 餐廳', () => { + before(() => { + this.ensureAuthenticated = sinon.stub( + helpers, 'ensureAuthenticated' + ).returns(true); + this.getUser = sinon.stub( + helpers, 'getUser' + ).returns({id: 1}); + this.likeMock = dbMock.define('Like', { + id: 1, + UserId: 1, + RestaurantId: 2, + }); + + this.likeMock.$queryInterface.$useHandler((query, queryOptions,done) => { + if (query === 'destroy') { + const {UserId, RestaurantId} = queryOptions[0].where; + mockLikeData = mockLikeData.filter(d => !(d.UserId === UserId && d.RestaurantId === RestaurantId)) + + return Promise.resolve(this.likeMock.build(mockLikeData)); + } else if (query === 'findAll') { + return Promise.resolve(mockLikeData); + } + }); + + this.userController = proxyquire('../controllers/userController', { + '../models': {Like: this.likeMock} + }); + }) + + it(" DELETE /like/:restaurantId ", async () => { + const req = mockRequest({params: {id: 1, restaurantId: 2}}); + const res = mockResponse(); + + await this.userController.removeLike(req, res); + + const likes = await this.likeMock.findAll(); + likes.should.have.lengthOf(0); + }); + + after(async () => { + this.ensureAuthenticated.restore(); + this.getUser.restore(); + }) + }); +}) \ No newline at end of file From a7d56e15313697ca22f380dfd6c9e1557fe2be8a Mon Sep 17 00:00:00 2001 From: Stanney Date: Tue, 19 Oct 2021 02:10:30 +0800 Subject: [PATCH 007/198] add A22 test --- tests/A22.test.js | 143 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 tests/A22.test.js diff --git a/tests/A22.test.js b/tests/A22.test.js new file mode 100644 index 00000000..0ee0c78f --- /dev/null +++ b/tests/A22.test.js @@ -0,0 +1,143 @@ +const chai = require('chai') +const request = require('supertest') +const sinon = require('sinon') +const should = chai.should() + +const app = require('../app') +const helpers = require('../_helpers'); +const SequelizeMock = require('sequelize-mock'); +const proxyquire = require('proxyquire'); + +const dbMock = new SequelizeMock({autoQueryFallback: false}); + +const mockRequest = (query) => { + return { + ...query, + flash: sinon.spy() + }; +}; +const mockResponse = () => { + return { + redirect: sinon.spy(), + render: sinon.spy(), + } +}; + +let mockRestaurantData = [{ + id: 1, + name: 'Restaurant1', + tel: 'tel', + address: 'address', + opening_hours: 'opening_hours', + description: "test description", + FavoritedUsers: [{ + UserId: 1, + }] +}, { + id: 2, + name: 'Restaurant2', + tel: 'tel', + address: 'address', + opening_hours: 'opening_hours', + description: 'description', + CategoryId: 1, + FavoritedUsers: [] +}] + +describe('# A22: TOP 10 人氣餐廳 ', function () { + + context('# [網址正確、畫面正常執行]', () => { + before(async () => { + this.ensureAuthenticated = sinon.stub( + helpers, 'ensureAuthenticated' + ).returns(true); + this.getUser = sinon.stub( + helpers, 'getUser' + ).returns({ id: 1, Followings: [], FavoritedRestaurants: [] }); + }) + + it(" GET /restaurants/top ", (done) => { + request(app) + .get('/restaurants/top') + .end(function (err, res) { + done() + }); + }); + + after(async () => { + this.ensureAuthenticated.restore(); + this.getUser.restore(); + }) + + }) + + context('# [當你點擊畫面上的「加入最愛 / 移除最愛」按鈕時,會重新計算「收藏數」的數字]', () => { + before(async () => { + this.ensureAuthenticated = sinon.stub( + helpers, 'ensureAuthenticated' + ).returns(true); + this.getUser = sinon.stub( + helpers, 'getUser' + ).returns({ id: 1, Followings: [], FavoritedRestaurants: [] }); + + this.restaurantMock = dbMock.define('Restaurant'); + this.restaurantMock.$queryInterface.$useHandler((query, queryOptions,done) => { + if (query === 'findAll') { + return Promise.resolve(mockRestaurantData.map(d => this.restaurantMock.build(d))); + } + }); + this.restController = proxyquire('../controllers/restController', { + '../models': { + Restaurant: this.restaurantMock, + } + }); + + this.favoriteMock = dbMock.define('Favorite'); + this.favoriteMock.create = this.favoriteMock.upsert; + this.favoriteMock.$queryInterface.$useHandler((query, queryOptions,done) => { + if (query === 'upsert') { + const {UserId, RestaurantId} = queryOptions[0]; + const restaurant = mockRestaurantData.find(d => d.id === RestaurantId); + restaurant.FavoritedUsers.push({UserId: UserId}); + return Promise.resolve(mockRestaurantData.map(d => this.restaurantMock.build(d))); + } else if (query === 'destroy') { + const {UserId, RestaurantId} = queryOptions[0].where; + const restaurant = mockRestaurantData.find(d => d.id === RestaurantId); + restaurant.FavoritedUsers = restaurant.FavoritedUsers.filter(d => !(d.UserId === UserId)) + return Promise.resolve(mockRestaurantData.map(d => this.restaurantMock.build(d))); + } + }); + + this.userController = proxyquire('../controllers/userController', { + '../models': {Favorite: this.favoriteMock} + }) + }) + + it(" POST favorite ", async () => { + const req = mockRequest({user: { FavoritedRestaurants: []}, params: {restaurantId: 2}}); + const res = mockResponse(); + + await this.userController.addFavorite(req, res); + await this.restController.getTopRestaurant(req, res); + + res.render.getCall(0).args[1].restaurants[1].FavoritedCount.should.equal(1); + }) + + it(" DELETE favorite ", async() => { + const req = mockRequest({user: { FavoritedRestaurants: []}, params: {restaurantId: 1}}); + const res = mockResponse(); + + await this.userController.removeFavorite(req, res); + await this.restController.getTopRestaurant(req, res); + + res.render.getCall(0).args[1].restaurants[0].id.should.equal(2); + }) + + after(async () => { + this.ensureAuthenticated.restore(); + this.getUser.restore(); + }) + + }) + +}) \ No newline at end of file From d4a739111193241d59ead5ae5d49605d05e7363d Mon Sep 17 00:00:00 2001 From: Stanney Date: Tue, 19 Oct 2021 12:21:34 +0800 Subject: [PATCH 008/198] add commnets for A17 --- tests/A17.test.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/A17.test.js b/tests/A17.test.js index fe0fe948..13aa8b71 100644 --- a/tests/A17.test.js +++ b/tests/A17.test.js @@ -24,6 +24,7 @@ const mockResponse = () => { describe('# A17', () => { describe('登入測試: POST /signin', function(){ + // 以下測試會發出真實的 request 測試資料庫是否有正確的 user 資料 it('#1 密碼錯誤', function(done){ request(app) .post('/signin') @@ -47,7 +48,7 @@ describe('# A17', () => { .post('/signin') .type('urlencoded') .send('email=root@example.com&password=12345678') - .expect('Location', '/') + .expect('Location', '/restaurants') .expect(302, done) }) }); @@ -71,7 +72,7 @@ describe('# A17', () => { context('# [顯示使用者清單]', () => { it(" GET /admin/users ", async () => { - // 製作假 request & response + // 模擬 request & response const req = mockRequest(); const res = mockResponse(); @@ -85,7 +86,7 @@ describe('# A17', () => { context('# [修改使用者權限] for admin', () => { before(() => { - // 製作假 db 資料: UserMock + // 模擬 user db 資料: UserMock this.UserMock = dbMock.define('User', { id: 1, email: 'root@example.com', @@ -125,7 +126,7 @@ describe('# A17', () => { isAdmin: false, }, { instanceMethods: { - // 模擬 update 函數,並依據傳入的變數,跟著修正,如果傳入 {isAdmin: true},假資料中的 isAdmin 變會變成 true + // 模擬 update 函數,並依據傳入的變數,跟著修正,如果傳入 {isAdmin: true} update: (changes) => { this.UserMock._defaults = {...changes}; return Promise.resolve(); From e981eb0fe7ac8243658482ab3f17ea0464fae38b Mon Sep 17 00:00:00 2001 From: Stanney Date: Tue, 19 Oct 2021 12:21:49 +0800 Subject: [PATCH 009/198] add comments for A19 --- tests/A19.test.js | 44 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/tests/A19.test.js b/tests/A19.test.js index 996f9939..fd8cf8d7 100644 --- a/tests/A19.test.js +++ b/tests/A19.test.js @@ -28,37 +28,45 @@ describe('# A19', () => { describe('# A19: 建立 User Profile', function() { context('# [瀏覽 Profile]', () => { before(() => { + // 模擬驗證資料 this.ensureAuthenticated = sinon.stub( helpers, 'ensureAuthenticated' ).returns(true); this.getUser = sinon.stub( helpers, 'getUser' ).returns({id: 1}); + // 模擬 User db 資料 this.UserMock = dbMock.define('User', { id: 1, email: 'root@example.com', name: 'admin', isAdmin: false, }); - + // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) this.UserMock.findByPk = (id) => this.UserMock.findOne({where: {id: id}}); - + // 將 userController 中的 User db 取代成 User mock db this.userController = proxyquire('../../controllers/userController', { '../models': { User: this.UserMock } }); }) it(" GET /users/:id ", async () => { + // 模擬發出 request, 帶入 params.id = 1 const req = mockRequest({params: {id: 1}}); const res = mockResponse(); - + + // 測試 userController.getUser await this.userController.getUser(req, res); + // 執行完後,call res.render 的參數測試 + // 1. 第一個參數是不是 profile + // 2. 第二個參數裡回傳的資料有 userData 而且 name 是 admin res.render.getCall(0).args[0].should.equal('profile'); res.render.getCall(0).args[1].userData.name.should.equal("admin"); }); after(async () => { + // 清除模擬驗證資料 this.ensureAuthenticated.restore(); this.getUser.restore(); }) @@ -66,37 +74,47 @@ describe('# A19', () => { context('# [瀏覽編輯 Profile 頁面]', () => { before(() => { + // 製作假驗證資料 this.ensureAuthenticated = sinon.stub( helpers, 'ensureAuthenticated' ).returns(true); this.getUser = sinon.stub( helpers, 'getUser' ).returns({id: 1}); + + // 模擬 User db 資料 this.UserMock = dbMock.define('User', { id: 1, email: 'root@example.com', name: 'admin', isAdmin: false, }); - + // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) this.UserMock.findByPk = (id) => this.UserMock.findOne({where: {id: id}}); + // 將 userController 中的 User db 取代成 User mock db this.userController = proxyquire('../../controllers/userController', { '../models': { User: this.UserMock } }); }) it(" GET /users/:id/edit ", async () => { + // 模擬 request & response const req = mockRequest({params: {id: 1}}); const res = mockResponse(); + // 測試 adminController.editUser function await this.userController.editUser(req, res); + // 執行完 editUser 後,call res.render 的參數測試 + // 1. 第一個參數是不是 edit + // 2. 第二個參數裡回傳的資料有 user 而且 name 是 admin res.render.getCall(0).args[0].should.equal('edit'); res.render.getCall(0).args[1].user.name.should.equal("admin"); }); after(async () => { + // 清除模擬驗證資料 this.ensureAuthenticated.restore(); this.getUser.restore(); }) @@ -104,12 +122,14 @@ describe('# A19', () => { context('# [編輯 Profile]', () => { before(async() => { + // 模擬 request & response this.ensureAuthenticated = sinon.stub( helpers, 'ensureAuthenticated' ).returns(true); this.getUser = sinon.stub( helpers, 'getUser' ).returns({id: 1}); + // 製作假 db 資料: UserMock this.UserMock = dbMock.define('User', { id: 1, email: 'root@example.com', @@ -117,38 +137,44 @@ describe('# A19', () => { isAdmin: false, }, { instanceMethods: { + // 模擬 update 函數,並依據傳入的變數,跟著修正,如果傳入 {isAdmin: true} update: (changes) => { this.UserMock._defaults = {...changes}; return Promise.resolve(); } } }); - + // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) this.UserMock.findByPk = (id) => this.UserMock.findOne({where: {id: id}}); - + // 將 userController 中的 User db 取代成 User mock db this.userController = proxyquire('../../controllers/userController', { '../models': { User: this.UserMock } }); }) it(" PUT /users/:id ", async () => { + // 模擬發送 request, 帶了 params.id = 1, body.name = amdin2, body.email = admin_test@gmail.com const req = mockRequest({ params: {id: 1}, body: {name: "admin2", email: "admin_test@gmail.com"} }); const res = mockResponse(); - - await this.userController.putUser(req, res); + // 修改模擬資料, call putUser function + await this.userController.putUser(req, res); + + // 測試成功修改 user 資料後,是否有回傳正確訊息 req.flash.calledWith('success_messages', 'user was updated successfully').should.be.true; + // 測試成功修改 user 資料後,是否有導向正確 route res.redirect.calledWith('/users/1').should.be.true; - + // 修改後,測試模擬資料中是否有成功修正, name = amdin2, email = admin_test@gmail.com const user = await this.UserMock.findOne({where: {id: 1}}); user.name.should.equal('admin2'); user.email.should.equal('admin_test@gmail.com'); }); after(async () => { + // 清除模擬驗證資料 this.ensureAuthenticated.restore(); this.getUser.restore(); }) From 5396cf6532c90e3a9d1dc4582fe8301dd989cbdb Mon Sep 17 00:00:00 2001 From: Stanney Date: Tue, 19 Oct 2021 12:22:01 +0800 Subject: [PATCH 010/198] add comments for A22 --- tests/A20.test.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/A20.test.js b/tests/A20.test.js index 5ed231a0..6ff35593 100644 --- a/tests/A20.test.js +++ b/tests/A20.test.js @@ -27,6 +27,7 @@ describe('# A20: 餐廳資訊整理:Dashboard', function() { context('# [Q1: Dashboard - 1 - controller / view / route]', () => { before(async() => { + // 模擬 user, restaurant, category, comment 資料 this.UserMock = dbMock.define('User', { id: 1, email: 'root@example.com', @@ -45,11 +46,12 @@ describe('# A20: 餐廳資訊整理:Dashboard', function() { id: 1, text: "gogogo" }) - - this.RestaurantMock.belongsTo(this.CategoryMock); + // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) this.RestaurantMock.findByPk = (id) => this.RestaurantMock.findOne({where: {id: id}}); + // 將 count 的 function 預設回傳假資料數目 1 this.CommentMock.count = () => 1; + // 將 restController 中的 User,Category,Restaurant,Comment 都用模擬資料取代 this.restController = proxyquire('../controllers/restController', { '../models': { User: this.UserMock, @@ -63,9 +65,10 @@ describe('# A20: 餐廳資訊整理:Dashboard', function() { it(" GET /restaurants/:id/dashboard ", async () => { const req = mockRequest({params: {id: 1}}); const res = mockResponse() - + // call getDashBoard function await this.restController.getDashBoard(req, res); + // 確認執行 getDasgBoard 之後,回傳的 res.render 的參數是否與模擬資料相同 res.render.getCall(0).args[0].should.equal('dashboard'); res.render.getCall(0).args[1].restaurant.name.should.equal('銷魂麵'); res.render.getCall(0).args[1].n_comments.should.equal(1); From dab9f1d2d186ca36be9826945321aeb94889bffd Mon Sep 17 00:00:00 2001 From: Stanney Date: Tue, 19 Oct 2021 12:22:16 +0800 Subject: [PATCH 011/198] add comments for A21 --- tests/A21.test.js | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/tests/A21.test.js b/tests/A21.test.js index b5f9dd0d..5a82f84a 100644 --- a/tests/A21.test.js +++ b/tests/A21.test.js @@ -23,33 +23,42 @@ const mockResponse = () => { } }; +// 模擬 Like 資料 let mockLikeData = [{ UserId: 1, RestaurantId: 2, }] describe('# A21: Like / Unlike', function () { - context('# Q1: 使用者可以 Like 餐廳', () => { before(() => { + // 模擬驗證資料 this.ensureAuthenticated = sinon.stub( helpers, 'ensureAuthenticated' ).returns(true); this.getUser = sinon.stub( helpers, 'getUser' ).returns({id: 1}); + + // this.mockLikeData = []; this.likeMock = dbMock.define('Like'); + // 因為 mock 中的 create 有問題,因此指向 upsert function, 這樣可以在 useHandler 中取得 create 呼叫 this.likeMock.create = this.likeMock.upsert; + // 模擬 Like.create and Like.finall 的 function this.likeMock.$queryInterface.$useHandler((query, queryOptions,done) => { if (query === 'upsert') { + // create 時會帶 UserId 跟 RestaurantId (ex: Like.create({ UserId: 1, RestaurantId: 2})) const {UserId, RestaurantId} = queryOptions[0]; + // 新增這個 Like 的資訊到模擬資料裡 this.mockLikeData.push({ UserId, RestaurantId }); + // 回傳模擬資料 return Promise.resolve(this.likeMock.build(this.mockLikeData)); } else if (query === 'findAll') { + // 回傳模擬資料 return Promise.resolve(this.mockLikeData); } }); @@ -63,14 +72,16 @@ describe('# A21: Like / Unlike', function () { const req = mockRequest({params: {id: 1, restaurantId: 2}}); const res = mockResponse(); + // 呼叫 userController 中的 addLike await this.userController.addLike(req, res); const likes = await this.likeMock.findAll(); - console.log(likes); + // 確認呼叫 addLike 後,模擬資料中的 like 資料會多一個 likes.should.have.lengthOf(1); }); after(async () => { + // 清除驗證資料 this.ensureAuthenticated.restore(); this.getUser.restore(); }) @@ -78,21 +89,25 @@ describe('# A21: Like / Unlike', function () { context('# Q1: 使用者可以 unLike 餐廳', () => { before(() => { + // 模擬驗證資料 this.ensureAuthenticated = sinon.stub( helpers, 'ensureAuthenticated' ).returns(true); this.getUser = sinon.stub( helpers, 'getUser' ).returns({id: 1}); + // 模擬 Like db 資料 this.likeMock = dbMock.define('Like', { id: 1, UserId: 1, RestaurantId: 2, }); - + // 模擬 Like.destroy and Like.finall 的 function this.likeMock.$queryInterface.$useHandler((query, queryOptions,done) => { if (query === 'destroy') { const {UserId, RestaurantId} = queryOptions[0].where; + // destroy 可以從 where 取得要刪除的資料 + // 因此就可以模擬將模擬資料中的資料刪除 mockLikeData = mockLikeData.filter(d => !(d.UserId === UserId && d.RestaurantId === RestaurantId)) return Promise.resolve(this.likeMock.build(mockLikeData)); @@ -107,16 +122,20 @@ describe('# A21: Like / Unlike', function () { }) it(" DELETE /like/:restaurantId ", async () => { + // 模擬發出 request, 帶入 params.id = 1, restaurantId = 2 const req = mockRequest({params: {id: 1, restaurantId: 2}}); const res = mockResponse(); + // call DELETE /like/:restaurantId route 對應的 userController.removeLike await this.userController.removeLike(req, res); + // 刪除後,模擬資料會是空的 const likes = await this.likeMock.findAll(); likes.should.have.lengthOf(0); }); after(async () => { + // 清除模擬驗證資料 this.ensureAuthenticated.restore(); this.getUser.restore(); }) From c7a336da58970fb98f516deb9739c49ee8635de2 Mon Sep 17 00:00:00 2001 From: Stanney Date: Tue, 19 Oct 2021 12:22:31 +0800 Subject: [PATCH 012/198] add comments for A22 --- tests/A22.test.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/tests/A22.test.js b/tests/A22.test.js index 0ee0c78f..ab41c162 100644 --- a/tests/A22.test.js +++ b/tests/A22.test.js @@ -22,7 +22,7 @@ const mockResponse = () => { render: sinon.spy(), } }; - +// 模擬的餐廳資料 let mockRestaurantData = [{ id: 1, name: 'Restaurant1', @@ -48,6 +48,7 @@ describe('# A22: TOP 10 人氣餐廳 ', function () { context('# [網址正確、畫面正常執行]', () => { before(async () => { + // 模擬驗證資料 this.ensureAuthenticated = sinon.stub( helpers, 'ensureAuthenticated' ).returns(true); @@ -60,11 +61,14 @@ describe('# A22: TOP 10 人氣餐廳 ', function () { request(app) .get('/restaurants/top') .end(function (err, res) { + // 檢查回傳的資料中,是否有 Top 10 人氣餐廳資訊 + res.text.should.include('Top 10 人氣餐廳') // 可以正確執行,沒有 error 即可通過 done() }); }); after(async () => { + // 清除模擬驗證資料 this.ensureAuthenticated.restore(); this.getUser.restore(); }) @@ -73,6 +77,7 @@ describe('# A22: TOP 10 人氣餐廳 ', function () { context('# [當你點擊畫面上的「加入最愛 / 移除最愛」按鈕時,會重新計算「收藏數」的數字]', () => { before(async () => { + // 模擬驗證資料 this.ensureAuthenticated = sinon.stub( helpers, 'ensureAuthenticated' ).returns(true); @@ -80,27 +85,33 @@ describe('# A22: TOP 10 人氣餐廳 ', function () { helpers, 'getUser' ).returns({ id: 1, Followings: [], FavoritedRestaurants: [] }); + // 模擬 Restaurant db 資料 this.restaurantMock = dbMock.define('Restaurant'); this.restaurantMock.$queryInterface.$useHandler((query, queryOptions,done) => { if (query === 'findAll') { return Promise.resolve(mockRestaurantData.map(d => this.restaurantMock.build(d))); } }); + + // 將 restController 中的 Restaurant db 取代成 Restaurant mock db this.restController = proxyquire('../controllers/restController', { '../models': { Restaurant: this.restaurantMock, } }); + // 模擬 Favorite db 資料 this.favoriteMock = dbMock.define('Favorite'); this.favoriteMock.create = this.favoriteMock.upsert; this.favoriteMock.$queryInterface.$useHandler((query, queryOptions,done) => { if (query === 'upsert') { + // 新增 favortie 資料到模擬資料 const {UserId, RestaurantId} = queryOptions[0]; const restaurant = mockRestaurantData.find(d => d.id === RestaurantId); restaurant.FavoritedUsers.push({UserId: UserId}); return Promise.resolve(mockRestaurantData.map(d => this.restaurantMock.build(d))); } else if (query === 'destroy') { + // 刪除模擬資料中的某一筆 favortie 資料 const {UserId, RestaurantId} = queryOptions[0].where; const restaurant = mockRestaurantData.find(d => d.id === RestaurantId); restaurant.FavoritedUsers = restaurant.FavoritedUsers.filter(d => !(d.UserId === UserId)) @@ -108,32 +119,42 @@ describe('# A22: TOP 10 人氣餐廳 ', function () { } }); + // 將 userController 中的 Favorite db 取代成 Favorite mock db this.userController = proxyquire('../controllers/userController', { '../models': {Favorite: this.favoriteMock} }) }) it(" POST favorite ", async () => { + // 模擬 request 新增 user id = 1, 喜愛 restaurant id = 2 const req = mockRequest({user: { FavoritedRestaurants: []}, params: {restaurantId: 2}}); const res = mockResponse(); + // call addFavorite await this.userController.addFavorite(req, res); + + // 確認 getTopRestaurant 取得餐廳 2 的 favorite 數量會不同 await this.restController.getTopRestaurant(req, res); res.render.getCall(0).args[1].restaurants[1].FavoritedCount.should.equal(1); }) it(" DELETE favorite ", async() => { + // 模擬 request 新增 user id = 1, 不喜愛 restaurant id = 1 const req = mockRequest({user: { FavoritedRestaurants: []}, params: {restaurantId: 1}}); const res = mockResponse(); + // call removeFavorite await this.userController.removeFavorite(req, res); + // 取得餐廳排序資料 await this.restController.getTopRestaurant(req, res); + // 確認餐廳排序有變,第一個餐廳資料 id = 2 res.render.getCall(0).args[1].restaurants[0].id.should.equal(2); }) after(async () => { + // 清除模擬驗證資料 this.ensureAuthenticated.restore(); this.getUser.restore(); }) From a17581143603f12a12983e74a508c8011501ae1d Mon Sep 17 00:00:00 2001 From: Carrot7712 Date: Wed, 27 Oct 2021 14:26:09 +0800 Subject: [PATCH 013/198] adjust coding style --- _helpers.js | 6 +- models/index.js | 20 ++-- routes/index.js | 1 - tests/A17.test.js | 149 +++++++++++++------------ tests/A19.test.js | 186 +++++++++++++++---------------- tests/A20.test.js | 65 ++++++----- tests/A21.test.js | 136 +++++++++++------------ tests/A22.test.js | 272 +++++++++++++++++++++++++--------------------- tests/index.js | 21 ++-- 9 files changed, 451 insertions(+), 405 deletions(-) diff --git a/_helpers.js b/_helpers.js index 798e5325..61e51b67 100644 --- a/_helpers.js +++ b/_helpers.js @@ -1,12 +1,12 @@ function ensureAuthenticated(req) { - return req.isAuthenticated(); + return req.isAuthenticated() } function getUser(req) { - return req.user; + return req.user } module.exports = { ensureAuthenticated, getUser, -}; \ No newline at end of file +} diff --git a/models/index.js b/models/index.js index 66860356..3d701d98 100644 --- a/models/index.js +++ b/models/index.js @@ -13,22 +13,28 @@ let sequelize if (config.use_env_variable) { 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 + ) } // 動態引入其他 models -fs - .readdirSync(__dirname) - .filter(file => { - return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js') +fs.readdirSync(__dirname) + .filter((file) => { + return ( + file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js' + ) }) - .forEach(file => { + .forEach((file) => { const model = sequelize['import'](path.join(__dirname, file)) db[model.name] = model }) // 設定 Models 之間的關聯 -Object.keys(db).forEach(modelName => { +Object.keys(db).forEach((modelName) => { if (db[modelName].associate) { db[modelName].associate(db) } diff --git a/routes/index.js b/routes/index.js index fc55e842..b20f151b 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,5 +1,4 @@ module.exports = (app) => { - app.get('/', (req, res) => { res.send('Hello World!') }) diff --git a/tests/A17.test.js b/tests/A17.test.js index 13aa8b71..6320dca3 100644 --- a/tests/A17.test.js +++ b/tests/A17.test.js @@ -2,30 +2,30 @@ const chai = require('chai') const request = require('supertest') const sinon = require('sinon') const should = chai.should() -const SequelizeMock = require('sequelize-mock'); -const proxyquire = require('proxyquire'); +const SequelizeMock = require('sequelize-mock') +const proxyquire = require('proxyquire') -const dbMock = new SequelizeMock(); +const dbMock = new SequelizeMock() -const app = require('../../app'); -const { classToInvokable } = require('sequelize/types/lib/utils'); +const app = require('../../app') +const { classToInvokable } = require('sequelize/types/lib/utils') const mockRequest = (query) => { return { ...query, - flash: sinon.spy() - }; -}; + flash: sinon.spy(), + } +} const mockResponse = () => { return { - redirect: sinon.spy() + redirect: sinon.spy(), } -}; +} describe('# A17', () => { - describe('登入測試: POST /signin', function(){ + describe('登入測試: POST /signin', function () { // 以下測試會發出真實的 request 測試資料庫是否有正確的 user 資料 - it('#1 密碼錯誤', function(done){ + it('#1 密碼錯誤', function (done) { request(app) .post('/signin') .type('urlencoded') @@ -34,7 +34,7 @@ describe('# A17', () => { .expect(302, done) }) - it('#2 帳號錯誤', function(done){ + it('#2 帳號錯誤', function (done) { request(app) .post('/signin') .type('urlencoded') @@ -43,7 +43,7 @@ describe('# A17', () => { .expect(302, done) }) - it('#3 成功登入', function(done){ + it('#3 成功登入', function (done) { request(app) .post('/signin') .type('urlencoded') @@ -51,7 +51,7 @@ describe('# A17', () => { .expect('Location', '/restaurants') .expect(302, done) }) - }); + }) describe('# A17: 使用者權限管理', function () { before(() => { @@ -61,29 +61,30 @@ describe('# A17', () => { email: 'root@example.com', name: 'admin', isAdmin: false, - }); + }) // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) - this.UserMock.findByPk = (id) => this.UserMock.findOne({where: {id: id}}); + this.UserMock.findByPk = (id) => + this.UserMock.findOne({ where: { id: id } }) // 將 adminController 中的 User db 取代成 User mock db this.adminController = proxyquire('../../controllers/adminController', { - '../models': { User: this.UserMock } - }); + '../models': { User: this.UserMock }, + }) }) - + context('# [顯示使用者清單]', () => { - it(" GET /admin/users ", async () => { + it(' GET /admin/users ', async () => { // 模擬 request & response - const req = mockRequest(); - const res = mockResponse(); + const req = mockRequest() + const res = mockResponse() // 測試 adminController.getUsers function - await this.adminController.getUsers(req, res); + await this.adminController.getUsers(req, res) // render 裡的第二個參數,回傳了 users, 名稱必須跟假資料一樣是 admin - res.render.getCall(0).args[1].users[0].name.should.equal('admin'); - }); + res.render.getCall(0).args[1].users[0].name.should.equal('admin') + }) }) - + context('# [修改使用者權限] for admin', () => { before(() => { // 模擬 user db 資料: UserMock @@ -92,72 +93,84 @@ describe('# A17', () => { email: 'root@example.com', name: 'admin', isAdmin: true, - }); + }) // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) // 將 count 的 function 預設回傳假資料數目 1 - this.UserMock.count = () => 1; - this.UserMock.findByPk = (id) => this.UserMock.findOne({where: {id: id}}); - // 將 adminController 中的 User db 取代成 User mock db + this.UserMock.count = () => 1 + this.UserMock.findByPk = (id) => + this.UserMock.findOne({ where: { id: id } }) + // 將 adminController 中的 User db 取代成 User mock db this.adminController = proxyquire('../../controllers/adminController', { - '../models': { User: this.UserMock } - }); + '../models': { User: this.UserMock }, + }) }) - it(" PUT /admin/users/:id/toggleAdmin ", async () => { - const req = mockRequest({params: {id: 1}}); - const res = mockResponse(); + it(' PUT /admin/users/:id/toggleAdmin ', async () => { + const req = mockRequest({ params: { id: 1 } }) + const res = mockResponse() // 測試 adminController.toggleAdmin function - await this.adminController.toggleAdmin(req, res); + await this.adminController.toggleAdmin(req, res) // 執行完 toggleAdmin 後,res.flash 有被呼叫而且呼叫的訊息正確 - req.flash.calledWith('error_messages', 'core manager can not be changed').should.be.true; + req.flash.calledWith( + 'error_messages', + 'core manager can not be changed' + ).should.be.true // 執行完 toggleAmin 後 res.redirect 有被呼叫而且 redirect 到 back - res.redirect.calledWith('back').should.be.true; - }); + res.redirect.calledWith('back').should.be.true + }) }) context('# [修改使用者權限] for user', () => { before(() => { - this.UserMock = dbMock.define('User', { - id: 1, - email: 'user@example.com', - name: 'user', - isAdmin: false, - }, { - instanceMethods: { - // 模擬 update 函數,並依據傳入的變數,跟著修正,如果傳入 {isAdmin: true} - update: (changes) => { - this.UserMock._defaults = {...changes}; - return Promise.resolve(); - } + this.UserMock = dbMock.define( + 'User', + { + id: 1, + email: 'user@example.com', + name: 'user', + isAdmin: false, + }, + { + instanceMethods: { + // 模擬 update 函數,並依據傳入的變數,跟著修正,如果傳入 {isAdmin: true} + update: (changes) => { + this.UserMock._defaults = { ...changes } + return Promise.resolve() + }, + }, } - }); + ) + + this.UserMock.count = () => 1 + this.UserMock.findByPk = (id) => + this.UserMock.findOne({ where: { id: id } }) - this.UserMock.count = () => 1; - this.UserMock.findByPk = (id) => this.UserMock.findOne({where: {id: id}}); - this.adminController = proxyquire('../../controllers/adminController', { - '../models': { User: this.UserMock } - }); + '../models': { User: this.UserMock }, + }) }) - it(" PUT /admin/users/:id/toggleAdmin ", async (done) => { - const req = mockRequest({params: {id: 1}}); - const res = mockResponse(); + it(' PUT /admin/users/:id/toggleAdmin ', async (done) => { + const req = mockRequest({ params: { id: 1 } }) + const res = mockResponse() - await this.adminController.toggleAdmin(req, res); + await this.adminController.toggleAdmin(req, res) - req.flash.calledWith('success_messages', 'user was successfully to update').should.be.true; - res.redirect.calledWith('/admin/users').should.be.true; + req.flash.calledWith( + 'success_messages', + 'user was successfully to update' + ).should.be.true + res.redirect.calledWith('/admin/users').should.be.true // 執行完 toggleAmin 後,因為假資料中使用者是 user@example.com,因此可以修改權限 isAdmin 從 false -> true // 所以這邊將假資料撈出,比對看是否有修改到 - const user = await this.UserMock.findOne({where: {id: 1}}); - user.isAdmin.should.equal(true); + const user = await this.UserMock.findOne({ where: { id: 1 } }) + user.isAdmin.should.equal(true) done() - }); + }) }) }) -}) \ No newline at end of file +}) diff --git a/tests/A19.test.js b/tests/A19.test.js index fd8cf8d7..d00fa12f 100644 --- a/tests/A19.test.js +++ b/tests/A19.test.js @@ -6,178 +6,182 @@ const should = chai.should() const app = require('../../app') const routes = require('../../routes/index') const db = require('../../models') -const helpers = require('../../_helpers'); -const SequelizeMock = require('sequelize-mock'); -const proxyquire = require('proxyquire'); +const helpers = require('../../_helpers') +const SequelizeMock = require('sequelize-mock') +const proxyquire = require('proxyquire') -const dbMock = new SequelizeMock(); +const dbMock = new SequelizeMock() const mockRequest = (query) => { return { ...query, - flash: sinon.spy() - }; -}; + flash: sinon.spy(), + } +} const mockResponse = () => { return { redirect: sinon.spy(), render: sinon.spy(), } -}; +} describe('# A19', () => { - describe('# A19: 建立 User Profile', function() { + describe('# A19: 建立 User Profile', function () { context('# [瀏覽 Profile]', () => { before(() => { // 模擬驗證資料 - this.ensureAuthenticated = sinon.stub( - helpers, 'ensureAuthenticated' - ).returns(true); - this.getUser = sinon.stub( - helpers, 'getUser' - ).returns({id: 1}); + this.ensureAuthenticated = sinon + .stub(helpers, 'ensureAuthenticated') + .returns(true) + this.getUser = sinon.stub(helpers, 'getUser').returns({ id: 1 }) // 模擬 User db 資料 this.UserMock = dbMock.define('User', { id: 1, email: 'root@example.com', name: 'admin', isAdmin: false, - }); + }) // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) - this.UserMock.findByPk = (id) => this.UserMock.findOne({where: {id: id}}); + this.UserMock.findByPk = (id) => + this.UserMock.findOne({ where: { id: id } }) // 將 userController 中的 User db 取代成 User mock db this.userController = proxyquire('../../controllers/userController', { - '../models': { User: this.UserMock } - }); + '../models': { User: this.UserMock }, + }) }) - it(" GET /users/:id ", async () => { + it(' GET /users/:id ', async () => { // 模擬發出 request, 帶入 params.id = 1 - const req = mockRequest({params: {id: 1}}); - const res = mockResponse(); + const req = mockRequest({ params: { id: 1 } }) + const res = mockResponse() // 測試 userController.getUser - await this.userController.getUser(req, res); + await this.userController.getUser(req, res) // 執行完後,call res.render 的參數測試 // 1. 第一個參數是不是 profile // 2. 第二個參數裡回傳的資料有 userData 而且 name 是 admin - res.render.getCall(0).args[0].should.equal('profile'); - res.render.getCall(0).args[1].userData.name.should.equal("admin"); - }); + res.render.getCall(0).args[0].should.equal('profile') + res.render.getCall(0).args[1].userData.name.should.equal('admin') + }) after(async () => { // 清除模擬驗證資料 - this.ensureAuthenticated.restore(); - this.getUser.restore(); + this.ensureAuthenticated.restore() + this.getUser.restore() }) }) context('# [瀏覽編輯 Profile 頁面]', () => { before(() => { // 製作假驗證資料 - this.ensureAuthenticated = sinon.stub( - helpers, 'ensureAuthenticated' - ).returns(true); - this.getUser = sinon.stub( - helpers, 'getUser' - ).returns({id: 1}); - - // 模擬 User db 資料 + this.ensureAuthenticated = sinon + .stub(helpers, 'ensureAuthenticated') + .returns(true) + this.getUser = sinon.stub(helpers, 'getUser').returns({ id: 1 }) + + // 模擬 User db 資料 this.UserMock = dbMock.define('User', { id: 1, email: 'root@example.com', name: 'admin', isAdmin: false, - }); + }) // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) - this.UserMock.findByPk = (id) => this.UserMock.findOne({where: {id: id}}); - + this.UserMock.findByPk = (id) => + this.UserMock.findOne({ where: { id: id } }) + // 將 userController 中的 User db 取代成 User mock db this.userController = proxyquire('../../controllers/userController', { - '../models': { User: this.UserMock } - }); + '../models': { User: this.UserMock }, + }) }) - it(" GET /users/:id/edit ", async () => { + it(' GET /users/:id/edit ', async () => { // 模擬 request & response - const req = mockRequest({params: {id: 1}}); - const res = mockResponse(); - + const req = mockRequest({ params: { id: 1 } }) + const res = mockResponse() + // 測試 adminController.editUser function - await this.userController.editUser(req, res); + await this.userController.editUser(req, res) // 執行完 editUser 後,call res.render 的參數測試 // 1. 第一個參數是不是 edit // 2. 第二個參數裡回傳的資料有 user 而且 name 是 admin - res.render.getCall(0).args[0].should.equal('edit'); - res.render.getCall(0).args[1].user.name.should.equal("admin"); - }); + res.render.getCall(0).args[0].should.equal('edit') + res.render.getCall(0).args[1].user.name.should.equal('admin') + }) after(async () => { // 清除模擬驗證資料 - this.ensureAuthenticated.restore(); - this.getUser.restore(); + this.ensureAuthenticated.restore() + this.getUser.restore() }) }) context('# [編輯 Profile]', () => { - before(async() => { + before(async () => { // 模擬 request & response - this.ensureAuthenticated = sinon.stub( - helpers, 'ensureAuthenticated' - ).returns(true); - this.getUser = sinon.stub( - helpers, 'getUser' - ).returns({id: 1}); + this.ensureAuthenticated = sinon + .stub(helpers, 'ensureAuthenticated') + .returns(true) + this.getUser = sinon.stub(helpers, 'getUser').returns({ id: 1 }) // 製作假 db 資料: UserMock - this.UserMock = dbMock.define('User', { - id: 1, - email: 'root@example.com', - name: 'admin', - isAdmin: false, - }, { - instanceMethods: { - // 模擬 update 函數,並依據傳入的變數,跟著修正,如果傳入 {isAdmin: true} - update: (changes) => { - this.UserMock._defaults = {...changes}; - return Promise.resolve(); - } + this.UserMock = dbMock.define( + 'User', + { + id: 1, + email: 'root@example.com', + name: 'admin', + isAdmin: false, + }, + { + instanceMethods: { + // 模擬 update 函數,並依據傳入的變數,跟著修正,如果傳入 {isAdmin: true} + update: (changes) => { + this.UserMock._defaults = { ...changes } + return Promise.resolve() + }, + }, } - }); + ) // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) - this.UserMock.findByPk = (id) => this.UserMock.findOne({where: {id: id}}); - // 將 userController 中的 User db 取代成 User mock db + this.UserMock.findByPk = (id) => + this.UserMock.findOne({ where: { id: id } }) + // 將 userController 中的 User db 取代成 User mock db this.userController = proxyquire('../../controllers/userController', { - '../models': { User: this.UserMock } - }); + '../models': { User: this.UserMock }, + }) }) - it(" PUT /users/:id ", async () => { + it(' PUT /users/:id ', async () => { // 模擬發送 request, 帶了 params.id = 1, body.name = amdin2, body.email = admin_test@gmail.com const req = mockRequest({ - params: {id: 1}, - body: {name: "admin2", email: "admin_test@gmail.com"} - }); - const res = mockResponse(); + params: { id: 1 }, + body: { name: 'admin2', email: 'admin_test@gmail.com' }, + }) + const res = mockResponse() // 修改模擬資料, call putUser function - await this.userController.putUser(req, res); - + await this.userController.putUser(req, res) + // 測試成功修改 user 資料後,是否有回傳正確訊息 - req.flash.calledWith('success_messages', 'user was updated successfully').should.be.true; + req.flash.calledWith( + 'success_messages', + 'user was updated successfully' + ).should.be.true // 測試成功修改 user 資料後,是否有導向正確 route - res.redirect.calledWith('/users/1').should.be.true; + res.redirect.calledWith('/users/1').should.be.true // 修改後,測試模擬資料中是否有成功修正, name = amdin2, email = admin_test@gmail.com - const user = await this.UserMock.findOne({where: {id: 1}}); - user.name.should.equal('admin2'); - user.email.should.equal('admin_test@gmail.com'); - }); + const user = await this.UserMock.findOne({ where: { id: 1 } }) + user.name.should.equal('admin2') + user.email.should.equal('admin_test@gmail.com') + }) after(async () => { // 清除模擬驗證資料 - this.ensureAuthenticated.restore(); - this.getUser.restore(); + this.ensureAuthenticated.restore() + this.getUser.restore() }) }) }) -}) \ No newline at end of file +}) diff --git a/tests/A20.test.js b/tests/A20.test.js index 6ff35593..e1d908b3 100644 --- a/tests/A20.test.js +++ b/tests/A20.test.js @@ -4,74 +4,73 @@ const sinon = require('sinon') const should = chai.should() const helpers = require('../_helpers') -const SequelizeMock = require('sequelize-mock'); -const proxyquire = require('proxyquire'); - -const dbMock = new SequelizeMock(); +const SequelizeMock = require('sequelize-mock') +const proxyquire = require('proxyquire') +const dbMock = new SequelizeMock() const mockRequest = (query) => { return { ...query, - flash: sinon.spy() - }; -}; + flash: sinon.spy(), + } +} const mockResponse = () => { return { redirect: sinon.spy(), render: sinon.spy(), } -}; +} -describe('# A20: 餐廳資訊整理:Dashboard', function() { - +describe('# A20: 餐廳資訊整理:Dashboard', function () { context('# [Q1: Dashboard - 1 - controller / view / route]', () => { - before(async() => { + before(async () => { // 模擬 user, restaurant, category, comment 資料 this.UserMock = dbMock.define('User', { id: 1, email: 'root@example.com', name: 'admin', isAdmin: false, - }); + }) this.RestaurantMock = dbMock.define('Restaurant', { id: 1, - name: '銷魂麵' - }); + name: '銷魂麵', + }) this.CategoryMock = dbMock.define('Category', { id: 1, - name: '食物' - }); + name: '食物', + }) this.CommentMock = dbMock.define('Comment', { id: 1, - text: "gogogo" + text: 'gogogo', }) // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) - this.RestaurantMock.findByPk = (id) => this.RestaurantMock.findOne({where: {id: id}}); + this.RestaurantMock.findByPk = (id) => + this.RestaurantMock.findOne({ where: { id: id } }) // 將 count 的 function 預設回傳假資料數目 1 - this.CommentMock.count = () => 1; - + this.CommentMock.count = () => 1 + // 將 restController 中的 User,Category,Restaurant,Comment 都用模擬資料取代 this.restController = proxyquire('../controllers/restController', { - '../models': { - User: this.UserMock, - Category: this.CategoryMock, + '../models': { + User: this.UserMock, + Category: this.CategoryMock, Restaurant: this.RestaurantMock, Comment: this.CommentMock, - } - }); + }, + }) }) - it(" GET /restaurants/:id/dashboard ", async () => { - const req = mockRequest({params: {id: 1}}); + it(' GET /restaurants/:id/dashboard ', async () => { + const req = mockRequest({ params: { id: 1 } }) const res = mockResponse() // call getDashBoard function - await this.restController.getDashBoard(req, res); + await this.restController.getDashBoard(req, res) // 確認執行 getDasgBoard 之後,回傳的 res.render 的參數是否與模擬資料相同 - res.render.getCall(0).args[0].should.equal('dashboard'); - res.render.getCall(0).args[1].restaurant.name.should.equal('銷魂麵'); - res.render.getCall(0).args[1].n_comments.should.equal(1); - }); + res.render.getCall(0).args[0].should.equal('dashboard') + res.render.getCall(0).args[1].restaurant.name.should.equal('銷魂麵') + res.render.getCall(0).args[1].n_comments.should.equal(1) + }) }) -}) \ No newline at end of file +}) diff --git a/tests/A21.test.js b/tests/A21.test.js index 5a82f84a..f8a30bfa 100644 --- a/tests/A21.test.js +++ b/tests/A21.test.js @@ -5,139 +5,139 @@ const should = chai.should() const db = require('../models') const helpers = require('../_helpers') -const SequelizeMock = require('sequelize-mock'); -const proxyquire = require('proxyquire'); +const SequelizeMock = require('sequelize-mock') +const proxyquire = require('proxyquire') -const dbMock = new SequelizeMock({autoQueryFallback: false}); +const dbMock = new SequelizeMock({ autoQueryFallback: false }) const mockRequest = (query) => { return { ...query, - flash: sinon.spy() - }; -}; + flash: sinon.spy(), + } +} const mockResponse = () => { return { redirect: sinon.spy(), render: sinon.spy(), } -}; +} // 模擬 Like 資料 -let mockLikeData = [{ - UserId: 1, - RestaurantId: 2, -}] +let mockLikeData = [ + { + UserId: 1, + RestaurantId: 2, + }, +] describe('# A21: Like / Unlike', function () { context('# Q1: 使用者可以 Like 餐廳', () => { before(() => { // 模擬驗證資料 - this.ensureAuthenticated = sinon.stub( - helpers, 'ensureAuthenticated' - ).returns(true); - this.getUser = sinon.stub( - helpers, 'getUser' - ).returns({id: 1}); - - // - this.mockLikeData = []; - this.likeMock = dbMock.define('Like'); + this.ensureAuthenticated = sinon + .stub(helpers, 'ensureAuthenticated') + .returns(true) + this.getUser = sinon.stub(helpers, 'getUser').returns({ id: 1 }) + + // + this.mockLikeData = [] + this.likeMock = dbMock.define('Like') // 因為 mock 中的 create 有問題,因此指向 upsert function, 這樣可以在 useHandler 中取得 create 呼叫 - this.likeMock.create = this.likeMock.upsert; + this.likeMock.create = this.likeMock.upsert // 模擬 Like.create and Like.finall 的 function - this.likeMock.$queryInterface.$useHandler((query, queryOptions,done) => { + this.likeMock.$queryInterface.$useHandler((query, queryOptions, done) => { if (query === 'upsert') { // create 時會帶 UserId 跟 RestaurantId (ex: Like.create({ UserId: 1, RestaurantId: 2})) - const {UserId, RestaurantId} = queryOptions[0]; - + const { UserId, RestaurantId } = queryOptions[0] + // 新增這個 Like 的資訊到模擬資料裡 - this.mockLikeData.push({ UserId, RestaurantId }); - + this.mockLikeData.push({ UserId, RestaurantId }) + // 回傳模擬資料 - return Promise.resolve(this.likeMock.build(this.mockLikeData)); + return Promise.resolve(this.likeMock.build(this.mockLikeData)) } else if (query === 'findAll') { // 回傳模擬資料 - return Promise.resolve(this.mockLikeData); + return Promise.resolve(this.mockLikeData) } - }); + }) this.userController = proxyquire('../controllers/userController', { - '../models': {Like: this.likeMock} - }); + '../models': { Like: this.likeMock }, + }) }) - it(" POST /like/:restaurantId ", async () => { - const req = mockRequest({params: {id: 1, restaurantId: 2}}); - const res = mockResponse(); + it(' POST /like/:restaurantId ', async () => { + const req = mockRequest({ params: { id: 1, restaurantId: 2 } }) + const res = mockResponse() // 呼叫 userController 中的 addLike - await this.userController.addLike(req, res); + await this.userController.addLike(req, res) - const likes = await this.likeMock.findAll(); + const likes = await this.likeMock.findAll() // 確認呼叫 addLike 後,模擬資料中的 like 資料會多一個 - likes.should.have.lengthOf(1); - }); + likes.should.have.lengthOf(1) + }) after(async () => { // 清除驗證資料 - this.ensureAuthenticated.restore(); - this.getUser.restore(); + this.ensureAuthenticated.restore() + this.getUser.restore() }) }) context('# Q1: 使用者可以 unLike 餐廳', () => { before(() => { // 模擬驗證資料 - this.ensureAuthenticated = sinon.stub( - helpers, 'ensureAuthenticated' - ).returns(true); - this.getUser = sinon.stub( - helpers, 'getUser' - ).returns({id: 1}); + this.ensureAuthenticated = sinon + .stub(helpers, 'ensureAuthenticated') + .returns(true) + this.getUser = sinon.stub(helpers, 'getUser').returns({ id: 1 }) // 模擬 Like db 資料 this.likeMock = dbMock.define('Like', { id: 1, UserId: 1, RestaurantId: 2, - }); - // 模擬 Like.destroy and Like.finall 的 function - this.likeMock.$queryInterface.$useHandler((query, queryOptions,done) => { + }) + // 模擬 Like.destroy and Like.finall 的 function + this.likeMock.$queryInterface.$useHandler((query, queryOptions, done) => { if (query === 'destroy') { - const {UserId, RestaurantId} = queryOptions[0].where; + const { UserId, RestaurantId } = queryOptions[0].where // destroy 可以從 where 取得要刪除的資料 // 因此就可以模擬將模擬資料中的資料刪除 - mockLikeData = mockLikeData.filter(d => !(d.UserId === UserId && d.RestaurantId === RestaurantId)) + mockLikeData = mockLikeData.filter( + (d) => !(d.UserId === UserId && d.RestaurantId === RestaurantId) + ) - return Promise.resolve(this.likeMock.build(mockLikeData)); + return Promise.resolve(this.likeMock.build(mockLikeData)) } else if (query === 'findAll') { - return Promise.resolve(mockLikeData); + return Promise.resolve(mockLikeData) } - }); + }) this.userController = proxyquire('../controllers/userController', { - '../models': {Like: this.likeMock} - }); + '../models': { Like: this.likeMock }, + }) }) - it(" DELETE /like/:restaurantId ", async () => { + it(' DELETE /like/:restaurantId ', async () => { // 模擬發出 request, 帶入 params.id = 1, restaurantId = 2 - const req = mockRequest({params: {id: 1, restaurantId: 2}}); - const res = mockResponse(); + const req = mockRequest({ params: { id: 1, restaurantId: 2 } }) + const res = mockResponse() // call DELETE /like/:restaurantId route 對應的 userController.removeLike - await this.userController.removeLike(req, res); + await this.userController.removeLike(req, res) // 刪除後,模擬資料會是空的 - const likes = await this.likeMock.findAll(); - likes.should.have.lengthOf(0); - }); + const likes = await this.likeMock.findAll() + likes.should.have.lengthOf(0) + }) after(async () => { // 清除模擬驗證資料 - this.ensureAuthenticated.restore(); - this.getUser.restore(); + this.ensureAuthenticated.restore() + this.getUser.restore() }) - }); -}) \ No newline at end of file + }) +}) diff --git a/tests/A22.test.js b/tests/A22.test.js index ab41c162..9791f74e 100644 --- a/tests/A22.test.js +++ b/tests/A22.test.js @@ -4,161 +4,189 @@ const sinon = require('sinon') const should = chai.should() const app = require('../app') -const helpers = require('../_helpers'); -const SequelizeMock = require('sequelize-mock'); -const proxyquire = require('proxyquire'); +const helpers = require('../_helpers') +const SequelizeMock = require('sequelize-mock') +const proxyquire = require('proxyquire') -const dbMock = new SequelizeMock({autoQueryFallback: false}); +const dbMock = new SequelizeMock({ autoQueryFallback: false }) const mockRequest = (query) => { return { ...query, - flash: sinon.spy() - }; -}; + flash: sinon.spy(), + } +} const mockResponse = () => { return { redirect: sinon.spy(), render: sinon.spy(), } -}; +} // 模擬的餐廳資料 -let mockRestaurantData = [{ - id: 1, - name: 'Restaurant1', - tel: 'tel', - address: 'address', - opening_hours: 'opening_hours', - description: "test description", - FavoritedUsers: [{ - UserId: 1, - }] -}, { - id: 2, - name: 'Restaurant2', - tel: 'tel', - address: 'address', - opening_hours: 'opening_hours', - description: 'description', - CategoryId: 1, - FavoritedUsers: [] -}] +let mockRestaurantData = [ + { + id: 1, + name: 'Restaurant1', + tel: 'tel', + address: 'address', + opening_hours: 'opening_hours', + description: 'test description', + FavoritedUsers: [ + { + UserId: 1, + }, + ], + }, + { + id: 2, + name: 'Restaurant2', + tel: 'tel', + address: 'address', + opening_hours: 'opening_hours', + description: 'description', + CategoryId: 1, + FavoritedUsers: [], + }, +] describe('# A22: TOP 10 人氣餐廳 ', function () { - context('# [網址正確、畫面正常執行]', () => { before(async () => { // 模擬驗證資料 - this.ensureAuthenticated = sinon.stub( - helpers, 'ensureAuthenticated' - ).returns(true); - this.getUser = sinon.stub( - helpers, 'getUser' - ).returns({ id: 1, Followings: [], FavoritedRestaurants: [] }); + this.ensureAuthenticated = sinon + .stub(helpers, 'ensureAuthenticated') + .returns(true) + this.getUser = sinon + .stub(helpers, 'getUser') + .returns({ id: 1, Followings: [], FavoritedRestaurants: [] }) }) - it(" GET /restaurants/top ", (done) => { + it(' GET /restaurants/top ', (done) => { request(app) .get('/restaurants/top') .end(function (err, res) { // 檢查回傳的資料中,是否有 Top 10 人氣餐廳資訊 - res.text.should.include('Top 10 人氣餐廳') // 可以正確執行,沒有 error 即可通過 + res.text.should.include('Top 10 人氣餐廳') // 可以正確執行,沒有 error 即可通過 done() - }); - }); + }) + }) after(async () => { // 清除模擬驗證資料 - this.ensureAuthenticated.restore(); - this.getUser.restore(); + this.ensureAuthenticated.restore() + this.getUser.restore() }) - }) - context('# [當你點擊畫面上的「加入最愛 / 移除最愛」按鈕時,會重新計算「收藏數」的數字]', () => { - before(async () => { - // 模擬驗證資料 - this.ensureAuthenticated = sinon.stub( - helpers, 'ensureAuthenticated' - ).returns(true); - this.getUser = sinon.stub( - helpers, 'getUser' - ).returns({ id: 1, Followings: [], FavoritedRestaurants: [] }); - - // 模擬 Restaurant db 資料 - this.restaurantMock = dbMock.define('Restaurant'); - this.restaurantMock.$queryInterface.$useHandler((query, queryOptions,done) => { - if (query === 'findAll') { - return Promise.resolve(mockRestaurantData.map(d => this.restaurantMock.build(d))); - } - }); - - // 將 restController 中的 Restaurant db 取代成 Restaurant mock db - this.restController = proxyquire('../controllers/restController', { - '../models': { - Restaurant: this.restaurantMock, - } - }); - - // 模擬 Favorite db 資料 - this.favoriteMock = dbMock.define('Favorite'); - this.favoriteMock.create = this.favoriteMock.upsert; - this.favoriteMock.$queryInterface.$useHandler((query, queryOptions,done) => { - if (query === 'upsert') { - // 新增 favortie 資料到模擬資料 - const {UserId, RestaurantId} = queryOptions[0]; - const restaurant = mockRestaurantData.find(d => d.id === RestaurantId); - restaurant.FavoritedUsers.push({UserId: UserId}); - return Promise.resolve(mockRestaurantData.map(d => this.restaurantMock.build(d))); - } else if (query === 'destroy') { - // 刪除模擬資料中的某一筆 favortie 資料 - const {UserId, RestaurantId} = queryOptions[0].where; - const restaurant = mockRestaurantData.find(d => d.id === RestaurantId); - restaurant.FavoritedUsers = restaurant.FavoritedUsers.filter(d => !(d.UserId === UserId)) - return Promise.resolve(mockRestaurantData.map(d => this.restaurantMock.build(d))); - } - }); - - // 將 userController 中的 Favorite db 取代成 Favorite mock db - this.userController = proxyquire('../controllers/userController', { - '../models': {Favorite: this.favoriteMock} + context( + '# [當你點擊畫面上的「加入最愛 / 移除最愛」按鈕時,會重新計算「收藏數」的數字]', + () => { + before(async () => { + // 模擬驗證資料 + this.ensureAuthenticated = sinon + .stub(helpers, 'ensureAuthenticated') + .returns(true) + this.getUser = sinon + .stub(helpers, 'getUser') + .returns({ id: 1, Followings: [], FavoritedRestaurants: [] }) + + // 模擬 Restaurant db 資料 + this.restaurantMock = dbMock.define('Restaurant') + this.restaurantMock.$queryInterface.$useHandler( + (query, queryOptions, done) => { + if (query === 'findAll') { + return Promise.resolve( + mockRestaurantData.map((d) => this.restaurantMock.build(d)) + ) + } + } + ) + + // 將 restController 中的 Restaurant db 取代成 Restaurant mock db + this.restController = proxyquire('../controllers/restController', { + '../models': { + Restaurant: this.restaurantMock, + }, + }) + + // 模擬 Favorite db 資料 + this.favoriteMock = dbMock.define('Favorite') + this.favoriteMock.create = this.favoriteMock.upsert + this.favoriteMock.$queryInterface.$useHandler( + (query, queryOptions, done) => { + if (query === 'upsert') { + // 新增 favortie 資料到模擬資料 + const { UserId, RestaurantId } = queryOptions[0] + const restaurant = mockRestaurantData.find( + (d) => d.id === RestaurantId + ) + restaurant.FavoritedUsers.push({ UserId: UserId }) + return Promise.resolve( + mockRestaurantData.map((d) => this.restaurantMock.build(d)) + ) + } else if (query === 'destroy') { + // 刪除模擬資料中的某一筆 favortie 資料 + const { UserId, RestaurantId } = queryOptions[0].where + const restaurant = mockRestaurantData.find( + (d) => d.id === RestaurantId + ) + restaurant.FavoritedUsers = restaurant.FavoritedUsers.filter( + (d) => !(d.UserId === UserId) + ) + return Promise.resolve( + mockRestaurantData.map((d) => this.restaurantMock.build(d)) + ) + } + } + ) + + // 將 userController 中的 Favorite db 取代成 Favorite mock db + this.userController = proxyquire('../controllers/userController', { + '../models': { Favorite: this.favoriteMock }, + }) }) - }) - - it(" POST favorite ", async () => { - // 模擬 request 新增 user id = 1, 喜愛 restaurant id = 2 - const req = mockRequest({user: { FavoritedRestaurants: []}, params: {restaurantId: 2}}); - const res = mockResponse(); - - // call addFavorite - await this.userController.addFavorite(req, res); - - // 確認 getTopRestaurant 取得餐廳 2 的 favorite 數量會不同 - await this.restController.getTopRestaurant(req, res); - res.render.getCall(0).args[1].restaurants[1].FavoritedCount.should.equal(1); - }) - - it(" DELETE favorite ", async() => { - // 模擬 request 新增 user id = 1, 不喜愛 restaurant id = 1 - const req = mockRequest({user: { FavoritedRestaurants: []}, params: {restaurantId: 1}}); - const res = mockResponse(); + it(' POST favorite ', async () => { + // 模擬 request 新增 user id = 1, 喜愛 restaurant id = 2 + const req = mockRequest({ + user: { FavoritedRestaurants: [] }, + params: { restaurantId: 2 }, + }) + const res = mockResponse() - // call removeFavorite - await this.userController.removeFavorite(req, res); - // 取得餐廳排序資料 - await this.restController.getTopRestaurant(req, res); + // call addFavorite + await this.userController.addFavorite(req, res) - // 確認餐廳排序有變,第一個餐廳資料 id = 2 - res.render.getCall(0).args[1].restaurants[0].id.should.equal(2); - }) + // 確認 getTopRestaurant 取得餐廳 2 的 favorite 數量會不同 + await this.restController.getTopRestaurant(req, res) - after(async () => { - // 清除模擬驗證資料 - this.ensureAuthenticated.restore(); - this.getUser.restore(); - }) + res.render + .getCall(0) + .args[1].restaurants[1].FavoritedCount.should.equal(1) + }) - }) + it(' DELETE favorite ', async () => { + // 模擬 request 新增 user id = 1, 不喜愛 restaurant id = 1 + const req = mockRequest({ + user: { FavoritedRestaurants: [] }, + params: { restaurantId: 1 }, + }) + const res = mockResponse() + + // call removeFavorite + await this.userController.removeFavorite(req, res) + // 取得餐廳排序資料 + await this.restController.getTopRestaurant(req, res) + + // 確認餐廳排序有變,第一個餐廳資料 id = 2 + res.render.getCall(0).args[1].restaurants[0].id.should.equal(2) + }) -}) \ No newline at end of file + after(async () => { + // 清除模擬驗證資料 + this.ensureAuthenticated.restore() + this.getUser.restore() + }) + } + ) +}) diff --git a/tests/index.js b/tests/index.js index b1d92960..57535e46 100644 --- a/tests/index.js +++ b/tests/index.js @@ -2,17 +2,14 @@ const request = require('supertest') const app = require('../app') -describe('# 測試環境初始化', function() { - +describe('# 測試環境初始化', function () { context('# First Test Case', () => { - - it(" GET /admin/users ", (done) => { - request(app) - .get('/') - .end(function(err, res) { - done() - }); - }); - + it(' GET /admin/users ', (done) => { + request(app) + .get('/') + .end(function (err, res) { + done() + }) + }) }) -}) \ No newline at end of file +}) From 39950b9594308b07a78471d1db7638fad7f7f0da Mon Sep 17 00:00:00 2001 From: Carrot7712 Date: Wed, 27 Oct 2021 22:17:00 +0800 Subject: [PATCH 014/198] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=AE=8A=E6=95=B8?= =?UTF-8?q?=E5=90=8D=E8=88=87=E8=A8=BB=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/A17.test.js | 77 +++++++++++++++++++++++++++++------------------ tests/A19.test.js | 64 +++++++++++++++++++++------------------ tests/A20.test.js | 15 ++++++--- tests/A21.test.js | 51 +++++++++++++++++-------------- tests/A22.test.js | 75 +++++++++++++++++++++++++-------------------- 5 files changed, 164 insertions(+), 118 deletions(-) diff --git a/tests/A17.test.js b/tests/A17.test.js index 6320dca3..03a6b21e 100644 --- a/tests/A17.test.js +++ b/tests/A17.test.js @@ -23,45 +23,54 @@ const mockResponse = () => { } describe('# A17', () => { - describe('登入測試: POST /signin', function () { - // 以下測試會發出真實的 request 測試資料庫是否有正確的 user 資料 - it('#1 密碼錯誤', function (done) { + describe('登入測試: POST /signin', function(){ + // 以下測試會發出請求,測試資料庫內是否有作業指定的使用者資料 + // 測試資料的來源是真實的資料庫 + it('#1 密碼錯誤', function(done){ request(app) + // 對 POST /signin 發出請求,參數是錯誤的密碼 .post('/signin') .type('urlencoded') .send('email=root@example.com&password=123') + // 期待登入驗證回應失敗,重新導向 /signin .expect('Location', '/signin') .expect(302, done) }) - it('#2 帳號錯誤', function (done) { + it('#2 帳號錯誤', function(done){ request(app) + // 對 POST /signin 發出請求,參數是錯誤的帳號 .post('/signin') .type('urlencoded') .send('email=tu&password=12345678') + // 期待登入驗證回應失敗,重新導向 /signin .expect('Location', '/signin') .expect(302, done) }) - it('#3 成功登入', function (done) { + it('#3 成功登入', function(done){ request(app) + // 對 POST /signin 發出請求,參數是作業指定的使用者帳號密碼 .post('/signin') .type('urlencoded') .send('email=root@example.com&password=12345678') + // 期待登入驗證成功,重新導向 /restaurants .expect('Location', '/restaurants') .expect(302, done) }) - }) + }); describe('# A17: 使用者權限管理', function () { before(() => { - // 製作假 db 資料: UserMock + // 製作假資料 + // 下個 context 會用這筆資料進行測試 this.UserMock = dbMock.define('User', { id: 1, email: 'root@example.com', name: 'admin', isAdmin: false, }) + // 模擬 Sequelize 行為 // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) this.UserMock.findByPk = (id) => this.UserMock.findOne({ where: { id: id } }) @@ -74,26 +83,30 @@ describe('# A17', () => { context('# [顯示使用者清單]', () => { it(' GET /admin/users ', async () => { // 模擬 request & response - const req = mockRequest() + const req = mockRequest() // 對 GET /admin/users 發出請求 const res = mockResponse() - // 測試 adminController.getUsers function + // 測試作業指定的 adminController.getUsers 函式 await this.adminController.getUsers(req, res) - // render 裡的第二個參數,回傳了 users, 名稱必須跟假資料一樣是 admin + // getUser 執行完畢後,應呼叫 res.render + // res.render 的第 2 個參數應是 users + // 根據測試資料,users 中的第 1 筆資料,name 屬性值應該要是 'admin' res.render.getCall(0).args[1].users[0].name.should.equal('admin') }) }) context('# [修改使用者權限] for admin', () => { before(() => { - // 模擬 user db 資料: UserMock + // 製作假資料 + // 本 context 會用這筆資料進行測試 this.UserMock = dbMock.define('User', { id: 1, email: 'root@example.com', name: 'admin', - isAdmin: true, + isAdmin: true, // 是管理者 }) + // 模擬 Sequelize 行為 // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) // 將 count 的 function 預設回傳假資料數目 1 this.UserMock.count = () => 1 @@ -106,35 +119,37 @@ describe('# A17', () => { }) it(' PUT /admin/users/:id/toggleAdmin ', async () => { - const req = mockRequest({ params: { id: 1 } }) + // 模擬 request & response + const req = mockRequest({ params: { id: 1 } }) // 帶入 params.id = 1,對 PUT /admin/users/1/toggleAdmin 發出請求 const res = mockResponse() - // 測試 adminController.toggleAdmin function + // 測試作業指定的 adminController.toggleAdmin 函式 await this.adminController.toggleAdmin(req, res) - // 執行完 toggleAdmin 後,res.flash 有被呼叫而且呼叫的訊息正確 - req.flash.calledWith( - 'error_messages', - 'core manager can not be changed' - ).should.be.true - // 執行完 toggleAmin 後 res.redirect 有被呼叫而且 redirect 到 back + // toggleAdmin 正確執行的話,應呼叫 req.flash + // req.flash 的參數應該要與下列字串一致 + req.flash.calledWith('error_messages','禁止變更管理者權限').should.be.true + + // toggleAdmin 執行完畢後,應呼叫 res.redirect 並重新導向上一頁 res.redirect.calledWith('back').should.be.true }) }) context('# [修改使用者權限] for user', () => { before(() => { + // 製作假資料 + // 本 context 會用這筆資料進行測試 this.UserMock = dbMock.define( 'User', { id: 1, email: 'user@example.com', name: 'user', - isAdmin: false, + isAdmin: false, // 非管理者 }, { instanceMethods: { - // 模擬 update 函數,並依據傳入的變數,跟著修正,如果傳入 {isAdmin: true} + // 模擬一個會改變 admin 權限的函式 update: (changes) => { this.UserMock._defaults = { ...changes } return Promise.resolve() @@ -142,7 +157,7 @@ describe('# A17', () => { }, } ) - + // 模擬 Sequelize 行為 this.UserMock.count = () => 1 this.UserMock.findByPk = (id) => this.UserMock.findOne({ where: { id: id } }) @@ -153,19 +168,21 @@ describe('# A17', () => { }) it(' PUT /admin/users/:id/toggleAdmin ', async (done) => { - const req = mockRequest({ params: { id: 1 } }) + // 模擬 request & response + const req = mockRequest({ params: { id: 1 } }) // 帶入 params.id = 1,對 PUT /admin/users/1/toggleAdmin 發出請求 const res = mockResponse() + // 測試作業指定的 adminController.toggleAdmin 函式 await this.adminController.toggleAdmin(req, res) - req.flash.calledWith( - 'success_messages', - 'user was successfully to update' - ).should.be.true + // toggleAdmin 正確執行的話,應呼叫 req.flash + // req.flash 的參數應與下列字串一致 + req.flash.calledWith('success_messages','使用者權限變更成功').should.be.true + // toggleAdmin 執行完畢後,應呼叫 res.redirect 並重新導向 /admin/users res.redirect.calledWith('/admin/users').should.be.true - // 執行完 toggleAmin 後,因為假資料中使用者是 user@example.com,因此可以修改權限 isAdmin 從 false -> true - // 所以這邊將假資料撈出,比對看是否有修改到 + // toggleAmin 執行完畢後,假資料中 id:1 使用者的應該要是 isAdmin:true + // 將假資料撈出,比對確認有成功修改到 const user = await this.UserMock.findOne({ where: { id: 1 } }) user.isAdmin.should.equal(true) diff --git a/tests/A19.test.js b/tests/A19.test.js index d00fa12f..5b2b8f29 100644 --- a/tests/A19.test.js +++ b/tests/A19.test.js @@ -28,18 +28,20 @@ describe('# A19', () => { describe('# A19: 建立 User Profile', function () { context('# [瀏覽 Profile]', () => { before(() => { - // 模擬驗證資料 + // 模擬登入驗證 this.ensureAuthenticated = sinon .stub(helpers, 'ensureAuthenticated') .returns(true) this.getUser = sinon.stub(helpers, 'getUser').returns({ id: 1 }) - // 模擬 User db 資料 + // 製作假資料 + // 本 context 會用這筆資料進行測試 this.UserMock = dbMock.define('User', { id: 1, email: 'root@example.com', name: 'admin', isAdmin: false, }) + // 模擬 Sequelize 行為 // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) this.UserMock.findByPk = (id) => this.UserMock.findOne({ where: { id: id } }) @@ -50,18 +52,18 @@ describe('# A19', () => { }) it(' GET /users/:id ', async () => { - // 模擬發出 request, 帶入 params.id = 1 - const req = mockRequest({ params: { id: 1 } }) + // 模擬 request & response + const req = mockRequest({ params: { id: 1 } }) // 帶入 params.id = 1,對 GET /users/1 發出請求 const res = mockResponse() - // 測試 userController.getUser + // 測試作業指定的 userController.getUser 函式 await this.userController.getUser(req, res) - // 執行完後,call res.render 的參數測試 - // 1. 第一個參數是不是 profile - // 2. 第二個參數裡回傳的資料有 userData 而且 name 是 admin + // toggleAdmin 執行完畢後,應呼叫 res.render + // res.render 的第 1 個參數要是 'profile' + // res.render 的第 2 個參數要是 user,其 name 屬性的值應是 'admin' res.render.getCall(0).args[0].should.equal('profile') - res.render.getCall(0).args[1].userData.name.should.equal('admin') + res.render.getCall(0).args[1].user.name.should.equal('admin') }) after(async () => { @@ -73,19 +75,21 @@ describe('# A19', () => { context('# [瀏覽編輯 Profile 頁面]', () => { before(() => { - // 製作假驗證資料 + // 模擬登入驗證 this.ensureAuthenticated = sinon .stub(helpers, 'ensureAuthenticated') .returns(true) this.getUser = sinon.stub(helpers, 'getUser').returns({ id: 1 }) - // 模擬 User db 資料 + // 製作假資料 + // 本 context 會用這筆資料進行測試 this.UserMock = dbMock.define('User', { id: 1, email: 'root@example.com', name: 'admin', isAdmin: false, }) + // 模擬 Sequelize 行為 // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) this.UserMock.findByPk = (id) => this.UserMock.findOne({ where: { id: id } }) @@ -98,15 +102,15 @@ describe('# A19', () => { it(' GET /users/:id/edit ', async () => { // 模擬 request & response - const req = mockRequest({ params: { id: 1 } }) + const req = mockRequest({ params: { id: 1 } }) // 帶入 params.id = 1,對 GET /users/1/edit 發出請求 const res = mockResponse() - // 測試 adminController.editUser function + // 測試作業指定的 adminController.editUser 函式 await this.userController.editUser(req, res) - // 執行完 editUser 後,call res.render 的參數測試 - // 1. 第一個參數是不是 edit - // 2. 第二個參數裡回傳的資料有 user 而且 name 是 admin + // editUser 執行完畢後,應呼叫 res.render + // res.render 的第 1 個參數要是 'edit' + // res.render 的第 2 個參數要是 user,其 name 屬性的值應是 'admin' res.render.getCall(0).args[0].should.equal('edit') res.render.getCall(0).args[1].user.name.should.equal('admin') }) @@ -120,12 +124,13 @@ describe('# A19', () => { context('# [編輯 Profile]', () => { before(async () => { - // 模擬 request & response + // 模擬登入驗證 this.ensureAuthenticated = sinon .stub(helpers, 'ensureAuthenticated') .returns(true) this.getUser = sinon.stub(helpers, 'getUser').returns({ id: 1 }) - // 製作假 db 資料: UserMock + // 製作假資料 + // 本 context 會用這筆資料進行測試 this.UserMock = dbMock.define( 'User', { @@ -136,7 +141,7 @@ describe('# A19', () => { }, { instanceMethods: { - // 模擬 update 函數,並依據傳入的變數,跟著修正,如果傳入 {isAdmin: true} + // 模擬一個會改變 admin 權限的函式 update: (changes) => { this.UserMock._defaults = { ...changes } return Promise.resolve() @@ -144,6 +149,7 @@ describe('# A19', () => { }, } ) + // 模擬 Sequelize 行為 // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) this.UserMock.findByPk = (id) => this.UserMock.findOne({ where: { id: id } }) @@ -154,24 +160,24 @@ describe('# A19', () => { }) it(' PUT /users/:id ', async () => { - // 模擬發送 request, 帶了 params.id = 1, body.name = amdin2, body.email = admin_test@gmail.com + // 模擬 request & response + // 對 PUT /users/1 發出 request,並夾帶 body.name = amdin2, body.email = admin_test@gmail.com const req = mockRequest({ params: { id: 1 }, body: { name: 'admin2', email: 'admin_test@gmail.com' }, - }) + }) const res = mockResponse() - // 修改模擬資料, call putUser function + // 測試作業指定的 userController.putUser 函式 await this.userController.putUser(req, res) - // 測試成功修改 user 資料後,是否有回傳正確訊息 - req.flash.calledWith( - 'success_messages', - 'user was updated successfully' - ).should.be.true - // 測試成功修改 user 資料後,是否有導向正確 route + // putUser 正確執行的話,應呼叫 req.flash + // req.flash 的參數應與下列字串一致 + req.flash.calledWith('success_messages','使用者資料編輯成功').should.be.true + // putUser 執行完畢後,應呼叫 res.redirect 並重新導向 /users/1 res.redirect.calledWith('/users/1').should.be.true - // 修改後,測試模擬資料中是否有成功修正, name = amdin2, email = admin_test@gmail.com + // putUser 執行完畢後,id:1 使用者的 name 和 email 應該已被修改 + // 將假資料撈出,比對確認有成功修改到 const user = await this.UserMock.findOne({ where: { id: 1 } }) user.name.should.equal('admin2') user.email.should.equal('admin_test@gmail.com') diff --git a/tests/A20.test.js b/tests/A20.test.js index e1d908b3..3f922f0b 100644 --- a/tests/A20.test.js +++ b/tests/A20.test.js @@ -25,7 +25,8 @@ const mockResponse = () => { describe('# A20: 餐廳資訊整理:Dashboard', function () { context('# [Q1: Dashboard - 1 - controller / view / route]', () => { before(async () => { - // 模擬 user, restaurant, category, comment 資料 + // 製作假資料 + // 本 context 會用這筆資料進行測試 this.UserMock = dbMock.define('User', { id: 1, email: 'root@example.com', @@ -44,6 +45,7 @@ describe('# A20: 餐廳資訊整理:Dashboard', function () { id: 1, text: 'gogogo', }) + // 模擬 Sequelize 行為 // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) this.RestaurantMock.findByPk = (id) => this.RestaurantMock.findOne({ where: { id: id } }) @@ -62,15 +64,18 @@ describe('# A20: 餐廳資訊整理:Dashboard', function () { }) it(' GET /restaurants/:id/dashboard ', async () => { - const req = mockRequest({ params: { id: 1 } }) + // 模擬 request & response + const req = mockRequest({ params: { id: 1 } }) // 帶入 params.id = 1,對 GET /restaurants/1/dashboard 發出請求 const res = mockResponse() - // call getDashBoard function + // 測試 restController.getDashBoard 函式 await this.restController.getDashBoard(req, res) - // 確認執行 getDasgBoard 之後,回傳的 res.render 的參數是否與模擬資料相同 + // getDashBoard 執行完畢後,應呼叫 res.render + // res.render 的第 1 個參數要是 'dashboard' + // res.render 的第 2 個參數要包含 restaurant,其 name 屬性的值應是 '銷魂麵' res.render.getCall(0).args[0].should.equal('dashboard') res.render.getCall(0).args[1].restaurant.name.should.equal('銷魂麵') - res.render.getCall(0).args[1].n_comments.should.equal(1) + res.render.getCall(0).args[1].n_comments.should.equal(1) // question** }) }) }) diff --git a/tests/A21.test.js b/tests/A21.test.js index f8a30bfa..b2a1f83e 100644 --- a/tests/A21.test.js +++ b/tests/A21.test.js @@ -23,37 +23,38 @@ const mockResponse = () => { } } -// 模擬 Like 資料 +// 建立模擬的 Like 資料 let mockLikeData = [ { - UserId: 1, - RestaurantId: 2, + userId: 1, + restaurantId: 2, }, ] describe('# A21: Like / Unlike', function () { context('# Q1: 使用者可以 Like 餐廳', () => { before(() => { - // 模擬驗證資料 + // 模擬登入驗證 this.ensureAuthenticated = sinon .stub(helpers, 'ensureAuthenticated') .returns(true) this.getUser = sinon.stub(helpers, 'getUser').returns({ id: 1 }) - // + // 建立了一個模擬的 Like table,裡面目前是空的 + // 模擬 Sequelize 行為 this.mockLikeData = [] this.likeMock = dbMock.define('Like') // 因為 mock 中的 create 有問題,因此指向 upsert function, 這樣可以在 useHandler 中取得 create 呼叫 this.likeMock.create = this.likeMock.upsert - // 模擬 Like.create and Like.finall 的 function + // 模擬 Like.create and Like.findall 的 function this.likeMock.$queryInterface.$useHandler((query, queryOptions, done) => { if (query === 'upsert') { - // create 時會帶 UserId 跟 RestaurantId (ex: Like.create({ UserId: 1, RestaurantId: 2})) - const { UserId, RestaurantId } = queryOptions[0] + // create 時會帶 userId 跟 restaurantId (ex: Like.create({ userId: 1, restaurantId: 2})) + const { userId, restaurantId } = queryOptions[0] // 新增這個 Like 的資訊到模擬資料裡 - this.mockLikeData.push({ UserId, RestaurantId }) + this.mockLikeData.push({ userId, restaurantId }) // 回傳模擬資料 return Promise.resolve(this.likeMock.build(this.mockLikeData)) @@ -69,14 +70,15 @@ describe('# A21: Like / Unlike', function () { }) it(' POST /like/:restaurantId ', async () => { - const req = mockRequest({ params: { id: 1, restaurantId: 2 } }) + // 模擬 request & response + const req = mockRequest({ params: { id: 1, restaurantId: 2 } }) // 帶入 params.id = 1,對 POST /like/2 發出請求 const res = mockResponse() - // 呼叫 userController 中的 addLike + // 測試 userController.addLike 函式 await this.userController.addLike(req, res) - + // 將模擬的 Like table 內的資料全數撈出 const likes = await this.likeMock.findAll() - // 確認呼叫 addLike 後,模擬資料中的 like 資料會多一個 + // addLike 執行完畢後,Like table 應會從空的 -> 變成有 1 筆資料 likes.should.have.lengthOf(1) }) @@ -89,25 +91,28 @@ describe('# A21: Like / Unlike', function () { context('# Q1: 使用者可以 unLike 餐廳', () => { before(() => { - // 模擬驗證資料 + // 模擬登入驗證 this.ensureAuthenticated = sinon .stub(helpers, 'ensureAuthenticated') .returns(true) this.getUser = sinon.stub(helpers, 'getUser').returns({ id: 1 }) - // 模擬 Like db 資料 + // 製作假資料 + // 下個 context 會用這筆資料進行測試 + // 模擬 Like table 裡目前有 1 筆資料如下 this.likeMock = dbMock.define('Like', { id: 1, - UserId: 1, - RestaurantId: 2, + userId: 1, + restaurantId: 2, }) + // 模擬 Sequelize 行為 // 模擬 Like.destroy and Like.finall 的 function this.likeMock.$queryInterface.$useHandler((query, queryOptions, done) => { if (query === 'destroy') { - const { UserId, RestaurantId } = queryOptions[0].where + const { userId, restaurantId } = queryOptions[0].where // destroy 可以從 where 取得要刪除的資料 // 因此就可以模擬將模擬資料中的資料刪除 mockLikeData = mockLikeData.filter( - (d) => !(d.UserId === UserId && d.RestaurantId === RestaurantId) + (d) => !(d.userId === userId && d.restaurantId === restaurantId) ) return Promise.resolve(this.likeMock.build(mockLikeData)) @@ -122,15 +127,17 @@ describe('# A21: Like / Unlike', function () { }) it(' DELETE /like/:restaurantId ', async () => { + // 模擬 request & response // 模擬發出 request, 帶入 params.id = 1, restaurantId = 2 - const req = mockRequest({ params: { id: 1, restaurantId: 2 } }) + const req = mockRequest({ params: { id: 1, restaurantId: 2 } }) // 帶入 params.id = 1,對 DELETE /like/2 發出請求 const res = mockResponse() - // call DELETE /like/:restaurantId route 對應的 userController.removeLike + // 測試作業指定的 userController.removeLike 函式 await this.userController.removeLike(req, res) - // 刪除後,模擬資料會是空的 + // 將模擬的 Like table 內的資料全數撈出 const likes = await this.likeMock.findAll() + // addLike 執行完畢後,Like table 應會從有 1 筆資料 -> 變成空的 likes.should.have.lengthOf(0) }) diff --git a/tests/A22.test.js b/tests/A22.test.js index 9791f74e..4d379460 100644 --- a/tests/A22.test.js +++ b/tests/A22.test.js @@ -22,7 +22,7 @@ const mockResponse = () => { render: sinon.spy(), } } -// 模擬的餐廳資料 +// 模擬 2 間餐廳資料 let mockRestaurantData = [ { id: 1, @@ -31,9 +31,9 @@ let mockRestaurantData = [ address: 'address', opening_hours: 'opening_hours', description: 'test description', - FavoritedUsers: [ + favoritedUsers: [ { - UserId: 1, + userId: 1, }, ], }, @@ -44,29 +44,33 @@ let mockRestaurantData = [ address: 'address', opening_hours: 'opening_hours', description: 'description', - CategoryId: 1, - FavoritedUsers: [], + categoryId: 1, + favoritedUsers: [], }, ] describe('# A22: TOP 10 人氣餐廳 ', function () { context('# [網址正確、畫面正常執行]', () => { before(async () => { - // 模擬驗證資料 + // 模擬登入驗證 this.ensureAuthenticated = sinon .stub(helpers, 'ensureAuthenticated') .returns(true) + // 模擬 getUser 函式,負責取得使用者資料 this.getUser = sinon .stub(helpers, 'getUser') - .returns({ id: 1, Followings: [], FavoritedRestaurants: [] }) + .returns({ id: 1, followings: [], favoritedRestaurants: [] }) }) it(' GET /restaurants/top ', (done) => { + // 對 GET /restaurants/top 發出請求 request(app) .get('/restaurants/top') .end(function (err, res) { - // 檢查回傳的資料中,是否有 Top 10 人氣餐廳資訊 - res.text.should.include('Top 10 人氣餐廳') // 可以正確執行,沒有 error 即可通過 + // 回應中應包含字串'Top 10 人氣餐廳' + // 若請求路徑正確,controller 執行後,會呼叫 res.render + // res.render 回傳的 view 樣板裡應包含字串'Top 10 人氣餐廳' + res.text.should.include('Top 10 人氣餐廳') done() }) }) @@ -82,15 +86,17 @@ describe('# A22: TOP 10 人氣餐廳 ', function () { '# [當你點擊畫面上的「加入最愛 / 移除最愛」按鈕時,會重新計算「收藏數」的數字]', () => { before(async () => { - // 模擬驗證資料 + // 模擬登入驗證 this.ensureAuthenticated = sinon .stub(helpers, 'ensureAuthenticated') .returns(true) + // 模擬 getUser 函式,負責取得使用者資料 this.getUser = sinon .stub(helpers, 'getUser') - .returns({ id: 1, Followings: [], FavoritedRestaurants: [] }) + .returns({ id: 1, followings: [], favoritedRestaurants: [] }) - // 模擬 Restaurant db 資料 + // 建立了一個模擬的 Restaurant table,裡面放入 2 間餐廳資料 + // 模擬 Sequelize 行為 this.restaurantMock = dbMock.define('Restaurant') this.restaurantMock.$queryInterface.$useHandler( (query, queryOptions, done) => { @@ -109,29 +115,29 @@ describe('# A22: TOP 10 人氣餐廳 ', function () { }, }) - // 模擬 Favorite db 資料 + // 建立了一個模擬的 Favorite table,裡面有 1 筆資料 this.favoriteMock = dbMock.define('Favorite') this.favoriteMock.create = this.favoriteMock.upsert this.favoriteMock.$queryInterface.$useHandler( (query, queryOptions, done) => { if (query === 'upsert') { // 新增 favortie 資料到模擬資料 - const { UserId, RestaurantId } = queryOptions[0] + const { userId, restaurantId } = queryOptions[0] const restaurant = mockRestaurantData.find( - (d) => d.id === RestaurantId + (d) => d.id === restaurantId ) - restaurant.FavoritedUsers.push({ UserId: UserId }) + restaurant.favoritedUsers.push({ userId: userId }) return Promise.resolve( mockRestaurantData.map((d) => this.restaurantMock.build(d)) ) } else if (query === 'destroy') { // 刪除模擬資料中的某一筆 favortie 資料 - const { UserId, RestaurantId } = queryOptions[0].where + const { userId, restaurantId } = queryOptions[0].where const restaurant = mockRestaurantData.find( - (d) => d.id === RestaurantId + (d) => d.id === restaurantId ) - restaurant.FavoritedUsers = restaurant.FavoritedUsers.filter( - (d) => !(d.UserId === UserId) + restaurant.favoritedUsers = restaurant.favoritedUsers.filter( + (d) => !(d.userId === userId) ) return Promise.resolve( mockRestaurantData.map((d) => this.restaurantMock.build(d)) @@ -147,38 +153,43 @@ describe('# A22: TOP 10 人氣餐廳 ', function () { }) it(' POST favorite ', async () => { - // 模擬 request 新增 user id = 1, 喜愛 restaurant id = 2 + // 模擬 request & response + // 對 POST /favorite/2 發出請求,夾帶 user.favoritedRestaurants const req = mockRequest({ - user: { FavoritedRestaurants: [] }, + user: { favoritedRestaurants: [] }, params: { restaurantId: 2 }, }) const res = mockResponse() - // call addFavorite + // 測試 userController.addFavorite 函式 await this.userController.addFavorite(req, res) - // 確認 getTopRestaurant 取得餐廳 2 的 favorite 數量會不同 + // 取得餐廳排序資料 await this.restController.getTopRestaurant(req, res) - - res.render - .getCall(0) - .args[1].restaurants[1].FavoritedCount.should.equal(1) + + // addFavorite 執行完畢後,應呼叫 res.render + // res.render 的第 2 個參數要包含 restaurants + // restaurant 當中的第 2 筆資料 favoritedCount 屬性的值應是 1 (被 1 個使用者加入最愛了) + res.render.getCall(0).args[1].restaurants[1].favoritedCount.should.equal(1) }) it(' DELETE favorite ', async () => { - // 模擬 request 新增 user id = 1, 不喜愛 restaurant id = 1 + // 模擬 request & response + // 對 DELETE /favorite/1 發出請求,夾入 user.favoritedRestaurants const req = mockRequest({ - user: { FavoritedRestaurants: [] }, + user: { favoritedRestaurants: [] }, params: { restaurantId: 1 }, }) const res = mockResponse() - // call removeFavorite + // 測試 userController.removeFavorite 函式 await this.userController.removeFavorite(req, res) // 取得餐廳排序資料 await this.restController.getTopRestaurant(req, res) - // 確認餐廳排序有變,第一個餐廳資料 id = 2 + // removeFavorite 執行完畢後,應呼叫 res.render + // res.render 的第 2 個參數要包含 restaurants + // restaurants 當中的第 1 筆資料 id 屬性值應是 2 (id:1 的那家餐廳被刪除了) res.render.getCall(0).args[1].restaurants[0].id.should.equal(2) }) From 82de6c86d86a736b5dec9aa6fde9b1be8168bb41 Mon Sep 17 00:00:00 2001 From: Stanney Date: Thu, 28 Oct 2021 23:57:34 +0800 Subject: [PATCH 015/198] refactor mock db in unit test / integrated into helpers/unitTestHelpers --- helpers/unitTestHelpers.js | 85 ++++++++++++++++++++++++++++++++++++++ tests/A17.test.js | 67 +++++++++--------------------- tests/A19.test.js | 50 ++++------------------ tests/A20.test.js | 37 ++++++----------- tests/A21.test.js | 58 ++++---------------------- tests/A22.test.js | 60 ++++----------------------- 6 files changed, 143 insertions(+), 214 deletions(-) create mode 100644 helpers/unitTestHelpers.js diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js new file mode 100644 index 00000000..a7157681 --- /dev/null +++ b/helpers/unitTestHelpers.js @@ -0,0 +1,85 @@ +const SequelizeMock = require('sequelize-mock') +const proxyquire = require('proxyquire') + +const dbMock = new SequelizeMock() + +const createModelMock = (name, defaultValue, data, joinedTableName) => { + const mockModel = dbMock.define(name, defaultValue, { + instanceMethods: { + update: (changes) => { + mockModel._defaults = {...changes} + return Promise.resolve() + } + } + }); + + // 模擬 Sequelize 行為 + // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) + mockModel.findByPk = (id) => mockModel.findOne({where: {id: id}}) + // 將 count 的 function 預設回傳假資料數目 1 + mockModel.count = () => 1 + // 因為 mock 中的 create 有問題,因此指向 upsert function, 這樣可以在 useHandler 中取得 create 呼叫 + mockModel.create = mockModel.upsert + + // modify middleware + if (joinedTableName) { + mockModel.$queryInterface.$useHandler((query, queryOptions) => { + if (query === 'upsert') { + // 新增 joinTable 資料到模擬資料 + const {UserId, RestaurantId} = queryOptions[0]; + const restaurant = data.find(d => d.id === RestaurantId) + restaurant[joinedTableName].push({UserId: UserId}); + return Promise.resolve(data.map(d => mockModel.build(d))) + } else if (query === 'findAll') { + // 回傳模擬資料 + return Promise.resolve(data.map(d => mockModel.build(d))) + }else if (query === 'destroy') { + // destroy 可以從 where 取得要刪除的資料 + // 因此就可以模擬將模擬資料中的資料刪除 + // 刪除模擬資料中的某一筆 joinTable 資料 + const {UserId, RestaurantId} = queryOptions[0].where; + const restaurant = data.find(d => d.id === RestaurantId) + restaurant[joinedTableName] = restaurant[joinedTableName].filter(d => !(d.UserId === UserId)) + return Promise.resolve(data.map(d => mockModel.build(d))) + } + }); + } else { + mockModel.$queryInterface.$useHandler((query, queryOptions,done) => { + if (query === 'upsert') { + // create 時會帶 userId 跟 restaurantId (ex: Like.create({ userId: 1, restaurantId: 2})) + const {UserId, RestaurantId} = queryOptions[0] + + // 新增這個 Like 的資訊到模擬資料裡 + data.push({ UserId, RestaurantId }) + + // 回傳模擬資料 + return Promise.resolve(mockModel.build(data)) + } else if (query === 'findAll') { + // 回傳模擬資料 + return Promise.resolve(data.map(d => mockModel.build(d))) + } else if (query === 'destroy') { + // destroy 可以從 where 取得要刪除的資料 + // 因此就可以模擬將模擬資料中的資料刪除 + const {UserId, RestaurantId} = queryOptions[0].where + data = data.filter(d => !(d.UserId === UserId && d.RestaurantId === RestaurantId)) + + return Promise.resolve(mockModel.build(data)) + } + }); + } + + return mockModel; +} + +const createControllerProxy = (path, model) => { + const controller = proxyquire(path, { + '../models': model + }); + + return controller; +} + +module.exports = { + createModelMock, + createControllerProxy +} \ No newline at end of file diff --git a/tests/A17.test.js b/tests/A17.test.js index 03a6b21e..3f0e633e 100644 --- a/tests/A17.test.js +++ b/tests/A17.test.js @@ -2,13 +2,10 @@ const chai = require('chai') const request = require('supertest') const sinon = require('sinon') const should = chai.should() -const SequelizeMock = require('sequelize-mock') -const proxyquire = require('proxyquire') - -const dbMock = new SequelizeMock() const app = require('../../app') const { classToInvokable } = require('sequelize/types/lib/utils') +const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers'); const mockRequest = (query) => { return { @@ -64,20 +61,15 @@ describe('# A17', () => { before(() => { // 製作假資料 // 下個 context 會用這筆資料進行測試 - this.UserMock = dbMock.define('User', { + this.UserMock = createModelMock('User', { id: 1, email: 'root@example.com', name: 'admin', isAdmin: false, }) - // 模擬 Sequelize 行為 - // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) - this.UserMock.findByPk = (id) => - this.UserMock.findOne({ where: { id: id } }) + // 將 adminController 中的 User db 取代成 User mock db - this.adminController = proxyquire('../../controllers/adminController', { - '../models': { User: this.UserMock }, - }) + this.adminController = createControllerProxy('../../controllers/adminController', { User: this.UserMock }) }) context('# [顯示使用者清單]', () => { @@ -100,22 +92,18 @@ describe('# A17', () => { before(() => { // 製作假資料 // 本 context 會用這筆資料進行測試 - this.UserMock = dbMock.define('User', { - id: 1, - email: 'root@example.com', - name: 'admin', - isAdmin: true, // 是管理者 - }) - // 模擬 Sequelize 行為 - // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) - // 將 count 的 function 預設回傳假資料數目 1 - this.UserMock.count = () => 1 - this.UserMock.findByPk = (id) => - this.UserMock.findOne({ where: { id: id } }) + this.UserMock = createModelMock( + 'User', + { + id: 1, + email: 'root@example.com', + name: 'admin', + isAdmin: true, // 是管理者 + } + ) + // 將 adminController 中的 User db 取代成 User mock db - this.adminController = proxyquire('../../controllers/adminController', { - '../models': { User: this.UserMock }, - }) + this.adminController = createControllerProxy('../../controllers/adminController', { User: this.UserMock }) }) it(' PUT /admin/users/:id/toggleAdmin ', async () => { @@ -137,34 +125,19 @@ describe('# A17', () => { context('# [修改使用者權限] for user', () => { before(() => { - // 製作假資料 - // 本 context 會用這筆資料進行測試 - this.UserMock = dbMock.define( + // 製作假資料 + // 本 context 會用這筆資料進行測試 + this.UserMock = createModelMock( 'User', { id: 1, email: 'user@example.com', name: 'user', isAdmin: false, // 非管理者 - }, - { - instanceMethods: { - // 模擬一個會改變 admin 權限的函式 - update: (changes) => { - this.UserMock._defaults = { ...changes } - return Promise.resolve() - }, - }, } ) - // 模擬 Sequelize 行為 - this.UserMock.count = () => 1 - this.UserMock.findByPk = (id) => - this.UserMock.findOne({ where: { id: id } }) - - this.adminController = proxyquire('../../controllers/adminController', { - '../models': { User: this.UserMock }, - }) + // 將 adminController 中的 User db 取代成 User mock db + this.adminController = createControllerProxy('../../controllers/adminController', { User: this.UserMock }) }) it(' PUT /admin/users/:id/toggleAdmin ', async (done) => { diff --git a/tests/A19.test.js b/tests/A19.test.js index 5b2b8f29..d5caf3fd 100644 --- a/tests/A19.test.js +++ b/tests/A19.test.js @@ -3,14 +3,7 @@ const request = require('supertest') const sinon = require('sinon') const should = chai.should() -const app = require('../../app') -const routes = require('../../routes/index') -const db = require('../../models') -const helpers = require('../../_helpers') -const SequelizeMock = require('sequelize-mock') -const proxyquire = require('proxyquire') - -const dbMock = new SequelizeMock() +const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers'); const mockRequest = (query) => { return { @@ -35,20 +28,15 @@ describe('# A19', () => { this.getUser = sinon.stub(helpers, 'getUser').returns({ id: 1 }) // 製作假資料 // 本 context 會用這筆資料進行測試 - this.UserMock = dbMock.define('User', { + this.UserMock = createModelMock('User', { id: 1, email: 'root@example.com', name: 'admin', isAdmin: false, }) - // 模擬 Sequelize 行為 - // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) - this.UserMock.findByPk = (id) => - this.UserMock.findOne({ where: { id: id } }) + // 將 userController 中的 User db 取代成 User mock db - this.userController = proxyquire('../../controllers/userController', { - '../models': { User: this.UserMock }, - }) + this.userController = createControllerProxy('../../controllers/userController', { User: this.UserMock }) }) it(' GET /users/:id ', async () => { @@ -83,21 +71,15 @@ describe('# A19', () => { // 製作假資料 // 本 context 會用這筆資料進行測試 - this.UserMock = dbMock.define('User', { + this.UserMock = createModelMock('User', { id: 1, email: 'root@example.com', name: 'admin', isAdmin: false, }) - // 模擬 Sequelize 行為 - // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) - this.UserMock.findByPk = (id) => - this.UserMock.findOne({ where: { id: id } }) // 將 userController 中的 User db 取代成 User mock db - this.userController = proxyquire('../../controllers/userController', { - '../models': { User: this.UserMock }, - }) + this.userController = createControllerProxy('../../controllers/userController', { User: this.UserMock }) }) it(' GET /users/:id/edit ', async () => { @@ -131,32 +113,18 @@ describe('# A19', () => { this.getUser = sinon.stub(helpers, 'getUser').returns({ id: 1 }) // 製作假資料 // 本 context 會用這筆資料進行測試 - this.UserMock = dbMock.define( + this.UserMock = createModelMock( 'User', { id: 1, email: 'root@example.com', name: 'admin', isAdmin: false, - }, - { - instanceMethods: { - // 模擬一個會改變 admin 權限的函式 - update: (changes) => { - this.UserMock._defaults = { ...changes } - return Promise.resolve() - }, - }, } ) - // 模擬 Sequelize 行為 - // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) - this.UserMock.findByPk = (id) => - this.UserMock.findOne({ where: { id: id } }) + // 將 userController 中的 User db 取代成 User mock db - this.userController = proxyquire('../../controllers/userController', { - '../models': { User: this.UserMock }, - }) + this.userController = createControllerProxy('../../controllers/userController', { User: this.UserMock }) }) it(' PUT /users/:id ', async () => { diff --git a/tests/A20.test.js b/tests/A20.test.js index 3f922f0b..5fd74c83 100644 --- a/tests/A20.test.js +++ b/tests/A20.test.js @@ -4,10 +4,7 @@ const sinon = require('sinon') const should = chai.should() const helpers = require('../_helpers') -const SequelizeMock = require('sequelize-mock') -const proxyquire = require('proxyquire') - -const dbMock = new SequelizeMock() +const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers'); const mockRequest = (query) => { return { @@ -27,39 +24,31 @@ describe('# A20: 餐廳資訊整理:Dashboard', function () { before(async () => { // 製作假資料 // 本 context 會用這筆資料進行測試 - this.UserMock = dbMock.define('User', { + this.UserMock = createModelMock('User', { id: 1, email: 'root@example.com', name: 'admin', isAdmin: false, }) - this.RestaurantMock = dbMock.define('Restaurant', { + this.RestaurantMock = createModelMock('Restaurant', { id: 1, - name: '銷魂麵', + name: '銷魂麵' }) - this.CategoryMock = dbMock.define('Category', { + this.CategoryMock = createModelMock('Category', { id: 1, - name: '食物', + name: '食物' }) - this.CommentMock = dbMock.define('Comment', { + this.CommentMock = createModelMock('Comment', { id: 1, - text: 'gogogo', + text: "gogogo" }) - // 模擬 Sequelize 行為 - // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) - this.RestaurantMock.findByPk = (id) => - this.RestaurantMock.findOne({ where: { id: id } }) - // 將 count 的 function 預設回傳假資料數目 1 - this.CommentMock.count = () => 1 // 將 restController 中的 User,Category,Restaurant,Comment 都用模擬資料取代 - this.restController = proxyquire('../controllers/restController', { - '../models': { - User: this.UserMock, - Category: this.CategoryMock, - Restaurant: this.RestaurantMock, - Comment: this.CommentMock, - }, + this.restController = createControllerProxy('../controllers/restController', { + User: this.UserMock, + Category: this.CategoryMock, + Restaurant: this.RestaurantMock, + Comment: this.CommentMock, }) }) diff --git a/tests/A21.test.js b/tests/A21.test.js index b2a1f83e..6e7df3d9 100644 --- a/tests/A21.test.js +++ b/tests/A21.test.js @@ -5,10 +5,7 @@ const should = chai.should() const db = require('../models') const helpers = require('../_helpers') -const SequelizeMock = require('sequelize-mock') -const proxyquire = require('proxyquire') - -const dbMock = new SequelizeMock({ autoQueryFallback: false }) +const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers') const mockRequest = (query) => { return { @@ -43,30 +40,9 @@ describe('# A21: Like / Unlike', function () { // 建立了一個模擬的 Like table,裡面目前是空的 // 模擬 Sequelize 行為 this.mockLikeData = [] - this.likeMock = dbMock.define('Like') - // 因為 mock 中的 create 有問題,因此指向 upsert function, 這樣可以在 useHandler 中取得 create 呼叫 - this.likeMock.create = this.likeMock.upsert - - // 模擬 Like.create and Like.findall 的 function - this.likeMock.$queryInterface.$useHandler((query, queryOptions, done) => { - if (query === 'upsert') { - // create 時會帶 userId 跟 restaurantId (ex: Like.create({ userId: 1, restaurantId: 2})) - const { userId, restaurantId } = queryOptions[0] - - // 新增這個 Like 的資訊到模擬資料裡 - this.mockLikeData.push({ userId, restaurantId }) + this.likeMock = createModelMock('Like', null, this.mockLikeData) - // 回傳模擬資料 - return Promise.resolve(this.likeMock.build(this.mockLikeData)) - } else if (query === 'findAll') { - // 回傳模擬資料 - return Promise.resolve(this.mockLikeData) - } - }) - - this.userController = proxyquire('../controllers/userController', { - '../models': { Like: this.likeMock }, - }) + this.userController = createControllerProxy('../controllers/userController', {Like: this.likeMock}) }) it(' POST /like/:restaurantId ', async () => { @@ -99,31 +75,13 @@ describe('# A21: Like / Unlike', function () { // 製作假資料 // 下個 context 會用這筆資料進行測試 // 模擬 Like table 裡目前有 1 筆資料如下 - this.likeMock = dbMock.define('Like', { + this.likeMock = createModelMock('Like', { id: 1, - userId: 1, - restaurantId: 2, - }) - // 模擬 Sequelize 行為 - // 模擬 Like.destroy and Like.finall 的 function - this.likeMock.$queryInterface.$useHandler((query, queryOptions, done) => { - if (query === 'destroy') { - const { userId, restaurantId } = queryOptions[0].where - // destroy 可以從 where 取得要刪除的資料 - // 因此就可以模擬將模擬資料中的資料刪除 - mockLikeData = mockLikeData.filter( - (d) => !(d.userId === userId && d.restaurantId === restaurantId) - ) - - return Promise.resolve(this.likeMock.build(mockLikeData)) - } else if (query === 'findAll') { - return Promise.resolve(mockLikeData) - } - }) + UserId: 1, + RestaurantId: 2, + }, mockLikeData); - this.userController = proxyquire('../controllers/userController', { - '../models': { Like: this.likeMock }, - }) + this.userController = createControllerProxy('../controllers/userController', { Like: this.likeMock }) }) it(' DELETE /like/:restaurantId ', async () => { diff --git a/tests/A22.test.js b/tests/A22.test.js index 4d379460..86cd3734 100644 --- a/tests/A22.test.js +++ b/tests/A22.test.js @@ -5,10 +5,7 @@ const should = chai.should() const app = require('../app') const helpers = require('../_helpers') -const SequelizeMock = require('sequelize-mock') -const proxyquire = require('proxyquire') - -const dbMock = new SequelizeMock({ autoQueryFallback: false }) +const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers') const mockRequest = (query) => { return { @@ -95,61 +92,20 @@ describe('# A22: TOP 10 人氣餐廳 ', function () { .stub(helpers, 'getUser') .returns({ id: 1, followings: [], favoritedRestaurants: [] }) - // 建立了一個模擬的 Restaurant table,裡面放入 2 間餐廳資料 - // 模擬 Sequelize 行為 - this.restaurantMock = dbMock.define('Restaurant') - this.restaurantMock.$queryInterface.$useHandler( - (query, queryOptions, done) => { - if (query === 'findAll') { - return Promise.resolve( - mockRestaurantData.map((d) => this.restaurantMock.build(d)) - ) - } - } - ) + // 建立了一個模擬的 Restaurant table,裡面放入 2 間餐廳資料 + // 模擬 Sequelize 行為 + this.restaurantMock = createModelMock('Restaurant', null, mockRestaurantData) // 將 restController 中的 Restaurant db 取代成 Restaurant mock db - this.restController = proxyquire('../controllers/restController', { - '../models': { - Restaurant: this.restaurantMock, - }, + this.restController = createControllerProxy('../controllers/restController', { + Restaurant: this.restaurantMock, }) // 建立了一個模擬的 Favorite table,裡面有 1 筆資料 - this.favoriteMock = dbMock.define('Favorite') - this.favoriteMock.create = this.favoriteMock.upsert - this.favoriteMock.$queryInterface.$useHandler( - (query, queryOptions, done) => { - if (query === 'upsert') { - // 新增 favortie 資料到模擬資料 - const { userId, restaurantId } = queryOptions[0] - const restaurant = mockRestaurantData.find( - (d) => d.id === restaurantId - ) - restaurant.favoritedUsers.push({ userId: userId }) - return Promise.resolve( - mockRestaurantData.map((d) => this.restaurantMock.build(d)) - ) - } else if (query === 'destroy') { - // 刪除模擬資料中的某一筆 favortie 資料 - const { userId, restaurantId } = queryOptions[0].where - const restaurant = mockRestaurantData.find( - (d) => d.id === restaurantId - ) - restaurant.favoritedUsers = restaurant.favoritedUsers.filter( - (d) => !(d.userId === userId) - ) - return Promise.resolve( - mockRestaurantData.map((d) => this.restaurantMock.build(d)) - ) - } - } - ) + this.favoriteMock = createModelMock('Favorite', null, mockRestaurantData, 'FavoritedUsers') // 將 userController 中的 Favorite db 取代成 Favorite mock db - this.userController = proxyquire('../controllers/userController', { - '../models': { Favorite: this.favoriteMock }, - }) + this.userController = createControllerProxy('../controllers/userController', {Favorite: this.favoriteMock}) }) it(' POST favorite ', async () => { From c9b201e0bdb860c4b9f00692844311824fa0e789 Mon Sep 17 00:00:00 2001 From: Stanney Date: Fri, 29 Oct 2021 00:06:56 +0800 Subject: [PATCH 016/198] remove n_comments check due to follow model answer --- tests/A20.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/A20.test.js b/tests/A20.test.js index 5fd74c83..aa78d28f 100644 --- a/tests/A20.test.js +++ b/tests/A20.test.js @@ -64,7 +64,6 @@ describe('# A20: 餐廳資訊整理:Dashboard', function () { // res.render 的第 2 個參數要包含 restaurant,其 name 屬性的值應是 '銷魂麵' res.render.getCall(0).args[0].should.equal('dashboard') res.render.getCall(0).args[1].restaurant.name.should.equal('銷魂麵') - res.render.getCall(0).args[1].n_comments.should.equal(1) // question** }) }) }) From 50476ab9b1f242ebee3f85e7774198c2a53eddae Mon Sep 17 00:00:00 2001 From: Carrot7712 Date: Fri, 29 Oct 2021 03:08:25 +0800 Subject: [PATCH 017/198] revise comments --- config/config.json | 2 +- tests/A17.test.js | 8 +++++--- tests/A19.test.js | 11 +++++++---- tests/A20.test.js | 2 +- tests/A21.test.js | 5 +++-- tests/A22.test.js | 5 ++--- 6 files changed, 19 insertions(+), 14 deletions(-) diff --git a/config/config.json b/config/config.json index bfb32f98..56636a1c 100644 --- a/config/config.json +++ b/config/config.json @@ -1,7 +1,7 @@ { "development": { "username": "root", - "password": "", + "password": "password", "database": "forum", "host": "127.0.0.1", "dialect": "mysql" diff --git a/tests/A17.test.js b/tests/A17.test.js index 3f0e633e..6065446e 100644 --- a/tests/A17.test.js +++ b/tests/A17.test.js @@ -58,9 +58,10 @@ describe('# A17', () => { }); describe('# A17: 使用者權限管理', function () { + // 前置準備 before(() => { // 製作假資料 - // 下個 context 會用這筆資料進行測試 + // 本 context 會用這筆資料進行測試 this.UserMock = createModelMock('User', { id: 1, email: 'root@example.com', @@ -68,10 +69,11 @@ describe('# A17', () => { isAdmin: false, }) - // 將 adminController 中的 User db 取代成 User mock db + // 修改 adminController 中的資料庫連線設定,由連向真實的資料庫 -> 改為連向模擬的 User table this.adminController = createControllerProxy('../../controllers/adminController', { User: this.UserMock }) }) - + + // 開始測試 context('# [顯示使用者清單]', () => { it(' GET /admin/users ', async () => { // 模擬 request & response diff --git a/tests/A19.test.js b/tests/A19.test.js index d5caf3fd..228aafad 100644 --- a/tests/A19.test.js +++ b/tests/A19.test.js @@ -20,6 +20,7 @@ const mockResponse = () => { describe('# A19', () => { describe('# A19: 建立 User Profile', function () { context('# [瀏覽 Profile]', () => { + // 前置準備 before(() => { // 模擬登入驗證 this.ensureAuthenticated = sinon @@ -35,10 +36,11 @@ describe('# A19', () => { isAdmin: false, }) - // 將 userController 中的 User db 取代成 User mock db + // 修改 userController 中的資料庫連線設定,由連向真實的資料庫 -> 改為連向模擬的 User table this.userController = createControllerProxy('../../controllers/userController', { User: this.UserMock }) }) + // 開始測試 it(' GET /users/:id ', async () => { // 模擬 request & response const req = mockRequest({ params: { id: 1 } }) // 帶入 params.id = 1,對 GET /users/1 發出請求 @@ -53,7 +55,8 @@ describe('# A19', () => { res.render.getCall(0).args[0].should.equal('profile') res.render.getCall(0).args[1].user.name.should.equal('admin') }) - + + // 測試完畢,清除資料 after(async () => { // 清除模擬驗證資料 this.ensureAuthenticated.restore() @@ -78,7 +81,7 @@ describe('# A19', () => { isAdmin: false, }) - // 將 userController 中的 User db 取代成 User mock db + // 連向模擬的 User table this.userController = createControllerProxy('../../controllers/userController', { User: this.UserMock }) }) @@ -123,7 +126,7 @@ describe('# A19', () => { } ) - // 將 userController 中的 User db 取代成 User mock db + // 連向模擬的 User table this.userController = createControllerProxy('../../controllers/userController', { User: this.UserMock }) }) diff --git a/tests/A20.test.js b/tests/A20.test.js index aa78d28f..42b5806a 100644 --- a/tests/A20.test.js +++ b/tests/A20.test.js @@ -43,7 +43,7 @@ describe('# A20: 餐廳資訊整理:Dashboard', function () { text: "gogogo" }) - // 將 restController 中的 User,Category,Restaurant,Comment 都用模擬資料取代 + // 連向模擬的 tables this.restController = createControllerProxy('../controllers/restController', { User: this.UserMock, Category: this.CategoryMock, diff --git a/tests/A21.test.js b/tests/A21.test.js index 6e7df3d9..8005a71b 100644 --- a/tests/A21.test.js +++ b/tests/A21.test.js @@ -38,10 +38,10 @@ describe('# A21: Like / Unlike', function () { this.getUser = sinon.stub(helpers, 'getUser').returns({ id: 1 }) // 建立了一個模擬的 Like table,裡面目前是空的 - // 模擬 Sequelize 行為 this.mockLikeData = [] this.likeMock = createModelMock('Like', null, this.mockLikeData) - + + // 連向模擬的 Like table this.userController = createControllerProxy('../controllers/userController', {Like: this.likeMock}) }) @@ -81,6 +81,7 @@ describe('# A21: Like / Unlike', function () { RestaurantId: 2, }, mockLikeData); + // 連向模擬的 Like table this.userController = createControllerProxy('../controllers/userController', { Like: this.likeMock }) }) diff --git a/tests/A22.test.js b/tests/A22.test.js index 86cd3734..793e6996 100644 --- a/tests/A22.test.js +++ b/tests/A22.test.js @@ -93,10 +93,9 @@ describe('# A22: TOP 10 人氣餐廳 ', function () { .returns({ id: 1, followings: [], favoritedRestaurants: [] }) // 建立了一個模擬的 Restaurant table,裡面放入 2 間餐廳資料 - // 模擬 Sequelize 行為 this.restaurantMock = createModelMock('Restaurant', null, mockRestaurantData) - // 將 restController 中的 Restaurant db 取代成 Restaurant mock db + // 連向模擬的 Restaurant table this.restController = createControllerProxy('../controllers/restController', { Restaurant: this.restaurantMock, }) @@ -104,7 +103,7 @@ describe('# A22: TOP 10 人氣餐廳 ', function () { // 建立了一個模擬的 Favorite table,裡面有 1 筆資料 this.favoriteMock = createModelMock('Favorite', null, mockRestaurantData, 'FavoritedUsers') - // 將 userController 中的 Favorite db 取代成 Favorite mock db + // 連向模擬的 Favorite table this.userController = createControllerProxy('../controllers/userController', {Favorite: this.favoriteMock}) }) From b3ddfcdbcb25410a54aad1f00131dbcf2437eee8 Mon Sep 17 00:00:00 2001 From: Stanney Date: Sat, 30 Oct 2021 00:01:32 +0800 Subject: [PATCH 018/198] add A21 test userId and restaurantId with addLike function --- tests/A21.test.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/A21.test.js b/tests/A21.test.js index 8005a71b..ee638370 100644 --- a/tests/A21.test.js +++ b/tests/A21.test.js @@ -47,7 +47,7 @@ describe('# A21: Like / Unlike', function () { it(' POST /like/:restaurantId ', async () => { // 模擬 request & response - const req = mockRequest({ params: { id: 1, restaurantId: 2 } }) // 帶入 params.id = 1,對 POST /like/2 發出請求 + const req = mockRequest({ params: { restaurantId: 2 } }) // 帶入 params.restaurantId = 2,對 POST /like/2 發出請求 const res = mockResponse() // 測試 userController.addLike 函式 @@ -56,6 +56,10 @@ describe('# A21: Like / Unlike', function () { const likes = await this.likeMock.findAll() // addLike 執行完畢後,Like table 應會從空的 -> 變成有 1 筆資料 likes.should.have.lengthOf(1) + // 資料裡的 UserId 應該會跟我們傳入的 user id 一樣 + likes[0].UserId.should.equal(1) + // 資料裡的 RestaurantId 會跟我們傳入的 params.restaurantId 一樣 + likes[0].RestaurantId.should.equal(2) }) after(async () => { From 6c5f774a8a66577c1e49751696d6a05d14a0262c Mon Sep 17 00:00:00 2001 From: Stanney Date: Sat, 30 Oct 2021 00:13:36 +0800 Subject: [PATCH 019/198] add viewCounts check in A20 --- tests/A20.test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/A20.test.js b/tests/A20.test.js index 42b5806a..53f44d72 100644 --- a/tests/A20.test.js +++ b/tests/A20.test.js @@ -32,7 +32,8 @@ describe('# A20: 餐廳資訊整理:Dashboard', function () { }) this.RestaurantMock = createModelMock('Restaurant', { id: 1, - name: '銷魂麵' + name: '銷魂麵', + viewCounts: 3 }) this.CategoryMock = createModelMock('Category', { id: 1, @@ -64,6 +65,7 @@ describe('# A20: 餐廳資訊整理:Dashboard', function () { // res.render 的第 2 個參數要包含 restaurant,其 name 屬性的值應是 '銷魂麵' res.render.getCall(0).args[0].should.equal('dashboard') res.render.getCall(0).args[1].restaurant.name.should.equal('銷魂麵') + res.render.getCall(0).args[1].restaurant.viewCounts.should.equal(3) }) }) }) From 2ad4b59c4151c60300079f7143e49836ce1684e2 Mon Sep 17 00:00:00 2001 From: Stanney Date: Sat, 30 Oct 2021 00:17:26 +0800 Subject: [PATCH 020/198] add new test comment --- tests/A20.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/A20.test.js b/tests/A20.test.js index 53f44d72..a832e034 100644 --- a/tests/A20.test.js +++ b/tests/A20.test.js @@ -63,6 +63,7 @@ describe('# A20: 餐廳資訊整理:Dashboard', function () { // getDashBoard 執行完畢後,應呼叫 res.render // res.render 的第 1 個參數要是 'dashboard' // res.render 的第 2 個參數要包含 restaurant,其 name 屬性的值應是 '銷魂麵' + // res.render 的地 3 個參數要包含 restaurant,其 viewCounts 值應該是 3 res.render.getCall(0).args[0].should.equal('dashboard') res.render.getCall(0).args[1].restaurant.name.should.equal('銷魂麵') res.render.getCall(0).args[1].restaurant.viewCounts.should.equal(3) From a2df6a46bdc38176dacc8cee1a2e28783fd9af53 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Sun, 31 Oct 2021 13:04:23 +0800 Subject: [PATCH 021/198] remove other test and rename A17 -> R01 --- tests/A19.test.js | 164 ----------------------------- tests/A20.test.js | 72 ------------- tests/A21.test.js | 113 -------------------- tests/A22.test.js | 158 --------------------------- tests/{A17.test.js => R01.test.js} | 4 +- 5 files changed, 2 insertions(+), 509 deletions(-) delete mode 100644 tests/A19.test.js delete mode 100644 tests/A20.test.js delete mode 100644 tests/A21.test.js delete mode 100644 tests/A22.test.js rename tests/{A17.test.js => R01.test.js} (98%) diff --git a/tests/A19.test.js b/tests/A19.test.js deleted file mode 100644 index 228aafad..00000000 --- a/tests/A19.test.js +++ /dev/null @@ -1,164 +0,0 @@ -const chai = require('chai') -const request = require('supertest') -const sinon = require('sinon') -const should = chai.should() - -const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers'); - -const mockRequest = (query) => { - return { - ...query, - flash: sinon.spy(), - } -} -const mockResponse = () => { - return { - redirect: sinon.spy(), - render: sinon.spy(), - } -} -describe('# A19', () => { - describe('# A19: 建立 User Profile', function () { - context('# [瀏覽 Profile]', () => { - // 前置準備 - before(() => { - // 模擬登入驗證 - this.ensureAuthenticated = sinon - .stub(helpers, 'ensureAuthenticated') - .returns(true) - this.getUser = sinon.stub(helpers, 'getUser').returns({ id: 1 }) - // 製作假資料 - // 本 context 會用這筆資料進行測試 - this.UserMock = createModelMock('User', { - id: 1, - email: 'root@example.com', - name: 'admin', - isAdmin: false, - }) - - // 修改 userController 中的資料庫連線設定,由連向真實的資料庫 -> 改為連向模擬的 User table - this.userController = createControllerProxy('../../controllers/userController', { User: this.UserMock }) - }) - - // 開始測試 - it(' GET /users/:id ', async () => { - // 模擬 request & response - const req = mockRequest({ params: { id: 1 } }) // 帶入 params.id = 1,對 GET /users/1 發出請求 - const res = mockResponse() - - // 測試作業指定的 userController.getUser 函式 - await this.userController.getUser(req, res) - - // toggleAdmin 執行完畢後,應呼叫 res.render - // res.render 的第 1 個參數要是 'profile' - // res.render 的第 2 個參數要是 user,其 name 屬性的值應是 'admin' - res.render.getCall(0).args[0].should.equal('profile') - res.render.getCall(0).args[1].user.name.should.equal('admin') - }) - - // 測試完畢,清除資料 - after(async () => { - // 清除模擬驗證資料 - this.ensureAuthenticated.restore() - this.getUser.restore() - }) - }) - - context('# [瀏覽編輯 Profile 頁面]', () => { - before(() => { - // 模擬登入驗證 - this.ensureAuthenticated = sinon - .stub(helpers, 'ensureAuthenticated') - .returns(true) - this.getUser = sinon.stub(helpers, 'getUser').returns({ id: 1 }) - - // 製作假資料 - // 本 context 會用這筆資料進行測試 - this.UserMock = createModelMock('User', { - id: 1, - email: 'root@example.com', - name: 'admin', - isAdmin: false, - }) - - // 連向模擬的 User table - this.userController = createControllerProxy('../../controllers/userController', { User: this.UserMock }) - }) - - it(' GET /users/:id/edit ', async () => { - // 模擬 request & response - const req = mockRequest({ params: { id: 1 } }) // 帶入 params.id = 1,對 GET /users/1/edit 發出請求 - const res = mockResponse() - - // 測試作業指定的 adminController.editUser 函式 - await this.userController.editUser(req, res) - - // editUser 執行完畢後,應呼叫 res.render - // res.render 的第 1 個參數要是 'edit' - // res.render 的第 2 個參數要是 user,其 name 屬性的值應是 'admin' - res.render.getCall(0).args[0].should.equal('edit') - res.render.getCall(0).args[1].user.name.should.equal('admin') - }) - - after(async () => { - // 清除模擬驗證資料 - this.ensureAuthenticated.restore() - this.getUser.restore() - }) - }) - - context('# [編輯 Profile]', () => { - before(async () => { - // 模擬登入驗證 - this.ensureAuthenticated = sinon - .stub(helpers, 'ensureAuthenticated') - .returns(true) - this.getUser = sinon.stub(helpers, 'getUser').returns({ id: 1 }) - // 製作假資料 - // 本 context 會用這筆資料進行測試 - this.UserMock = createModelMock( - 'User', - { - id: 1, - email: 'root@example.com', - name: 'admin', - isAdmin: false, - } - ) - - // 連向模擬的 User table - this.userController = createControllerProxy('../../controllers/userController', { User: this.UserMock }) - }) - - it(' PUT /users/:id ', async () => { - // 模擬 request & response - // 對 PUT /users/1 發出 request,並夾帶 body.name = amdin2, body.email = admin_test@gmail.com - const req = mockRequest({ - params: { id: 1 }, - body: { name: 'admin2', email: 'admin_test@gmail.com' }, - }) - const res = mockResponse() - - // 測試作業指定的 userController.putUser 函式 - await this.userController.putUser(req, res) - - // putUser 正確執行的話,應呼叫 req.flash - // req.flash 的參數應與下列字串一致 - req.flash.calledWith('success_messages','使用者資料編輯成功').should.be.true - // putUser 執行完畢後,應呼叫 res.redirect 並重新導向 /users/1 - res.redirect.calledWith('/users/1').should.be.true - // putUser 執行完畢後,id:1 使用者的 name 和 email 應該已被修改 - // 將假資料撈出,比對確認有成功修改到 - const user = await this.UserMock.findOne({ where: { id: 1 } }) - user.name.should.equal('admin2') - user.email.should.equal('admin_test@gmail.com') - }) - - after(async () => { - // 清除模擬驗證資料 - this.ensureAuthenticated.restore() - this.getUser.restore() - }) - }) - }) -}) diff --git a/tests/A20.test.js b/tests/A20.test.js deleted file mode 100644 index a832e034..00000000 --- a/tests/A20.test.js +++ /dev/null @@ -1,72 +0,0 @@ -const chai = require('chai') -const request = require('supertest') -const sinon = require('sinon') -const should = chai.should() - -const helpers = require('../_helpers') -const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers'); - -const mockRequest = (query) => { - return { - ...query, - flash: sinon.spy(), - } -} -const mockResponse = () => { - return { - redirect: sinon.spy(), - render: sinon.spy(), - } -} - -describe('# A20: 餐廳資訊整理:Dashboard', function () { - context('# [Q1: Dashboard - 1 - controller / view / route]', () => { - before(async () => { - // 製作假資料 - // 本 context 會用這筆資料進行測試 - this.UserMock = createModelMock('User', { - id: 1, - email: 'root@example.com', - name: 'admin', - isAdmin: false, - }) - this.RestaurantMock = createModelMock('Restaurant', { - id: 1, - name: '銷魂麵', - viewCounts: 3 - }) - this.CategoryMock = createModelMock('Category', { - id: 1, - name: '食物' - }) - this.CommentMock = createModelMock('Comment', { - id: 1, - text: "gogogo" - }) - - // 連向模擬的 tables - this.restController = createControllerProxy('../controllers/restController', { - User: this.UserMock, - Category: this.CategoryMock, - Restaurant: this.RestaurantMock, - Comment: this.CommentMock, - }) - }) - - it(' GET /restaurants/:id/dashboard ', async () => { - // 模擬 request & response - const req = mockRequest({ params: { id: 1 } }) // 帶入 params.id = 1,對 GET /restaurants/1/dashboard 發出請求 - const res = mockResponse() - // 測試 restController.getDashBoard 函式 - await this.restController.getDashBoard(req, res) - - // getDashBoard 執行完畢後,應呼叫 res.render - // res.render 的第 1 個參數要是 'dashboard' - // res.render 的第 2 個參數要包含 restaurant,其 name 屬性的值應是 '銷魂麵' - // res.render 的地 3 個參數要包含 restaurant,其 viewCounts 值應該是 3 - res.render.getCall(0).args[0].should.equal('dashboard') - res.render.getCall(0).args[1].restaurant.name.should.equal('銷魂麵') - res.render.getCall(0).args[1].restaurant.viewCounts.should.equal(3) - }) - }) -}) diff --git a/tests/A21.test.js b/tests/A21.test.js deleted file mode 100644 index ee638370..00000000 --- a/tests/A21.test.js +++ /dev/null @@ -1,113 +0,0 @@ -const chai = require('chai') -const request = require('supertest') -const sinon = require('sinon') -const should = chai.should() - -const db = require('../models') -const helpers = require('../_helpers') -const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers') - -const mockRequest = (query) => { - return { - ...query, - flash: sinon.spy(), - } -} -const mockResponse = () => { - return { - redirect: sinon.spy(), - render: sinon.spy(), - } -} - -// 建立模擬的 Like 資料 -let mockLikeData = [ - { - userId: 1, - restaurantId: 2, - }, -] - -describe('# A21: Like / Unlike', function () { - context('# Q1: 使用者可以 Like 餐廳', () => { - before(() => { - // 模擬登入驗證 - this.ensureAuthenticated = sinon - .stub(helpers, 'ensureAuthenticated') - .returns(true) - this.getUser = sinon.stub(helpers, 'getUser').returns({ id: 1 }) - - // 建立了一個模擬的 Like table,裡面目前是空的 - this.mockLikeData = [] - this.likeMock = createModelMock('Like', null, this.mockLikeData) - - // 連向模擬的 Like table - this.userController = createControllerProxy('../controllers/userController', {Like: this.likeMock}) - }) - - it(' POST /like/:restaurantId ', async () => { - // 模擬 request & response - const req = mockRequest({ params: { restaurantId: 2 } }) // 帶入 params.restaurantId = 2,對 POST /like/2 發出請求 - const res = mockResponse() - - // 測試 userController.addLike 函式 - await this.userController.addLike(req, res) - // 將模擬的 Like table 內的資料全數撈出 - const likes = await this.likeMock.findAll() - // addLike 執行完畢後,Like table 應會從空的 -> 變成有 1 筆資料 - likes.should.have.lengthOf(1) - // 資料裡的 UserId 應該會跟我們傳入的 user id 一樣 - likes[0].UserId.should.equal(1) - // 資料裡的 RestaurantId 會跟我們傳入的 params.restaurantId 一樣 - likes[0].RestaurantId.should.equal(2) - }) - - after(async () => { - // 清除驗證資料 - this.ensureAuthenticated.restore() - this.getUser.restore() - }) - }) - - context('# Q1: 使用者可以 unLike 餐廳', () => { - before(() => { - // 模擬登入驗證 - this.ensureAuthenticated = sinon - .stub(helpers, 'ensureAuthenticated') - .returns(true) - this.getUser = sinon.stub(helpers, 'getUser').returns({ id: 1 }) - // 製作假資料 - // 下個 context 會用這筆資料進行測試 - // 模擬 Like table 裡目前有 1 筆資料如下 - this.likeMock = createModelMock('Like', { - id: 1, - UserId: 1, - RestaurantId: 2, - }, mockLikeData); - - // 連向模擬的 Like table - this.userController = createControllerProxy('../controllers/userController', { Like: this.likeMock }) - }) - - it(' DELETE /like/:restaurantId ', async () => { - // 模擬 request & response - // 模擬發出 request, 帶入 params.id = 1, restaurantId = 2 - const req = mockRequest({ params: { id: 1, restaurantId: 2 } }) // 帶入 params.id = 1,對 DELETE /like/2 發出請求 - const res = mockResponse() - - // 測試作業指定的 userController.removeLike 函式 - await this.userController.removeLike(req, res) - - // 將模擬的 Like table 內的資料全數撈出 - const likes = await this.likeMock.findAll() - // addLike 執行完畢後,Like table 應會從有 1 筆資料 -> 變成空的 - likes.should.have.lengthOf(0) - }) - - after(async () => { - // 清除模擬驗證資料 - this.ensureAuthenticated.restore() - this.getUser.restore() - }) - }) -}) diff --git a/tests/A22.test.js b/tests/A22.test.js deleted file mode 100644 index 793e6996..00000000 --- a/tests/A22.test.js +++ /dev/null @@ -1,158 +0,0 @@ -const chai = require('chai') -const request = require('supertest') -const sinon = require('sinon') -const should = chai.should() - -const app = require('../app') -const helpers = require('../_helpers') -const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers') - -const mockRequest = (query) => { - return { - ...query, - flash: sinon.spy(), - } -} -const mockResponse = () => { - return { - redirect: sinon.spy(), - render: sinon.spy(), - } -} -// 模擬 2 間餐廳資料 -let mockRestaurantData = [ - { - id: 1, - name: 'Restaurant1', - tel: 'tel', - address: 'address', - opening_hours: 'opening_hours', - description: 'test description', - favoritedUsers: [ - { - userId: 1, - }, - ], - }, - { - id: 2, - name: 'Restaurant2', - tel: 'tel', - address: 'address', - opening_hours: 'opening_hours', - description: 'description', - categoryId: 1, - favoritedUsers: [], - }, -] - -describe('# A22: TOP 10 人氣餐廳 ', function () { - context('# [網址正確、畫面正常執行]', () => { - before(async () => { - // 模擬登入驗證 - this.ensureAuthenticated = sinon - .stub(helpers, 'ensureAuthenticated') - .returns(true) - // 模擬 getUser 函式,負責取得使用者資料 - this.getUser = sinon - .stub(helpers, 'getUser') - .returns({ id: 1, followings: [], favoritedRestaurants: [] }) - }) - - it(' GET /restaurants/top ', (done) => { - // 對 GET /restaurants/top 發出請求 - request(app) - .get('/restaurants/top') - .end(function (err, res) { - // 回應中應包含字串'Top 10 人氣餐廳' - // 若請求路徑正確,controller 執行後,會呼叫 res.render - // res.render 回傳的 view 樣板裡應包含字串'Top 10 人氣餐廳' - res.text.should.include('Top 10 人氣餐廳') - done() - }) - }) - - after(async () => { - // 清除模擬驗證資料 - this.ensureAuthenticated.restore() - this.getUser.restore() - }) - }) - - context( - '# [當你點擊畫面上的「加入最愛 / 移除最愛」按鈕時,會重新計算「收藏數」的數字]', - () => { - before(async () => { - // 模擬登入驗證 - this.ensureAuthenticated = sinon - .stub(helpers, 'ensureAuthenticated') - .returns(true) - // 模擬 getUser 函式,負責取得使用者資料 - this.getUser = sinon - .stub(helpers, 'getUser') - .returns({ id: 1, followings: [], favoritedRestaurants: [] }) - - // 建立了一個模擬的 Restaurant table,裡面放入 2 間餐廳資料 - this.restaurantMock = createModelMock('Restaurant', null, mockRestaurantData) - - // 連向模擬的 Restaurant table - this.restController = createControllerProxy('../controllers/restController', { - Restaurant: this.restaurantMock, - }) - - // 建立了一個模擬的 Favorite table,裡面有 1 筆資料 - this.favoriteMock = createModelMock('Favorite', null, mockRestaurantData, 'FavoritedUsers') - - // 連向模擬的 Favorite table - this.userController = createControllerProxy('../controllers/userController', {Favorite: this.favoriteMock}) - }) - - it(' POST favorite ', async () => { - // 模擬 request & response - // 對 POST /favorite/2 發出請求,夾帶 user.favoritedRestaurants - const req = mockRequest({ - user: { favoritedRestaurants: [] }, - params: { restaurantId: 2 }, - }) - const res = mockResponse() - - // 測試 userController.addFavorite 函式 - await this.userController.addFavorite(req, res) - - // 取得餐廳排序資料 - await this.restController.getTopRestaurant(req, res) - - // addFavorite 執行完畢後,應呼叫 res.render - // res.render 的第 2 個參數要包含 restaurants - // restaurant 當中的第 2 筆資料 favoritedCount 屬性的值應是 1 (被 1 個使用者加入最愛了) - res.render.getCall(0).args[1].restaurants[1].favoritedCount.should.equal(1) - }) - - it(' DELETE favorite ', async () => { - // 模擬 request & response - // 對 DELETE /favorite/1 發出請求,夾入 user.favoritedRestaurants - const req = mockRequest({ - user: { favoritedRestaurants: [] }, - params: { restaurantId: 1 }, - }) - const res = mockResponse() - - // 測試 userController.removeFavorite 函式 - await this.userController.removeFavorite(req, res) - // 取得餐廳排序資料 - await this.restController.getTopRestaurant(req, res) - - // removeFavorite 執行完畢後,應呼叫 res.render - // res.render 的第 2 個參數要包含 restaurants - // restaurants 當中的第 1 筆資料 id 屬性值應是 2 (id:1 的那家餐廳被刪除了) - res.render.getCall(0).args[1].restaurants[0].id.should.equal(2) - }) - - after(async () => { - // 清除模擬驗證資料 - this.ensureAuthenticated.restore() - this.getUser.restore() - }) - } - ) -}) diff --git a/tests/A17.test.js b/tests/R01.test.js similarity index 98% rename from tests/A17.test.js rename to tests/R01.test.js index 6065446e..38d41967 100644 --- a/tests/A17.test.js +++ b/tests/R01.test.js @@ -19,7 +19,7 @@ const mockResponse = () => { } } -describe('# A17', () => { +describe('# R01', () => { describe('登入測試: POST /signin', function(){ // 以下測試會發出請求,測試資料庫內是否有作業指定的使用者資料 // 測試資料的來源是真實的資料庫 @@ -57,7 +57,7 @@ describe('# A17', () => { }) }); - describe('# A17: 使用者權限管理', function () { + describe('# R01: 使用者權限管理', function () { // 前置準備 before(() => { // 製作假資料 From 2f9c683cab88eb95f6deb12f34ef83d1ae9c3e7d Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Sun, 31 Oct 2021 13:07:46 +0800 Subject: [PATCH 022/198] changed all test file name --- tests/{A17.test.js => R01.test.js} | 4 ++-- tests/{A19.test.js => R02.test.js} | 4 ++-- tests/{A20.test.js => R03.test.js} | 2 +- tests/{A21.test.js => R04.test.js} | 2 +- tests/{A22.test.js => R05.test.js} | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) rename tests/{A17.test.js => R01.test.js} (98%) rename tests/{A19.test.js => R02.test.js} (98%) rename tests/{A20.test.js => R03.test.js} (97%) rename tests/{A21.test.js => R04.test.js} (98%) rename tests/{A22.test.js => R05.test.js} (98%) diff --git a/tests/A17.test.js b/tests/R01.test.js similarity index 98% rename from tests/A17.test.js rename to tests/R01.test.js index 6065446e..38d41967 100644 --- a/tests/A17.test.js +++ b/tests/R01.test.js @@ -19,7 +19,7 @@ const mockResponse = () => { } } -describe('# A17', () => { +describe('# R01', () => { describe('登入測試: POST /signin', function(){ // 以下測試會發出請求,測試資料庫內是否有作業指定的使用者資料 // 測試資料的來源是真實的資料庫 @@ -57,7 +57,7 @@ describe('# A17', () => { }) }); - describe('# A17: 使用者權限管理', function () { + describe('# R01: 使用者權限管理', function () { // 前置準備 before(() => { // 製作假資料 diff --git a/tests/A19.test.js b/tests/R02.test.js similarity index 98% rename from tests/A19.test.js rename to tests/R02.test.js index 228aafad..ea3739e9 100644 --- a/tests/A19.test.js +++ b/tests/R02.test.js @@ -17,8 +17,8 @@ const mockResponse = () => { render: sinon.spy(), } } -describe('# A19', () => { - describe('# A19: 建立 User Profile', function () { +describe('# R02', () => { + describe('# R02: 建立 User Profile', function () { context('# [瀏覽 Profile]', () => { // 前置準備 before(() => { diff --git a/tests/A20.test.js b/tests/R03.test.js similarity index 97% rename from tests/A20.test.js rename to tests/R03.test.js index a832e034..bc7d1ade 100644 --- a/tests/A20.test.js +++ b/tests/R03.test.js @@ -19,7 +19,7 @@ const mockResponse = () => { } } -describe('# A20: 餐廳資訊整理:Dashboard', function () { +describe('# R03: 餐廳資訊整理:Dashboard', function () { context('# [Q1: Dashboard - 1 - controller / view / route]', () => { before(async () => { // 製作假資料 diff --git a/tests/A21.test.js b/tests/R04.test.js similarity index 98% rename from tests/A21.test.js rename to tests/R04.test.js index ee638370..86bbb191 100644 --- a/tests/A21.test.js +++ b/tests/R04.test.js @@ -28,7 +28,7 @@ let mockLikeData = [ }, ] -describe('# A21: Like / Unlike', function () { +describe('# R04: Like / Unlike', function () { context('# Q1: 使用者可以 Like 餐廳', () => { before(() => { // 模擬登入驗證 diff --git a/tests/A22.test.js b/tests/R05.test.js similarity index 98% rename from tests/A22.test.js rename to tests/R05.test.js index 793e6996..a0ef79cc 100644 --- a/tests/A22.test.js +++ b/tests/R05.test.js @@ -46,7 +46,7 @@ let mockRestaurantData = [ }, ] -describe('# A22: TOP 10 人氣餐廳 ', function () { +describe('# R05: TOP 10 人氣餐廳 ', function () { context('# [網址正確、畫面正常執行]', () => { before(async () => { // 模擬登入驗證 From d3a66ddaea0ef1bf0b9deda1454cb2fa2505d0db Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Sun, 31 Oct 2021 13:09:29 +0800 Subject: [PATCH 023/198] remove R03/04/05 test --- tests/R03.test.js | 72 --------------------- tests/R04.test.js | 113 --------------------------------- tests/R05.test.js | 158 ---------------------------------------------- 3 files changed, 343 deletions(-) delete mode 100644 tests/R03.test.js delete mode 100644 tests/R04.test.js delete mode 100644 tests/R05.test.js diff --git a/tests/R03.test.js b/tests/R03.test.js deleted file mode 100644 index bc7d1ade..00000000 --- a/tests/R03.test.js +++ /dev/null @@ -1,72 +0,0 @@ -const chai = require('chai') -const request = require('supertest') -const sinon = require('sinon') -const should = chai.should() - -const helpers = require('../_helpers') -const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers'); - -const mockRequest = (query) => { - return { - ...query, - flash: sinon.spy(), - } -} -const mockResponse = () => { - return { - redirect: sinon.spy(), - render: sinon.spy(), - } -} - -describe('# R03: 餐廳資訊整理:Dashboard', function () { - context('# [Q1: Dashboard - 1 - controller / view / route]', () => { - before(async () => { - // 製作假資料 - // 本 context 會用這筆資料進行測試 - this.UserMock = createModelMock('User', { - id: 1, - email: 'root@example.com', - name: 'admin', - isAdmin: false, - }) - this.RestaurantMock = createModelMock('Restaurant', { - id: 1, - name: '銷魂麵', - viewCounts: 3 - }) - this.CategoryMock = createModelMock('Category', { - id: 1, - name: '食物' - }) - this.CommentMock = createModelMock('Comment', { - id: 1, - text: "gogogo" - }) - - // 連向模擬的 tables - this.restController = createControllerProxy('../controllers/restController', { - User: this.UserMock, - Category: this.CategoryMock, - Restaurant: this.RestaurantMock, - Comment: this.CommentMock, - }) - }) - - it(' GET /restaurants/:id/dashboard ', async () => { - // 模擬 request & response - const req = mockRequest({ params: { id: 1 } }) // 帶入 params.id = 1,對 GET /restaurants/1/dashboard 發出請求 - const res = mockResponse() - // 測試 restController.getDashBoard 函式 - await this.restController.getDashBoard(req, res) - - // getDashBoard 執行完畢後,應呼叫 res.render - // res.render 的第 1 個參數要是 'dashboard' - // res.render 的第 2 個參數要包含 restaurant,其 name 屬性的值應是 '銷魂麵' - // res.render 的地 3 個參數要包含 restaurant,其 viewCounts 值應該是 3 - res.render.getCall(0).args[0].should.equal('dashboard') - res.render.getCall(0).args[1].restaurant.name.should.equal('銷魂麵') - res.render.getCall(0).args[1].restaurant.viewCounts.should.equal(3) - }) - }) -}) diff --git a/tests/R04.test.js b/tests/R04.test.js deleted file mode 100644 index 86bbb191..00000000 --- a/tests/R04.test.js +++ /dev/null @@ -1,113 +0,0 @@ -const chai = require('chai') -const request = require('supertest') -const sinon = require('sinon') -const should = chai.should() - -const db = require('../models') -const helpers = require('../_helpers') -const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers') - -const mockRequest = (query) => { - return { - ...query, - flash: sinon.spy(), - } -} -const mockResponse = () => { - return { - redirect: sinon.spy(), - render: sinon.spy(), - } -} - -// 建立模擬的 Like 資料 -let mockLikeData = [ - { - userId: 1, - restaurantId: 2, - }, -] - -describe('# R04: Like / Unlike', function () { - context('# Q1: 使用者可以 Like 餐廳', () => { - before(() => { - // 模擬登入驗證 - this.ensureAuthenticated = sinon - .stub(helpers, 'ensureAuthenticated') - .returns(true) - this.getUser = sinon.stub(helpers, 'getUser').returns({ id: 1 }) - - // 建立了一個模擬的 Like table,裡面目前是空的 - this.mockLikeData = [] - this.likeMock = createModelMock('Like', null, this.mockLikeData) - - // 連向模擬的 Like table - this.userController = createControllerProxy('../controllers/userController', {Like: this.likeMock}) - }) - - it(' POST /like/:restaurantId ', async () => { - // 模擬 request & response - const req = mockRequest({ params: { restaurantId: 2 } }) // 帶入 params.restaurantId = 2,對 POST /like/2 發出請求 - const res = mockResponse() - - // 測試 userController.addLike 函式 - await this.userController.addLike(req, res) - // 將模擬的 Like table 內的資料全數撈出 - const likes = await this.likeMock.findAll() - // addLike 執行完畢後,Like table 應會從空的 -> 變成有 1 筆資料 - likes.should.have.lengthOf(1) - // 資料裡的 UserId 應該會跟我們傳入的 user id 一樣 - likes[0].UserId.should.equal(1) - // 資料裡的 RestaurantId 會跟我們傳入的 params.restaurantId 一樣 - likes[0].RestaurantId.should.equal(2) - }) - - after(async () => { - // 清除驗證資料 - this.ensureAuthenticated.restore() - this.getUser.restore() - }) - }) - - context('# Q1: 使用者可以 unLike 餐廳', () => { - before(() => { - // 模擬登入驗證 - this.ensureAuthenticated = sinon - .stub(helpers, 'ensureAuthenticated') - .returns(true) - this.getUser = sinon.stub(helpers, 'getUser').returns({ id: 1 }) - // 製作假資料 - // 下個 context 會用這筆資料進行測試 - // 模擬 Like table 裡目前有 1 筆資料如下 - this.likeMock = createModelMock('Like', { - id: 1, - UserId: 1, - RestaurantId: 2, - }, mockLikeData); - - // 連向模擬的 Like table - this.userController = createControllerProxy('../controllers/userController', { Like: this.likeMock }) - }) - - it(' DELETE /like/:restaurantId ', async () => { - // 模擬 request & response - // 模擬發出 request, 帶入 params.id = 1, restaurantId = 2 - const req = mockRequest({ params: { id: 1, restaurantId: 2 } }) // 帶入 params.id = 1,對 DELETE /like/2 發出請求 - const res = mockResponse() - - // 測試作業指定的 userController.removeLike 函式 - await this.userController.removeLike(req, res) - - // 將模擬的 Like table 內的資料全數撈出 - const likes = await this.likeMock.findAll() - // addLike 執行完畢後,Like table 應會從有 1 筆資料 -> 變成空的 - likes.should.have.lengthOf(0) - }) - - after(async () => { - // 清除模擬驗證資料 - this.ensureAuthenticated.restore() - this.getUser.restore() - }) - }) -}) diff --git a/tests/R05.test.js b/tests/R05.test.js deleted file mode 100644 index a0ef79cc..00000000 --- a/tests/R05.test.js +++ /dev/null @@ -1,158 +0,0 @@ -const chai = require('chai') -const request = require('supertest') -const sinon = require('sinon') -const should = chai.should() - -const app = require('../app') -const helpers = require('../_helpers') -const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers') - -const mockRequest = (query) => { - return { - ...query, - flash: sinon.spy(), - } -} -const mockResponse = () => { - return { - redirect: sinon.spy(), - render: sinon.spy(), - } -} -// 模擬 2 間餐廳資料 -let mockRestaurantData = [ - { - id: 1, - name: 'Restaurant1', - tel: 'tel', - address: 'address', - opening_hours: 'opening_hours', - description: 'test description', - favoritedUsers: [ - { - userId: 1, - }, - ], - }, - { - id: 2, - name: 'Restaurant2', - tel: 'tel', - address: 'address', - opening_hours: 'opening_hours', - description: 'description', - categoryId: 1, - favoritedUsers: [], - }, -] - -describe('# R05: TOP 10 人氣餐廳 ', function () { - context('# [網址正確、畫面正常執行]', () => { - before(async () => { - // 模擬登入驗證 - this.ensureAuthenticated = sinon - .stub(helpers, 'ensureAuthenticated') - .returns(true) - // 模擬 getUser 函式,負責取得使用者資料 - this.getUser = sinon - .stub(helpers, 'getUser') - .returns({ id: 1, followings: [], favoritedRestaurants: [] }) - }) - - it(' GET /restaurants/top ', (done) => { - // 對 GET /restaurants/top 發出請求 - request(app) - .get('/restaurants/top') - .end(function (err, res) { - // 回應中應包含字串'Top 10 人氣餐廳' - // 若請求路徑正確,controller 執行後,會呼叫 res.render - // res.render 回傳的 view 樣板裡應包含字串'Top 10 人氣餐廳' - res.text.should.include('Top 10 人氣餐廳') - done() - }) - }) - - after(async () => { - // 清除模擬驗證資料 - this.ensureAuthenticated.restore() - this.getUser.restore() - }) - }) - - context( - '# [當你點擊畫面上的「加入最愛 / 移除最愛」按鈕時,會重新計算「收藏數」的數字]', - () => { - before(async () => { - // 模擬登入驗證 - this.ensureAuthenticated = sinon - .stub(helpers, 'ensureAuthenticated') - .returns(true) - // 模擬 getUser 函式,負責取得使用者資料 - this.getUser = sinon - .stub(helpers, 'getUser') - .returns({ id: 1, followings: [], favoritedRestaurants: [] }) - - // 建立了一個模擬的 Restaurant table,裡面放入 2 間餐廳資料 - this.restaurantMock = createModelMock('Restaurant', null, mockRestaurantData) - - // 連向模擬的 Restaurant table - this.restController = createControllerProxy('../controllers/restController', { - Restaurant: this.restaurantMock, - }) - - // 建立了一個模擬的 Favorite table,裡面有 1 筆資料 - this.favoriteMock = createModelMock('Favorite', null, mockRestaurantData, 'FavoritedUsers') - - // 連向模擬的 Favorite table - this.userController = createControllerProxy('../controllers/userController', {Favorite: this.favoriteMock}) - }) - - it(' POST favorite ', async () => { - // 模擬 request & response - // 對 POST /favorite/2 發出請求,夾帶 user.favoritedRestaurants - const req = mockRequest({ - user: { favoritedRestaurants: [] }, - params: { restaurantId: 2 }, - }) - const res = mockResponse() - - // 測試 userController.addFavorite 函式 - await this.userController.addFavorite(req, res) - - // 取得餐廳排序資料 - await this.restController.getTopRestaurant(req, res) - - // addFavorite 執行完畢後,應呼叫 res.render - // res.render 的第 2 個參數要包含 restaurants - // restaurant 當中的第 2 筆資料 favoritedCount 屬性的值應是 1 (被 1 個使用者加入最愛了) - res.render.getCall(0).args[1].restaurants[1].favoritedCount.should.equal(1) - }) - - it(' DELETE favorite ', async () => { - // 模擬 request & response - // 對 DELETE /favorite/1 發出請求,夾入 user.favoritedRestaurants - const req = mockRequest({ - user: { favoritedRestaurants: [] }, - params: { restaurantId: 1 }, - }) - const res = mockResponse() - - // 測試 userController.removeFavorite 函式 - await this.userController.removeFavorite(req, res) - // 取得餐廳排序資料 - await this.restController.getTopRestaurant(req, res) - - // removeFavorite 執行完畢後,應呼叫 res.render - // res.render 的第 2 個參數要包含 restaurants - // restaurants 當中的第 1 筆資料 id 屬性值應是 2 (id:1 的那家餐廳被刪除了) - res.render.getCall(0).args[1].restaurants[0].id.should.equal(2) - }) - - after(async () => { - // 清除模擬驗證資料 - this.ensureAuthenticated.restore() - this.getUser.restore() - }) - } - ) -}) From e0dbcb62099ea60df1e6da8f56596b7190488f84 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Sun, 31 Oct 2021 13:10:14 +0800 Subject: [PATCH 024/198] remove R04/05 test --- tests/R04.test.js | 113 --------------------------------- tests/R05.test.js | 158 ---------------------------------------------- 2 files changed, 271 deletions(-) delete mode 100644 tests/R04.test.js delete mode 100644 tests/R05.test.js diff --git a/tests/R04.test.js b/tests/R04.test.js deleted file mode 100644 index 86bbb191..00000000 --- a/tests/R04.test.js +++ /dev/null @@ -1,113 +0,0 @@ -const chai = require('chai') -const request = require('supertest') -const sinon = require('sinon') -const should = chai.should() - -const db = require('../models') -const helpers = require('../_helpers') -const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers') - -const mockRequest = (query) => { - return { - ...query, - flash: sinon.spy(), - } -} -const mockResponse = () => { - return { - redirect: sinon.spy(), - render: sinon.spy(), - } -} - -// 建立模擬的 Like 資料 -let mockLikeData = [ - { - userId: 1, - restaurantId: 2, - }, -] - -describe('# R04: Like / Unlike', function () { - context('# Q1: 使用者可以 Like 餐廳', () => { - before(() => { - // 模擬登入驗證 - this.ensureAuthenticated = sinon - .stub(helpers, 'ensureAuthenticated') - .returns(true) - this.getUser = sinon.stub(helpers, 'getUser').returns({ id: 1 }) - - // 建立了一個模擬的 Like table,裡面目前是空的 - this.mockLikeData = [] - this.likeMock = createModelMock('Like', null, this.mockLikeData) - - // 連向模擬的 Like table - this.userController = createControllerProxy('../controllers/userController', {Like: this.likeMock}) - }) - - it(' POST /like/:restaurantId ', async () => { - // 模擬 request & response - const req = mockRequest({ params: { restaurantId: 2 } }) // 帶入 params.restaurantId = 2,對 POST /like/2 發出請求 - const res = mockResponse() - - // 測試 userController.addLike 函式 - await this.userController.addLike(req, res) - // 將模擬的 Like table 內的資料全數撈出 - const likes = await this.likeMock.findAll() - // addLike 執行完畢後,Like table 應會從空的 -> 變成有 1 筆資料 - likes.should.have.lengthOf(1) - // 資料裡的 UserId 應該會跟我們傳入的 user id 一樣 - likes[0].UserId.should.equal(1) - // 資料裡的 RestaurantId 會跟我們傳入的 params.restaurantId 一樣 - likes[0].RestaurantId.should.equal(2) - }) - - after(async () => { - // 清除驗證資料 - this.ensureAuthenticated.restore() - this.getUser.restore() - }) - }) - - context('# Q1: 使用者可以 unLike 餐廳', () => { - before(() => { - // 模擬登入驗證 - this.ensureAuthenticated = sinon - .stub(helpers, 'ensureAuthenticated') - .returns(true) - this.getUser = sinon.stub(helpers, 'getUser').returns({ id: 1 }) - // 製作假資料 - // 下個 context 會用這筆資料進行測試 - // 模擬 Like table 裡目前有 1 筆資料如下 - this.likeMock = createModelMock('Like', { - id: 1, - UserId: 1, - RestaurantId: 2, - }, mockLikeData); - - // 連向模擬的 Like table - this.userController = createControllerProxy('../controllers/userController', { Like: this.likeMock }) - }) - - it(' DELETE /like/:restaurantId ', async () => { - // 模擬 request & response - // 模擬發出 request, 帶入 params.id = 1, restaurantId = 2 - const req = mockRequest({ params: { id: 1, restaurantId: 2 } }) // 帶入 params.id = 1,對 DELETE /like/2 發出請求 - const res = mockResponse() - - // 測試作業指定的 userController.removeLike 函式 - await this.userController.removeLike(req, res) - - // 將模擬的 Like table 內的資料全數撈出 - const likes = await this.likeMock.findAll() - // addLike 執行完畢後,Like table 應會從有 1 筆資料 -> 變成空的 - likes.should.have.lengthOf(0) - }) - - after(async () => { - // 清除模擬驗證資料 - this.ensureAuthenticated.restore() - this.getUser.restore() - }) - }) -}) diff --git a/tests/R05.test.js b/tests/R05.test.js deleted file mode 100644 index a0ef79cc..00000000 --- a/tests/R05.test.js +++ /dev/null @@ -1,158 +0,0 @@ -const chai = require('chai') -const request = require('supertest') -const sinon = require('sinon') -const should = chai.should() - -const app = require('../app') -const helpers = require('../_helpers') -const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers') - -const mockRequest = (query) => { - return { - ...query, - flash: sinon.spy(), - } -} -const mockResponse = () => { - return { - redirect: sinon.spy(), - render: sinon.spy(), - } -} -// 模擬 2 間餐廳資料 -let mockRestaurantData = [ - { - id: 1, - name: 'Restaurant1', - tel: 'tel', - address: 'address', - opening_hours: 'opening_hours', - description: 'test description', - favoritedUsers: [ - { - userId: 1, - }, - ], - }, - { - id: 2, - name: 'Restaurant2', - tel: 'tel', - address: 'address', - opening_hours: 'opening_hours', - description: 'description', - categoryId: 1, - favoritedUsers: [], - }, -] - -describe('# R05: TOP 10 人氣餐廳 ', function () { - context('# [網址正確、畫面正常執行]', () => { - before(async () => { - // 模擬登入驗證 - this.ensureAuthenticated = sinon - .stub(helpers, 'ensureAuthenticated') - .returns(true) - // 模擬 getUser 函式,負責取得使用者資料 - this.getUser = sinon - .stub(helpers, 'getUser') - .returns({ id: 1, followings: [], favoritedRestaurants: [] }) - }) - - it(' GET /restaurants/top ', (done) => { - // 對 GET /restaurants/top 發出請求 - request(app) - .get('/restaurants/top') - .end(function (err, res) { - // 回應中應包含字串'Top 10 人氣餐廳' - // 若請求路徑正確,controller 執行後,會呼叫 res.render - // res.render 回傳的 view 樣板裡應包含字串'Top 10 人氣餐廳' - res.text.should.include('Top 10 人氣餐廳') - done() - }) - }) - - after(async () => { - // 清除模擬驗證資料 - this.ensureAuthenticated.restore() - this.getUser.restore() - }) - }) - - context( - '# [當你點擊畫面上的「加入最愛 / 移除最愛」按鈕時,會重新計算「收藏數」的數字]', - () => { - before(async () => { - // 模擬登入驗證 - this.ensureAuthenticated = sinon - .stub(helpers, 'ensureAuthenticated') - .returns(true) - // 模擬 getUser 函式,負責取得使用者資料 - this.getUser = sinon - .stub(helpers, 'getUser') - .returns({ id: 1, followings: [], favoritedRestaurants: [] }) - - // 建立了一個模擬的 Restaurant table,裡面放入 2 間餐廳資料 - this.restaurantMock = createModelMock('Restaurant', null, mockRestaurantData) - - // 連向模擬的 Restaurant table - this.restController = createControllerProxy('../controllers/restController', { - Restaurant: this.restaurantMock, - }) - - // 建立了一個模擬的 Favorite table,裡面有 1 筆資料 - this.favoriteMock = createModelMock('Favorite', null, mockRestaurantData, 'FavoritedUsers') - - // 連向模擬的 Favorite table - this.userController = createControllerProxy('../controllers/userController', {Favorite: this.favoriteMock}) - }) - - it(' POST favorite ', async () => { - // 模擬 request & response - // 對 POST /favorite/2 發出請求,夾帶 user.favoritedRestaurants - const req = mockRequest({ - user: { favoritedRestaurants: [] }, - params: { restaurantId: 2 }, - }) - const res = mockResponse() - - // 測試 userController.addFavorite 函式 - await this.userController.addFavorite(req, res) - - // 取得餐廳排序資料 - await this.restController.getTopRestaurant(req, res) - - // addFavorite 執行完畢後,應呼叫 res.render - // res.render 的第 2 個參數要包含 restaurants - // restaurant 當中的第 2 筆資料 favoritedCount 屬性的值應是 1 (被 1 個使用者加入最愛了) - res.render.getCall(0).args[1].restaurants[1].favoritedCount.should.equal(1) - }) - - it(' DELETE favorite ', async () => { - // 模擬 request & response - // 對 DELETE /favorite/1 發出請求,夾入 user.favoritedRestaurants - const req = mockRequest({ - user: { favoritedRestaurants: [] }, - params: { restaurantId: 1 }, - }) - const res = mockResponse() - - // 測試 userController.removeFavorite 函式 - await this.userController.removeFavorite(req, res) - // 取得餐廳排序資料 - await this.restController.getTopRestaurant(req, res) - - // removeFavorite 執行完畢後,應呼叫 res.render - // res.render 的第 2 個參數要包含 restaurants - // restaurants 當中的第 1 筆資料 id 屬性值應是 2 (id:1 的那家餐廳被刪除了) - res.render.getCall(0).args[1].restaurants[0].id.should.equal(2) - }) - - after(async () => { - // 清除模擬驗證資料 - this.ensureAuthenticated.restore() - this.getUser.restore() - }) - } - ) -}) From 5a83153e5ccf2e215b2a711792c3e8e5f383f1c5 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Sun, 31 Oct 2021 13:10:58 +0800 Subject: [PATCH 025/198] remove R05 test --- tests/R05.test.js | 158 ---------------------------------------------- 1 file changed, 158 deletions(-) delete mode 100644 tests/R05.test.js diff --git a/tests/R05.test.js b/tests/R05.test.js deleted file mode 100644 index a0ef79cc..00000000 --- a/tests/R05.test.js +++ /dev/null @@ -1,158 +0,0 @@ -const chai = require('chai') -const request = require('supertest') -const sinon = require('sinon') -const should = chai.should() - -const app = require('../app') -const helpers = require('../_helpers') -const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers') - -const mockRequest = (query) => { - return { - ...query, - flash: sinon.spy(), - } -} -const mockResponse = () => { - return { - redirect: sinon.spy(), - render: sinon.spy(), - } -} -// 模擬 2 間餐廳資料 -let mockRestaurantData = [ - { - id: 1, - name: 'Restaurant1', - tel: 'tel', - address: 'address', - opening_hours: 'opening_hours', - description: 'test description', - favoritedUsers: [ - { - userId: 1, - }, - ], - }, - { - id: 2, - name: 'Restaurant2', - tel: 'tel', - address: 'address', - opening_hours: 'opening_hours', - description: 'description', - categoryId: 1, - favoritedUsers: [], - }, -] - -describe('# R05: TOP 10 人氣餐廳 ', function () { - context('# [網址正確、畫面正常執行]', () => { - before(async () => { - // 模擬登入驗證 - this.ensureAuthenticated = sinon - .stub(helpers, 'ensureAuthenticated') - .returns(true) - // 模擬 getUser 函式,負責取得使用者資料 - this.getUser = sinon - .stub(helpers, 'getUser') - .returns({ id: 1, followings: [], favoritedRestaurants: [] }) - }) - - it(' GET /restaurants/top ', (done) => { - // 對 GET /restaurants/top 發出請求 - request(app) - .get('/restaurants/top') - .end(function (err, res) { - // 回應中應包含字串'Top 10 人氣餐廳' - // 若請求路徑正確,controller 執行後,會呼叫 res.render - // res.render 回傳的 view 樣板裡應包含字串'Top 10 人氣餐廳' - res.text.should.include('Top 10 人氣餐廳') - done() - }) - }) - - after(async () => { - // 清除模擬驗證資料 - this.ensureAuthenticated.restore() - this.getUser.restore() - }) - }) - - context( - '# [當你點擊畫面上的「加入最愛 / 移除最愛」按鈕時,會重新計算「收藏數」的數字]', - () => { - before(async () => { - // 模擬登入驗證 - this.ensureAuthenticated = sinon - .stub(helpers, 'ensureAuthenticated') - .returns(true) - // 模擬 getUser 函式,負責取得使用者資料 - this.getUser = sinon - .stub(helpers, 'getUser') - .returns({ id: 1, followings: [], favoritedRestaurants: [] }) - - // 建立了一個模擬的 Restaurant table,裡面放入 2 間餐廳資料 - this.restaurantMock = createModelMock('Restaurant', null, mockRestaurantData) - - // 連向模擬的 Restaurant table - this.restController = createControllerProxy('../controllers/restController', { - Restaurant: this.restaurantMock, - }) - - // 建立了一個模擬的 Favorite table,裡面有 1 筆資料 - this.favoriteMock = createModelMock('Favorite', null, mockRestaurantData, 'FavoritedUsers') - - // 連向模擬的 Favorite table - this.userController = createControllerProxy('../controllers/userController', {Favorite: this.favoriteMock}) - }) - - it(' POST favorite ', async () => { - // 模擬 request & response - // 對 POST /favorite/2 發出請求,夾帶 user.favoritedRestaurants - const req = mockRequest({ - user: { favoritedRestaurants: [] }, - params: { restaurantId: 2 }, - }) - const res = mockResponse() - - // 測試 userController.addFavorite 函式 - await this.userController.addFavorite(req, res) - - // 取得餐廳排序資料 - await this.restController.getTopRestaurant(req, res) - - // addFavorite 執行完畢後,應呼叫 res.render - // res.render 的第 2 個參數要包含 restaurants - // restaurant 當中的第 2 筆資料 favoritedCount 屬性的值應是 1 (被 1 個使用者加入最愛了) - res.render.getCall(0).args[1].restaurants[1].favoritedCount.should.equal(1) - }) - - it(' DELETE favorite ', async () => { - // 模擬 request & response - // 對 DELETE /favorite/1 發出請求,夾入 user.favoritedRestaurants - const req = mockRequest({ - user: { favoritedRestaurants: [] }, - params: { restaurantId: 1 }, - }) - const res = mockResponse() - - // 測試 userController.removeFavorite 函式 - await this.userController.removeFavorite(req, res) - // 取得餐廳排序資料 - await this.restController.getTopRestaurant(req, res) - - // removeFavorite 執行完畢後,應呼叫 res.render - // res.render 的第 2 個參數要包含 restaurants - // restaurants 當中的第 1 筆資料 id 屬性值應是 2 (id:1 的那家餐廳被刪除了) - res.render.getCall(0).args[1].restaurants[0].id.should.equal(2) - }) - - after(async () => { - // 清除模擬驗證資料 - this.ensureAuthenticated.restore() - this.getUser.restore() - }) - } - ) -}) From 914871590b449f471147579952f39df72512e664 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 15:14:53 +0800 Subject: [PATCH 026/198] fixed R01 ../../app issue & not found sequelize lib --- tests/R01.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/R01.test.js b/tests/R01.test.js index 38d41967..07778434 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -3,8 +3,7 @@ const request = require('supertest') const sinon = require('sinon') const should = chai.should() -const app = require('../../app') -const { classToInvokable } = require('sequelize/types/lib/utils') +const app = require('../app') const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers'); const mockRequest = (query) => { From 154a12e2d4cba762c014fe35bfb60f6f3df16455 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 15:14:53 +0800 Subject: [PATCH 027/198] fixed R01 ../../app issue & not found sequelize lib --- tests/R01.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/R01.test.js b/tests/R01.test.js index 38d41967..07778434 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -3,8 +3,7 @@ const request = require('supertest') const sinon = require('sinon') const should = chai.should() -const app = require('../../app') -const { classToInvokable } = require('sequelize/types/lib/utils') +const app = require('../app') const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers'); const mockRequest = (query) => { From c3b664cfbf2217fb7b006dd920a53660c8f46f05 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 15:14:53 +0800 Subject: [PATCH 028/198] fixed R01 ../../app issue & not found sequelize lib --- tests/R01.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/R01.test.js b/tests/R01.test.js index 38d41967..07778434 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -3,8 +3,7 @@ const request = require('supertest') const sinon = require('sinon') const should = chai.should() -const app = require('../../app') -const { classToInvokable } = require('sequelize/types/lib/utils') +const app = require('../app') const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers'); const mockRequest = (query) => { From 67b9b325d614e911bcaf418e3d84b7a02e2b002d Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 15:14:53 +0800 Subject: [PATCH 029/198] fixed R01 ../../app issue & not found sequelize lib --- tests/R01.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/R01.test.js b/tests/R01.test.js index 38d41967..07778434 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -3,8 +3,7 @@ const request = require('supertest') const sinon = require('sinon') const should = chai.should() -const app = require('../../app') -const { classToInvokable } = require('sequelize/types/lib/utils') +const app = require('../app') const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers'); const mockRequest = (query) => { From 3e9b544f7e24f3b20a3c53ec7783b7c7e5aa8347 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 15:14:53 +0800 Subject: [PATCH 030/198] fixed R01 ../../app issue & not found sequelize lib --- tests/R01.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/R01.test.js b/tests/R01.test.js index 38d41967..07778434 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -3,8 +3,7 @@ const request = require('supertest') const sinon = require('sinon') const should = chai.should() -const app = require('../../app') -const { classToInvokable } = require('sequelize/types/lib/utils') +const app = require('../app') const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers'); const mockRequest = (query) => { From ba687334daaf8456d0585b52418ffbe090ad7ca0 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 20:17:36 +0800 Subject: [PATCH 031/198] fixed controller path wrong issue / check data exist --- helpers/unitTestHelpers.js | 4 ++-- tests/R01.test.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js index a7157681..ea4cc7b1 100644 --- a/helpers/unitTestHelpers.js +++ b/helpers/unitTestHelpers.js @@ -32,7 +32,7 @@ const createModelMock = (name, defaultValue, data, joinedTableName) => { return Promise.resolve(data.map(d => mockModel.build(d))) } else if (query === 'findAll') { // 回傳模擬資料 - return Promise.resolve(data.map(d => mockModel.build(d))) + return Promise.resolve( data ? data.map(d => mockModel.build(d)) : []) }else if (query === 'destroy') { // destroy 可以從 where 取得要刪除的資料 // 因此就可以模擬將模擬資料中的資料刪除 @@ -56,7 +56,7 @@ const createModelMock = (name, defaultValue, data, joinedTableName) => { return Promise.resolve(mockModel.build(data)) } else if (query === 'findAll') { // 回傳模擬資料 - return Promise.resolve(data.map(d => mockModel.build(d))) + return Promise.resolve(data ? data.map(d => mockModel.build(d)) : []) } else if (query === 'destroy') { // destroy 可以從 where 取得要刪除的資料 // 因此就可以模擬將模擬資料中的資料刪除 diff --git a/tests/R01.test.js b/tests/R01.test.js index 07778434..f98a96ac 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -69,7 +69,7 @@ describe('# R01', () => { }) // 修改 adminController 中的資料庫連線設定,由連向真實的資料庫 -> 改為連向模擬的 User table - this.adminController = createControllerProxy('../../controllers/adminController', { User: this.UserMock }) + this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) }) // 開始測試 @@ -104,7 +104,7 @@ describe('# R01', () => { ) // 將 adminController 中的 User db 取代成 User mock db - this.adminController = createControllerProxy('../../controllers/adminController', { User: this.UserMock }) + this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) }) it(' PUT /admin/users/:id/toggleAdmin ', async () => { @@ -138,7 +138,7 @@ describe('# R01', () => { } ) // 將 adminController 中的 User db 取代成 User mock db - this.adminController = createControllerProxy('../../controllers/adminController', { User: this.UserMock }) + this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) }) it(' PUT /admin/users/:id/toggleAdmin ', async (done) => { From 3f256dcc2ba68a58e22bf6ae1fd3cf95ee3c6ab3 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 20:17:36 +0800 Subject: [PATCH 032/198] fixed controller path wrong issue / check data exist --- helpers/unitTestHelpers.js | 4 ++-- tests/R01.test.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js index a7157681..ea4cc7b1 100644 --- a/helpers/unitTestHelpers.js +++ b/helpers/unitTestHelpers.js @@ -32,7 +32,7 @@ const createModelMock = (name, defaultValue, data, joinedTableName) => { return Promise.resolve(data.map(d => mockModel.build(d))) } else if (query === 'findAll') { // 回傳模擬資料 - return Promise.resolve(data.map(d => mockModel.build(d))) + return Promise.resolve( data ? data.map(d => mockModel.build(d)) : []) }else if (query === 'destroy') { // destroy 可以從 where 取得要刪除的資料 // 因此就可以模擬將模擬資料中的資料刪除 @@ -56,7 +56,7 @@ const createModelMock = (name, defaultValue, data, joinedTableName) => { return Promise.resolve(mockModel.build(data)) } else if (query === 'findAll') { // 回傳模擬資料 - return Promise.resolve(data.map(d => mockModel.build(d))) + return Promise.resolve(data ? data.map(d => mockModel.build(d)) : []) } else if (query === 'destroy') { // destroy 可以從 where 取得要刪除的資料 // 因此就可以模擬將模擬資料中的資料刪除 diff --git a/tests/R01.test.js b/tests/R01.test.js index 07778434..f98a96ac 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -69,7 +69,7 @@ describe('# R01', () => { }) // 修改 adminController 中的資料庫連線設定,由連向真實的資料庫 -> 改為連向模擬的 User table - this.adminController = createControllerProxy('../../controllers/adminController', { User: this.UserMock }) + this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) }) // 開始測試 @@ -104,7 +104,7 @@ describe('# R01', () => { ) // 將 adminController 中的 User db 取代成 User mock db - this.adminController = createControllerProxy('../../controllers/adminController', { User: this.UserMock }) + this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) }) it(' PUT /admin/users/:id/toggleAdmin ', async () => { @@ -138,7 +138,7 @@ describe('# R01', () => { } ) // 將 adminController 中的 User db 取代成 User mock db - this.adminController = createControllerProxy('../../controllers/adminController', { User: this.UserMock }) + this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) }) it(' PUT /admin/users/:id/toggleAdmin ', async (done) => { From 0ab85cd3943e1de42f1ef8992ade086c6b8f4680 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 20:21:36 +0800 Subject: [PATCH 033/198] fixed wrong path --- tests/R02.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/R02.test.js b/tests/R02.test.js index ea3739e9..5b7cadb2 100644 --- a/tests/R02.test.js +++ b/tests/R02.test.js @@ -37,7 +37,7 @@ describe('# R02', () => { }) // 修改 userController 中的資料庫連線設定,由連向真實的資料庫 -> 改為連向模擬的 User table - this.userController = createControllerProxy('../../controllers/userController', { User: this.UserMock }) + this.userController = createControllerProxy('../controllers/userController', { User: this.UserMock }) }) // 開始測試 @@ -82,7 +82,7 @@ describe('# R02', () => { }) // 連向模擬的 User table - this.userController = createControllerProxy('../../controllers/userController', { User: this.UserMock }) + this.userController = createControllerProxy('../controllers/userController', { User: this.UserMock }) }) it(' GET /users/:id/edit ', async () => { @@ -127,7 +127,7 @@ describe('# R02', () => { ) // 連向模擬的 User table - this.userController = createControllerProxy('../../controllers/userController', { User: this.UserMock }) + this.userController = createControllerProxy('../controllers/userController', { User: this.UserMock }) }) it(' PUT /users/:id ', async () => { From a342d2d97f2da6c14d7386ae1a8df939340da73f Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 20:17:36 +0800 Subject: [PATCH 034/198] fixed controller path wrong issue / check data exist --- helpers/unitTestHelpers.js | 4 ++-- tests/R01.test.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js index a7157681..ea4cc7b1 100644 --- a/helpers/unitTestHelpers.js +++ b/helpers/unitTestHelpers.js @@ -32,7 +32,7 @@ const createModelMock = (name, defaultValue, data, joinedTableName) => { return Promise.resolve(data.map(d => mockModel.build(d))) } else if (query === 'findAll') { // 回傳模擬資料 - return Promise.resolve(data.map(d => mockModel.build(d))) + return Promise.resolve( data ? data.map(d => mockModel.build(d)) : []) }else if (query === 'destroy') { // destroy 可以從 where 取得要刪除的資料 // 因此就可以模擬將模擬資料中的資料刪除 @@ -56,7 +56,7 @@ const createModelMock = (name, defaultValue, data, joinedTableName) => { return Promise.resolve(mockModel.build(data)) } else if (query === 'findAll') { // 回傳模擬資料 - return Promise.resolve(data.map(d => mockModel.build(d))) + return Promise.resolve(data ? data.map(d => mockModel.build(d)) : []) } else if (query === 'destroy') { // destroy 可以從 where 取得要刪除的資料 // 因此就可以模擬將模擬資料中的資料刪除 diff --git a/tests/R01.test.js b/tests/R01.test.js index 07778434..f98a96ac 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -69,7 +69,7 @@ describe('# R01', () => { }) // 修改 adminController 中的資料庫連線設定,由連向真實的資料庫 -> 改為連向模擬的 User table - this.adminController = createControllerProxy('../../controllers/adminController', { User: this.UserMock }) + this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) }) // 開始測試 @@ -104,7 +104,7 @@ describe('# R01', () => { ) // 將 adminController 中的 User db 取代成 User mock db - this.adminController = createControllerProxy('../../controllers/adminController', { User: this.UserMock }) + this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) }) it(' PUT /admin/users/:id/toggleAdmin ', async () => { @@ -138,7 +138,7 @@ describe('# R01', () => { } ) // 將 adminController 中的 User db 取代成 User mock db - this.adminController = createControllerProxy('../../controllers/adminController', { User: this.UserMock }) + this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) }) it(' PUT /admin/users/:id/toggleAdmin ', async (done) => { From b10ee15ef5278be8d5b747177f00b3371fea90f2 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 20:21:36 +0800 Subject: [PATCH 035/198] fixed wrong path --- tests/R02.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/R02.test.js b/tests/R02.test.js index ea3739e9..5b7cadb2 100644 --- a/tests/R02.test.js +++ b/tests/R02.test.js @@ -37,7 +37,7 @@ describe('# R02', () => { }) // 修改 userController 中的資料庫連線設定,由連向真實的資料庫 -> 改為連向模擬的 User table - this.userController = createControllerProxy('../../controllers/userController', { User: this.UserMock }) + this.userController = createControllerProxy('../controllers/userController', { User: this.UserMock }) }) // 開始測試 @@ -82,7 +82,7 @@ describe('# R02', () => { }) // 連向模擬的 User table - this.userController = createControllerProxy('../../controllers/userController', { User: this.UserMock }) + this.userController = createControllerProxy('../controllers/userController', { User: this.UserMock }) }) it(' GET /users/:id/edit ', async () => { @@ -127,7 +127,7 @@ describe('# R02', () => { ) // 連向模擬的 User table - this.userController = createControllerProxy('../../controllers/userController', { User: this.UserMock }) + this.userController = createControllerProxy('../controllers/userController', { User: this.UserMock }) }) it(' PUT /users/:id ', async () => { From 28011ed1321de93890822ee380606614e03093c4 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 20:17:36 +0800 Subject: [PATCH 036/198] fixed controller path wrong issue / check data exist --- helpers/unitTestHelpers.js | 4 ++-- tests/R01.test.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js index a7157681..ea4cc7b1 100644 --- a/helpers/unitTestHelpers.js +++ b/helpers/unitTestHelpers.js @@ -32,7 +32,7 @@ const createModelMock = (name, defaultValue, data, joinedTableName) => { return Promise.resolve(data.map(d => mockModel.build(d))) } else if (query === 'findAll') { // 回傳模擬資料 - return Promise.resolve(data.map(d => mockModel.build(d))) + return Promise.resolve( data ? data.map(d => mockModel.build(d)) : []) }else if (query === 'destroy') { // destroy 可以從 where 取得要刪除的資料 // 因此就可以模擬將模擬資料中的資料刪除 @@ -56,7 +56,7 @@ const createModelMock = (name, defaultValue, data, joinedTableName) => { return Promise.resolve(mockModel.build(data)) } else if (query === 'findAll') { // 回傳模擬資料 - return Promise.resolve(data.map(d => mockModel.build(d))) + return Promise.resolve(data ? data.map(d => mockModel.build(d)) : []) } else if (query === 'destroy') { // destroy 可以從 where 取得要刪除的資料 // 因此就可以模擬將模擬資料中的資料刪除 diff --git a/tests/R01.test.js b/tests/R01.test.js index 07778434..f98a96ac 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -69,7 +69,7 @@ describe('# R01', () => { }) // 修改 adminController 中的資料庫連線設定,由連向真實的資料庫 -> 改為連向模擬的 User table - this.adminController = createControllerProxy('../../controllers/adminController', { User: this.UserMock }) + this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) }) // 開始測試 @@ -104,7 +104,7 @@ describe('# R01', () => { ) // 將 adminController 中的 User db 取代成 User mock db - this.adminController = createControllerProxy('../../controllers/adminController', { User: this.UserMock }) + this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) }) it(' PUT /admin/users/:id/toggleAdmin ', async () => { @@ -138,7 +138,7 @@ describe('# R01', () => { } ) // 將 adminController 中的 User db 取代成 User mock db - this.adminController = createControllerProxy('../../controllers/adminController', { User: this.UserMock }) + this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) }) it(' PUT /admin/users/:id/toggleAdmin ', async (done) => { From 9b8b4b1e11e58c64a2393bd56e2fbb467d2dc926 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 20:21:36 +0800 Subject: [PATCH 037/198] fixed wrong path --- tests/R02.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/R02.test.js b/tests/R02.test.js index ea3739e9..5b7cadb2 100644 --- a/tests/R02.test.js +++ b/tests/R02.test.js @@ -37,7 +37,7 @@ describe('# R02', () => { }) // 修改 userController 中的資料庫連線設定,由連向真實的資料庫 -> 改為連向模擬的 User table - this.userController = createControllerProxy('../../controllers/userController', { User: this.UserMock }) + this.userController = createControllerProxy('../controllers/userController', { User: this.UserMock }) }) // 開始測試 @@ -82,7 +82,7 @@ describe('# R02', () => { }) // 連向模擬的 User table - this.userController = createControllerProxy('../../controllers/userController', { User: this.UserMock }) + this.userController = createControllerProxy('../controllers/userController', { User: this.UserMock }) }) it(' GET /users/:id/edit ', async () => { @@ -127,7 +127,7 @@ describe('# R02', () => { ) // 連向模擬的 User table - this.userController = createControllerProxy('../../controllers/userController', { User: this.UserMock }) + this.userController = createControllerProxy('../controllers/userController', { User: this.UserMock }) }) it(' PUT /users/:id ', async () => { From 5a77ed614fd1c80238a25e9f47fbfebc56627f0d Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 20:17:36 +0800 Subject: [PATCH 038/198] fixed controller path wrong issue / check data exist --- helpers/unitTestHelpers.js | 4 ++-- tests/R01.test.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js index a7157681..ea4cc7b1 100644 --- a/helpers/unitTestHelpers.js +++ b/helpers/unitTestHelpers.js @@ -32,7 +32,7 @@ const createModelMock = (name, defaultValue, data, joinedTableName) => { return Promise.resolve(data.map(d => mockModel.build(d))) } else if (query === 'findAll') { // 回傳模擬資料 - return Promise.resolve(data.map(d => mockModel.build(d))) + return Promise.resolve( data ? data.map(d => mockModel.build(d)) : []) }else if (query === 'destroy') { // destroy 可以從 where 取得要刪除的資料 // 因此就可以模擬將模擬資料中的資料刪除 @@ -56,7 +56,7 @@ const createModelMock = (name, defaultValue, data, joinedTableName) => { return Promise.resolve(mockModel.build(data)) } else if (query === 'findAll') { // 回傳模擬資料 - return Promise.resolve(data.map(d => mockModel.build(d))) + return Promise.resolve(data ? data.map(d => mockModel.build(d)) : []) } else if (query === 'destroy') { // destroy 可以從 where 取得要刪除的資料 // 因此就可以模擬將模擬資料中的資料刪除 diff --git a/tests/R01.test.js b/tests/R01.test.js index 07778434..f98a96ac 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -69,7 +69,7 @@ describe('# R01', () => { }) // 修改 adminController 中的資料庫連線設定,由連向真實的資料庫 -> 改為連向模擬的 User table - this.adminController = createControllerProxy('../../controllers/adminController', { User: this.UserMock }) + this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) }) // 開始測試 @@ -104,7 +104,7 @@ describe('# R01', () => { ) // 將 adminController 中的 User db 取代成 User mock db - this.adminController = createControllerProxy('../../controllers/adminController', { User: this.UserMock }) + this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) }) it(' PUT /admin/users/:id/toggleAdmin ', async () => { @@ -138,7 +138,7 @@ describe('# R01', () => { } ) // 將 adminController 中的 User db 取代成 User mock db - this.adminController = createControllerProxy('../../controllers/adminController', { User: this.UserMock }) + this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) }) it(' PUT /admin/users/:id/toggleAdmin ', async (done) => { From c044bab74490bcea76f46fdadec2feb5476d9075 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 20:21:36 +0800 Subject: [PATCH 039/198] fixed wrong path --- tests/R02.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/R02.test.js b/tests/R02.test.js index ea3739e9..5b7cadb2 100644 --- a/tests/R02.test.js +++ b/tests/R02.test.js @@ -37,7 +37,7 @@ describe('# R02', () => { }) // 修改 userController 中的資料庫連線設定,由連向真實的資料庫 -> 改為連向模擬的 User table - this.userController = createControllerProxy('../../controllers/userController', { User: this.UserMock }) + this.userController = createControllerProxy('../controllers/userController', { User: this.UserMock }) }) // 開始測試 @@ -82,7 +82,7 @@ describe('# R02', () => { }) // 連向模擬的 User table - this.userController = createControllerProxy('../../controllers/userController', { User: this.UserMock }) + this.userController = createControllerProxy('../controllers/userController', { User: this.UserMock }) }) it(' GET /users/:id/edit ', async () => { @@ -127,7 +127,7 @@ describe('# R02', () => { ) // 連向模擬的 User table - this.userController = createControllerProxy('../../controllers/userController', { User: this.UserMock }) + this.userController = createControllerProxy('../controllers/userController', { User: this.UserMock }) }) it(' PUT /users/:id ', async () => { From e39f3b4c4931da926bf912815108e4cd4fdb89da Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 21:50:30 +0800 Subject: [PATCH 040/198] add mockRequest / mockResponse --- helpers/unitTestHelpers.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js index ea4cc7b1..35719e1a 100644 --- a/helpers/unitTestHelpers.js +++ b/helpers/unitTestHelpers.js @@ -79,7 +79,22 @@ const createControllerProxy = (path, model) => { return controller; } +const mockRequest = (query) => { + return { + ...query, + flash: sinon.spy(), + } +} +const mockResponse = () => { + return { + redirect: sinon.spy(), + render: sinon.spy(), + } +} + module.exports = { createModelMock, - createControllerProxy + createControllerProxy, + mockRequest, + mockResponse } \ No newline at end of file From 47a1370593ed4db7fc580450788d184bb7413049 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 21:51:23 +0800 Subject: [PATCH 041/198] use new mockRequest / mockResponse --- tests/R01.test.js | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/tests/R01.test.js b/tests/R01.test.js index f98a96ac..e9024d9e 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -4,19 +4,7 @@ const sinon = require('sinon') const should = chai.should() const app = require('../app') -const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers'); - -const mockRequest = (query) => { - return { - ...query, - flash: sinon.spy(), - } -} -const mockResponse = () => { - return { - redirect: sinon.spy(), - } -} +const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers'); describe('# R01', () => { describe('登入測試: POST /signin', function(){ @@ -159,8 +147,6 @@ describe('# R01', () => { // 將假資料撈出,比對確認有成功修改到 const user = await this.UserMock.findOne({ where: { id: 1 } }) user.isAdmin.should.equal(true) - - done() }) }) }) From 371709f07d80a19d78f0b8d0ba9a82b1aa0182c7 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 21:52:15 +0800 Subject: [PATCH 042/198] R02 use new mockRequest / mockResponse --- tests/R02.test.js | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/tests/R02.test.js b/tests/R02.test.js index 5b7cadb2..6463e997 100644 --- a/tests/R02.test.js +++ b/tests/R02.test.js @@ -3,20 +3,8 @@ const request = require('supertest') const sinon = require('sinon') const should = chai.should() -const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers'); - -const mockRequest = (query) => { - return { - ...query, - flash: sinon.spy(), - } -} -const mockResponse = () => { - return { - redirect: sinon.spy(), - render: sinon.spy(), - } -} +const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers'); + describe('# R02', () => { describe('# R02: 建立 User Profile', function () { context('# [瀏覽 Profile]', () => { From e45367f2d4a0648f534bab260eee5080c624a7bf Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 21:50:30 +0800 Subject: [PATCH 043/198] add mockRequest / mockResponse --- helpers/unitTestHelpers.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js index ea4cc7b1..35719e1a 100644 --- a/helpers/unitTestHelpers.js +++ b/helpers/unitTestHelpers.js @@ -79,7 +79,22 @@ const createControllerProxy = (path, model) => { return controller; } +const mockRequest = (query) => { + return { + ...query, + flash: sinon.spy(), + } +} +const mockResponse = () => { + return { + redirect: sinon.spy(), + render: sinon.spy(), + } +} + module.exports = { createModelMock, - createControllerProxy + createControllerProxy, + mockRequest, + mockResponse } \ No newline at end of file From d487f4afcdfd27e247aa57eef3a5caadb84c6a00 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 21:51:23 +0800 Subject: [PATCH 044/198] use new mockRequest / mockResponse --- tests/R01.test.js | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/tests/R01.test.js b/tests/R01.test.js index f98a96ac..e9024d9e 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -4,19 +4,7 @@ const sinon = require('sinon') const should = chai.should() const app = require('../app') -const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers'); - -const mockRequest = (query) => { - return { - ...query, - flash: sinon.spy(), - } -} -const mockResponse = () => { - return { - redirect: sinon.spy(), - } -} +const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers'); describe('# R01', () => { describe('登入測試: POST /signin', function(){ @@ -159,8 +147,6 @@ describe('# R01', () => { // 將假資料撈出,比對確認有成功修改到 const user = await this.UserMock.findOne({ where: { id: 1 } }) user.isAdmin.should.equal(true) - - done() }) }) }) From 73fc123d27ccec774163b66a9a3c1eca7fe59e42 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 21:57:12 +0800 Subject: [PATCH 045/198] require sinon --- helpers/unitTestHelpers.js | 1 + 1 file changed, 1 insertion(+) diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js index 35719e1a..b6e47239 100644 --- a/helpers/unitTestHelpers.js +++ b/helpers/unitTestHelpers.js @@ -1,5 +1,6 @@ const SequelizeMock = require('sequelize-mock') const proxyquire = require('proxyquire') +const sinon = require('sinon') const dbMock = new SequelizeMock() From 5224b9a3e0866d314dd5346067c0de7f7a1fc25b Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 21:57:25 +0800 Subject: [PATCH 046/198] remove sinon --- tests/R01.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/R01.test.js b/tests/R01.test.js index e9024d9e..0a15875b 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -1,6 +1,5 @@ const chai = require('chai') const request = require('supertest') -const sinon = require('sinon') const should = chai.should() const app = require('../app') From 5d61a3029bd40bc0d0ae59f334e248e2a4c5b800 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 21:50:30 +0800 Subject: [PATCH 047/198] add mockRequest / mockResponse --- helpers/unitTestHelpers.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js index ea4cc7b1..35719e1a 100644 --- a/helpers/unitTestHelpers.js +++ b/helpers/unitTestHelpers.js @@ -79,7 +79,22 @@ const createControllerProxy = (path, model) => { return controller; } +const mockRequest = (query) => { + return { + ...query, + flash: sinon.spy(), + } +} +const mockResponse = () => { + return { + redirect: sinon.spy(), + render: sinon.spy(), + } +} + module.exports = { createModelMock, - createControllerProxy + createControllerProxy, + mockRequest, + mockResponse } \ No newline at end of file From 51b22d50df8eff05fc26944bfc502d53151c7b76 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 21:51:23 +0800 Subject: [PATCH 048/198] use new mockRequest / mockResponse --- tests/R01.test.js | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/tests/R01.test.js b/tests/R01.test.js index f98a96ac..e9024d9e 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -4,19 +4,7 @@ const sinon = require('sinon') const should = chai.should() const app = require('../app') -const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers'); - -const mockRequest = (query) => { - return { - ...query, - flash: sinon.spy(), - } -} -const mockResponse = () => { - return { - redirect: sinon.spy(), - } -} +const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers'); describe('# R01', () => { describe('登入測試: POST /signin', function(){ @@ -159,8 +147,6 @@ describe('# R01', () => { // 將假資料撈出,比對確認有成功修改到 const user = await this.UserMock.findOne({ where: { id: 1 } }) user.isAdmin.should.equal(true) - - done() }) }) }) From eba6e760aa83e13ba046d6fafeb17560152d00e8 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 21:57:12 +0800 Subject: [PATCH 049/198] require sinon --- helpers/unitTestHelpers.js | 1 + 1 file changed, 1 insertion(+) diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js index 35719e1a..b6e47239 100644 --- a/helpers/unitTestHelpers.js +++ b/helpers/unitTestHelpers.js @@ -1,5 +1,6 @@ const SequelizeMock = require('sequelize-mock') const proxyquire = require('proxyquire') +const sinon = require('sinon') const dbMock = new SequelizeMock() From 70db2a5a30afc5be1c5eaa83ffe6b71ced12d8dd Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 21:57:25 +0800 Subject: [PATCH 050/198] remove sinon --- tests/R01.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/R01.test.js b/tests/R01.test.js index e9024d9e..0a15875b 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -1,6 +1,5 @@ const chai = require('chai') const request = require('supertest') -const sinon = require('sinon') const should = chai.should() const app = require('../app') From 94b4d563a28a8ed36dabb695ffce20c94659f7ba Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 21:57:25 +0800 Subject: [PATCH 051/198] remove sinon --- tests/R01.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/R01.test.js b/tests/R01.test.js index f98a96ac..3d8c0e04 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -1,6 +1,5 @@ const chai = require('chai') const request = require('supertest') -const sinon = require('sinon') const should = chai.should() const app = require('../app') From 2d1863180f319ed2a536ee1f37facf0d90c5bfed Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 21:50:30 +0800 Subject: [PATCH 052/198] add mockRequest / mockResponse --- helpers/unitTestHelpers.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js index ea4cc7b1..35719e1a 100644 --- a/helpers/unitTestHelpers.js +++ b/helpers/unitTestHelpers.js @@ -79,7 +79,22 @@ const createControllerProxy = (path, model) => { return controller; } +const mockRequest = (query) => { + return { + ...query, + flash: sinon.spy(), + } +} +const mockResponse = () => { + return { + redirect: sinon.spy(), + render: sinon.spy(), + } +} + module.exports = { createModelMock, - createControllerProxy + createControllerProxy, + mockRequest, + mockResponse } \ No newline at end of file From a79fdd0149d0e127598ef85d97d692ff0e04c558 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 21:51:23 +0800 Subject: [PATCH 053/198] use new mockRequest / mockResponse --- tests/R01.test.js | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/tests/R01.test.js b/tests/R01.test.js index 3d8c0e04..0a15875b 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -3,19 +3,7 @@ const request = require('supertest') const should = chai.should() const app = require('../app') -const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers'); - -const mockRequest = (query) => { - return { - ...query, - flash: sinon.spy(), - } -} -const mockResponse = () => { - return { - redirect: sinon.spy(), - } -} +const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers'); describe('# R01', () => { describe('登入測試: POST /signin', function(){ @@ -158,8 +146,6 @@ describe('# R01', () => { // 將假資料撈出,比對確認有成功修改到 const user = await this.UserMock.findOne({ where: { id: 1 } }) user.isAdmin.should.equal(true) - - done() }) }) }) From 7fef8c78760f41165f1ca6c0b7f884f73b862feb Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 21:57:12 +0800 Subject: [PATCH 054/198] require sinon --- helpers/unitTestHelpers.js | 1 + 1 file changed, 1 insertion(+) diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js index 35719e1a..b6e47239 100644 --- a/helpers/unitTestHelpers.js +++ b/helpers/unitTestHelpers.js @@ -1,5 +1,6 @@ const SequelizeMock = require('sequelize-mock') const proxyquire = require('proxyquire') +const sinon = require('sinon') const dbMock = new SequelizeMock() From c8beeb246238b869815f97322ec374ca74c08007 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 21:50:30 +0800 Subject: [PATCH 055/198] add mockRequest / mockResponse --- helpers/unitTestHelpers.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js index ea4cc7b1..35719e1a 100644 --- a/helpers/unitTestHelpers.js +++ b/helpers/unitTestHelpers.js @@ -79,7 +79,22 @@ const createControllerProxy = (path, model) => { return controller; } +const mockRequest = (query) => { + return { + ...query, + flash: sinon.spy(), + } +} +const mockResponse = () => { + return { + redirect: sinon.spy(), + render: sinon.spy(), + } +} + module.exports = { createModelMock, - createControllerProxy + createControllerProxy, + mockRequest, + mockResponse } \ No newline at end of file From 3a85cde88da61cc277afc5c97d49aea013f64bea Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 21:51:23 +0800 Subject: [PATCH 056/198] use new mockRequest / mockResponse --- tests/R01.test.js | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/tests/R01.test.js b/tests/R01.test.js index f98a96ac..e9024d9e 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -4,19 +4,7 @@ const sinon = require('sinon') const should = chai.should() const app = require('../app') -const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers'); - -const mockRequest = (query) => { - return { - ...query, - flash: sinon.spy(), - } -} -const mockResponse = () => { - return { - redirect: sinon.spy(), - } -} +const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers'); describe('# R01', () => { describe('登入測試: POST /signin', function(){ @@ -159,8 +147,6 @@ describe('# R01', () => { // 將假資料撈出,比對確認有成功修改到 const user = await this.UserMock.findOne({ where: { id: 1 } }) user.isAdmin.should.equal(true) - - done() }) }) }) From 61c6de70d674a847ed540826c75a2a3e5f24f79f Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 21:57:12 +0800 Subject: [PATCH 057/198] require sinon --- helpers/unitTestHelpers.js | 1 + 1 file changed, 1 insertion(+) diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js index 35719e1a..b6e47239 100644 --- a/helpers/unitTestHelpers.js +++ b/helpers/unitTestHelpers.js @@ -1,5 +1,6 @@ const SequelizeMock = require('sequelize-mock') const proxyquire = require('proxyquire') +const sinon = require('sinon') const dbMock = new SequelizeMock() From 892762191bf197205484e6fd170b028eb64298a5 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 21:57:25 +0800 Subject: [PATCH 058/198] remove sinon --- tests/R01.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/R01.test.js b/tests/R01.test.js index e9024d9e..0a15875b 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -1,6 +1,5 @@ const chai = require('chai') const request = require('supertest') -const sinon = require('sinon') const should = chai.should() const app = require('../app') From 44176de93cd04f6ef3d658661054c7067ef93d9a Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 21:57:12 +0800 Subject: [PATCH 059/198] require sinon --- helpers/unitTestHelpers.js | 1 + 1 file changed, 1 insertion(+) diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js index 35719e1a..b6e47239 100644 --- a/helpers/unitTestHelpers.js +++ b/helpers/unitTestHelpers.js @@ -1,5 +1,6 @@ const SequelizeMock = require('sequelize-mock') const proxyquire = require('proxyquire') +const sinon = require('sinon') const dbMock = new SequelizeMock() From 89341828af406862f2ff642d8050b0c3e100b610 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 21:57:25 +0800 Subject: [PATCH 060/198] remove sinon --- tests/R01.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/R01.test.js b/tests/R01.test.js index e9024d9e..0a15875b 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -1,6 +1,5 @@ const chai = require('chai') const request = require('supertest') -const sinon = require('sinon') const should = chai.should() const app = require('../app') From 4219d1f41d2f212140838551eed065b061b1e509 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 22:34:38 +0800 Subject: [PATCH 061/198] return [defaulvalue] when findall data empty --- helpers/unitTestHelpers.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js index b6e47239..4a777f4c 100644 --- a/helpers/unitTestHelpers.js +++ b/helpers/unitTestHelpers.js @@ -33,6 +33,9 @@ const createModelMock = (name, defaultValue, data, joinedTableName) => { return Promise.resolve(data.map(d => mockModel.build(d))) } else if (query === 'findAll') { // 回傳模擬資料 + if (!data) { + return mockModel.build([defaultValue]); + } return Promise.resolve( data ? data.map(d => mockModel.build(d)) : []) }else if (query === 'destroy') { // destroy 可以從 where 取得要刪除的資料 @@ -57,6 +60,9 @@ const createModelMock = (name, defaultValue, data, joinedTableName) => { return Promise.resolve(mockModel.build(data)) } else if (query === 'findAll') { // 回傳模擬資料 + if (!data) { + return mockModel.build([defaultValue]); + } return Promise.resolve(data ? data.map(d => mockModel.build(d)) : []) } else if (query === 'destroy') { // destroy 可以從 where 取得要刪除的資料 From 9bf30ca0089c2c21723f40d14d75ee768d209c98 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 22:34:38 +0800 Subject: [PATCH 062/198] return [defaulvalue] when findall data empty --- helpers/unitTestHelpers.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js index b6e47239..4a777f4c 100644 --- a/helpers/unitTestHelpers.js +++ b/helpers/unitTestHelpers.js @@ -33,6 +33,9 @@ const createModelMock = (name, defaultValue, data, joinedTableName) => { return Promise.resolve(data.map(d => mockModel.build(d))) } else if (query === 'findAll') { // 回傳模擬資料 + if (!data) { + return mockModel.build([defaultValue]); + } return Promise.resolve( data ? data.map(d => mockModel.build(d)) : []) }else if (query === 'destroy') { // destroy 可以從 where 取得要刪除的資料 @@ -57,6 +60,9 @@ const createModelMock = (name, defaultValue, data, joinedTableName) => { return Promise.resolve(mockModel.build(data)) } else if (query === 'findAll') { // 回傳模擬資料 + if (!data) { + return mockModel.build([defaultValue]); + } return Promise.resolve(data ? data.map(d => mockModel.build(d)) : []) } else if (query === 'destroy') { // destroy 可以從 where 取得要刪除的資料 From 05b87a2915b4d920a79e74797bd3a74574740c0f Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Tue, 2 Nov 2021 19:53:43 +0800 Subject: [PATCH 063/198] remove done param --- tests/R01.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/R01.test.js b/tests/R01.test.js index 0a15875b..2765ccfb 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -128,7 +128,7 @@ describe('# R01', () => { this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) }) - it(' PUT /admin/users/:id/toggleAdmin ', async (done) => { + it(' PUT /admin/users/:id/toggleAdmin ', async () => { // 模擬 request & response const req = mockRequest({ params: { id: 1 } }) // 帶入 params.id = 1,對 PUT /admin/users/1/toggleAdmin 發出請求 const res = mockResponse() From c014106ac7ed2244dbdf154ab97ba867fdfbe076 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Tue, 2 Nov 2021 19:53:43 +0800 Subject: [PATCH 064/198] remove done param --- tests/R01.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/R01.test.js b/tests/R01.test.js index 0a15875b..2765ccfb 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -128,7 +128,7 @@ describe('# R01', () => { this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) }) - it(' PUT /admin/users/:id/toggleAdmin ', async (done) => { + it(' PUT /admin/users/:id/toggleAdmin ', async () => { // 模擬 request & response const req = mockRequest({ params: { id: 1 } }) // 帶入 params.id = 1,對 PUT /admin/users/1/toggleAdmin 發出請求 const res = mockResponse() From 6eecc8ae14c26000f3a6cc22f2d0baa6b3df4dc3 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Tue, 2 Nov 2021 19:53:43 +0800 Subject: [PATCH 065/198] remove done param --- tests/R01.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/R01.test.js b/tests/R01.test.js index 0a15875b..2765ccfb 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -128,7 +128,7 @@ describe('# R01', () => { this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) }) - it(' PUT /admin/users/:id/toggleAdmin ', async (done) => { + it(' PUT /admin/users/:id/toggleAdmin ', async () => { // 模擬 request & response const req = mockRequest({ params: { id: 1 } }) // 帶入 params.id = 1,對 PUT /admin/users/1/toggleAdmin 發出請求 const res = mockResponse() From a04125fba9446382179d3c3d3661219eda0975b4 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Tue, 2 Nov 2021 19:53:43 +0800 Subject: [PATCH 066/198] remove done param --- tests/R01.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/R01.test.js b/tests/R01.test.js index 0a15875b..2765ccfb 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -128,7 +128,7 @@ describe('# R01', () => { this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) }) - it(' PUT /admin/users/:id/toggleAdmin ', async (done) => { + it(' PUT /admin/users/:id/toggleAdmin ', async () => { // 模擬 request & response const req = mockRequest({ params: { id: 1 } }) // 帶入 params.id = 1,對 PUT /admin/users/1/toggleAdmin 發出請求 const res = mockResponse() From 96e3daac4d61b3155847cf0396c0c3240b2b21ad Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Tue, 2 Nov 2021 19:53:43 +0800 Subject: [PATCH 067/198] remove done param --- tests/R01.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/R01.test.js b/tests/R01.test.js index 0a15875b..2765ccfb 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -128,7 +128,7 @@ describe('# R01', () => { this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) }) - it(' PUT /admin/users/:id/toggleAdmin ', async (done) => { + it(' PUT /admin/users/:id/toggleAdmin ', async () => { // 模擬 request & response const req = mockRequest({ params: { id: 1 } }) // 帶入 params.id = 1,對 PUT /admin/users/1/toggleAdmin 發出請求 const res = mockResponse() From a1cf9c3e517a11d3844a39377fe62f42ad2c3956 Mon Sep 17 00:00:00 2001 From: Carrot7712 Date: Wed, 3 Nov 2021 15:28:42 +0800 Subject: [PATCH 068/198] update readme config.json package.json --- README.md | 26 +- config/config.json | 14 +- package-lock.json | 2540 +------------------------------------------- package.json | 28 +- 4 files changed, 39 insertions(+), 2569 deletions(-) diff --git a/README.md b/README.md index e4cd70f1..9172af88 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ 1. Fork 2. git clone -3. 更改專案名稱 ## 初始化 ### Initialize @@ -16,14 +15,6 @@ npm install ``` create database forum; -create database forum_test; -``` - -### 切換環境 - -``` -export NODE_ENV=test # 切換到測試環境,如果在等號後加其他的字串,則會切到其他的環境 -echo $NODE_ENV # 印出目前使用的環境 ``` ### 執行測試 @@ -32,11 +23,11 @@ npm run test ``` ## 下載作業規格 -以 A17 為例 +以 R01 為例 ``` -git checkout -b A17 # 開新分支 -git merge origin/A17-test # 下載作業規格 +git checkout -b R01 # 開新分支 +git merge origin/R01-test # 下載作業規格 npm run test # 直到綠燈全亮 git add . @@ -46,11 +37,16 @@ git commit -m "...." ## 繳交作業 ``` -git push origin A17 # 上傳本地進度 +git push origin R01 # 上傳本地進度 ``` 接著改成到 GitHub 來發 PR。 ## 共用帳號 -請一律設定一個共用的 root user -root@example.com,登入密碼 12345678 +請一律設定下面 2 組帳號以利驗收: +* 第一組帳號有 admin 權限: + * email: root@example.com + * password: 12345678 +* 第二組帳號沒有 admin 權限: + * email: user1@example.com + * password: 12345678 \ No newline at end of file diff --git a/config/config.json b/config/config.json index 56636a1c..2b8be8e7 100644 --- a/config/config.json +++ b/config/config.json @@ -6,16 +6,12 @@ "host": "127.0.0.1", "dialect": "mysql" }, - "test": { + "production": { "username": "root", - "password": "password", - "database": "forum_test", + "password": null, + "database": "database_production", "host": "127.0.0.1", - "dialect": "mysql", - "logging": false - }, - "production": { - "use_env_variable": "CLEARDB_SILVER_URL" + "dialect": "mysql" }, "travis": { "username": "travis", @@ -24,4 +20,4 @@ "dialect": "mysql", "logging": false } -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index 7ebc04a3..bb00f848 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,2480 +1,8 @@ { "name": "forum-express-workspace", "version": "1.0.0", - "lockfileVersion": 2, + "lockfileVersion": 1, "requires": true, - "packages": { - "": { - "name": "forum-express-workspace", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "chai": "^4.2.0", - "express": "^4.17.1", - "mocha": "^8.2.0", - "mysql2": "^2.2.5", - "sequelize": "^6.3.5", - "sequelize-cli": "^6.2.0", - "sinon": "^9.2.0", - "supertest": "^5.0.0" - }, - "devDependencies": { - "proxyquire": "^2.1.3", - "sequelize-mock": "^0.10.2" - } - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", - "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", - "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/formatio": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-5.0.1.tgz", - "integrity": "sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==", - "dependencies": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^5.0.2" - } - }, - "node_modules/@sinonjs/samsam": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.2.0.tgz", - "integrity": "sha512-CaIcyX5cDsjcW/ab7HposFWzV1kC++4HNsfnEdFJa7cP1QIuILAKV+BgfeqRXhcnSAc76r/Rh/O5C+300BwUIw==", - "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/node": { - "version": "14.14.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.5.tgz", - "integrity": "sha512-H5Wn24s/ZOukBmDn03nnGTp18A60ny9AmCwnEcgJiTgSGsCO7k+NWP7zjCCbhlcnVCoI+co52dUAt9GMhOSULw==" - }, - "node_modules/@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==" - }, - "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.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "dependencies": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "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/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" - }, - "node_modules/anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "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=" - }, - "node_modules/balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "node_modules/binary-extensions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", - "engines": { - "node": ">=8" - } - }, - "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.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "dependencies": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" - }, - "engines": { - "node": ">= 0.8" - } - }, - "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/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "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.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", - "engines": { - "node": ">= 0.8" - } - }, - "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.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", - "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "pathval": "^1.1.0", - "type-detect": "^4.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chalk/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/chalk/node_modules/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==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/chalk/node_modules/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==" - }, - "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/chokidar": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", - "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", - "dependencies": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.1.2" - } - }, - "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/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==", - "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==" - }, - "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.12", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", - "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "node_modules/content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "dependencies": { - "safe-buffer": "5.1.2" - }, - "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.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", - "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.2", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", - "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==" - }, - "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/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/denque": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", - "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==", - "engines": { - "node": ">=0.10" - } - }, - "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/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "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/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/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/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/es5-ext": { - "version": "0.10.53", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", - "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", - "dependencies": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" - } - }, - "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": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", - "dependencies": { - "accepts": "~1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", - "content-type": "~1.0.4", - "cookie": "0.4.0", - "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.5", - "qs": "6.7.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "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/ext": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", - "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", - "dependencies": { - "type": "^2.0.0" - } - }, - "node_modules/ext/node_modules/type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.1.0.tgz", - "integrity": "sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA==" - }, - "node_modules/fast-safe-stringify": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", - "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" - }, - "node_modules/fill-keys": { - "version": "1.0.2", - "resolved": "http://npm.ooapi.com:4873/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/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==", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "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/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": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "bin": { - "flat": "cli.js" - } - }, - "node_modules/form-data": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", - "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/formidable": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", - "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==", - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" - } - }, - "node_modules/forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", - "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/fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "deprecated": "\"Please update to latest v2.3 or v2.2\"", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "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/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "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": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" - }, - "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-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/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": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "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.12.0", - "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", - "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=", - "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.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "node_modules/ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "deprecated": "Please update to ini >=1.3.6 to avoid a prototype pollution issue", - "engines": { - "node": "*" - } - }, - "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-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==", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.0.0.tgz", - "integrity": "sha512-jq1AH6C8MuteOoBPwkxHafmByhL9j5q4OaPGdbuD+ZtQJVzH+i6E3BJDQcBA09k57i2Hh2yQbEG8yObZ0jdlWw==", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "engines": { - "node": ">=0.10.0" - } - }, - "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-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-object": { - "version": "1.0.2", - "resolved": "http://npm.ooapi.com:4873/is-object/-/is-object-1.0.2.tgz", - "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "engines": { - "node": ">=8" - } - }, - "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/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "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.13.0", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.13.0.tgz", - "integrity": "sha512-/Tbp1OVzZjbwzwJQFIlYLm9eWQ+3aYbBXLSaqb1mEJzhcQAfrqMMQYtjb6io+U6KpD0ID4F+Id3/xcjH3l/sqA==", - "dependencies": { - "config-chain": "^1.1.12", - "editorconfig": "^0.15.3", - "glob": "^7.1.3", - "mkdirp": "^1.0.4", - "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" - } - }, - "node_modules/js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", - "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.1.1", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz", - "integrity": "sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==" - }, - "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.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" - }, - "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": "4.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", - "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", - "dependencies": { - "chalk": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "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/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/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.14", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", - "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.45", - "es6-weak-map": "^2.0.2", - "event-emitter": "^0.3.5", - "is-promise": "^2.1", - "lru-queue": "0.1", - "next-tick": "1", - "timers-ext": "^0.1.5" - } - }, - "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/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.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", - "dependencies": { - "mime-db": "1.44.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/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.0.tgz", - "integrity": "sha512-lEWEMq2LMfNJMKeuEwb5UELi+OgFDollXaytR5ggQcHpzG3NP/R7rvixAvF+9/lLsTWhWG+4yD2M70GsM06nxw==", - "dependencies": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.4.3", - "debug": "4.2.0", - "diff": "4.0.2", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.1.6", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "3.14.0", - "log-symbols": "4.0.0", - "minimatch": "3.0.4", - "ms": "2.1.2", - "nanoid": "3.1.12", - "serialize-javascript": "5.0.1", - "strip-json-comments": "3.1.1", - "supports-color": "7.2.0", - "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.0.2", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 10.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } - }, - "node_modules/mocha/node_modules/debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "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.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/mocha/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/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/mocha/node_modules/p-limit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", - "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/module-not-found-error": { - "version": "1.0.1", - "resolved": "http://npm.ooapi.com:4873/module-not-found-error/-/module-not-found-error-1.0.1.tgz", - "integrity": "sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=", - "dev": true - }, - "node_modules/moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", - "engines": { - "node": "*" - } - }, - "node_modules/moment-timezone": { - "version": "0.5.31", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.31.tgz", - "integrity": "sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==", - "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": "2.2.5", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.2.5.tgz", - "integrity": "sha512-XRqPNxcZTpmFdXbJqb+/CtYVLCx14x1RTeNMD4954L331APu75IC74GDqnZMEt1kwaXy6TySo55rF2F3YJS78g==", - "dependencies": { - "denque": "^1.4.1", - "generate-function": "^2.3.1", - "iconv-lite": "^0.6.2", - "long": "^4.0.0", - "lru-cache": "^6.0.0", - "named-placeholders": "^1.1.2", - "seq-queue": "^0.0.5", - "sqlstring": "^2.3.2" - }, - "engines": { - "node": ">= 8.0" - } - }, - "node_modules/mysql2/node_modules/iconv-lite": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", - "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "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/nanoid": { - "version": "3.1.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz", - "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || >=13.7" - } - }, - "node_modules/negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" - }, - "node_modules/nise": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/nise/-/nise-4.0.4.tgz", - "integrity": "sha512-bTTRUNlemx6deJa+ZyoCUTRvH3liK5+N6VQZ4NIw90AgDXY6iPnsqplNFf6STcj+ePk0H/xqxnP75Lr0J0Fq3A==", - "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/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/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/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "engines": { - "node": ">=0.10.0" - } - }, - "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/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/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.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" - }, - "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.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", - "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", - "engines": { - "node": "*" - } - }, - "node_modules/picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "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.6", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", - "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", - "dependencies": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxyquire": { - "version": "2.1.3", - "resolved": "http://npm.ooapi.com:4873/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.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "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.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "dependencies": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "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.18.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz", - "integrity": "sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA==", - "dependencies": { - "is-core-module": "^2.0.0", - "path-parse": "^1.0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/retry-as-promised": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-3.2.0.tgz", - "integrity": "sha512-CybGs60B7oYU/qSQ6kuaFmRd9sTZ6oXSc0toqePvV74Ac6/IFZSI1ReFQmtCN+uvW1Mtqdwpvt/LGOiCBAY2Mg==", - "dependencies": { - "any-promise": "^1.3.0" - } - }, - "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==" - }, - "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": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", - "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.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/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/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.3.5", - "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.3.5.tgz", - "integrity": "sha512-MiwiPkYSA8NWttRKAXdU9h0TxP6HAc1fl7qZmMO/VQqQOND83G4nZLXd0kWILtAoT9cxtZgFqeb/MPYgEeXwsw==", - "dependencies": { - "debug": "^4.1.1", - "dottie": "^2.0.0", - "inflection": "1.12.0", - "lodash": "^4.17.15", - "moment": "^2.26.0", - "moment-timezone": "^0.5.31", - "retry-as-promised": "^3.2.0", - "semver": "^7.3.2", - "sequelize-pool": "^6.0.0", - "toposort-class": "^1.0.1", - "uuid": "^8.1.0", - "validator": "^10.11.0", - "wkx": "^0.5.0" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependenciesMeta": { - "mariadb": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "pg": { - "optional": true - }, - "pg-hstore": { - "optional": true - }, - "sqlite3": { - "optional": true - }, - "tedious": { - "optional": true - } - } - }, - "node_modules/sequelize-cli": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/sequelize-cli/-/sequelize-cli-6.2.0.tgz", - "integrity": "sha512-6WQ2x91hg30dUn66mXHnzvHATZ4pyI1GHSNbS/TNN/vRR4BLRSLijadeMgC8zqmKDsL0VqzVVopJWfJakuP++Q==", - "dependencies": { - "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.3.0", - "yargs": "^13.1.0" - }, - "bin": { - "sequelize": "lib/sequelize", - "sequelize-cli": "lib/sequelize" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/sequelize-mock": { - "version": "0.10.2", - "resolved": "http://npm.ooapi.com:4873/sequelize-mock/-/sequelize-mock-0.10.2.tgz", - "integrity": "sha1-GdOXHM2utbhkFwwkznkqinHxRL0=", - "dev": true, - "dependencies": { - "bluebird": "^3.4.6", - "inflection": "^1.10.0", - "lodash": "^4.16.4" - } - }, - "node_modules/sequelize-pool": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-6.1.0.tgz", - "integrity": "sha512-4YwEw3ZgK/tY/so+GfnSgXkdwIJJ1I32uZJztIEgZeAO6HMgj64OzySbWLgxj+tXhZCJnzRfkY9gINw8Ft8ZMg==", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/sequelize/node_modules/debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "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.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "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/serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.1" - }, - "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.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" - }, - "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": "9.2.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.0.tgz", - "integrity": "sha512-eSNXz1XMcGEMHw08NJXSyTHIu6qTCOiN8x9ODACmZpNQpr0aXTBXBnI4xTzQzR+TEpOmLiKowGf9flCuKIzsbw==", - "dependencies": { - "@sinonjs/commons": "^1.8.1", - "@sinonjs/fake-timers": "^6.0.1", - "@sinonjs/formatio": "^5.0.1", - "@sinonjs/samsam": "^5.2.0", - "diff": "^4.0.2", - "nise": "^4.0.4", - "supports-color": "^7.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" - } - }, - "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.2", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz", - "integrity": "sha512-vF4ZbYdKS8OnoJAWBmMxCQDkiEBkGQYU7UZPtL8flbDRSNkhaXvRJ279ZtI6M+zDaQovVU4tuRgzK5fVhvFAhg==", - "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.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string_decoder/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/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/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/strip-ansi/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/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==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/superagent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-6.1.0.tgz", - "integrity": "sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg==", - "dependencies": { - "component-emitter": "^1.3.0", - "cookiejar": "^2.1.2", - "debug": "^4.1.1", - "fast-safe-stringify": "^2.0.7", - "form-data": "^3.0.0", - "formidable": "^1.2.2", - "methods": "^1.1.2", - "mime": "^2.4.6", - "qs": "^6.9.4", - "readable-stream": "^3.6.0", - "semver": "^7.3.2" - }, - "engines": { - "node": ">= 7.0.0" - } - }, - "node_modules/superagent/node_modules/debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "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.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/superagent/node_modules/mime": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/superagent/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/superagent/node_modules/qs": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", - "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==", - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/supertest": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-5.0.0.tgz", - "integrity": "sha512-2JAWpPrUOZF4hHH5ZTCN2xjKXvJS3AEwPNXl0HUseHsfcXFvMy9kcsufIHCNAmQ5hlGCvgeAqaR5PBEouN3hlQ==", - "dependencies": { - "methods": "1.1.2", - "superagent": "6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "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/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/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==", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "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/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/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=" - }, - "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.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", - "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/validator": { - "version": "10.11.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", - "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==", - "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": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "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/wide-align/node_modules/ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "engines": { - "node": ">=4" - } - }, - "node_modules/wide-align/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/wide-align/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/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/workerpool": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.2.tgz", - "integrity": "sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==" - }, - "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/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.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" - }, - "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/yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "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" - } - }, - "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" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser/node_modules/camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs-unparser/node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, "dependencies": { "@sinonjs/commons": { "version": "1.8.1", @@ -3053,16 +581,6 @@ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" }, - "fill-keys": { - "version": "1.0.2", - "resolved": "http://npm.ooapi.com:4873/fill-keys/-/fill-keys-1.0.2.tgz", - "integrity": "sha1-mo+jb06K1jTjv2tPPIiCVRRS6yA=", - "dev": true, - "requires": { - "is-object": "~1.0.1", - "merge-descriptors": "~1.0.0" - } - }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -3304,12 +822,6 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, - "is-object": { - "version": "1.0.2", - "resolved": "http://npm.ooapi.com:4873/is-object/-/is-object-1.0.2.tgz", - "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", - "dev": true - }, "is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -3563,12 +1075,6 @@ } } }, - "module-not-found-error": { - "version": "1.0.1", - "resolved": "http://npm.ooapi.com:4873/module-not-found-error/-/module-not-found-error-1.0.1.tgz", - "integrity": "sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=", - "dev": true - }, "moment": { "version": "2.29.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", @@ -3772,17 +1278,6 @@ "ipaddr.js": "1.9.1" } }, - "proxyquire": { - "version": "2.1.3", - "resolved": "http://npm.ooapi.com:4873/proxyquire/-/proxyquire-2.1.3.tgz", - "integrity": "sha512-BQWfCqYM+QINd+yawJz23tbBM40VIGXOdDw3X344KcclI/gtBbdWF6SlQ4nK/bYhF9d27KYug9WzljHC6B9Ysg==", - "dev": true, - "requires": { - "fill-keys": "^1.0.2", - "module-not-found-error": "^1.0.1", - "resolve": "^1.11.1" - } - }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -3958,17 +1453,6 @@ "yargs": "^13.1.0" } }, - "sequelize-mock": { - "version": "0.10.2", - "resolved": "http://npm.ooapi.com:4873/sequelize-mock/-/sequelize-mock-0.10.2.tgz", - "integrity": "sha1-GdOXHM2utbhkFwwkznkqinHxRL0=", - "dev": true, - "requires": { - "bluebird": "^3.4.6", - "inflection": "^1.10.0", - "lodash": "^4.16.4" - } - }, "sequelize-pool": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-6.1.0.tgz", @@ -4037,6 +1521,16 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, + "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==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -4052,16 +1546,6 @@ } } }, - "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==", - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -4364,4 +1848,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index c25bd055..5f6f1d0f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,6 @@ { - "name": "forum-express-workspace", - "version": "1.0.0", - "description": "", + "name": "forum-express-grading", + "version": "2.0.0", "main": "_helpers.js", "scripts": { "start": "node app.js", @@ -10,26 +9,21 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/v123582/forum-express-workspace.git" + "url": "git+https://github.com/ALPHACamp/forum-express-grading.git" }, - "author": "", - "license": "ISC", - "bugs": { - "url": "https://github.com/v123582/forum-express-workspace/issues" - }, - "homepage": "https://github.com/v123582/forum-express-workspace#readme", "dependencies": { - "chai": "^4.2.0", "express": "^4.17.1", - "mocha": "^8.2.0", "mysql2": "^2.2.5", + "nodemon": "^2.0.12", "sequelize": "^6.3.5", - "sequelize-cli": "^6.2.0", - "sinon": "^9.2.0", - "supertest": "^5.0.0" + "sequelize-cli": "^6.2.0" }, "devDependencies": { + "chai": "^4.2.0", + "mocha": "^8.2.0", "proxyquire": "^2.1.3", - "sequelize-mock": "^0.10.2" + "sequelize-mock": "^0.10.2", + "sinon": "^9.2.0", + "supertest": "^5.0.0" } -} +} \ No newline at end of file From 5e1573a5ba7da44df7530b009b8ed565a552a62a Mon Sep 17 00:00:00 2001 From: Serena Tseng <51045074+Carrot7712@users.noreply.github.com> Date: Sat, 6 Nov 2021 00:14:56 +0800 Subject: [PATCH 069/198] Update .travis.yml --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9635c20c..fdf50b79 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,9 +11,9 @@ env: # 在 install 前執行的指令 before_install: - - mysql -e 'CREATE DATABASE IF NOT EXISTS forum_test default character set utf8mb4 collate utf8mb4_unicode_ci;' + - mysql -e 'CREATE DATABASE IF NOT EXISTS forum default character set utf8mb4 collate utf8mb4_unicode_ci;' - npm install sequelize-cli -g # 在執行測試之前的指令 before_script: - - sequelize db:migrate \ No newline at end of file + - sequelize db:migrate From c71da4437577e06e72e85113c9ae4eb5726a3f6d Mon Sep 17 00:00:00 2001 From: Serena Tseng <51045074+Carrot7712@users.noreply.github.com> Date: Sat, 6 Nov 2021 00:29:51 +0800 Subject: [PATCH 070/198] Update .travis.yml --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index fdf50b79..f08c1b5e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - "10" + - "14" services: - mysql @@ -16,4 +16,5 @@ before_install: # 在執行測試之前的指令 before_script: - - sequelize db:migrate + - sequelize db:migrate + - sequelize db:seed:all From 1da8f648fee9345d65ebbf74d734448d28d7f869 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 21:52:15 +0800 Subject: [PATCH 071/198] R02 use new mockRequest / mockResponse --- tests/R02.test.js | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/tests/R02.test.js b/tests/R02.test.js index 5b7cadb2..6463e997 100644 --- a/tests/R02.test.js +++ b/tests/R02.test.js @@ -3,20 +3,8 @@ const request = require('supertest') const sinon = require('sinon') const should = chai.should() -const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers'); - -const mockRequest = (query) => { - return { - ...query, - flash: sinon.spy(), - } -} -const mockResponse = () => { - return { - redirect: sinon.spy(), - render: sinon.spy(), - } -} +const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers'); + describe('# R02', () => { describe('# R02: 建立 User Profile', function () { context('# [瀏覽 Profile]', () => { From dcc8f0ea6ecedcd515f536bd5283b7e6bdced693 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 8 Nov 2021 01:15:19 +0800 Subject: [PATCH 072/198] add helper ref --- tests/R02.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/R02.test.js b/tests/R02.test.js index 6463e997..26c927b3 100644 --- a/tests/R02.test.js +++ b/tests/R02.test.js @@ -3,6 +3,8 @@ const request = require('supertest') const sinon = require('sinon') const should = chai.should() +const helpers = require('../_helpers'); + const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers'); describe('# R02', () => { From 67998043646f14f7c8216a027e9fb0d3c8b1803a Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 8 Nov 2021 01:33:03 +0800 Subject: [PATCH 073/198] remove mockRequest, mockResponse --- tests/R04.test.js | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/tests/R04.test.js b/tests/R04.test.js index 86bbb191..d736b2f3 100644 --- a/tests/R04.test.js +++ b/tests/R04.test.js @@ -5,20 +5,7 @@ const should = chai.should() const db = require('../models') const helpers = require('../_helpers') -const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers') - -const mockRequest = (query) => { - return { - ...query, - flash: sinon.spy(), - } -} -const mockResponse = () => { - return { - redirect: sinon.spy(), - render: sinon.spy(), - } -} +const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers') // 建立模擬的 Like 資料 let mockLikeData = [ From 97752aed67c573008942e139deeb44600d39d072 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 8 Nov 2021 01:47:04 +0800 Subject: [PATCH 074/198] use new mockRequest, mockResponse --- tests/R05.test.js | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/tests/R05.test.js b/tests/R05.test.js index a0ef79cc..3709204f 100644 --- a/tests/R05.test.js +++ b/tests/R05.test.js @@ -5,20 +5,8 @@ const should = chai.should() const app = require('../app') const helpers = require('../_helpers') -const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers') - -const mockRequest = (query) => { - return { - ...query, - flash: sinon.spy(), - } -} -const mockResponse = () => { - return { - redirect: sinon.spy(), - render: sinon.spy(), - } -} +const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers') + // 模擬 2 間餐廳資料 let mockRestaurantData = [ { From d54a7fd60d9ea2c09a52c8b690ad4e42c89d5379 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 21:52:15 +0800 Subject: [PATCH 075/198] R02 use new mockRequest / mockResponse --- tests/R02.test.js | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/tests/R02.test.js b/tests/R02.test.js index 5b7cadb2..6463e997 100644 --- a/tests/R02.test.js +++ b/tests/R02.test.js @@ -3,20 +3,8 @@ const request = require('supertest') const sinon = require('sinon') const should = chai.should() -const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers'); - -const mockRequest = (query) => { - return { - ...query, - flash: sinon.spy(), - } -} -const mockResponse = () => { - return { - redirect: sinon.spy(), - render: sinon.spy(), - } -} +const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers'); + describe('# R02', () => { describe('# R02: 建立 User Profile', function () { context('# [瀏覽 Profile]', () => { From dde5d1d2e63bdb7e3ce4ead51f43da070411cdd3 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 8 Nov 2021 01:15:19 +0800 Subject: [PATCH 076/198] add helper ref --- tests/R02.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/R02.test.js b/tests/R02.test.js index 6463e997..26c927b3 100644 --- a/tests/R02.test.js +++ b/tests/R02.test.js @@ -3,6 +3,8 @@ const request = require('supertest') const sinon = require('sinon') const should = chai.should() +const helpers = require('../_helpers'); + const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers'); describe('# R02', () => { From ec34393e3b61d8033d4c8d0f06d2e73943130600 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 1 Nov 2021 21:52:15 +0800 Subject: [PATCH 077/198] R02 use new mockRequest / mockResponse --- tests/R02.test.js | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/tests/R02.test.js b/tests/R02.test.js index 5b7cadb2..6463e997 100644 --- a/tests/R02.test.js +++ b/tests/R02.test.js @@ -3,20 +3,8 @@ const request = require('supertest') const sinon = require('sinon') const should = chai.should() -const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers'); - -const mockRequest = (query) => { - return { - ...query, - flash: sinon.spy(), - } -} -const mockResponse = () => { - return { - redirect: sinon.spy(), - render: sinon.spy(), - } -} +const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers'); + describe('# R02', () => { describe('# R02: 建立 User Profile', function () { context('# [瀏覽 Profile]', () => { From 8b395b251ea18469c3a8145f44dcd8f950b1273f Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 8 Nov 2021 01:15:19 +0800 Subject: [PATCH 078/198] add helper ref --- tests/R02.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/R02.test.js b/tests/R02.test.js index 6463e997..26c927b3 100644 --- a/tests/R02.test.js +++ b/tests/R02.test.js @@ -3,6 +3,8 @@ const request = require('supertest') const sinon = require('sinon') const should = chai.should() +const helpers = require('../_helpers'); + const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers'); describe('# R02', () => { From 59826d43fa094aa1c74f633401b37d3800aaedca Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 8 Nov 2021 01:33:03 +0800 Subject: [PATCH 079/198] remove mockRequest, mockResponse --- tests/R04.test.js | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/tests/R04.test.js b/tests/R04.test.js index 86bbb191..d736b2f3 100644 --- a/tests/R04.test.js +++ b/tests/R04.test.js @@ -5,20 +5,7 @@ const should = chai.should() const db = require('../models') const helpers = require('../_helpers') -const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers') - -const mockRequest = (query) => { - return { - ...query, - flash: sinon.spy(), - } -} -const mockResponse = () => { - return { - redirect: sinon.spy(), - render: sinon.spy(), - } -} +const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers') // 建立模擬的 Like 資料 let mockLikeData = [ From c74a905ac0c6edb6f27c099306359d95adac67d4 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 8 Nov 2021 01:15:19 +0800 Subject: [PATCH 080/198] add helper ref --- tests/R02.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/R02.test.js b/tests/R02.test.js index 6463e997..26c927b3 100644 --- a/tests/R02.test.js +++ b/tests/R02.test.js @@ -3,6 +3,8 @@ const request = require('supertest') const sinon = require('sinon') const should = chai.should() +const helpers = require('../_helpers'); + const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers'); describe('# R02', () => { From 049790e9f5dca0d19b8d23a59f7baed7f9df3667 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 8 Nov 2021 01:52:24 +0800 Subject: [PATCH 081/198] use new mockRequest, mockResponse --- tests/R03.test.js | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/tests/R03.test.js b/tests/R03.test.js index bc7d1ade..f415be50 100644 --- a/tests/R03.test.js +++ b/tests/R03.test.js @@ -4,20 +4,7 @@ const sinon = require('sinon') const should = chai.should() const helpers = require('../_helpers') -const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers'); - -const mockRequest = (query) => { - return { - ...query, - flash: sinon.spy(), - } -} -const mockResponse = () => { - return { - redirect: sinon.spy(), - render: sinon.spy(), - } -} +const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers'); describe('# R03: 餐廳資訊整理:Dashboard', function () { context('# [Q1: Dashboard - 1 - controller / view / route]', () => { From a9d40824ecd1c10d420cfec2465d9528d426252e Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 8 Nov 2021 01:52:24 +0800 Subject: [PATCH 082/198] use new mockRequest, mockResponse --- tests/R03.test.js | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/tests/R03.test.js b/tests/R03.test.js index bc7d1ade..f415be50 100644 --- a/tests/R03.test.js +++ b/tests/R03.test.js @@ -4,20 +4,7 @@ const sinon = require('sinon') const should = chai.should() const helpers = require('../_helpers') -const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers'); - -const mockRequest = (query) => { - return { - ...query, - flash: sinon.spy(), - } -} -const mockResponse = () => { - return { - redirect: sinon.spy(), - render: sinon.spy(), - } -} +const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers'); describe('# R03: 餐廳資訊整理:Dashboard', function () { context('# [Q1: Dashboard - 1 - controller / view / route]', () => { From 7f8789b63c374c1c75c4da25fd808bd55e560a51 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 8 Nov 2021 01:52:24 +0800 Subject: [PATCH 083/198] use new mockRequest, mockResponse --- tests/R03.test.js | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/tests/R03.test.js b/tests/R03.test.js index bc7d1ade..f415be50 100644 --- a/tests/R03.test.js +++ b/tests/R03.test.js @@ -4,20 +4,7 @@ const sinon = require('sinon') const should = chai.should() const helpers = require('../_helpers') -const { createModelMock, createControllerProxy } = require('../helpers/unitTestHelpers'); - -const mockRequest = (query) => { - return { - ...query, - flash: sinon.spy(), - } -} -const mockResponse = () => { - return { - redirect: sinon.spy(), - render: sinon.spy(), - } -} +const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers'); describe('# R03: 餐廳資訊整理:Dashboard', function () { context('# [Q1: Dashboard - 1 - controller / view / route]', () => { From 72b97383c0e5c062469191ad96ee0b4ac1911861 Mon Sep 17 00:00:00 2001 From: Carrot7712 Date: Wed, 3 Nov 2021 15:28:42 +0800 Subject: [PATCH 084/198] update readme config.json package.json --- README.md | 26 +- config/config.json | 14 +- package-lock.json | 2540 +------------------------------------------- package.json | 28 +- 4 files changed, 39 insertions(+), 2569 deletions(-) diff --git a/README.md b/README.md index e4cd70f1..9172af88 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ 1. Fork 2. git clone -3. 更改專案名稱 ## 初始化 ### Initialize @@ -16,14 +15,6 @@ npm install ``` create database forum; -create database forum_test; -``` - -### 切換環境 - -``` -export NODE_ENV=test # 切換到測試環境,如果在等號後加其他的字串,則會切到其他的環境 -echo $NODE_ENV # 印出目前使用的環境 ``` ### 執行測試 @@ -32,11 +23,11 @@ npm run test ``` ## 下載作業規格 -以 A17 為例 +以 R01 為例 ``` -git checkout -b A17 # 開新分支 -git merge origin/A17-test # 下載作業規格 +git checkout -b R01 # 開新分支 +git merge origin/R01-test # 下載作業規格 npm run test # 直到綠燈全亮 git add . @@ -46,11 +37,16 @@ git commit -m "...." ## 繳交作業 ``` -git push origin A17 # 上傳本地進度 +git push origin R01 # 上傳本地進度 ``` 接著改成到 GitHub 來發 PR。 ## 共用帳號 -請一律設定一個共用的 root user -root@example.com,登入密碼 12345678 +請一律設定下面 2 組帳號以利驗收: +* 第一組帳號有 admin 權限: + * email: root@example.com + * password: 12345678 +* 第二組帳號沒有 admin 權限: + * email: user1@example.com + * password: 12345678 \ No newline at end of file diff --git a/config/config.json b/config/config.json index 56636a1c..2b8be8e7 100644 --- a/config/config.json +++ b/config/config.json @@ -6,16 +6,12 @@ "host": "127.0.0.1", "dialect": "mysql" }, - "test": { + "production": { "username": "root", - "password": "password", - "database": "forum_test", + "password": null, + "database": "database_production", "host": "127.0.0.1", - "dialect": "mysql", - "logging": false - }, - "production": { - "use_env_variable": "CLEARDB_SILVER_URL" + "dialect": "mysql" }, "travis": { "username": "travis", @@ -24,4 +20,4 @@ "dialect": "mysql", "logging": false } -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index 7ebc04a3..bb00f848 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,2480 +1,8 @@ { "name": "forum-express-workspace", "version": "1.0.0", - "lockfileVersion": 2, + "lockfileVersion": 1, "requires": true, - "packages": { - "": { - "name": "forum-express-workspace", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "chai": "^4.2.0", - "express": "^4.17.1", - "mocha": "^8.2.0", - "mysql2": "^2.2.5", - "sequelize": "^6.3.5", - "sequelize-cli": "^6.2.0", - "sinon": "^9.2.0", - "supertest": "^5.0.0" - }, - "devDependencies": { - "proxyquire": "^2.1.3", - "sequelize-mock": "^0.10.2" - } - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", - "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", - "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/formatio": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-5.0.1.tgz", - "integrity": "sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==", - "dependencies": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^5.0.2" - } - }, - "node_modules/@sinonjs/samsam": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.2.0.tgz", - "integrity": "sha512-CaIcyX5cDsjcW/ab7HposFWzV1kC++4HNsfnEdFJa7cP1QIuILAKV+BgfeqRXhcnSAc76r/Rh/O5C+300BwUIw==", - "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/node": { - "version": "14.14.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.5.tgz", - "integrity": "sha512-H5Wn24s/ZOukBmDn03nnGTp18A60ny9AmCwnEcgJiTgSGsCO7k+NWP7zjCCbhlcnVCoI+co52dUAt9GMhOSULw==" - }, - "node_modules/@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==" - }, - "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.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "dependencies": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "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/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" - }, - "node_modules/anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "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=" - }, - "node_modules/balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "node_modules/binary-extensions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", - "engines": { - "node": ">=8" - } - }, - "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.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "dependencies": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" - }, - "engines": { - "node": ">= 0.8" - } - }, - "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/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "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.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", - "engines": { - "node": ">= 0.8" - } - }, - "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.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", - "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "pathval": "^1.1.0", - "type-detect": "^4.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chalk/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/chalk/node_modules/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==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/chalk/node_modules/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==" - }, - "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/chokidar": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", - "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", - "dependencies": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.1.2" - } - }, - "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/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==", - "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==" - }, - "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.12", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", - "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "node_modules/content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "dependencies": { - "safe-buffer": "5.1.2" - }, - "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.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", - "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.2", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", - "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==" - }, - "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/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/denque": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", - "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==", - "engines": { - "node": ">=0.10" - } - }, - "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/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "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/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/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/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/es5-ext": { - "version": "0.10.53", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", - "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", - "dependencies": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" - } - }, - "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": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", - "dependencies": { - "accepts": "~1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", - "content-type": "~1.0.4", - "cookie": "0.4.0", - "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.5", - "qs": "6.7.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "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/ext": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", - "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", - "dependencies": { - "type": "^2.0.0" - } - }, - "node_modules/ext/node_modules/type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.1.0.tgz", - "integrity": "sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA==" - }, - "node_modules/fast-safe-stringify": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", - "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" - }, - "node_modules/fill-keys": { - "version": "1.0.2", - "resolved": "http://npm.ooapi.com:4873/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/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==", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "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/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": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "bin": { - "flat": "cli.js" - } - }, - "node_modules/form-data": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", - "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/formidable": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", - "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==", - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" - } - }, - "node_modules/forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", - "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/fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "deprecated": "\"Please update to latest v2.3 or v2.2\"", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "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/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "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": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" - }, - "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-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/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": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "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.12.0", - "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", - "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=", - "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.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "node_modules/ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "deprecated": "Please update to ini >=1.3.6 to avoid a prototype pollution issue", - "engines": { - "node": "*" - } - }, - "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-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==", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.0.0.tgz", - "integrity": "sha512-jq1AH6C8MuteOoBPwkxHafmByhL9j5q4OaPGdbuD+ZtQJVzH+i6E3BJDQcBA09k57i2Hh2yQbEG8yObZ0jdlWw==", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "engines": { - "node": ">=0.10.0" - } - }, - "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-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-object": { - "version": "1.0.2", - "resolved": "http://npm.ooapi.com:4873/is-object/-/is-object-1.0.2.tgz", - "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "engines": { - "node": ">=8" - } - }, - "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/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "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.13.0", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.13.0.tgz", - "integrity": "sha512-/Tbp1OVzZjbwzwJQFIlYLm9eWQ+3aYbBXLSaqb1mEJzhcQAfrqMMQYtjb6io+U6KpD0ID4F+Id3/xcjH3l/sqA==", - "dependencies": { - "config-chain": "^1.1.12", - "editorconfig": "^0.15.3", - "glob": "^7.1.3", - "mkdirp": "^1.0.4", - "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" - } - }, - "node_modules/js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", - "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.1.1", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz", - "integrity": "sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==" - }, - "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.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" - }, - "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": "4.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", - "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", - "dependencies": { - "chalk": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "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/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/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.14", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", - "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.45", - "es6-weak-map": "^2.0.2", - "event-emitter": "^0.3.5", - "is-promise": "^2.1", - "lru-queue": "0.1", - "next-tick": "1", - "timers-ext": "^0.1.5" - } - }, - "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/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.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", - "dependencies": { - "mime-db": "1.44.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/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.0.tgz", - "integrity": "sha512-lEWEMq2LMfNJMKeuEwb5UELi+OgFDollXaytR5ggQcHpzG3NP/R7rvixAvF+9/lLsTWhWG+4yD2M70GsM06nxw==", - "dependencies": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.4.3", - "debug": "4.2.0", - "diff": "4.0.2", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.1.6", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "3.14.0", - "log-symbols": "4.0.0", - "minimatch": "3.0.4", - "ms": "2.1.2", - "nanoid": "3.1.12", - "serialize-javascript": "5.0.1", - "strip-json-comments": "3.1.1", - "supports-color": "7.2.0", - "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.0.2", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 10.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } - }, - "node_modules/mocha/node_modules/debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "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.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/mocha/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/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/mocha/node_modules/p-limit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", - "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/module-not-found-error": { - "version": "1.0.1", - "resolved": "http://npm.ooapi.com:4873/module-not-found-error/-/module-not-found-error-1.0.1.tgz", - "integrity": "sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=", - "dev": true - }, - "node_modules/moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", - "engines": { - "node": "*" - } - }, - "node_modules/moment-timezone": { - "version": "0.5.31", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.31.tgz", - "integrity": "sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==", - "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": "2.2.5", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.2.5.tgz", - "integrity": "sha512-XRqPNxcZTpmFdXbJqb+/CtYVLCx14x1RTeNMD4954L331APu75IC74GDqnZMEt1kwaXy6TySo55rF2F3YJS78g==", - "dependencies": { - "denque": "^1.4.1", - "generate-function": "^2.3.1", - "iconv-lite": "^0.6.2", - "long": "^4.0.0", - "lru-cache": "^6.0.0", - "named-placeholders": "^1.1.2", - "seq-queue": "^0.0.5", - "sqlstring": "^2.3.2" - }, - "engines": { - "node": ">= 8.0" - } - }, - "node_modules/mysql2/node_modules/iconv-lite": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", - "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "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/nanoid": { - "version": "3.1.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz", - "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || >=13.7" - } - }, - "node_modules/negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" - }, - "node_modules/nise": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/nise/-/nise-4.0.4.tgz", - "integrity": "sha512-bTTRUNlemx6deJa+ZyoCUTRvH3liK5+N6VQZ4NIw90AgDXY6iPnsqplNFf6STcj+ePk0H/xqxnP75Lr0J0Fq3A==", - "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/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/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/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "engines": { - "node": ">=0.10.0" - } - }, - "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/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/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.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" - }, - "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.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", - "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", - "engines": { - "node": "*" - } - }, - "node_modules/picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "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.6", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", - "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", - "dependencies": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxyquire": { - "version": "2.1.3", - "resolved": "http://npm.ooapi.com:4873/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.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "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.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "dependencies": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "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.18.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz", - "integrity": "sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA==", - "dependencies": { - "is-core-module": "^2.0.0", - "path-parse": "^1.0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/retry-as-promised": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-3.2.0.tgz", - "integrity": "sha512-CybGs60B7oYU/qSQ6kuaFmRd9sTZ6oXSc0toqePvV74Ac6/IFZSI1ReFQmtCN+uvW1Mtqdwpvt/LGOiCBAY2Mg==", - "dependencies": { - "any-promise": "^1.3.0" - } - }, - "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==" - }, - "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": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", - "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.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/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/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.3.5", - "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.3.5.tgz", - "integrity": "sha512-MiwiPkYSA8NWttRKAXdU9h0TxP6HAc1fl7qZmMO/VQqQOND83G4nZLXd0kWILtAoT9cxtZgFqeb/MPYgEeXwsw==", - "dependencies": { - "debug": "^4.1.1", - "dottie": "^2.0.0", - "inflection": "1.12.0", - "lodash": "^4.17.15", - "moment": "^2.26.0", - "moment-timezone": "^0.5.31", - "retry-as-promised": "^3.2.0", - "semver": "^7.3.2", - "sequelize-pool": "^6.0.0", - "toposort-class": "^1.0.1", - "uuid": "^8.1.0", - "validator": "^10.11.0", - "wkx": "^0.5.0" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependenciesMeta": { - "mariadb": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "pg": { - "optional": true - }, - "pg-hstore": { - "optional": true - }, - "sqlite3": { - "optional": true - }, - "tedious": { - "optional": true - } - } - }, - "node_modules/sequelize-cli": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/sequelize-cli/-/sequelize-cli-6.2.0.tgz", - "integrity": "sha512-6WQ2x91hg30dUn66mXHnzvHATZ4pyI1GHSNbS/TNN/vRR4BLRSLijadeMgC8zqmKDsL0VqzVVopJWfJakuP++Q==", - "dependencies": { - "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.3.0", - "yargs": "^13.1.0" - }, - "bin": { - "sequelize": "lib/sequelize", - "sequelize-cli": "lib/sequelize" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/sequelize-mock": { - "version": "0.10.2", - "resolved": "http://npm.ooapi.com:4873/sequelize-mock/-/sequelize-mock-0.10.2.tgz", - "integrity": "sha1-GdOXHM2utbhkFwwkznkqinHxRL0=", - "dev": true, - "dependencies": { - "bluebird": "^3.4.6", - "inflection": "^1.10.0", - "lodash": "^4.16.4" - } - }, - "node_modules/sequelize-pool": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-6.1.0.tgz", - "integrity": "sha512-4YwEw3ZgK/tY/so+GfnSgXkdwIJJ1I32uZJztIEgZeAO6HMgj64OzySbWLgxj+tXhZCJnzRfkY9gINw8Ft8ZMg==", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/sequelize/node_modules/debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "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.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "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/serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.1" - }, - "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.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" - }, - "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": "9.2.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.0.tgz", - "integrity": "sha512-eSNXz1XMcGEMHw08NJXSyTHIu6qTCOiN8x9ODACmZpNQpr0aXTBXBnI4xTzQzR+TEpOmLiKowGf9flCuKIzsbw==", - "dependencies": { - "@sinonjs/commons": "^1.8.1", - "@sinonjs/fake-timers": "^6.0.1", - "@sinonjs/formatio": "^5.0.1", - "@sinonjs/samsam": "^5.2.0", - "diff": "^4.0.2", - "nise": "^4.0.4", - "supports-color": "^7.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" - } - }, - "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.2", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz", - "integrity": "sha512-vF4ZbYdKS8OnoJAWBmMxCQDkiEBkGQYU7UZPtL8flbDRSNkhaXvRJ279ZtI6M+zDaQovVU4tuRgzK5fVhvFAhg==", - "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.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string_decoder/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/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/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/strip-ansi/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/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==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/superagent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-6.1.0.tgz", - "integrity": "sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg==", - "dependencies": { - "component-emitter": "^1.3.0", - "cookiejar": "^2.1.2", - "debug": "^4.1.1", - "fast-safe-stringify": "^2.0.7", - "form-data": "^3.0.0", - "formidable": "^1.2.2", - "methods": "^1.1.2", - "mime": "^2.4.6", - "qs": "^6.9.4", - "readable-stream": "^3.6.0", - "semver": "^7.3.2" - }, - "engines": { - "node": ">= 7.0.0" - } - }, - "node_modules/superagent/node_modules/debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "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.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/superagent/node_modules/mime": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/superagent/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/superagent/node_modules/qs": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", - "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==", - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/supertest": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-5.0.0.tgz", - "integrity": "sha512-2JAWpPrUOZF4hHH5ZTCN2xjKXvJS3AEwPNXl0HUseHsfcXFvMy9kcsufIHCNAmQ5hlGCvgeAqaR5PBEouN3hlQ==", - "dependencies": { - "methods": "1.1.2", - "superagent": "6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "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/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/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==", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "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/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/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=" - }, - "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.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", - "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/validator": { - "version": "10.11.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", - "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==", - "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": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "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/wide-align/node_modules/ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "engines": { - "node": ">=4" - } - }, - "node_modules/wide-align/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/wide-align/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/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/workerpool": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.2.tgz", - "integrity": "sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==" - }, - "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/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.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" - }, - "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/yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "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" - } - }, - "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" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser/node_modules/camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs-unparser/node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, "dependencies": { "@sinonjs/commons": { "version": "1.8.1", @@ -3053,16 +581,6 @@ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" }, - "fill-keys": { - "version": "1.0.2", - "resolved": "http://npm.ooapi.com:4873/fill-keys/-/fill-keys-1.0.2.tgz", - "integrity": "sha1-mo+jb06K1jTjv2tPPIiCVRRS6yA=", - "dev": true, - "requires": { - "is-object": "~1.0.1", - "merge-descriptors": "~1.0.0" - } - }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -3304,12 +822,6 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, - "is-object": { - "version": "1.0.2", - "resolved": "http://npm.ooapi.com:4873/is-object/-/is-object-1.0.2.tgz", - "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", - "dev": true - }, "is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -3563,12 +1075,6 @@ } } }, - "module-not-found-error": { - "version": "1.0.1", - "resolved": "http://npm.ooapi.com:4873/module-not-found-error/-/module-not-found-error-1.0.1.tgz", - "integrity": "sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=", - "dev": true - }, "moment": { "version": "2.29.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", @@ -3772,17 +1278,6 @@ "ipaddr.js": "1.9.1" } }, - "proxyquire": { - "version": "2.1.3", - "resolved": "http://npm.ooapi.com:4873/proxyquire/-/proxyquire-2.1.3.tgz", - "integrity": "sha512-BQWfCqYM+QINd+yawJz23tbBM40VIGXOdDw3X344KcclI/gtBbdWF6SlQ4nK/bYhF9d27KYug9WzljHC6B9Ysg==", - "dev": true, - "requires": { - "fill-keys": "^1.0.2", - "module-not-found-error": "^1.0.1", - "resolve": "^1.11.1" - } - }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -3958,17 +1453,6 @@ "yargs": "^13.1.0" } }, - "sequelize-mock": { - "version": "0.10.2", - "resolved": "http://npm.ooapi.com:4873/sequelize-mock/-/sequelize-mock-0.10.2.tgz", - "integrity": "sha1-GdOXHM2utbhkFwwkznkqinHxRL0=", - "dev": true, - "requires": { - "bluebird": "^3.4.6", - "inflection": "^1.10.0", - "lodash": "^4.16.4" - } - }, "sequelize-pool": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-6.1.0.tgz", @@ -4037,6 +1521,16 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, + "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==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -4052,16 +1546,6 @@ } } }, - "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==", - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -4364,4 +1848,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index c25bd055..5f6f1d0f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,6 @@ { - "name": "forum-express-workspace", - "version": "1.0.0", - "description": "", + "name": "forum-express-grading", + "version": "2.0.0", "main": "_helpers.js", "scripts": { "start": "node app.js", @@ -10,26 +9,21 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/v123582/forum-express-workspace.git" + "url": "git+https://github.com/ALPHACamp/forum-express-grading.git" }, - "author": "", - "license": "ISC", - "bugs": { - "url": "https://github.com/v123582/forum-express-workspace/issues" - }, - "homepage": "https://github.com/v123582/forum-express-workspace#readme", "dependencies": { - "chai": "^4.2.0", "express": "^4.17.1", - "mocha": "^8.2.0", "mysql2": "^2.2.5", + "nodemon": "^2.0.12", "sequelize": "^6.3.5", - "sequelize-cli": "^6.2.0", - "sinon": "^9.2.0", - "supertest": "^5.0.0" + "sequelize-cli": "^6.2.0" }, "devDependencies": { + "chai": "^4.2.0", + "mocha": "^8.2.0", "proxyquire": "^2.1.3", - "sequelize-mock": "^0.10.2" + "sequelize-mock": "^0.10.2", + "sinon": "^9.2.0", + "supertest": "^5.0.0" } -} +} \ No newline at end of file From 8c88719874fe6693bfbd7888c51aa3b6fe61ce2e Mon Sep 17 00:00:00 2001 From: Serena Tseng <51045074+Carrot7712@users.noreply.github.com> Date: Sat, 6 Nov 2021 00:14:56 +0800 Subject: [PATCH 085/198] Update .travis.yml --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9635c20c..fdf50b79 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,9 +11,9 @@ env: # 在 install 前執行的指令 before_install: - - mysql -e 'CREATE DATABASE IF NOT EXISTS forum_test default character set utf8mb4 collate utf8mb4_unicode_ci;' + - mysql -e 'CREATE DATABASE IF NOT EXISTS forum default character set utf8mb4 collate utf8mb4_unicode_ci;' - npm install sequelize-cli -g # 在執行測試之前的指令 before_script: - - sequelize db:migrate \ No newline at end of file + - sequelize db:migrate From ec2c6dfa2008eeafedabd2335ae7b81a08fdb0b8 Mon Sep 17 00:00:00 2001 From: Serena Tseng <51045074+Carrot7712@users.noreply.github.com> Date: Sat, 6 Nov 2021 00:29:51 +0800 Subject: [PATCH 086/198] Update .travis.yml --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index fdf50b79..f08c1b5e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - "10" + - "14" services: - mysql @@ -16,4 +16,5 @@ before_install: # 在執行測試之前的指令 before_script: - - sequelize db:migrate + - sequelize db:migrate + - sequelize db:seed:all From 80d4dd130931d520be0ebe7b31e772c9bf6dbf04 Mon Sep 17 00:00:00 2001 From: Carrot7712 Date: Wed, 3 Nov 2021 15:28:42 +0800 Subject: [PATCH 087/198] update readme config.json package.json --- README.md | 26 +- config/config.json | 14 +- package-lock.json | 2540 +------------------------------------------- package.json | 28 +- 4 files changed, 39 insertions(+), 2569 deletions(-) diff --git a/README.md b/README.md index e4cd70f1..9172af88 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ 1. Fork 2. git clone -3. 更改專案名稱 ## 初始化 ### Initialize @@ -16,14 +15,6 @@ npm install ``` create database forum; -create database forum_test; -``` - -### 切換環境 - -``` -export NODE_ENV=test # 切換到測試環境,如果在等號後加其他的字串,則會切到其他的環境 -echo $NODE_ENV # 印出目前使用的環境 ``` ### 執行測試 @@ -32,11 +23,11 @@ npm run test ``` ## 下載作業規格 -以 A17 為例 +以 R01 為例 ``` -git checkout -b A17 # 開新分支 -git merge origin/A17-test # 下載作業規格 +git checkout -b R01 # 開新分支 +git merge origin/R01-test # 下載作業規格 npm run test # 直到綠燈全亮 git add . @@ -46,11 +37,16 @@ git commit -m "...." ## 繳交作業 ``` -git push origin A17 # 上傳本地進度 +git push origin R01 # 上傳本地進度 ``` 接著改成到 GitHub 來發 PR。 ## 共用帳號 -請一律設定一個共用的 root user -root@example.com,登入密碼 12345678 +請一律設定下面 2 組帳號以利驗收: +* 第一組帳號有 admin 權限: + * email: root@example.com + * password: 12345678 +* 第二組帳號沒有 admin 權限: + * email: user1@example.com + * password: 12345678 \ No newline at end of file diff --git a/config/config.json b/config/config.json index 56636a1c..2b8be8e7 100644 --- a/config/config.json +++ b/config/config.json @@ -6,16 +6,12 @@ "host": "127.0.0.1", "dialect": "mysql" }, - "test": { + "production": { "username": "root", - "password": "password", - "database": "forum_test", + "password": null, + "database": "database_production", "host": "127.0.0.1", - "dialect": "mysql", - "logging": false - }, - "production": { - "use_env_variable": "CLEARDB_SILVER_URL" + "dialect": "mysql" }, "travis": { "username": "travis", @@ -24,4 +20,4 @@ "dialect": "mysql", "logging": false } -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index 7ebc04a3..bb00f848 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,2480 +1,8 @@ { "name": "forum-express-workspace", "version": "1.0.0", - "lockfileVersion": 2, + "lockfileVersion": 1, "requires": true, - "packages": { - "": { - "name": "forum-express-workspace", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "chai": "^4.2.0", - "express": "^4.17.1", - "mocha": "^8.2.0", - "mysql2": "^2.2.5", - "sequelize": "^6.3.5", - "sequelize-cli": "^6.2.0", - "sinon": "^9.2.0", - "supertest": "^5.0.0" - }, - "devDependencies": { - "proxyquire": "^2.1.3", - "sequelize-mock": "^0.10.2" - } - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", - "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", - "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/formatio": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-5.0.1.tgz", - "integrity": "sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==", - "dependencies": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^5.0.2" - } - }, - "node_modules/@sinonjs/samsam": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.2.0.tgz", - "integrity": "sha512-CaIcyX5cDsjcW/ab7HposFWzV1kC++4HNsfnEdFJa7cP1QIuILAKV+BgfeqRXhcnSAc76r/Rh/O5C+300BwUIw==", - "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/node": { - "version": "14.14.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.5.tgz", - "integrity": "sha512-H5Wn24s/ZOukBmDn03nnGTp18A60ny9AmCwnEcgJiTgSGsCO7k+NWP7zjCCbhlcnVCoI+co52dUAt9GMhOSULw==" - }, - "node_modules/@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==" - }, - "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.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "dependencies": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "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/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" - }, - "node_modules/anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "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=" - }, - "node_modules/balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "node_modules/binary-extensions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", - "engines": { - "node": ">=8" - } - }, - "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.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "dependencies": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" - }, - "engines": { - "node": ">= 0.8" - } - }, - "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/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "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.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", - "engines": { - "node": ">= 0.8" - } - }, - "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.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", - "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "pathval": "^1.1.0", - "type-detect": "^4.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chalk/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/chalk/node_modules/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==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/chalk/node_modules/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==" - }, - "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/chokidar": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", - "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", - "dependencies": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.1.2" - } - }, - "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/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==", - "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==" - }, - "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.12", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", - "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "node_modules/content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "dependencies": { - "safe-buffer": "5.1.2" - }, - "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.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", - "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.2", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", - "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==" - }, - "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/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/denque": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", - "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==", - "engines": { - "node": ">=0.10" - } - }, - "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/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "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/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/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/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/es5-ext": { - "version": "0.10.53", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", - "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", - "dependencies": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" - } - }, - "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": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", - "dependencies": { - "accepts": "~1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", - "content-type": "~1.0.4", - "cookie": "0.4.0", - "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.5", - "qs": "6.7.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "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/ext": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", - "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", - "dependencies": { - "type": "^2.0.0" - } - }, - "node_modules/ext/node_modules/type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.1.0.tgz", - "integrity": "sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA==" - }, - "node_modules/fast-safe-stringify": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", - "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" - }, - "node_modules/fill-keys": { - "version": "1.0.2", - "resolved": "http://npm.ooapi.com:4873/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/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==", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "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/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": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "bin": { - "flat": "cli.js" - } - }, - "node_modules/form-data": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", - "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/formidable": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", - "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==", - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" - } - }, - "node_modules/forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", - "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/fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "deprecated": "\"Please update to latest v2.3 or v2.2\"", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "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/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "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": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" - }, - "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-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/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": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "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.12.0", - "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", - "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=", - "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.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "node_modules/ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "deprecated": "Please update to ini >=1.3.6 to avoid a prototype pollution issue", - "engines": { - "node": "*" - } - }, - "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-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==", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.0.0.tgz", - "integrity": "sha512-jq1AH6C8MuteOoBPwkxHafmByhL9j5q4OaPGdbuD+ZtQJVzH+i6E3BJDQcBA09k57i2Hh2yQbEG8yObZ0jdlWw==", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "engines": { - "node": ">=0.10.0" - } - }, - "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-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-object": { - "version": "1.0.2", - "resolved": "http://npm.ooapi.com:4873/is-object/-/is-object-1.0.2.tgz", - "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "engines": { - "node": ">=8" - } - }, - "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/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "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.13.0", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.13.0.tgz", - "integrity": "sha512-/Tbp1OVzZjbwzwJQFIlYLm9eWQ+3aYbBXLSaqb1mEJzhcQAfrqMMQYtjb6io+U6KpD0ID4F+Id3/xcjH3l/sqA==", - "dependencies": { - "config-chain": "^1.1.12", - "editorconfig": "^0.15.3", - "glob": "^7.1.3", - "mkdirp": "^1.0.4", - "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" - } - }, - "node_modules/js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", - "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.1.1", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz", - "integrity": "sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==" - }, - "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.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" - }, - "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": "4.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", - "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", - "dependencies": { - "chalk": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "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/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/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.14", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", - "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.45", - "es6-weak-map": "^2.0.2", - "event-emitter": "^0.3.5", - "is-promise": "^2.1", - "lru-queue": "0.1", - "next-tick": "1", - "timers-ext": "^0.1.5" - } - }, - "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/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.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", - "dependencies": { - "mime-db": "1.44.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/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.0.tgz", - "integrity": "sha512-lEWEMq2LMfNJMKeuEwb5UELi+OgFDollXaytR5ggQcHpzG3NP/R7rvixAvF+9/lLsTWhWG+4yD2M70GsM06nxw==", - "dependencies": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.4.3", - "debug": "4.2.0", - "diff": "4.0.2", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.1.6", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "3.14.0", - "log-symbols": "4.0.0", - "minimatch": "3.0.4", - "ms": "2.1.2", - "nanoid": "3.1.12", - "serialize-javascript": "5.0.1", - "strip-json-comments": "3.1.1", - "supports-color": "7.2.0", - "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.0.2", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 10.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } - }, - "node_modules/mocha/node_modules/debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "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.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/mocha/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/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/mocha/node_modules/p-limit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", - "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/module-not-found-error": { - "version": "1.0.1", - "resolved": "http://npm.ooapi.com:4873/module-not-found-error/-/module-not-found-error-1.0.1.tgz", - "integrity": "sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=", - "dev": true - }, - "node_modules/moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", - "engines": { - "node": "*" - } - }, - "node_modules/moment-timezone": { - "version": "0.5.31", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.31.tgz", - "integrity": "sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==", - "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": "2.2.5", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.2.5.tgz", - "integrity": "sha512-XRqPNxcZTpmFdXbJqb+/CtYVLCx14x1RTeNMD4954L331APu75IC74GDqnZMEt1kwaXy6TySo55rF2F3YJS78g==", - "dependencies": { - "denque": "^1.4.1", - "generate-function": "^2.3.1", - "iconv-lite": "^0.6.2", - "long": "^4.0.0", - "lru-cache": "^6.0.0", - "named-placeholders": "^1.1.2", - "seq-queue": "^0.0.5", - "sqlstring": "^2.3.2" - }, - "engines": { - "node": ">= 8.0" - } - }, - "node_modules/mysql2/node_modules/iconv-lite": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", - "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "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/nanoid": { - "version": "3.1.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz", - "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || >=13.7" - } - }, - "node_modules/negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" - }, - "node_modules/nise": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/nise/-/nise-4.0.4.tgz", - "integrity": "sha512-bTTRUNlemx6deJa+ZyoCUTRvH3liK5+N6VQZ4NIw90AgDXY6iPnsqplNFf6STcj+ePk0H/xqxnP75Lr0J0Fq3A==", - "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/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/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/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "engines": { - "node": ">=0.10.0" - } - }, - "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/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/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.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" - }, - "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.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", - "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", - "engines": { - "node": "*" - } - }, - "node_modules/picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "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.6", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", - "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", - "dependencies": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxyquire": { - "version": "2.1.3", - "resolved": "http://npm.ooapi.com:4873/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.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "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.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "dependencies": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "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.18.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz", - "integrity": "sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA==", - "dependencies": { - "is-core-module": "^2.0.0", - "path-parse": "^1.0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/retry-as-promised": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-3.2.0.tgz", - "integrity": "sha512-CybGs60B7oYU/qSQ6kuaFmRd9sTZ6oXSc0toqePvV74Ac6/IFZSI1ReFQmtCN+uvW1Mtqdwpvt/LGOiCBAY2Mg==", - "dependencies": { - "any-promise": "^1.3.0" - } - }, - "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==" - }, - "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": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", - "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.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/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/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.3.5", - "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.3.5.tgz", - "integrity": "sha512-MiwiPkYSA8NWttRKAXdU9h0TxP6HAc1fl7qZmMO/VQqQOND83G4nZLXd0kWILtAoT9cxtZgFqeb/MPYgEeXwsw==", - "dependencies": { - "debug": "^4.1.1", - "dottie": "^2.0.0", - "inflection": "1.12.0", - "lodash": "^4.17.15", - "moment": "^2.26.0", - "moment-timezone": "^0.5.31", - "retry-as-promised": "^3.2.0", - "semver": "^7.3.2", - "sequelize-pool": "^6.0.0", - "toposort-class": "^1.0.1", - "uuid": "^8.1.0", - "validator": "^10.11.0", - "wkx": "^0.5.0" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependenciesMeta": { - "mariadb": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "pg": { - "optional": true - }, - "pg-hstore": { - "optional": true - }, - "sqlite3": { - "optional": true - }, - "tedious": { - "optional": true - } - } - }, - "node_modules/sequelize-cli": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/sequelize-cli/-/sequelize-cli-6.2.0.tgz", - "integrity": "sha512-6WQ2x91hg30dUn66mXHnzvHATZ4pyI1GHSNbS/TNN/vRR4BLRSLijadeMgC8zqmKDsL0VqzVVopJWfJakuP++Q==", - "dependencies": { - "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.3.0", - "yargs": "^13.1.0" - }, - "bin": { - "sequelize": "lib/sequelize", - "sequelize-cli": "lib/sequelize" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/sequelize-mock": { - "version": "0.10.2", - "resolved": "http://npm.ooapi.com:4873/sequelize-mock/-/sequelize-mock-0.10.2.tgz", - "integrity": "sha1-GdOXHM2utbhkFwwkznkqinHxRL0=", - "dev": true, - "dependencies": { - "bluebird": "^3.4.6", - "inflection": "^1.10.0", - "lodash": "^4.16.4" - } - }, - "node_modules/sequelize-pool": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-6.1.0.tgz", - "integrity": "sha512-4YwEw3ZgK/tY/so+GfnSgXkdwIJJ1I32uZJztIEgZeAO6HMgj64OzySbWLgxj+tXhZCJnzRfkY9gINw8Ft8ZMg==", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/sequelize/node_modules/debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "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.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "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/serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.1" - }, - "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.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" - }, - "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": "9.2.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.0.tgz", - "integrity": "sha512-eSNXz1XMcGEMHw08NJXSyTHIu6qTCOiN8x9ODACmZpNQpr0aXTBXBnI4xTzQzR+TEpOmLiKowGf9flCuKIzsbw==", - "dependencies": { - "@sinonjs/commons": "^1.8.1", - "@sinonjs/fake-timers": "^6.0.1", - "@sinonjs/formatio": "^5.0.1", - "@sinonjs/samsam": "^5.2.0", - "diff": "^4.0.2", - "nise": "^4.0.4", - "supports-color": "^7.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" - } - }, - "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.2", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz", - "integrity": "sha512-vF4ZbYdKS8OnoJAWBmMxCQDkiEBkGQYU7UZPtL8flbDRSNkhaXvRJ279ZtI6M+zDaQovVU4tuRgzK5fVhvFAhg==", - "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.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string_decoder/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/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/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/strip-ansi/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/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==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/superagent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-6.1.0.tgz", - "integrity": "sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg==", - "dependencies": { - "component-emitter": "^1.3.0", - "cookiejar": "^2.1.2", - "debug": "^4.1.1", - "fast-safe-stringify": "^2.0.7", - "form-data": "^3.0.0", - "formidable": "^1.2.2", - "methods": "^1.1.2", - "mime": "^2.4.6", - "qs": "^6.9.4", - "readable-stream": "^3.6.0", - "semver": "^7.3.2" - }, - "engines": { - "node": ">= 7.0.0" - } - }, - "node_modules/superagent/node_modules/debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "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.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/superagent/node_modules/mime": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/superagent/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/superagent/node_modules/qs": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", - "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==", - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/supertest": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-5.0.0.tgz", - "integrity": "sha512-2JAWpPrUOZF4hHH5ZTCN2xjKXvJS3AEwPNXl0HUseHsfcXFvMy9kcsufIHCNAmQ5hlGCvgeAqaR5PBEouN3hlQ==", - "dependencies": { - "methods": "1.1.2", - "superagent": "6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "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/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/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==", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "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/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/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=" - }, - "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.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", - "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/validator": { - "version": "10.11.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", - "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==", - "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": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "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/wide-align/node_modules/ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "engines": { - "node": ">=4" - } - }, - "node_modules/wide-align/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/wide-align/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/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/workerpool": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.2.tgz", - "integrity": "sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==" - }, - "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/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.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" - }, - "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/yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "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" - } - }, - "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" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser/node_modules/camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs-unparser/node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, "dependencies": { "@sinonjs/commons": { "version": "1.8.1", @@ -3053,16 +581,6 @@ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" }, - "fill-keys": { - "version": "1.0.2", - "resolved": "http://npm.ooapi.com:4873/fill-keys/-/fill-keys-1.0.2.tgz", - "integrity": "sha1-mo+jb06K1jTjv2tPPIiCVRRS6yA=", - "dev": true, - "requires": { - "is-object": "~1.0.1", - "merge-descriptors": "~1.0.0" - } - }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -3304,12 +822,6 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, - "is-object": { - "version": "1.0.2", - "resolved": "http://npm.ooapi.com:4873/is-object/-/is-object-1.0.2.tgz", - "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", - "dev": true - }, "is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -3563,12 +1075,6 @@ } } }, - "module-not-found-error": { - "version": "1.0.1", - "resolved": "http://npm.ooapi.com:4873/module-not-found-error/-/module-not-found-error-1.0.1.tgz", - "integrity": "sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=", - "dev": true - }, "moment": { "version": "2.29.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", @@ -3772,17 +1278,6 @@ "ipaddr.js": "1.9.1" } }, - "proxyquire": { - "version": "2.1.3", - "resolved": "http://npm.ooapi.com:4873/proxyquire/-/proxyquire-2.1.3.tgz", - "integrity": "sha512-BQWfCqYM+QINd+yawJz23tbBM40VIGXOdDw3X344KcclI/gtBbdWF6SlQ4nK/bYhF9d27KYug9WzljHC6B9Ysg==", - "dev": true, - "requires": { - "fill-keys": "^1.0.2", - "module-not-found-error": "^1.0.1", - "resolve": "^1.11.1" - } - }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -3958,17 +1453,6 @@ "yargs": "^13.1.0" } }, - "sequelize-mock": { - "version": "0.10.2", - "resolved": "http://npm.ooapi.com:4873/sequelize-mock/-/sequelize-mock-0.10.2.tgz", - "integrity": "sha1-GdOXHM2utbhkFwwkznkqinHxRL0=", - "dev": true, - "requires": { - "bluebird": "^3.4.6", - "inflection": "^1.10.0", - "lodash": "^4.16.4" - } - }, "sequelize-pool": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-6.1.0.tgz", @@ -4037,6 +1521,16 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, + "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==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -4052,16 +1546,6 @@ } } }, - "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==", - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -4364,4 +1848,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index c25bd055..5f6f1d0f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,6 @@ { - "name": "forum-express-workspace", - "version": "1.0.0", - "description": "", + "name": "forum-express-grading", + "version": "2.0.0", "main": "_helpers.js", "scripts": { "start": "node app.js", @@ -10,26 +9,21 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/v123582/forum-express-workspace.git" + "url": "git+https://github.com/ALPHACamp/forum-express-grading.git" }, - "author": "", - "license": "ISC", - "bugs": { - "url": "https://github.com/v123582/forum-express-workspace/issues" - }, - "homepage": "https://github.com/v123582/forum-express-workspace#readme", "dependencies": { - "chai": "^4.2.0", "express": "^4.17.1", - "mocha": "^8.2.0", "mysql2": "^2.2.5", + "nodemon": "^2.0.12", "sequelize": "^6.3.5", - "sequelize-cli": "^6.2.0", - "sinon": "^9.2.0", - "supertest": "^5.0.0" + "sequelize-cli": "^6.2.0" }, "devDependencies": { + "chai": "^4.2.0", + "mocha": "^8.2.0", "proxyquire": "^2.1.3", - "sequelize-mock": "^0.10.2" + "sequelize-mock": "^0.10.2", + "sinon": "^9.2.0", + "supertest": "^5.0.0" } -} +} \ No newline at end of file From 7dc7a56d0d7dea94585dd0f47db7143139dce60c Mon Sep 17 00:00:00 2001 From: Serena Tseng <51045074+Carrot7712@users.noreply.github.com> Date: Sat, 6 Nov 2021 00:14:56 +0800 Subject: [PATCH 088/198] Update .travis.yml --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9635c20c..fdf50b79 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,9 +11,9 @@ env: # 在 install 前執行的指令 before_install: - - mysql -e 'CREATE DATABASE IF NOT EXISTS forum_test default character set utf8mb4 collate utf8mb4_unicode_ci;' + - mysql -e 'CREATE DATABASE IF NOT EXISTS forum default character set utf8mb4 collate utf8mb4_unicode_ci;' - npm install sequelize-cli -g # 在執行測試之前的指令 before_script: - - sequelize db:migrate \ No newline at end of file + - sequelize db:migrate From 88575dacb96db474e35bd8386b962cc25bed5ca6 Mon Sep 17 00:00:00 2001 From: Serena Tseng <51045074+Carrot7712@users.noreply.github.com> Date: Sat, 6 Nov 2021 00:29:51 +0800 Subject: [PATCH 089/198] Update .travis.yml --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index fdf50b79..f08c1b5e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - "10" + - "14" services: - mysql @@ -16,4 +16,5 @@ before_install: # 在執行測試之前的指令 before_script: - - sequelize db:migrate + - sequelize db:migrate + - sequelize db:seed:all From dd4522bd69820fa0fdd7e30080ee89310bebbbbd Mon Sep 17 00:00:00 2001 From: Carrot7712 Date: Wed, 3 Nov 2021 15:28:42 +0800 Subject: [PATCH 090/198] update readme config.json package.json --- README.md | 26 +- config/config.json | 14 +- package-lock.json | 2540 +------------------------------------------- package.json | 28 +- 4 files changed, 39 insertions(+), 2569 deletions(-) diff --git a/README.md b/README.md index e4cd70f1..9172af88 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ 1. Fork 2. git clone -3. 更改專案名稱 ## 初始化 ### Initialize @@ -16,14 +15,6 @@ npm install ``` create database forum; -create database forum_test; -``` - -### 切換環境 - -``` -export NODE_ENV=test # 切換到測試環境,如果在等號後加其他的字串,則會切到其他的環境 -echo $NODE_ENV # 印出目前使用的環境 ``` ### 執行測試 @@ -32,11 +23,11 @@ npm run test ``` ## 下載作業規格 -以 A17 為例 +以 R01 為例 ``` -git checkout -b A17 # 開新分支 -git merge origin/A17-test # 下載作業規格 +git checkout -b R01 # 開新分支 +git merge origin/R01-test # 下載作業規格 npm run test # 直到綠燈全亮 git add . @@ -46,11 +37,16 @@ git commit -m "...." ## 繳交作業 ``` -git push origin A17 # 上傳本地進度 +git push origin R01 # 上傳本地進度 ``` 接著改成到 GitHub 來發 PR。 ## 共用帳號 -請一律設定一個共用的 root user -root@example.com,登入密碼 12345678 +請一律設定下面 2 組帳號以利驗收: +* 第一組帳號有 admin 權限: + * email: root@example.com + * password: 12345678 +* 第二組帳號沒有 admin 權限: + * email: user1@example.com + * password: 12345678 \ No newline at end of file diff --git a/config/config.json b/config/config.json index 56636a1c..2b8be8e7 100644 --- a/config/config.json +++ b/config/config.json @@ -6,16 +6,12 @@ "host": "127.0.0.1", "dialect": "mysql" }, - "test": { + "production": { "username": "root", - "password": "password", - "database": "forum_test", + "password": null, + "database": "database_production", "host": "127.0.0.1", - "dialect": "mysql", - "logging": false - }, - "production": { - "use_env_variable": "CLEARDB_SILVER_URL" + "dialect": "mysql" }, "travis": { "username": "travis", @@ -24,4 +20,4 @@ "dialect": "mysql", "logging": false } -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index 7ebc04a3..bb00f848 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,2480 +1,8 @@ { "name": "forum-express-workspace", "version": "1.0.0", - "lockfileVersion": 2, + "lockfileVersion": 1, "requires": true, - "packages": { - "": { - "name": "forum-express-workspace", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "chai": "^4.2.0", - "express": "^4.17.1", - "mocha": "^8.2.0", - "mysql2": "^2.2.5", - "sequelize": "^6.3.5", - "sequelize-cli": "^6.2.0", - "sinon": "^9.2.0", - "supertest": "^5.0.0" - }, - "devDependencies": { - "proxyquire": "^2.1.3", - "sequelize-mock": "^0.10.2" - } - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", - "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", - "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/formatio": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-5.0.1.tgz", - "integrity": "sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==", - "dependencies": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^5.0.2" - } - }, - "node_modules/@sinonjs/samsam": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.2.0.tgz", - "integrity": "sha512-CaIcyX5cDsjcW/ab7HposFWzV1kC++4HNsfnEdFJa7cP1QIuILAKV+BgfeqRXhcnSAc76r/Rh/O5C+300BwUIw==", - "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/node": { - "version": "14.14.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.5.tgz", - "integrity": "sha512-H5Wn24s/ZOukBmDn03nnGTp18A60ny9AmCwnEcgJiTgSGsCO7k+NWP7zjCCbhlcnVCoI+co52dUAt9GMhOSULw==" - }, - "node_modules/@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==" - }, - "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.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "dependencies": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "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/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" - }, - "node_modules/anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "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=" - }, - "node_modules/balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "node_modules/binary-extensions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", - "engines": { - "node": ">=8" - } - }, - "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.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "dependencies": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" - }, - "engines": { - "node": ">= 0.8" - } - }, - "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/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "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.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", - "engines": { - "node": ">= 0.8" - } - }, - "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.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", - "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "pathval": "^1.1.0", - "type-detect": "^4.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chalk/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/chalk/node_modules/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==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/chalk/node_modules/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==" - }, - "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/chokidar": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", - "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", - "dependencies": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.1.2" - } - }, - "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/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==", - "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==" - }, - "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.12", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", - "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "node_modules/content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "dependencies": { - "safe-buffer": "5.1.2" - }, - "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.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", - "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.2", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", - "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==" - }, - "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/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/denque": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", - "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==", - "engines": { - "node": ">=0.10" - } - }, - "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/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "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/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/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/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/es5-ext": { - "version": "0.10.53", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", - "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", - "dependencies": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" - } - }, - "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": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", - "dependencies": { - "accepts": "~1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", - "content-type": "~1.0.4", - "cookie": "0.4.0", - "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.5", - "qs": "6.7.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "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/ext": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", - "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", - "dependencies": { - "type": "^2.0.0" - } - }, - "node_modules/ext/node_modules/type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.1.0.tgz", - "integrity": "sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA==" - }, - "node_modules/fast-safe-stringify": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", - "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" - }, - "node_modules/fill-keys": { - "version": "1.0.2", - "resolved": "http://npm.ooapi.com:4873/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/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==", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "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/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": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "bin": { - "flat": "cli.js" - } - }, - "node_modules/form-data": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", - "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/formidable": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", - "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==", - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" - } - }, - "node_modules/forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", - "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/fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "deprecated": "\"Please update to latest v2.3 or v2.2\"", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "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/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "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": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" - }, - "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-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/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": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "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.12.0", - "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", - "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=", - "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.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "node_modules/ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "deprecated": "Please update to ini >=1.3.6 to avoid a prototype pollution issue", - "engines": { - "node": "*" - } - }, - "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-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==", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.0.0.tgz", - "integrity": "sha512-jq1AH6C8MuteOoBPwkxHafmByhL9j5q4OaPGdbuD+ZtQJVzH+i6E3BJDQcBA09k57i2Hh2yQbEG8yObZ0jdlWw==", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "engines": { - "node": ">=0.10.0" - } - }, - "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-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-object": { - "version": "1.0.2", - "resolved": "http://npm.ooapi.com:4873/is-object/-/is-object-1.0.2.tgz", - "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "engines": { - "node": ">=8" - } - }, - "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/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "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.13.0", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.13.0.tgz", - "integrity": "sha512-/Tbp1OVzZjbwzwJQFIlYLm9eWQ+3aYbBXLSaqb1mEJzhcQAfrqMMQYtjb6io+U6KpD0ID4F+Id3/xcjH3l/sqA==", - "dependencies": { - "config-chain": "^1.1.12", - "editorconfig": "^0.15.3", - "glob": "^7.1.3", - "mkdirp": "^1.0.4", - "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" - } - }, - "node_modules/js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", - "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.1.1", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz", - "integrity": "sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==" - }, - "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.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" - }, - "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": "4.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", - "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", - "dependencies": { - "chalk": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "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/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/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.14", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", - "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.45", - "es6-weak-map": "^2.0.2", - "event-emitter": "^0.3.5", - "is-promise": "^2.1", - "lru-queue": "0.1", - "next-tick": "1", - "timers-ext": "^0.1.5" - } - }, - "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/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.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", - "dependencies": { - "mime-db": "1.44.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/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.0.tgz", - "integrity": "sha512-lEWEMq2LMfNJMKeuEwb5UELi+OgFDollXaytR5ggQcHpzG3NP/R7rvixAvF+9/lLsTWhWG+4yD2M70GsM06nxw==", - "dependencies": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.4.3", - "debug": "4.2.0", - "diff": "4.0.2", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.1.6", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "3.14.0", - "log-symbols": "4.0.0", - "minimatch": "3.0.4", - "ms": "2.1.2", - "nanoid": "3.1.12", - "serialize-javascript": "5.0.1", - "strip-json-comments": "3.1.1", - "supports-color": "7.2.0", - "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.0.2", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 10.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } - }, - "node_modules/mocha/node_modules/debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "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.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/mocha/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/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/mocha/node_modules/p-limit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", - "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/module-not-found-error": { - "version": "1.0.1", - "resolved": "http://npm.ooapi.com:4873/module-not-found-error/-/module-not-found-error-1.0.1.tgz", - "integrity": "sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=", - "dev": true - }, - "node_modules/moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", - "engines": { - "node": "*" - } - }, - "node_modules/moment-timezone": { - "version": "0.5.31", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.31.tgz", - "integrity": "sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==", - "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": "2.2.5", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.2.5.tgz", - "integrity": "sha512-XRqPNxcZTpmFdXbJqb+/CtYVLCx14x1RTeNMD4954L331APu75IC74GDqnZMEt1kwaXy6TySo55rF2F3YJS78g==", - "dependencies": { - "denque": "^1.4.1", - "generate-function": "^2.3.1", - "iconv-lite": "^0.6.2", - "long": "^4.0.0", - "lru-cache": "^6.0.0", - "named-placeholders": "^1.1.2", - "seq-queue": "^0.0.5", - "sqlstring": "^2.3.2" - }, - "engines": { - "node": ">= 8.0" - } - }, - "node_modules/mysql2/node_modules/iconv-lite": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", - "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "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/nanoid": { - "version": "3.1.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz", - "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || >=13.7" - } - }, - "node_modules/negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" - }, - "node_modules/nise": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/nise/-/nise-4.0.4.tgz", - "integrity": "sha512-bTTRUNlemx6deJa+ZyoCUTRvH3liK5+N6VQZ4NIw90AgDXY6iPnsqplNFf6STcj+ePk0H/xqxnP75Lr0J0Fq3A==", - "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/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/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/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "engines": { - "node": ">=0.10.0" - } - }, - "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/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/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.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" - }, - "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.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", - "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", - "engines": { - "node": "*" - } - }, - "node_modules/picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "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.6", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", - "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", - "dependencies": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxyquire": { - "version": "2.1.3", - "resolved": "http://npm.ooapi.com:4873/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.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "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.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "dependencies": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "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.18.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz", - "integrity": "sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA==", - "dependencies": { - "is-core-module": "^2.0.0", - "path-parse": "^1.0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/retry-as-promised": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-3.2.0.tgz", - "integrity": "sha512-CybGs60B7oYU/qSQ6kuaFmRd9sTZ6oXSc0toqePvV74Ac6/IFZSI1ReFQmtCN+uvW1Mtqdwpvt/LGOiCBAY2Mg==", - "dependencies": { - "any-promise": "^1.3.0" - } - }, - "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==" - }, - "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": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", - "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.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/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/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.3.5", - "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.3.5.tgz", - "integrity": "sha512-MiwiPkYSA8NWttRKAXdU9h0TxP6HAc1fl7qZmMO/VQqQOND83G4nZLXd0kWILtAoT9cxtZgFqeb/MPYgEeXwsw==", - "dependencies": { - "debug": "^4.1.1", - "dottie": "^2.0.0", - "inflection": "1.12.0", - "lodash": "^4.17.15", - "moment": "^2.26.0", - "moment-timezone": "^0.5.31", - "retry-as-promised": "^3.2.0", - "semver": "^7.3.2", - "sequelize-pool": "^6.0.0", - "toposort-class": "^1.0.1", - "uuid": "^8.1.0", - "validator": "^10.11.0", - "wkx": "^0.5.0" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependenciesMeta": { - "mariadb": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "pg": { - "optional": true - }, - "pg-hstore": { - "optional": true - }, - "sqlite3": { - "optional": true - }, - "tedious": { - "optional": true - } - } - }, - "node_modules/sequelize-cli": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/sequelize-cli/-/sequelize-cli-6.2.0.tgz", - "integrity": "sha512-6WQ2x91hg30dUn66mXHnzvHATZ4pyI1GHSNbS/TNN/vRR4BLRSLijadeMgC8zqmKDsL0VqzVVopJWfJakuP++Q==", - "dependencies": { - "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.3.0", - "yargs": "^13.1.0" - }, - "bin": { - "sequelize": "lib/sequelize", - "sequelize-cli": "lib/sequelize" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/sequelize-mock": { - "version": "0.10.2", - "resolved": "http://npm.ooapi.com:4873/sequelize-mock/-/sequelize-mock-0.10.2.tgz", - "integrity": "sha1-GdOXHM2utbhkFwwkznkqinHxRL0=", - "dev": true, - "dependencies": { - "bluebird": "^3.4.6", - "inflection": "^1.10.0", - "lodash": "^4.16.4" - } - }, - "node_modules/sequelize-pool": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-6.1.0.tgz", - "integrity": "sha512-4YwEw3ZgK/tY/so+GfnSgXkdwIJJ1I32uZJztIEgZeAO6HMgj64OzySbWLgxj+tXhZCJnzRfkY9gINw8Ft8ZMg==", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/sequelize/node_modules/debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "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.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "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/serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.1" - }, - "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.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" - }, - "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": "9.2.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.0.tgz", - "integrity": "sha512-eSNXz1XMcGEMHw08NJXSyTHIu6qTCOiN8x9ODACmZpNQpr0aXTBXBnI4xTzQzR+TEpOmLiKowGf9flCuKIzsbw==", - "dependencies": { - "@sinonjs/commons": "^1.8.1", - "@sinonjs/fake-timers": "^6.0.1", - "@sinonjs/formatio": "^5.0.1", - "@sinonjs/samsam": "^5.2.0", - "diff": "^4.0.2", - "nise": "^4.0.4", - "supports-color": "^7.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" - } - }, - "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.2", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz", - "integrity": "sha512-vF4ZbYdKS8OnoJAWBmMxCQDkiEBkGQYU7UZPtL8flbDRSNkhaXvRJ279ZtI6M+zDaQovVU4tuRgzK5fVhvFAhg==", - "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.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string_decoder/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/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/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/strip-ansi/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/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==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/superagent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-6.1.0.tgz", - "integrity": "sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg==", - "dependencies": { - "component-emitter": "^1.3.0", - "cookiejar": "^2.1.2", - "debug": "^4.1.1", - "fast-safe-stringify": "^2.0.7", - "form-data": "^3.0.0", - "formidable": "^1.2.2", - "methods": "^1.1.2", - "mime": "^2.4.6", - "qs": "^6.9.4", - "readable-stream": "^3.6.0", - "semver": "^7.3.2" - }, - "engines": { - "node": ">= 7.0.0" - } - }, - "node_modules/superagent/node_modules/debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "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.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/superagent/node_modules/mime": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/superagent/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/superagent/node_modules/qs": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", - "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==", - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/supertest": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-5.0.0.tgz", - "integrity": "sha512-2JAWpPrUOZF4hHH5ZTCN2xjKXvJS3AEwPNXl0HUseHsfcXFvMy9kcsufIHCNAmQ5hlGCvgeAqaR5PBEouN3hlQ==", - "dependencies": { - "methods": "1.1.2", - "superagent": "6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "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/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/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==", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "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/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/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=" - }, - "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.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", - "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/validator": { - "version": "10.11.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", - "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==", - "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": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "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/wide-align/node_modules/ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "engines": { - "node": ">=4" - } - }, - "node_modules/wide-align/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/wide-align/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/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/workerpool": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.2.tgz", - "integrity": "sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==" - }, - "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/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.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" - }, - "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/yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "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" - } - }, - "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" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser/node_modules/camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs-unparser/node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, "dependencies": { "@sinonjs/commons": { "version": "1.8.1", @@ -3053,16 +581,6 @@ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" }, - "fill-keys": { - "version": "1.0.2", - "resolved": "http://npm.ooapi.com:4873/fill-keys/-/fill-keys-1.0.2.tgz", - "integrity": "sha1-mo+jb06K1jTjv2tPPIiCVRRS6yA=", - "dev": true, - "requires": { - "is-object": "~1.0.1", - "merge-descriptors": "~1.0.0" - } - }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -3304,12 +822,6 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, - "is-object": { - "version": "1.0.2", - "resolved": "http://npm.ooapi.com:4873/is-object/-/is-object-1.0.2.tgz", - "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", - "dev": true - }, "is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -3563,12 +1075,6 @@ } } }, - "module-not-found-error": { - "version": "1.0.1", - "resolved": "http://npm.ooapi.com:4873/module-not-found-error/-/module-not-found-error-1.0.1.tgz", - "integrity": "sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=", - "dev": true - }, "moment": { "version": "2.29.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", @@ -3772,17 +1278,6 @@ "ipaddr.js": "1.9.1" } }, - "proxyquire": { - "version": "2.1.3", - "resolved": "http://npm.ooapi.com:4873/proxyquire/-/proxyquire-2.1.3.tgz", - "integrity": "sha512-BQWfCqYM+QINd+yawJz23tbBM40VIGXOdDw3X344KcclI/gtBbdWF6SlQ4nK/bYhF9d27KYug9WzljHC6B9Ysg==", - "dev": true, - "requires": { - "fill-keys": "^1.0.2", - "module-not-found-error": "^1.0.1", - "resolve": "^1.11.1" - } - }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -3958,17 +1453,6 @@ "yargs": "^13.1.0" } }, - "sequelize-mock": { - "version": "0.10.2", - "resolved": "http://npm.ooapi.com:4873/sequelize-mock/-/sequelize-mock-0.10.2.tgz", - "integrity": "sha1-GdOXHM2utbhkFwwkznkqinHxRL0=", - "dev": true, - "requires": { - "bluebird": "^3.4.6", - "inflection": "^1.10.0", - "lodash": "^4.16.4" - } - }, "sequelize-pool": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-6.1.0.tgz", @@ -4037,6 +1521,16 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, + "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==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -4052,16 +1546,6 @@ } } }, - "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==", - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -4364,4 +1848,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index c25bd055..5f6f1d0f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,6 @@ { - "name": "forum-express-workspace", - "version": "1.0.0", - "description": "", + "name": "forum-express-grading", + "version": "2.0.0", "main": "_helpers.js", "scripts": { "start": "node app.js", @@ -10,26 +9,21 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/v123582/forum-express-workspace.git" + "url": "git+https://github.com/ALPHACamp/forum-express-grading.git" }, - "author": "", - "license": "ISC", - "bugs": { - "url": "https://github.com/v123582/forum-express-workspace/issues" - }, - "homepage": "https://github.com/v123582/forum-express-workspace#readme", "dependencies": { - "chai": "^4.2.0", "express": "^4.17.1", - "mocha": "^8.2.0", "mysql2": "^2.2.5", + "nodemon": "^2.0.12", "sequelize": "^6.3.5", - "sequelize-cli": "^6.2.0", - "sinon": "^9.2.0", - "supertest": "^5.0.0" + "sequelize-cli": "^6.2.0" }, "devDependencies": { + "chai": "^4.2.0", + "mocha": "^8.2.0", "proxyquire": "^2.1.3", - "sequelize-mock": "^0.10.2" + "sequelize-mock": "^0.10.2", + "sinon": "^9.2.0", + "supertest": "^5.0.0" } -} +} \ No newline at end of file From 894675fa3114515bbc9c80ec5984ff03d53713a6 Mon Sep 17 00:00:00 2001 From: Serena Tseng <51045074+Carrot7712@users.noreply.github.com> Date: Sat, 6 Nov 2021 00:14:56 +0800 Subject: [PATCH 091/198] Update .travis.yml --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9635c20c..fdf50b79 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,9 +11,9 @@ env: # 在 install 前執行的指令 before_install: - - mysql -e 'CREATE DATABASE IF NOT EXISTS forum_test default character set utf8mb4 collate utf8mb4_unicode_ci;' + - mysql -e 'CREATE DATABASE IF NOT EXISTS forum default character set utf8mb4 collate utf8mb4_unicode_ci;' - npm install sequelize-cli -g # 在執行測試之前的指令 before_script: - - sequelize db:migrate \ No newline at end of file + - sequelize db:migrate From da3a406d34be4f6e2cf149af3433045b77f853e0 Mon Sep 17 00:00:00 2001 From: Serena Tseng <51045074+Carrot7712@users.noreply.github.com> Date: Sat, 6 Nov 2021 00:29:51 +0800 Subject: [PATCH 092/198] Update .travis.yml --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index fdf50b79..f08c1b5e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - "10" + - "14" services: - mysql @@ -16,4 +16,5 @@ before_install: # 在執行測試之前的指令 before_script: - - sequelize db:migrate + - sequelize db:migrate + - sequelize db:seed:all From 29a2fb94a7c36d399522f24ff3724b81c1468574 Mon Sep 17 00:00:00 2001 From: Carrot7712 Date: Wed, 3 Nov 2021 15:28:42 +0800 Subject: [PATCH 093/198] update readme config.json package.json --- README.md | 26 +- config/config.json | 14 +- package-lock.json | 2540 +------------------------------------------- package.json | 28 +- 4 files changed, 39 insertions(+), 2569 deletions(-) diff --git a/README.md b/README.md index e4cd70f1..9172af88 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ 1. Fork 2. git clone -3. 更改專案名稱 ## 初始化 ### Initialize @@ -16,14 +15,6 @@ npm install ``` create database forum; -create database forum_test; -``` - -### 切換環境 - -``` -export NODE_ENV=test # 切換到測試環境,如果在等號後加其他的字串,則會切到其他的環境 -echo $NODE_ENV # 印出目前使用的環境 ``` ### 執行測試 @@ -32,11 +23,11 @@ npm run test ``` ## 下載作業規格 -以 A17 為例 +以 R01 為例 ``` -git checkout -b A17 # 開新分支 -git merge origin/A17-test # 下載作業規格 +git checkout -b R01 # 開新分支 +git merge origin/R01-test # 下載作業規格 npm run test # 直到綠燈全亮 git add . @@ -46,11 +37,16 @@ git commit -m "...." ## 繳交作業 ``` -git push origin A17 # 上傳本地進度 +git push origin R01 # 上傳本地進度 ``` 接著改成到 GitHub 來發 PR。 ## 共用帳號 -請一律設定一個共用的 root user -root@example.com,登入密碼 12345678 +請一律設定下面 2 組帳號以利驗收: +* 第一組帳號有 admin 權限: + * email: root@example.com + * password: 12345678 +* 第二組帳號沒有 admin 權限: + * email: user1@example.com + * password: 12345678 \ No newline at end of file diff --git a/config/config.json b/config/config.json index 56636a1c..2b8be8e7 100644 --- a/config/config.json +++ b/config/config.json @@ -6,16 +6,12 @@ "host": "127.0.0.1", "dialect": "mysql" }, - "test": { + "production": { "username": "root", - "password": "password", - "database": "forum_test", + "password": null, + "database": "database_production", "host": "127.0.0.1", - "dialect": "mysql", - "logging": false - }, - "production": { - "use_env_variable": "CLEARDB_SILVER_URL" + "dialect": "mysql" }, "travis": { "username": "travis", @@ -24,4 +20,4 @@ "dialect": "mysql", "logging": false } -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index 7ebc04a3..bb00f848 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,2480 +1,8 @@ { "name": "forum-express-workspace", "version": "1.0.0", - "lockfileVersion": 2, + "lockfileVersion": 1, "requires": true, - "packages": { - "": { - "name": "forum-express-workspace", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "chai": "^4.2.0", - "express": "^4.17.1", - "mocha": "^8.2.0", - "mysql2": "^2.2.5", - "sequelize": "^6.3.5", - "sequelize-cli": "^6.2.0", - "sinon": "^9.2.0", - "supertest": "^5.0.0" - }, - "devDependencies": { - "proxyquire": "^2.1.3", - "sequelize-mock": "^0.10.2" - } - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", - "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", - "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/formatio": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-5.0.1.tgz", - "integrity": "sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==", - "dependencies": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^5.0.2" - } - }, - "node_modules/@sinonjs/samsam": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.2.0.tgz", - "integrity": "sha512-CaIcyX5cDsjcW/ab7HposFWzV1kC++4HNsfnEdFJa7cP1QIuILAKV+BgfeqRXhcnSAc76r/Rh/O5C+300BwUIw==", - "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/node": { - "version": "14.14.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.5.tgz", - "integrity": "sha512-H5Wn24s/ZOukBmDn03nnGTp18A60ny9AmCwnEcgJiTgSGsCO7k+NWP7zjCCbhlcnVCoI+co52dUAt9GMhOSULw==" - }, - "node_modules/@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==" - }, - "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.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "dependencies": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "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/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" - }, - "node_modules/anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "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=" - }, - "node_modules/balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "node_modules/binary-extensions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", - "engines": { - "node": ">=8" - } - }, - "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.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "dependencies": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" - }, - "engines": { - "node": ">= 0.8" - } - }, - "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/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "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.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", - "engines": { - "node": ">= 0.8" - } - }, - "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.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", - "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "pathval": "^1.1.0", - "type-detect": "^4.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chalk/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/chalk/node_modules/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==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/chalk/node_modules/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==" - }, - "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/chokidar": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", - "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", - "dependencies": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.1.2" - } - }, - "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/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==", - "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==" - }, - "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.12", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", - "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "node_modules/content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "dependencies": { - "safe-buffer": "5.1.2" - }, - "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.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", - "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.2", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", - "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==" - }, - "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/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/denque": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", - "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==", - "engines": { - "node": ">=0.10" - } - }, - "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/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "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/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/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/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/es5-ext": { - "version": "0.10.53", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", - "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", - "dependencies": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" - } - }, - "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": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", - "dependencies": { - "accepts": "~1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", - "content-type": "~1.0.4", - "cookie": "0.4.0", - "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.5", - "qs": "6.7.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "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/ext": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", - "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", - "dependencies": { - "type": "^2.0.0" - } - }, - "node_modules/ext/node_modules/type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.1.0.tgz", - "integrity": "sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA==" - }, - "node_modules/fast-safe-stringify": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", - "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" - }, - "node_modules/fill-keys": { - "version": "1.0.2", - "resolved": "http://npm.ooapi.com:4873/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/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==", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "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/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": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "bin": { - "flat": "cli.js" - } - }, - "node_modules/form-data": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", - "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/formidable": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", - "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==", - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" - } - }, - "node_modules/forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", - "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/fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "deprecated": "\"Please update to latest v2.3 or v2.2\"", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "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/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "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": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" - }, - "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-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/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": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "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.12.0", - "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", - "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=", - "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.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "node_modules/ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "deprecated": "Please update to ini >=1.3.6 to avoid a prototype pollution issue", - "engines": { - "node": "*" - } - }, - "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-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==", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.0.0.tgz", - "integrity": "sha512-jq1AH6C8MuteOoBPwkxHafmByhL9j5q4OaPGdbuD+ZtQJVzH+i6E3BJDQcBA09k57i2Hh2yQbEG8yObZ0jdlWw==", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "engines": { - "node": ">=0.10.0" - } - }, - "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-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-object": { - "version": "1.0.2", - "resolved": "http://npm.ooapi.com:4873/is-object/-/is-object-1.0.2.tgz", - "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "engines": { - "node": ">=8" - } - }, - "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/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "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.13.0", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.13.0.tgz", - "integrity": "sha512-/Tbp1OVzZjbwzwJQFIlYLm9eWQ+3aYbBXLSaqb1mEJzhcQAfrqMMQYtjb6io+U6KpD0ID4F+Id3/xcjH3l/sqA==", - "dependencies": { - "config-chain": "^1.1.12", - "editorconfig": "^0.15.3", - "glob": "^7.1.3", - "mkdirp": "^1.0.4", - "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" - } - }, - "node_modules/js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", - "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.1.1", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz", - "integrity": "sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==" - }, - "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.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" - }, - "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": "4.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", - "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", - "dependencies": { - "chalk": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "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/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/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.14", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", - "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.45", - "es6-weak-map": "^2.0.2", - "event-emitter": "^0.3.5", - "is-promise": "^2.1", - "lru-queue": "0.1", - "next-tick": "1", - "timers-ext": "^0.1.5" - } - }, - "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/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.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", - "dependencies": { - "mime-db": "1.44.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/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.0.tgz", - "integrity": "sha512-lEWEMq2LMfNJMKeuEwb5UELi+OgFDollXaytR5ggQcHpzG3NP/R7rvixAvF+9/lLsTWhWG+4yD2M70GsM06nxw==", - "dependencies": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.4.3", - "debug": "4.2.0", - "diff": "4.0.2", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.1.6", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "3.14.0", - "log-symbols": "4.0.0", - "minimatch": "3.0.4", - "ms": "2.1.2", - "nanoid": "3.1.12", - "serialize-javascript": "5.0.1", - "strip-json-comments": "3.1.1", - "supports-color": "7.2.0", - "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.0.2", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 10.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } - }, - "node_modules/mocha/node_modules/debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "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.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/mocha/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/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/mocha/node_modules/p-limit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", - "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/module-not-found-error": { - "version": "1.0.1", - "resolved": "http://npm.ooapi.com:4873/module-not-found-error/-/module-not-found-error-1.0.1.tgz", - "integrity": "sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=", - "dev": true - }, - "node_modules/moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", - "engines": { - "node": "*" - } - }, - "node_modules/moment-timezone": { - "version": "0.5.31", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.31.tgz", - "integrity": "sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==", - "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": "2.2.5", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.2.5.tgz", - "integrity": "sha512-XRqPNxcZTpmFdXbJqb+/CtYVLCx14x1RTeNMD4954L331APu75IC74GDqnZMEt1kwaXy6TySo55rF2F3YJS78g==", - "dependencies": { - "denque": "^1.4.1", - "generate-function": "^2.3.1", - "iconv-lite": "^0.6.2", - "long": "^4.0.0", - "lru-cache": "^6.0.0", - "named-placeholders": "^1.1.2", - "seq-queue": "^0.0.5", - "sqlstring": "^2.3.2" - }, - "engines": { - "node": ">= 8.0" - } - }, - "node_modules/mysql2/node_modules/iconv-lite": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", - "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "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/nanoid": { - "version": "3.1.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz", - "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || >=13.7" - } - }, - "node_modules/negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" - }, - "node_modules/nise": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/nise/-/nise-4.0.4.tgz", - "integrity": "sha512-bTTRUNlemx6deJa+ZyoCUTRvH3liK5+N6VQZ4NIw90AgDXY6iPnsqplNFf6STcj+ePk0H/xqxnP75Lr0J0Fq3A==", - "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/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/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/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "engines": { - "node": ">=0.10.0" - } - }, - "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/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/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.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" - }, - "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.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", - "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", - "engines": { - "node": "*" - } - }, - "node_modules/picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "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.6", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", - "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", - "dependencies": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxyquire": { - "version": "2.1.3", - "resolved": "http://npm.ooapi.com:4873/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.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "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.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "dependencies": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "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.18.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz", - "integrity": "sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA==", - "dependencies": { - "is-core-module": "^2.0.0", - "path-parse": "^1.0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/retry-as-promised": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-3.2.0.tgz", - "integrity": "sha512-CybGs60B7oYU/qSQ6kuaFmRd9sTZ6oXSc0toqePvV74Ac6/IFZSI1ReFQmtCN+uvW1Mtqdwpvt/LGOiCBAY2Mg==", - "dependencies": { - "any-promise": "^1.3.0" - } - }, - "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==" - }, - "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": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", - "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.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/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/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.3.5", - "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.3.5.tgz", - "integrity": "sha512-MiwiPkYSA8NWttRKAXdU9h0TxP6HAc1fl7qZmMO/VQqQOND83G4nZLXd0kWILtAoT9cxtZgFqeb/MPYgEeXwsw==", - "dependencies": { - "debug": "^4.1.1", - "dottie": "^2.0.0", - "inflection": "1.12.0", - "lodash": "^4.17.15", - "moment": "^2.26.0", - "moment-timezone": "^0.5.31", - "retry-as-promised": "^3.2.0", - "semver": "^7.3.2", - "sequelize-pool": "^6.0.0", - "toposort-class": "^1.0.1", - "uuid": "^8.1.0", - "validator": "^10.11.0", - "wkx": "^0.5.0" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependenciesMeta": { - "mariadb": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "pg": { - "optional": true - }, - "pg-hstore": { - "optional": true - }, - "sqlite3": { - "optional": true - }, - "tedious": { - "optional": true - } - } - }, - "node_modules/sequelize-cli": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/sequelize-cli/-/sequelize-cli-6.2.0.tgz", - "integrity": "sha512-6WQ2x91hg30dUn66mXHnzvHATZ4pyI1GHSNbS/TNN/vRR4BLRSLijadeMgC8zqmKDsL0VqzVVopJWfJakuP++Q==", - "dependencies": { - "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.3.0", - "yargs": "^13.1.0" - }, - "bin": { - "sequelize": "lib/sequelize", - "sequelize-cli": "lib/sequelize" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/sequelize-mock": { - "version": "0.10.2", - "resolved": "http://npm.ooapi.com:4873/sequelize-mock/-/sequelize-mock-0.10.2.tgz", - "integrity": "sha1-GdOXHM2utbhkFwwkznkqinHxRL0=", - "dev": true, - "dependencies": { - "bluebird": "^3.4.6", - "inflection": "^1.10.0", - "lodash": "^4.16.4" - } - }, - "node_modules/sequelize-pool": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-6.1.0.tgz", - "integrity": "sha512-4YwEw3ZgK/tY/so+GfnSgXkdwIJJ1I32uZJztIEgZeAO6HMgj64OzySbWLgxj+tXhZCJnzRfkY9gINw8Ft8ZMg==", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/sequelize/node_modules/debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "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.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "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/serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.1" - }, - "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.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" - }, - "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": "9.2.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.0.tgz", - "integrity": "sha512-eSNXz1XMcGEMHw08NJXSyTHIu6qTCOiN8x9ODACmZpNQpr0aXTBXBnI4xTzQzR+TEpOmLiKowGf9flCuKIzsbw==", - "dependencies": { - "@sinonjs/commons": "^1.8.1", - "@sinonjs/fake-timers": "^6.0.1", - "@sinonjs/formatio": "^5.0.1", - "@sinonjs/samsam": "^5.2.0", - "diff": "^4.0.2", - "nise": "^4.0.4", - "supports-color": "^7.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" - } - }, - "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.2", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz", - "integrity": "sha512-vF4ZbYdKS8OnoJAWBmMxCQDkiEBkGQYU7UZPtL8flbDRSNkhaXvRJ279ZtI6M+zDaQovVU4tuRgzK5fVhvFAhg==", - "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.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string_decoder/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/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/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/strip-ansi/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/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==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/superagent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-6.1.0.tgz", - "integrity": "sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg==", - "dependencies": { - "component-emitter": "^1.3.0", - "cookiejar": "^2.1.2", - "debug": "^4.1.1", - "fast-safe-stringify": "^2.0.7", - "form-data": "^3.0.0", - "formidable": "^1.2.2", - "methods": "^1.1.2", - "mime": "^2.4.6", - "qs": "^6.9.4", - "readable-stream": "^3.6.0", - "semver": "^7.3.2" - }, - "engines": { - "node": ">= 7.0.0" - } - }, - "node_modules/superagent/node_modules/debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "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.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/superagent/node_modules/mime": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/superagent/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/superagent/node_modules/qs": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", - "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==", - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/supertest": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-5.0.0.tgz", - "integrity": "sha512-2JAWpPrUOZF4hHH5ZTCN2xjKXvJS3AEwPNXl0HUseHsfcXFvMy9kcsufIHCNAmQ5hlGCvgeAqaR5PBEouN3hlQ==", - "dependencies": { - "methods": "1.1.2", - "superagent": "6.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "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/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/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==", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "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/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/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=" - }, - "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.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", - "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/validator": { - "version": "10.11.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", - "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==", - "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": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "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/wide-align/node_modules/ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "engines": { - "node": ">=4" - } - }, - "node_modules/wide-align/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/wide-align/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/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/workerpool": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.2.tgz", - "integrity": "sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==" - }, - "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/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.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" - }, - "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/yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "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" - } - }, - "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" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser/node_modules/camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs-unparser/node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, "dependencies": { "@sinonjs/commons": { "version": "1.8.1", @@ -3053,16 +581,6 @@ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" }, - "fill-keys": { - "version": "1.0.2", - "resolved": "http://npm.ooapi.com:4873/fill-keys/-/fill-keys-1.0.2.tgz", - "integrity": "sha1-mo+jb06K1jTjv2tPPIiCVRRS6yA=", - "dev": true, - "requires": { - "is-object": "~1.0.1", - "merge-descriptors": "~1.0.0" - } - }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -3304,12 +822,6 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, - "is-object": { - "version": "1.0.2", - "resolved": "http://npm.ooapi.com:4873/is-object/-/is-object-1.0.2.tgz", - "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", - "dev": true - }, "is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -3563,12 +1075,6 @@ } } }, - "module-not-found-error": { - "version": "1.0.1", - "resolved": "http://npm.ooapi.com:4873/module-not-found-error/-/module-not-found-error-1.0.1.tgz", - "integrity": "sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=", - "dev": true - }, "moment": { "version": "2.29.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", @@ -3772,17 +1278,6 @@ "ipaddr.js": "1.9.1" } }, - "proxyquire": { - "version": "2.1.3", - "resolved": "http://npm.ooapi.com:4873/proxyquire/-/proxyquire-2.1.3.tgz", - "integrity": "sha512-BQWfCqYM+QINd+yawJz23tbBM40VIGXOdDw3X344KcclI/gtBbdWF6SlQ4nK/bYhF9d27KYug9WzljHC6B9Ysg==", - "dev": true, - "requires": { - "fill-keys": "^1.0.2", - "module-not-found-error": "^1.0.1", - "resolve": "^1.11.1" - } - }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -3958,17 +1453,6 @@ "yargs": "^13.1.0" } }, - "sequelize-mock": { - "version": "0.10.2", - "resolved": "http://npm.ooapi.com:4873/sequelize-mock/-/sequelize-mock-0.10.2.tgz", - "integrity": "sha1-GdOXHM2utbhkFwwkznkqinHxRL0=", - "dev": true, - "requires": { - "bluebird": "^3.4.6", - "inflection": "^1.10.0", - "lodash": "^4.16.4" - } - }, "sequelize-pool": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-6.1.0.tgz", @@ -4037,6 +1521,16 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, + "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==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -4052,16 +1546,6 @@ } } }, - "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==", - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -4364,4 +1848,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index c25bd055..5f6f1d0f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,6 @@ { - "name": "forum-express-workspace", - "version": "1.0.0", - "description": "", + "name": "forum-express-grading", + "version": "2.0.0", "main": "_helpers.js", "scripts": { "start": "node app.js", @@ -10,26 +9,21 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/v123582/forum-express-workspace.git" + "url": "git+https://github.com/ALPHACamp/forum-express-grading.git" }, - "author": "", - "license": "ISC", - "bugs": { - "url": "https://github.com/v123582/forum-express-workspace/issues" - }, - "homepage": "https://github.com/v123582/forum-express-workspace#readme", "dependencies": { - "chai": "^4.2.0", "express": "^4.17.1", - "mocha": "^8.2.0", "mysql2": "^2.2.5", + "nodemon": "^2.0.12", "sequelize": "^6.3.5", - "sequelize-cli": "^6.2.0", - "sinon": "^9.2.0", - "supertest": "^5.0.0" + "sequelize-cli": "^6.2.0" }, "devDependencies": { + "chai": "^4.2.0", + "mocha": "^8.2.0", "proxyquire": "^2.1.3", - "sequelize-mock": "^0.10.2" + "sequelize-mock": "^0.10.2", + "sinon": "^9.2.0", + "supertest": "^5.0.0" } -} +} \ No newline at end of file From dcd7e2a7843d0cdd113525a6063ab2cb06e44509 Mon Sep 17 00:00:00 2001 From: Serena Tseng <51045074+Carrot7712@users.noreply.github.com> Date: Sat, 6 Nov 2021 00:14:56 +0800 Subject: [PATCH 094/198] Update .travis.yml --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9635c20c..fdf50b79 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,9 +11,9 @@ env: # 在 install 前執行的指令 before_install: - - mysql -e 'CREATE DATABASE IF NOT EXISTS forum_test default character set utf8mb4 collate utf8mb4_unicode_ci;' + - mysql -e 'CREATE DATABASE IF NOT EXISTS forum default character set utf8mb4 collate utf8mb4_unicode_ci;' - npm install sequelize-cli -g # 在執行測試之前的指令 before_script: - - sequelize db:migrate \ No newline at end of file + - sequelize db:migrate From 5306afeadcf978db99951078c49ebba801078ff3 Mon Sep 17 00:00:00 2001 From: Serena Tseng <51045074+Carrot7712@users.noreply.github.com> Date: Sat, 6 Nov 2021 00:29:51 +0800 Subject: [PATCH 095/198] Update .travis.yml --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index fdf50b79..f08c1b5e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - "10" + - "14" services: - mysql @@ -16,4 +16,5 @@ before_install: # 在執行測試之前的指令 before_script: - - sequelize db:migrate + - sequelize db:migrate + - sequelize db:seed:all From 29dd8254ee1454a11186f04363fe81af086df7a0 Mon Sep 17 00:00:00 2001 From: Serena Tseng <51045074+Carrot7712@users.noreply.github.com> Date: Mon, 8 Nov 2021 22:37:30 +0800 Subject: [PATCH 096/198] Update config.json --- config/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.json b/config/config.json index 2b8be8e7..93cd6cd8 100644 --- a/config/config.json +++ b/config/config.json @@ -15,7 +15,7 @@ }, "travis": { "username": "travis", - "database": "forum_test", + "database": "forum", "host": "127.0.0.1", "dialect": "mysql", "logging": false From 20cab63518224dcd6e0a973fe29e573b79295680 Mon Sep 17 00:00:00 2001 From: Serena Tseng <51045074+Carrot7712@users.noreply.github.com> Date: Mon, 8 Nov 2021 22:37:42 +0800 Subject: [PATCH 097/198] Update config.json --- config/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.json b/config/config.json index 2b8be8e7..93cd6cd8 100644 --- a/config/config.json +++ b/config/config.json @@ -15,7 +15,7 @@ }, "travis": { "username": "travis", - "database": "forum_test", + "database": "forum", "host": "127.0.0.1", "dialect": "mysql", "logging": false From 844162a4a6c7ab05f36e7a0d4cd32f7cad92bce9 Mon Sep 17 00:00:00 2001 From: Serena Tseng <51045074+Carrot7712@users.noreply.github.com> Date: Mon, 8 Nov 2021 22:37:54 +0800 Subject: [PATCH 098/198] Update config.json --- config/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.json b/config/config.json index 2b8be8e7..93cd6cd8 100644 --- a/config/config.json +++ b/config/config.json @@ -15,7 +15,7 @@ }, "travis": { "username": "travis", - "database": "forum_test", + "database": "forum", "host": "127.0.0.1", "dialect": "mysql", "logging": false From 32a6374cdb77fdb1754c7adee2e7a6ffe8e5082f Mon Sep 17 00:00:00 2001 From: Serena Tseng <51045074+Carrot7712@users.noreply.github.com> Date: Mon, 8 Nov 2021 22:38:06 +0800 Subject: [PATCH 099/198] Update config.json --- config/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.json b/config/config.json index 2b8be8e7..93cd6cd8 100644 --- a/config/config.json +++ b/config/config.json @@ -15,7 +15,7 @@ }, "travis": { "username": "travis", - "database": "forum_test", + "database": "forum", "host": "127.0.0.1", "dialect": "mysql", "logging": false From e55d7f7c8f70cae1b5b9335055d300243f7bbde5 Mon Sep 17 00:00:00 2001 From: Serena Tseng <51045074+Carrot7712@users.noreply.github.com> Date: Mon, 8 Nov 2021 22:38:19 +0800 Subject: [PATCH 100/198] Update config.json --- config/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.json b/config/config.json index 2b8be8e7..93cd6cd8 100644 --- a/config/config.json +++ b/config/config.json @@ -15,7 +15,7 @@ }, "travis": { "username": "travis", - "database": "forum_test", + "database": "forum", "host": "127.0.0.1", "dialect": "mysql", "logging": false From 01fb2c510f2f9a0d1ba455760cd79231f7cbd849 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Mon, 8 Nov 2021 22:02:00 +0000 Subject: [PATCH 101/198] Initialize project --- app.js | 18 +++++++----- config/config.json | 17 +++++------- models/index.js | 44 ++++++++++++++---------------- package-lock.json | 68 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 5 files changed, 107 insertions(+), 41 deletions(-) diff --git a/app.js b/app.js index f70e64d6..8912b457 100644 --- a/app.js +++ b/app.js @@ -1,11 +1,15 @@ -const express = require('express') -const app = express() -const port = 3000 +const express = require("express"); +const handlebars = require("express-handlebars"); +const app = express(); +const port = 3000; + +app.engine("handlebars", handlebars()); +app.set("view engine", "handlebars"); app.listen(port, () => { - console.log(`Example app listening at http://localhost:${port}`) -}) + console.log(`Example app listening at http://localhost:${port}`); +}); -require('./routes')(app) +require("./routes")(app); -module.exports = app +module.exports = app; diff --git a/config/config.json b/config/config.json index 56636a1c..8af49d1b 100644 --- a/config/config.json +++ b/config/config.json @@ -6,22 +6,19 @@ "host": "127.0.0.1", "dialect": "mysql" }, - "test": { + "production": { "username": "root", - "password": "password", - "database": "forum_test", + "password": null, + "database": "database_production", "host": "127.0.0.1", - "dialect": "mysql", - "logging": false - }, - "production": { - "use_env_variable": "CLEARDB_SILVER_URL" + "dialect": "mysql" }, "travis": { "username": "travis", - "database": "forum_test", + "password": "forum_test", + "database": "database_test", "host": "127.0.0.1", "dialect": "mysql", "logging": false } -} \ No newline at end of file +} diff --git a/models/index.js b/models/index.js index 66860356..33f09e77 100644 --- a/models/index.js +++ b/models/index.js @@ -1,41 +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); } -// 動態引入其他 models 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 = sequelize['import'](path.join(__dirname, file)) - db[model.name] = model - }) + const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes); + db[model.name] = model; + }); -// 設定 Models 之間的關聯 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/package-lock.json b/package-lock.json index 6cdb461e..f8fe7fd8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -561,6 +561,36 @@ "vary": "~1.1.2" } }, + "express-handlebars": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/express-handlebars/-/express-handlebars-5.3.4.tgz", + "integrity": "sha512-b36grfkbXZItLLQV6cwzA20o6Zg4Eckke3PjHF4EGQIQLGs5IPMjpAxepdGb45A/bECekXzA9STzNqvEgrdRPw==", + "requires": { + "glob": "^7.2.0", + "graceful-fs": "^4.2.7", + "handlebars": "^4.7.7" + }, + "dependencies": { + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "requires": { + "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" + } + }, + "graceful-fs": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" + } + } + }, "ext": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", @@ -716,6 +746,18 @@ "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==" }, + "handlebars": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + } + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -985,6 +1027,11 @@ "brace-expansion": "^1.1.7" } }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -1152,6 +1199,11 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, "next-tick": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", @@ -1506,6 +1558,11 @@ "supports-color": "^7.1.0" } }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -1672,6 +1729,12 @@ "mime-types": "~2.1.24" } }, + "uglify-js": { + "version": "3.14.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.14.3.tgz", + "integrity": "sha512-mic3aOdiq01DuSVx0TseaEzMIVqebMZ0Z3vaeDhFEh9bsc24hV1TFvN74reA2vs08D0ZWfNjAcJ3UbVLaBss+g==", + "optional": true + }, "umzug": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/umzug/-/umzug-2.3.0.tgz", @@ -1768,6 +1831,11 @@ "@types/node": "*" } }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" + }, "workerpool": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.2.tgz", diff --git a/package.json b/package.json index dd99a5f1..1ac12175 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "dependencies": { "chai": "^4.2.0", "express": "^4.17.1", + "express-handlebars": "^5.3.4", "mocha": "^8.2.0", "mysql2": "^2.2.5", "sequelize": "^6.3.5", From c48093b947a5f19b3b7b003c8ee0e1b4d5f5f356 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Mon, 8 Nov 2021 22:14:46 +0000 Subject: [PATCH 102/198] set up restaurant entry point --- app.js | 2 +- controllers/restControllers.js | 7 +++++++ routes/index.js | 13 +++++++----- views/layouts/main.handlebars | 38 ++++++++++++++++++++++++++++++++++ views/restaurants.handlebars | 3 +++ 5 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 controllers/restControllers.js create mode 100644 views/layouts/main.handlebars create mode 100644 views/restaurants.handlebars diff --git a/app.js b/app.js index 8912b457..ef7a5499 100644 --- a/app.js +++ b/app.js @@ -3,7 +3,7 @@ const handlebars = require("express-handlebars"); const app = express(); const port = 3000; -app.engine("handlebars", handlebars()); +app.engine("handlebars", handlebars({ defaultLayout: "main" })); app.set("view engine", "handlebars"); app.listen(port, () => { diff --git a/controllers/restControllers.js b/controllers/restControllers.js new file mode 100644 index 00000000..1c77467b --- /dev/null +++ b/controllers/restControllers.js @@ -0,0 +1,7 @@ +let restControllers = { + getRestaurants: (req, res) => { + return res.render("restaurants"); + }, +}; + +module.exports = restControllers; diff --git a/routes/index.js b/routes/index.js index fc55e842..050e95db 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,6 +1,9 @@ -module.exports = (app) => { +const restControllers = require("../controllers/restControllers"); - app.get('/', (req, res) => { - res.send('Hello World!') - }) -} +module.exports = (app) => { + //如果使用者訪問首頁,就導向 /restaurants 的頁面 + app.get("/", (req, res) => { + res.redirect("restaurants"); + }); + app.get("/restaurants", restControllers.getRestaurants); +}; diff --git a/views/layouts/main.handlebars b/views/layouts/main.handlebars new file mode 100644 index 00000000..1d1a1ab8 --- /dev/null +++ b/views/layouts/main.handlebars @@ -0,0 +1,38 @@ + + + + + 餐廳評論網 + + + + +
+
+
+ {{{body}}} +
+
+
+ + + + + \ No newline at end of file diff --git a/views/restaurants.handlebars b/views/restaurants.handlebars new file mode 100644 index 00000000..97b6e76e --- /dev/null +++ b/views/restaurants.handlebars @@ -0,0 +1,3 @@ +
+ restaurants +
\ No newline at end of file From 5afa2dd2bbcc1a499455ff403cf52f3d1603a38a Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Mon, 8 Nov 2021 22:24:45 +0000 Subject: [PATCH 103/198] set up admin/restaurant --- _helpers.js | 12 ++++----- app.js | 20 +++++++-------- controllers/adminControllers.js | 7 ++++++ controllers/restControllers.js | 10 ++++---- models/index.js | 40 +++++++++++++++--------------- routes/index.js | 19 ++++++++------ tests/index.js | 21 +++++++--------- views/admin/restaurants.handlebars | 3 +++ 8 files changed, 72 insertions(+), 60 deletions(-) create mode 100644 controllers/adminControllers.js create mode 100644 views/admin/restaurants.handlebars diff --git a/_helpers.js b/_helpers.js index 798e5325..bff0b66b 100644 --- a/_helpers.js +++ b/_helpers.js @@ -1,12 +1,12 @@ -function ensureAuthenticated(req) { - return req.isAuthenticated(); +function ensureAuthenticated (req) { + return req.isAuthenticated() } -function getUser(req) { - return req.user; +function getUser (req) { + return req.user } module.exports = { ensureAuthenticated, - getUser, -}; \ No newline at end of file + getUser +} diff --git a/app.js b/app.js index ef7a5499..1b50ae34 100644 --- a/app.js +++ b/app.js @@ -1,15 +1,15 @@ -const express = require("express"); -const handlebars = require("express-handlebars"); -const app = express(); -const port = 3000; +const express = require('express') +const handlebars = require('express-handlebars') +const app = express() +const port = 3000 -app.engine("handlebars", handlebars({ defaultLayout: "main" })); -app.set("view engine", "handlebars"); +app.engine('handlebars', handlebars({ defaultLayout: 'main' })) +app.set('view engine', 'handlebars') app.listen(port, () => { - console.log(`Example app listening at http://localhost:${port}`); -}); + console.log(`Example app listening at http://localhost:${port}`) +}) -require("./routes")(app); +require('./routes')(app) -module.exports = app; +module.exports = app diff --git a/controllers/adminControllers.js b/controllers/adminControllers.js new file mode 100644 index 00000000..7a2fc15d --- /dev/null +++ b/controllers/adminControllers.js @@ -0,0 +1,7 @@ +const adminController = { + getRestaurants: (req, res) => { + return res.render('admin/restaurants') + } +} + +module.exports = adminController diff --git a/controllers/restControllers.js b/controllers/restControllers.js index 1c77467b..aedaa885 100644 --- a/controllers/restControllers.js +++ b/controllers/restControllers.js @@ -1,7 +1,7 @@ -let restControllers = { +const restControllers = { getRestaurants: (req, res) => { - return res.render("restaurants"); - }, -}; + return res.render('restaurants') + } +} -module.exports = restControllers; +module.exports = restControllers diff --git a/models/index.js b/models/index.js index 33f09e77..e92dffac 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/routes/index.js b/routes/index.js index 050e95db..c9fcdd22 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,9 +1,14 @@ -const restControllers = require("../controllers/restControllers"); +const restControllers = require('../controllers/restControllers') +const adminController = require('../controllers/adminControllers') module.exports = (app) => { - //如果使用者訪問首頁,就導向 /restaurants 的頁面 - app.get("/", (req, res) => { - res.redirect("restaurants"); - }); - app.get("/restaurants", restControllers.getRestaurants); -}; + // 如果使用者訪問首頁,就導向 /restaurants 的頁面 + app.get('/', (req, res) => { + res.redirect('restaurants') + }) + app.get('/restaurants', restControllers.getRestaurants) + app.get('/admin', (req, res) => { + res.redirect('/admin/restaurants') + }) + app.get('/admin/restaurants', adminController.getRestaurants) +} diff --git a/tests/index.js b/tests/index.js index b1d92960..57535e46 100644 --- a/tests/index.js +++ b/tests/index.js @@ -2,17 +2,14 @@ const request = require('supertest') const app = require('../app') -describe('# 測試環境初始化', function() { - +describe('# 測試環境初始化', function () { context('# First Test Case', () => { - - it(" GET /admin/users ", (done) => { - request(app) - .get('/') - .end(function(err, res) { - done() - }); - }); - + it(' GET /admin/users ', (done) => { + request(app) + .get('/') + .end(function (err, res) { + done() + }) + }) }) -}) \ No newline at end of file +}) diff --git a/views/admin/restaurants.handlebars b/views/admin/restaurants.handlebars new file mode 100644 index 00000000..c1e24fc9 --- /dev/null +++ b/views/admin/restaurants.handlebars @@ -0,0 +1,3 @@ +
+ admin restaurants +
\ No newline at end of file From 3c5d69ca4f21170f1eaa21b5d39ac7d718c8f661 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Mon, 8 Nov 2021 23:58:30 +0000 Subject: [PATCH 104/198] set up user model --- app.js | 21 ++++++++------- migrations/20211108233938-create-user.js | 33 ++++++++++++++++++++++++ models/user.js | 25 ++++++++++++++++++ 3 files changed, 69 insertions(+), 10 deletions(-) create mode 100644 migrations/20211108233938-create-user.js create mode 100644 models/user.js diff --git a/app.js b/app.js index 1b50ae34..347d38ae 100644 --- a/app.js +++ b/app.js @@ -1,15 +1,16 @@ -const express = require('express') -const handlebars = require('express-handlebars') -const app = express() -const port = 3000 +const express = require("express"); +const handlebars = require("express-handlebars"); +const db = require("./models"); +const app = express(); +const port = 3000; -app.engine('handlebars', handlebars({ defaultLayout: 'main' })) -app.set('view engine', 'handlebars') +app.engine("handlebars", handlebars({ defaultLayout: "main" })); +app.set("view engine", "handlebars"); app.listen(port, () => { - console.log(`Example app listening at http://localhost:${port}`) -}) + console.log(`Example app listening at http://localhost:${port}`); +}); -require('./routes')(app) +require("./routes")(app); -module.exports = app +module.exports = app; diff --git a/migrations/20211108233938-create-user.js b/migrations/20211108233938-create-user.js new file mode 100644 index 00000000..58a162b2 --- /dev/null +++ b/migrations/20211108233938-create-user.js @@ -0,0 +1,33 @@ +'use strict'; +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.createTable('Users', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + name: { + type: Sequelize.STRING + }, + email: { + type: Sequelize.STRING + }, + password: { + type: Sequelize.STRING + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }); + }, + down: async (queryInterface, Sequelize) => { + await queryInterface.dropTable('Users'); + } +}; \ No newline at end of file diff --git a/models/user.js b/models/user.js new file mode 100644 index 00000000..90c48b43 --- /dev/null +++ b/models/user.js @@ -0,0 +1,25 @@ +'use strict'; +const { + Model +} = require('sequelize'); +module.exports = (sequelize, DataTypes) => { + class User extends Model { + /** + * Helper method for defining associations. + * This method is not a part of Sequelize lifecycle. + * The `models/index` file will call this method automatically. + */ + static associate(models) { + // define association here + } + }; + User.init({ + name: DataTypes.STRING, + email: DataTypes.STRING, + password: DataTypes.STRING + }, { + sequelize, + modelName: 'User', + }); + return User; +}; \ No newline at end of file From 144f5e39281e5e96d6daf244c7c39f90d0e87ec4 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Mon, 8 Nov 2021 23:59:33 +0000 Subject: [PATCH 105/198] run standard fix --- app.js | 22 +++++++++++----------- migrations/20211108233938-create-user.js | 8 ++++---- models/user.js | 14 +++++++------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/app.js b/app.js index 347d38ae..aaec9161 100644 --- a/app.js +++ b/app.js @@ -1,16 +1,16 @@ -const express = require("express"); -const handlebars = require("express-handlebars"); -const db = require("./models"); -const app = express(); -const port = 3000; +const express = require('express') +const handlebars = require('express-handlebars') +const db = require('./models') +const app = express() +const port = 3000 -app.engine("handlebars", handlebars({ defaultLayout: "main" })); -app.set("view engine", "handlebars"); +app.engine('handlebars', handlebars({ defaultLayout: 'main' })) +app.set('view engine', 'handlebars') app.listen(port, () => { - console.log(`Example app listening at http://localhost:${port}`); -}); + console.log(`Example app listening at http://localhost:${port}`) +}) -require("./routes")(app); +require('./routes')(app) -module.exports = app; +module.exports = app diff --git a/migrations/20211108233938-create-user.js b/migrations/20211108233938-create-user.js index 58a162b2..969feb29 100644 --- a/migrations/20211108233938-create-user.js +++ b/migrations/20211108233938-create-user.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' module.exports = { up: async (queryInterface, Sequelize) => { await queryInterface.createTable('Users', { @@ -25,9 +25,9 @@ module.exports = { allowNull: false, type: Sequelize.DATE } - }); + }) }, down: async (queryInterface, Sequelize) => { - await queryInterface.dropTable('Users'); + await queryInterface.dropTable('Users') } -}; \ No newline at end of file +} diff --git a/models/user.js b/models/user.js index 90c48b43..705122cb 100644 --- a/models/user.js +++ b/models/user.js @@ -1,7 +1,7 @@ -'use strict'; +'use strict' const { Model -} = require('sequelize'); +} = require('sequelize') module.exports = (sequelize, DataTypes) => { class User extends Model { /** @@ -9,7 +9,7 @@ module.exports = (sequelize, DataTypes) => { * This method is not a part of Sequelize lifecycle. * The `models/index` file will call this method automatically. */ - static associate(models) { + static associate (models) { // define association here } }; @@ -19,7 +19,7 @@ module.exports = (sequelize, DataTypes) => { password: DataTypes.STRING }, { sequelize, - modelName: 'User', - }); - return User; -}; \ No newline at end of file + modelName: 'User' + }) + return User +} From ea3b1dd5eaec60764ee4afcbbf6d6e907b07b58f Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Tue, 9 Nov 2021 00:23:16 +0000 Subject: [PATCH 106/198] set up sign up page --- app.js | 25 +++++---- ...adminControllers.js => adminController.js} | 0 .../{restControllers.js => restController.js} | 0 controllers/userController.js | 25 +++++++++ package-lock.json | 5 ++ package.json | 2 + routes/index.js | 27 ++++++---- views/signup.handlebars | 53 +++++++++++++++++++ 8 files changed, 115 insertions(+), 22 deletions(-) rename controllers/{adminControllers.js => adminController.js} (100%) rename controllers/{restControllers.js => restController.js} (100%) create mode 100644 controllers/userController.js create mode 100644 views/signup.handlebars diff --git a/app.js b/app.js index aaec9161..a1d6bd2d 100644 --- a/app.js +++ b/app.js @@ -1,16 +1,19 @@ -const express = require('express') -const handlebars = require('express-handlebars') -const db = require('./models') -const app = express() -const port = 3000 +const express = require("express"); +const handlebars = require("express-handlebars"); +const db = require("./models"); +const bodyParser = require("body-parser"); +const app = express(); +const port = 3000; -app.engine('handlebars', handlebars({ defaultLayout: 'main' })) -app.set('view engine', 'handlebars') +app.engine("handlebars", handlebars({ defaultLayout: "main" })); +app.set("view engine", "handlebars"); +app.use(express.urlencoded({ extended: true })); app.listen(port, () => { - console.log(`Example app listening at http://localhost:${port}`) -}) + db.sequelize.sync(); + console.log(`Example app listening at http://localhost:${port}`); +}); -require('./routes')(app) +require("./routes")(app); -module.exports = app +module.exports = app; diff --git a/controllers/adminControllers.js b/controllers/adminController.js similarity index 100% rename from controllers/adminControllers.js rename to controllers/adminController.js diff --git a/controllers/restControllers.js b/controllers/restController.js similarity index 100% rename from controllers/restControllers.js rename to controllers/restController.js diff --git a/controllers/userController.js b/controllers/userController.js new file mode 100644 index 00000000..cc9d5996 --- /dev/null +++ b/controllers/userController.js @@ -0,0 +1,25 @@ +const bcrypt = require("bcrypt-nodejs"); +const db = require("../models"); +const User = db.User; + +let userController = { + signUpPage: (req, res) => { + return res.render("signup"); + }, + + signUp: (req, res) => { + User.create({ + name: req.body.name, + email: req.body.email, + password: bcrypt.hashSync( + req.body.password, + bcrypt.genSaltSync(10), + null + ), + }).then((user) => { + return res.redirect("/signin"); + }); + }, +}; + +module.exports = userController; diff --git a/package-lock.json b/package-lock.json index f8fe7fd8..2721e219 100644 --- a/package-lock.json +++ b/package-lock.json @@ -128,6 +128,11 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, + "bcrypt-nodejs": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/bcrypt-nodejs/-/bcrypt-nodejs-0.0.3.tgz", + "integrity": "sha1-xgkX8m3CNWYVZsaBBhwwPCsohCs=" + }, "binary-extensions": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", diff --git a/package.json b/package.json index 1ac12175..9bb10a3c 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,8 @@ }, "homepage": "https://github.com/v123582/forum-express-workspace#readme", "dependencies": { + "bcrypt-nodejs": "0.0.3", + "body-parser": "^1.19.0", "chai": "^4.2.0", "express": "^4.17.1", "express-handlebars": "^5.3.4", diff --git a/routes/index.js b/routes/index.js index c9fcdd22..c56f12d6 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,14 +1,19 @@ -const restControllers = require('../controllers/restControllers') -const adminController = require('../controllers/adminControllers') +const restControllers = require("../controllers/restController"); +const adminController = require("../controllers/adminController"); +const userController = require("../controllers/userController"); module.exports = (app) => { // 如果使用者訪問首頁,就導向 /restaurants 的頁面 - app.get('/', (req, res) => { - res.redirect('restaurants') - }) - app.get('/restaurants', restControllers.getRestaurants) - app.get('/admin', (req, res) => { - res.redirect('/admin/restaurants') - }) - app.get('/admin/restaurants', adminController.getRestaurants) -} + app.get("/", (req, res) => { + res.redirect("restaurants"); + }); + app.get("/restaurants", restControllers.getRestaurants); + + app.get("/admin", (req, res) => { + res.redirect("/admin/restaurants"); + }); + app.get("/admin/restaurants", adminController.getRestaurants); + + app.get("/signup", userController.signUpPage); + app.post("/signup", userController.signUp); +}; diff --git a/views/signup.handlebars b/views/signup.handlebars new file mode 100644 index 00000000..bf505570 --- /dev/null +++ b/views/signup.handlebars @@ -0,0 +1,53 @@ + \ No newline at end of file From 3ed95c529ceb55ae544bb454984c743d1cc949f1 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Tue, 9 Nov 2021 00:50:07 +0000 Subject: [PATCH 107/198] set up flash message for sign up page --- app.js | 39 ++++++++++++++++--------- controllers/userController.js | 48 ++++++++++++++++++------------ package-lock.json | 55 +++++++++++++++++++++++++++++++++++ package.json | 2 ++ routes/index.js | 28 +++++++++--------- views/layouts/main.handlebars | 19 ++++++++++++ 6 files changed, 144 insertions(+), 47 deletions(-) diff --git a/app.js b/app.js index a1d6bd2d..54524d4a 100644 --- a/app.js +++ b/app.js @@ -1,19 +1,30 @@ -const express = require("express"); -const handlebars = require("express-handlebars"); -const db = require("./models"); -const bodyParser = require("body-parser"); -const app = express(); -const port = 3000; +const express = require('express') +const handlebars = require('express-handlebars') +const db = require('./models') +const flash = require('connect-flash') +const session = require('express-session') -app.engine("handlebars", handlebars({ defaultLayout: "main" })); -app.set("view engine", "handlebars"); -app.use(express.urlencoded({ extended: true })); +const app = express() +const port = 3000 + +app.engine('handlebars', handlebars({ defaultLayout: 'main' })) +app.set('view engine', 'handlebars') +app.use(express.urlencoded({ extended: true })) +app.use(session({ secret: 'secret', resave: false, saveUninitialized: false })) +app.use(flash()) + +app.use((req, res, next) => { + res.locals.success_messages = req.flash('success_messages') + res.locals.error_messages = req.flash('error_messages') + next() +}) app.listen(port, () => { - db.sequelize.sync(); - console.log(`Example app listening at http://localhost:${port}`); -}); + // what is this for? + db.sequelize.sync() + console.log(`Example app listening at http://localhost:${port}`) +}) -require("./routes")(app); +require('./routes')(app) -module.exports = app; +module.exports = app diff --git a/controllers/userController.js b/controllers/userController.js index cc9d5996..bb813044 100644 --- a/controllers/userController.js +++ b/controllers/userController.js @@ -1,25 +1,35 @@ -const bcrypt = require("bcrypt-nodejs"); -const db = require("../models"); -const User = db.User; +const bcrypt = require('bcrypt-nodejs') +const db = require('../models') +const User = db.User -let userController = { +const userController = { signUpPage: (req, res) => { - return res.render("signup"); + return res.render('signup') }, signUp: (req, res) => { - User.create({ - name: req.body.name, - email: req.body.email, - password: bcrypt.hashSync( - req.body.password, - bcrypt.genSaltSync(10), - null - ), - }).then((user) => { - return res.redirect("/signin"); - }); - }, -}; + const { name, email, password, passwordCheck } = req.body + if (password !== passwordCheck) { + req.flash('error_messages', '兩次密碼輸入不同') + return res.redirect('/signup') + } else { + User.findOne({ where: { email } }).then((user) => { + if (user) { + req.flash('error_messages', '信箱已經註冊過') + return res.redirect('/signup') + } else { + User.create({ + name, + email, + password: bcrypt.hashSync(password, bcrypt.genSaltSync(10), null) + }).then((user) => { + req.flash('success_messages', '成功註冊帳號') + return res.redirect('/signin') + }) + } + }) + } + } +} -module.exports = userController; +module.exports = userController diff --git a/package-lock.json b/package-lock.json index 2721e219..9a122971 100644 --- a/package-lock.json +++ b/package-lock.json @@ -325,6 +325,11 @@ "proto-list": "~1.2.1" } }, + "connect-flash": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/connect-flash/-/connect-flash-0.1.1.tgz", + "integrity": "sha1-2GMPJtlaf4UfmVax6MxnMvO2qjA=" + }, "content-disposition": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", @@ -596,6 +601,38 @@ } } }, + "express-session": { + "version": "1.17.2", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.2.tgz", + "integrity": "sha512-mPcYcLA0lvh7D4Oqr5aNJFMtBMKPLl++OKKxkHzZ0U0oDq1rpKBnkR5f5vCHR26VeArlTOEF9td4x5IjICksRQ==", + "requires": { + "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" + }, + "dependencies": { + "cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "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==" + } + } + }, "ext": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", @@ -1257,6 +1294,11 @@ "ee-first": "1.1.1" } }, + "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==" + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1345,6 +1387,11 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -1740,6 +1787,14 @@ "integrity": "sha512-mic3aOdiq01DuSVx0TseaEzMIVqebMZ0Z3vaeDhFEh9bsc24hV1TFvN74reA2vs08D0ZWfNjAcJ3UbVLaBss+g==", "optional": true }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "requires": { + "random-bytes": "~1.0.0" + } + }, "umzug": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/umzug/-/umzug-2.3.0.tgz", diff --git a/package.json b/package.json index 9bb10a3c..b8a75dcd 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,10 @@ "bcrypt-nodejs": "0.0.3", "body-parser": "^1.19.0", "chai": "^4.2.0", + "connect-flash": "^0.1.1", "express": "^4.17.1", "express-handlebars": "^5.3.4", + "express-session": "^1.17.2", "mocha": "^8.2.0", "mysql2": "^2.2.5", "sequelize": "^6.3.5", diff --git a/routes/index.js b/routes/index.js index c56f12d6..1272961f 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,19 +1,19 @@ -const restControllers = require("../controllers/restController"); -const adminController = require("../controllers/adminController"); -const userController = require("../controllers/userController"); +const restControllers = require('../controllers/restController') +const adminController = require('../controllers/adminController') +const userController = require('../controllers/userController') module.exports = (app) => { // 如果使用者訪問首頁,就導向 /restaurants 的頁面 - app.get("/", (req, res) => { - res.redirect("restaurants"); - }); - app.get("/restaurants", restControllers.getRestaurants); + app.get('/', (req, res) => { + res.redirect('restaurants') + }) + app.get('/restaurants', restControllers.getRestaurants) - app.get("/admin", (req, res) => { - res.redirect("/admin/restaurants"); - }); - app.get("/admin/restaurants", adminController.getRestaurants); + app.get('/admin', (req, res) => { + res.redirect('/admin/restaurants') + }) + app.get('/admin/restaurants', adminController.getRestaurants) - app.get("/signup", userController.signUpPage); - app.post("/signup", userController.signUp); -}; + app.get('/signup', userController.signUpPage) + app.post('/signup', userController.signUp) +} diff --git a/views/layouts/main.handlebars b/views/layouts/main.handlebars index 1d1a1ab8..556777c8 100644 --- a/views/layouts/main.handlebars +++ b/views/layouts/main.handlebars @@ -15,6 +15,25 @@
+ {{#if success_messages}} + + {{/if}} + {{#if error_messages}} + + {{/if}} + {{{body}}}
From 9e6a03e32c49b3a5744be8df8c33a6638af040c8 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Tue, 9 Nov 2021 22:46:38 +0000 Subject: [PATCH 108/198] cimplete signin feature with passport authentication --- app.js | 45 +++++++++++++++++--------------- config/passport.js | 7 ++--- controllers/userController.js | 49 +++++++++++++++++++++++------------ package-lock.json | 32 +++++++++++++++++++++++ package.json | 3 +++ routes/index.js | 40 ++++++++++++++++++---------- views/signin.handlebars | 32 +++++++++++++++++++++++ 7 files changed, 153 insertions(+), 55 deletions(-) create mode 100644 views/signin.handlebars diff --git a/app.js b/app.js index 54524d4a..cc22da62 100644 --- a/app.js +++ b/app.js @@ -1,30 +1,33 @@ -const express = require('express') -const handlebars = require('express-handlebars') -const db = require('./models') -const flash = require('connect-flash') -const session = require('express-session') +const express = require("express"); +const handlebars = require("express-handlebars"); +const db = require("./models"); +const flash = require("connect-flash"); +const session = require("express-session"); +const passport = require("./config/passport"); -const app = express() -const port = 3000 +const app = express(); +const port = 3000; -app.engine('handlebars', handlebars({ defaultLayout: 'main' })) -app.set('view engine', 'handlebars') -app.use(express.urlencoded({ extended: true })) -app.use(session({ secret: 'secret', resave: false, saveUninitialized: false })) -app.use(flash()) +app.engine("handlebars", handlebars({ defaultLayout: "main" })); +app.set("view engine", "handlebars"); +app.use(express.urlencoded({ extended: true })); +app.use(session({ secret: "secret", resave: false, saveUninitialized: false })); +app.use(passport.initialize()); +app.use(passport.session()); +app.use(flash()); app.use((req, res, next) => { - res.locals.success_messages = req.flash('success_messages') - res.locals.error_messages = req.flash('error_messages') - next() -}) + res.locals.success_messages = req.flash("success_messages"); + res.locals.error_messages = req.flash("error_messages"); + next(); +}); app.listen(port, () => { // what is this for? - db.sequelize.sync() - console.log(`Example app listening at http://localhost:${port}`) -}) + db.sequelize.sync(); + console.log(`Example app listening at http://localhost:${port}`); +}); -require('./routes')(app) +require("./routes")(app); -module.exports = app +module.exports = app; diff --git a/config/passport.js b/config/passport.js index 33065abb..b0079082 100644 --- a/config/passport.js +++ b/config/passport.js @@ -26,21 +26,22 @@ passport.use( return cb( null, false, - req.flash("error_messages", "帳號或密碼輸入錯誤!") + req.flash("error_messages", "帳號或密碼輸入錯誤") ); + user = user.toJSON(); return cb(null, user); }); } ) ); -// serialize and deserialize user +// serialize and deserialize user. 只存id不存物件以便省空間 passport.serializeUser((user, cb) => { cb(null, user.id); }); passport.deserializeUser((id, cb) => { User.findByPk(id).then((user) => { - user = user.toJSON(); // 此處與影片示範不同 + user = user.toJSON(); return cb(null, user); }); }); diff --git a/controllers/userController.js b/controllers/userController.js index bb813044..d60c7546 100644 --- a/controllers/userController.js +++ b/controllers/userController.js @@ -1,35 +1,50 @@ -const bcrypt = require('bcrypt-nodejs') -const db = require('../models') -const User = db.User +const bcrypt = require("bcrypt-nodejs"); +const db = require("../models"); +const User = db.User; const userController = { signUpPage: (req, res) => { - return res.render('signup') + return res.render("signup"); }, signUp: (req, res) => { - const { name, email, password, passwordCheck } = req.body + const { name, email, password, passwordCheck } = req.body; if (password !== passwordCheck) { - req.flash('error_messages', '兩次密碼輸入不同') - return res.redirect('/signup') + req.flash("error_messages", "兩次密碼輸入不同"); + return res.redirect("/signup"); } else { User.findOne({ where: { email } }).then((user) => { if (user) { - req.flash('error_messages', '信箱已經註冊過') - return res.redirect('/signup') + req.flash("error_messages", "信箱已經註冊過"); + return res.redirect("/signup"); } else { User.create({ name, email, - password: bcrypt.hashSync(password, bcrypt.genSaltSync(10), null) + password: bcrypt.hashSync(password, bcrypt.genSaltSync(10), null), }).then((user) => { - req.flash('success_messages', '成功註冊帳號') - return res.redirect('/signin') - }) + req.flash("success_messages", "成功註冊帳號"); + return res.redirect("/signin"); + }); } - }) + }); } - } -} + }, + + signInPage: (req, res) => { + return res.render("signin"); + }, + + signIn: (req, res) => { + req.flash("success_messages", "成功登入!"); + res.redirect("/restaurants"); + }, + + logout: (req, res) => { + req.flash("success_message", "登出成功!"); + req.logout(); + res.redirect("/signin"); + }, +}; -module.exports = userController +module.exports = userController; diff --git a/package-lock.json b/package-lock.json index ecf8a655..8ce60712 100644 --- a/package-lock.json +++ b/package-lock.json @@ -195,6 +195,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": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" + }, "binary-extensions": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", @@ -1856,6 +1861,28 @@ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, + "passport": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.5.0.tgz", + "integrity": "sha512-ln+ue5YaNDS+fes6O5PCzXKSseY5u8MYhX9H5Co4s+HfYI5oqvnHKoOORLYDUPh+8tHvrxugF2GFcUA1Q1Gqfg==", + "requires": { + "passport-strategy": "1.x.x", + "pause": "0.0.1" + } + }, + "passport-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", + "integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=", + "requires": { + "passport-strategy": "1.x.x" + } + }, + "passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=" + }, "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -1881,6 +1908,11 @@ "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=" }, + "pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" + }, "picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", diff --git a/package.json b/package.json index a3a42bd9..e858a526 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ }, "dependencies": { "bcrypt-nodejs": "0.0.3", + "bcryptjs": "^2.4.3", "body-parser": "^1.19.0", "chai": "^4.2.0", "connect-flash": "^0.1.1", @@ -22,6 +23,8 @@ "mocha": "^8.2.0", "mysql2": "^2.2.5", "nodemon": "^2.0.12", + "passport": "^0.5.0", + "passport-local": "^1.0.0", "sequelize": "^6.3.5", "sequelize-cli": "^6.2.0" }, diff --git a/routes/index.js b/routes/index.js index 1272961f..17d9cc78 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,19 +1,31 @@ -const restControllers = require('../controllers/restController') -const adminController = require('../controllers/adminController') -const userController = require('../controllers/userController') +const restControllers = require("../controllers/restController"); +const adminController = require("../controllers/adminController"); +const userController = require("../controllers/userController"); +const passport = require("passport"); module.exports = (app) => { // 如果使用者訪問首頁,就導向 /restaurants 的頁面 - app.get('/', (req, res) => { - res.redirect('restaurants') - }) - app.get('/restaurants', restControllers.getRestaurants) + app.get("/", (req, res) => { + res.redirect("restaurants"); + }); + app.get("/restaurants", restControllers.getRestaurants); - app.get('/admin', (req, res) => { - res.redirect('/admin/restaurants') - }) - app.get('/admin/restaurants', adminController.getRestaurants) + app.get("/admin", (req, res) => { + res.redirect("/admin/restaurants"); + }); + app.get("/admin/restaurants", adminController.getRestaurants); - app.get('/signup', userController.signUpPage) - app.post('/signup', userController.signUp) -} + app.get("/signup", userController.signUpPage); + app.post("/signup", userController.signUp); + + app.get("/signin", userController.signInPage); + app.post( + "/signin", + passport.authenticate("local", { + failureRedirect: "/signin", + failureFlash: true, + }), + userController.signIn + ); + app.get("logout", userController.logout); +}; diff --git a/views/signin.handlebars b/views/signin.handlebars new file mode 100644 index 00000000..51a0a411 --- /dev/null +++ b/views/signin.handlebars @@ -0,0 +1,32 @@ + \ No newline at end of file From e21a0740b7250308487cc8202f3fa1fab64b6edb Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Tue, 9 Nov 2021 22:50:43 +0000 Subject: [PATCH 109/198] add Navbar --- app.js | 1 + views/layouts/main.handlebars | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/app.js b/app.js index cc22da62..36016523 100644 --- a/app.js +++ b/app.js @@ -19,6 +19,7 @@ app.use(flash()); app.use((req, res, next) => { res.locals.success_messages = req.flash("success_messages"); res.locals.error_messages = req.flash("error_messages"); + res.locals.user = req.user; next(); }); diff --git a/views/layouts/main.handlebars b/views/layouts/main.handlebars index 556777c8..3c1e9829 100644 --- a/views/layouts/main.handlebars +++ b/views/layouts/main.handlebars @@ -12,6 +12,34 @@ /> + {{! Navbar }} + +
From 6815e99141a07cef7aafe4b3e02669576dff43fc Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Wed, 10 Nov 2021 00:00:05 +0000 Subject: [PATCH 110/198] setup admin check --- app.js | 50 +++++++------- config/passport.js | 52 ++++++++------- controllers/userController.js | 47 ++++++------- .../20211109225159-add-isadmin-to-users.js | 14 ++++ models/user.js | 26 ++++---- routes/index.js | 66 ++++++++++++------- 6 files changed, 148 insertions(+), 107 deletions(-) create mode 100644 migrations/20211109225159-add-isadmin-to-users.js diff --git a/app.js b/app.js index 36016523..8ae4870d 100644 --- a/app.js +++ b/app.js @@ -1,34 +1,34 @@ -const express = require("express"); -const handlebars = require("express-handlebars"); -const db = require("./models"); -const flash = require("connect-flash"); -const session = require("express-session"); -const passport = require("./config/passport"); +const express = require('express') +const handlebars = require('express-handlebars') +const db = require('./models') +const flash = require('connect-flash') +const session = require('express-session') +const passport = require('./config/passport') -const app = express(); -const port = 3000; +const app = express() +const port = 3000 -app.engine("handlebars", handlebars({ defaultLayout: "main" })); -app.set("view engine", "handlebars"); -app.use(express.urlencoded({ extended: true })); -app.use(session({ secret: "secret", resave: false, saveUninitialized: false })); -app.use(passport.initialize()); -app.use(passport.session()); -app.use(flash()); +app.engine('handlebars', handlebars({ defaultLayout: 'main' })) +app.set('view engine', 'handlebars') +app.use(express.urlencoded({ extended: true })) +app.use(session({ secret: 'secret', resave: false, saveUninitialized: false })) +app.use(passport.initialize()) +app.use(passport.session()) +app.use(flash()) app.use((req, res, next) => { - res.locals.success_messages = req.flash("success_messages"); - res.locals.error_messages = req.flash("error_messages"); - res.locals.user = req.user; - next(); -}); + res.locals.success_messages = req.flash('success_messages') + res.locals.error_messages = req.flash('error_messages') + res.locals.user = req.user + next() +}) app.listen(port, () => { // what is this for? - db.sequelize.sync(); - console.log(`Example app listening at http://localhost:${port}`); -}); + db.sequelize.sync() + console.log(`Example app listening at http://localhost:${port}`) +}) -require("./routes")(app); +require('./routes')(app) -module.exports = app; +module.exports = app diff --git a/config/passport.js b/config/passport.js index b0079082..51cae323 100644 --- a/config/passport.js +++ b/config/passport.js @@ -1,49 +1,51 @@ -const passport = require("passport"); -const LocalStrategy = require("passport-local"); -const bcrypt = require("bcryptjs"); -const db = require("../models"); -const User = db.User; +const passport = require('passport') +const LocalStrategy = require('passport-local') +const bcrypt = require('bcryptjs') +const db = require('../models') +const User = db.User // setup passport strategy passport.use( new LocalStrategy( // customize user field { - usernameField: "email", - passwordField: "password", - passReqToCallback: true, + usernameField: 'email', + passwordField: 'password', + passReqToCallback: true }, // authenticate user (req, username, password, cb) => { User.findOne({ where: { email: username } }).then((user) => { - if (!user) + if (!user) { return cb( null, false, - req.flash("error_messages", "帳號或密碼輸入錯誤") - ); - if (!bcrypt.compareSync(password, user.password)) + req.flash('error_messages', '帳號或密碼輸入錯誤') + ) + } + if (!bcrypt.compareSync(password, user.password)) { return cb( null, false, - req.flash("error_messages", "帳號或密碼輸入錯誤") - ); - user = user.toJSON(); - return cb(null, user); - }); + req.flash('error_messages', '帳號或密碼輸入錯誤') + ) + } + user = user.toJSON() + return cb(null, user) + }) } ) -); +) // serialize and deserialize user. 只存id不存物件以便省空間 passport.serializeUser((user, cb) => { - cb(null, user.id); -}); + cb(null, user.id) +}) passport.deserializeUser((id, cb) => { User.findByPk(id).then((user) => { - user = user.toJSON(); - return cb(null, user); - }); -}); + user = user.toJSON() + return cb(null, user) + }) +}) -module.exports = passport; +module.exports = passport diff --git a/controllers/userController.js b/controllers/userController.js index d60c7546..a2e3298a 100644 --- a/controllers/userController.js +++ b/controllers/userController.js @@ -1,50 +1,51 @@ -const bcrypt = require("bcrypt-nodejs"); -const db = require("../models"); -const User = db.User; +const bcrypt = require('bcrypt-nodejs') +const db = require('../models') +const User = db.User const userController = { signUpPage: (req, res) => { - return res.render("signup"); + return res.render('signup') }, signUp: (req, res) => { - const { name, email, password, passwordCheck } = req.body; + const { name, email, password, passwordCheck } = req.body if (password !== passwordCheck) { - req.flash("error_messages", "兩次密碼輸入不同"); - return res.redirect("/signup"); + req.flash('error_messages', '兩次密碼輸入不同') + return res.redirect('/signup') } else { User.findOne({ where: { email } }).then((user) => { + console.log(user) if (user) { - req.flash("error_messages", "信箱已經註冊過"); - return res.redirect("/signup"); + req.flash('error_messages', '信箱已經註冊過') + return res.redirect('/signup') } else { User.create({ name, email, - password: bcrypt.hashSync(password, bcrypt.genSaltSync(10), null), + password: bcrypt.hashSync(password, bcrypt.genSaltSync(10), null) }).then((user) => { - req.flash("success_messages", "成功註冊帳號"); - return res.redirect("/signin"); - }); + req.flash('success_messages', '成功註冊帳號') + return res.redirect('/signin') + }) } - }); + }) } }, signInPage: (req, res) => { - return res.render("signin"); + return res.render('signin') }, signIn: (req, res) => { - req.flash("success_messages", "成功登入!"); - res.redirect("/restaurants"); + req.flash('success_messages', '成功登入!') + res.redirect('/restaurants') }, logout: (req, res) => { - req.flash("success_message", "登出成功!"); - req.logout(); - res.redirect("/signin"); - }, -}; + req.flash('success_message', '登出成功!') + req.logout() + res.redirect('/signin') + } +} -module.exports = userController; +module.exports = userController diff --git a/migrations/20211109225159-add-isadmin-to-users.js b/migrations/20211109225159-add-isadmin-to-users.js new file mode 100644 index 00000000..e48068c1 --- /dev/null +++ b/migrations/20211109225159-add-isadmin-to-users.js @@ -0,0 +1,14 @@ +'use strict' + +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.addColumn('Users', 'isAdmin', { + type: Sequelize.BOOLEAN, + defaultValue: false + }) + }, + + down: async (queryInterface, Sequelize) => { + await queryInterface.removeColumn('Users', 'isAdmin') + } +} diff --git a/models/user.js b/models/user.js index 705122cb..476a1c22 100644 --- a/models/user.js +++ b/models/user.js @@ -1,7 +1,5 @@ 'use strict' -const { - Model -} = require('sequelize') +const { Model } = require('sequelize') module.exports = (sequelize, DataTypes) => { class User extends Model { /** @@ -12,14 +10,18 @@ module.exports = (sequelize, DataTypes) => { static associate (models) { // define association here } - }; - User.init({ - name: DataTypes.STRING, - email: DataTypes.STRING, - password: DataTypes.STRING - }, { - sequelize, - modelName: 'User' - }) + } + User.init( + { + name: DataTypes.STRING, + email: DataTypes.STRING, + password: DataTypes.STRING, + isAdmin: DataTypes.BOOLEAN + }, + { + sequelize, + modelName: 'User' + } + ) return User } diff --git a/routes/index.js b/routes/index.js index 17d9cc78..61082acc 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,31 +1,53 @@ -const restControllers = require("../controllers/restController"); -const adminController = require("../controllers/adminController"); -const userController = require("../controllers/userController"); -const passport = require("passport"); +const restControllers = require('../controllers/restController') +const adminController = require('../controllers/adminController') +const userController = require('../controllers/userController') +const passport = require('passport') module.exports = (app) => { + const authenticated = (req, res, next) => { + if (req.isAuthenticated()) { + return next() + } + res.redirect('/singin') + } + + const authenticatedAdmin = (req, res, next) => { + if (req.isAuthenticated()) { + console.log(req.user) + if (req.user.isAdmin) { + return next() + } + return res.redirect('/') + } + res.redirect('/signin') + } + // 如果使用者訪問首頁,就導向 /restaurants 的頁面 - app.get("/", (req, res) => { - res.redirect("restaurants"); - }); - app.get("/restaurants", restControllers.getRestaurants); + app.get('/', authenticated, (req, res) => { + res.redirect('restaurants') + }) + app.get('/restaurants', restControllers.getRestaurants) - app.get("/admin", (req, res) => { - res.redirect("/admin/restaurants"); - }); - app.get("/admin/restaurants", adminController.getRestaurants); + app.get('/admin', authenticatedAdmin, (req, res) => { + res.redirect('/admin/restaurants') + }) + app.get( + '/admin/restaurants', + authenticatedAdmin, + adminController.getRestaurants + ) - app.get("/signup", userController.signUpPage); - app.post("/signup", userController.signUp); + app.get('/signup', userController.signUpPage) + app.post('/signup', userController.signUp) - app.get("/signin", userController.signInPage); + app.get('/signin', userController.signInPage) app.post( - "/signin", - passport.authenticate("local", { - failureRedirect: "/signin", - failureFlash: true, + '/signin', + passport.authenticate('local', { + failureRedirect: '/signin', + failureFlash: true }), userController.signIn - ); - app.get("logout", userController.logout); -}; + ) + app.get('logout', userController.logout) +} From e74effb78d7fe4da53bcd0a4a9c9796e84f63357 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Wed, 10 Nov 2021 00:10:30 +0000 Subject: [PATCH 111/198] set up restaurants admin page --- controllers/adminController.js | 13 +++-- .../20211110000301-create-restaurant.js | 39 +++++++++++++++ models/restaurant.js | 27 +++++++++++ views/admin/restaurants.handlebars | 48 +++++++++++++++++-- 4 files changed, 120 insertions(+), 7 deletions(-) create mode 100644 migrations/20211110000301-create-restaurant.js create mode 100644 models/restaurant.js diff --git a/controllers/adminController.js b/controllers/adminController.js index 7a2fc15d..a6ec81d7 100644 --- a/controllers/adminController.js +++ b/controllers/adminController.js @@ -1,7 +1,12 @@ +const db = require("../models"); +const Restaurant = db.Restaurant; + const adminController = { getRestaurants: (req, res) => { - return res.render('admin/restaurants') - } -} + return Restaurant.findAll({ raw: true, nest: true }).then((restaurants) => { + return res.render("admin/restaurants", { restaurants: restaurants }); + }); + }, +}; -module.exports = adminController +module.exports = adminController; diff --git a/migrations/20211110000301-create-restaurant.js b/migrations/20211110000301-create-restaurant.js new file mode 100644 index 00000000..9ae279d5 --- /dev/null +++ b/migrations/20211110000301-create-restaurant.js @@ -0,0 +1,39 @@ +'use strict'; +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.createTable('Restaurants', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + name: { + type: Sequelize.STRING + }, + tel: { + type: Sequelize.STRING + }, + address: { + type: Sequelize.STRING + }, + opening_hours: { + type: Sequelize.STRING + }, + description: { + type: Sequelize.TEXT + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }); + }, + down: async (queryInterface, Sequelize) => { + await queryInterface.dropTable('Restaurants'); + } +}; \ No newline at end of file diff --git a/models/restaurant.js b/models/restaurant.js new file mode 100644 index 00000000..b9db8597 --- /dev/null +++ b/models/restaurant.js @@ -0,0 +1,27 @@ +'use strict'; +const { + Model +} = require('sequelize'); +module.exports = (sequelize, DataTypes) => { + class Restaurant extends Model { + /** + * Helper method for defining associations. + * This method is not a part of Sequelize lifecycle. + * The `models/index` file will call this method automatically. + */ + static associate(models) { + // define association here + } + }; + Restaurant.init({ + name: DataTypes.STRING, + tel: DataTypes.STRING, + address: DataTypes.STRING, + opening_hours: DataTypes.STRING, + description: DataTypes.TEXT + }, { + sequelize, + modelName: 'Restaurant', + }); + return Restaurant; +}; \ No newline at end of file diff --git a/views/admin/restaurants.handlebars b/views/admin/restaurants.handlebars index c1e24fc9..fecfcebd 100644 --- a/views/admin/restaurants.handlebars +++ b/views/admin/restaurants.handlebars @@ -1,3 +1,45 @@ -
- admin restaurants -
\ No newline at end of file +

餐廳後台

+ + + + + +
+
+ + + + + + + + + + + {{#each restaurants}} + + + + + + {{/each}} + +
#Name#
{{this.id}}{{this.name}} + + +
+ +
+
\ No newline at end of file From 8aae24dfca520826b8bcf2b1e0089189a9818065 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Wed, 10 Nov 2021 23:12:54 +0000 Subject: [PATCH 112/198] view one restaurant --- controllers/adminController.js | 32 ++++++++ .../20211110000301-create-restaurant.js | 8 +- models/restaurant.js | 14 ++-- routes/index.js | 77 +++++++++++-------- views/admin/create.handlebars | 57 ++++++++++++++ views/admin/restaurant.handlebars | 26 +++++++ 6 files changed, 172 insertions(+), 42 deletions(-) create mode 100644 views/admin/create.handlebars create mode 100644 views/admin/restaurant.handlebars diff --git a/controllers/adminController.js b/controllers/adminController.js index a6ec81d7..d2f1e691 100644 --- a/controllers/adminController.js +++ b/controllers/adminController.js @@ -7,6 +7,38 @@ const adminController = { return res.render("admin/restaurants", { restaurants: restaurants }); }); }, + + createRestaurants: (req, res) => { + return res.render("admin/create"); + }, + + postRestaurants: (req, res) => { + if (!req.body.name) { + req.flash("error_messages", "name didn't exist"); + return res.redirect("back"); + } + const { name, tel, address, opening_hours, description } = req.body; + return Restaurant.create({ + name, + tel, + address, + opening_hours, + description, + }).then((restaurant) => { + req.flash("success_messages", "restaurant was successfully created"); + return res.redirect("/admin/restaurants"); + }); + }, + + getRestaurant: (req, res) => { + return Restaurant.findByPk(req.params.id, { raw: true, nest: true }).then( + (restaurant) => { + return res.render("admin/restaurant", { + restaurant: restaurant, + }); + } + ); + }, }; module.exports = adminController; diff --git a/migrations/20211110000301-create-restaurant.js b/migrations/20211110000301-create-restaurant.js index 9ae279d5..0315330e 100644 --- a/migrations/20211110000301-create-restaurant.js +++ b/migrations/20211110000301-create-restaurant.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' module.exports = { up: async (queryInterface, Sequelize) => { await queryInterface.createTable('Restaurants', { @@ -31,9 +31,9 @@ module.exports = { allowNull: false, type: Sequelize.DATE } - }); + }) }, down: async (queryInterface, Sequelize) => { - await queryInterface.dropTable('Restaurants'); + await queryInterface.dropTable('Restaurants') } -}; \ No newline at end of file +} diff --git a/models/restaurant.js b/models/restaurant.js index b9db8597..193acdab 100644 --- a/models/restaurant.js +++ b/models/restaurant.js @@ -1,7 +1,7 @@ -'use strict'; +'use strict' const { Model -} = require('sequelize'); +} = require('sequelize') module.exports = (sequelize, DataTypes) => { class Restaurant extends Model { /** @@ -9,7 +9,7 @@ module.exports = (sequelize, DataTypes) => { * This method is not a part of Sequelize lifecycle. * The `models/index` file will call this method automatically. */ - static associate(models) { + static associate (models) { // define association here } }; @@ -21,7 +21,7 @@ module.exports = (sequelize, DataTypes) => { description: DataTypes.TEXT }, { sequelize, - modelName: 'Restaurant', - }); - return Restaurant; -}; \ No newline at end of file + modelName: 'Restaurant' + }) + return Restaurant +} diff --git a/routes/index.js b/routes/index.js index 61082acc..2522ed7f 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,53 +1,68 @@ -const restControllers = require('../controllers/restController') -const adminController = require('../controllers/adminController') -const userController = require('../controllers/userController') -const passport = require('passport') +const restControllers = require("../controllers/restController"); +const adminController = require("../controllers/adminController"); +const userController = require("../controllers/userController"); +const passport = require("passport"); module.exports = (app) => { const authenticated = (req, res, next) => { if (req.isAuthenticated()) { - return next() + return next(); } - res.redirect('/singin') - } + res.redirect("/signin"); + }; const authenticatedAdmin = (req, res, next) => { if (req.isAuthenticated()) { - console.log(req.user) + console.log(req.user); if (req.user.isAdmin) { - return next() + return next(); } - return res.redirect('/') + return res.redirect("/"); } - res.redirect('/signin') - } + res.redirect("/signin"); + }; // 如果使用者訪問首頁,就導向 /restaurants 的頁面 - app.get('/', authenticated, (req, res) => { - res.redirect('restaurants') - }) - app.get('/restaurants', restControllers.getRestaurants) + app.get("/", authenticated, (req, res) => { + res.redirect("restaurants"); + }); + app.get("/restaurants", restControllers.getRestaurants); - app.get('/admin', authenticatedAdmin, (req, res) => { - res.redirect('/admin/restaurants') - }) + app.get("/admin", authenticatedAdmin, (req, res) => { + res.redirect("/admin/restaurants"); + }); app.get( - '/admin/restaurants', + "/admin/restaurants", authenticatedAdmin, adminController.getRestaurants - ) + ); - app.get('/signup', userController.signUpPage) - app.post('/signup', userController.signUp) + app.get("/signup", userController.signUpPage); + app.post("/signup", userController.signUp); - app.get('/signin', userController.signInPage) + app.get("/signin", userController.signInPage); app.post( - '/signin', - passport.authenticate('local', { - failureRedirect: '/signin', - failureFlash: true + "/signin", + passport.authenticate("local", { + failureRedirect: "/signin", + failureFlash: true, }), userController.signIn - ) - app.get('logout', userController.logout) -} + ); + app.get("/logout", userController.logout); + app.get( + "/admin/restaurants/create", + authenticatedAdmin, + adminController.createRestaurants + ); + app.post( + "/admin/restaurants", + authenticatedAdmin, + adminController.postRestaurants + ); + app.get( + "/admin/restaurants/:id", + authenticatedAdmin, + adminController.getRestaurant + ); +}; diff --git a/views/admin/create.handlebars b/views/admin/create.handlebars new file mode 100644 index 00000000..43587ef7 --- /dev/null +++ b/views/admin/create.handlebars @@ -0,0 +1,57 @@ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
\ No newline at end of file diff --git a/views/admin/restaurant.handlebars b/views/admin/restaurant.handlebars new file mode 100644 index 00000000..12b6cf30 --- /dev/null +++ b/views/admin/restaurant.handlebars @@ -0,0 +1,26 @@ +
+
+

{{restaurant.name}}

+
+ +
+
+
+
    +
  • Opening Hour: {{restaurant.opening_hours}}
  • +
  • Tel: {{restaurant.tel}}
  • +
  • Address: {{restaurant.address}}
  • +
+
+
+
+

{{restaurant.description}}

+
+
+
+ +
+ +
+
+回上一頁 \ No newline at end of file From 66aca39d52affaa33cf307665e8de91b3d93b82d Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Wed, 10 Nov 2021 23:40:18 +0000 Subject: [PATCH 113/198] edit a restaurant --- app.js | 52 +++++++++++---------- controllers/adminController.js | 35 +++++++++++++- package-lock.json | 21 +++++++++ package.json | 1 + routes/index.js | 10 ++++ views/admin/create.handlebars | 84 ++++++++++++---------------------- 6 files changed, 121 insertions(+), 82 deletions(-) diff --git a/app.js b/app.js index 8ae4870d..4a6cd741 100644 --- a/app.js +++ b/app.js @@ -1,34 +1,36 @@ -const express = require('express') -const handlebars = require('express-handlebars') -const db = require('./models') -const flash = require('connect-flash') -const session = require('express-session') -const passport = require('./config/passport') +const express = require("express"); +const handlebars = require("express-handlebars"); +const db = require("./models"); +const flash = require("connect-flash"); +const session = require("express-session"); +const passport = require("./config/passport"); +const methodOverride = require("method-override"); -const app = express() -const port = 3000 +const app = express(); +const port = 3000; -app.engine('handlebars', handlebars({ defaultLayout: 'main' })) -app.set('view engine', 'handlebars') -app.use(express.urlencoded({ extended: true })) -app.use(session({ secret: 'secret', resave: false, saveUninitialized: false })) -app.use(passport.initialize()) -app.use(passport.session()) -app.use(flash()) +app.engine("handlebars", handlebars({ defaultLayout: "main" })); +app.set("view engine", "handlebars"); +app.use(express.urlencoded({ extended: true })); +app.use(session({ secret: "secret", resave: false, saveUninitialized: false })); +app.use(passport.initialize()); +app.use(passport.session()); +app.use(flash()); +app.use(methodOverride("_method")); app.use((req, res, next) => { - res.locals.success_messages = req.flash('success_messages') - res.locals.error_messages = req.flash('error_messages') - res.locals.user = req.user - next() -}) + res.locals.success_messages = req.flash("success_messages"); + res.locals.error_messages = req.flash("error_messages"); + res.locals.user = req.user; + next(); +}); app.listen(port, () => { // what is this for? - db.sequelize.sync() - console.log(`Example app listening at http://localhost:${port}`) -}) + db.sequelize.sync(); + console.log(`Example app listening at http://localhost:${port}`); +}); -require('./routes')(app) +require("./routes")(app); -module.exports = app +module.exports = app; diff --git a/controllers/adminController.js b/controllers/adminController.js index d2f1e691..ad40dbeb 100644 --- a/controllers/adminController.js +++ b/controllers/adminController.js @@ -34,11 +34,44 @@ const adminController = { return Restaurant.findByPk(req.params.id, { raw: true, nest: true }).then( (restaurant) => { return res.render("admin/restaurant", { - restaurant: restaurant, + restaurant, }); } ); }, + + editRestaurant: (req, res) => { + return Restaurant.findByPk(req.params.id, { raw: true, nest: true }).then( + (restaurant) => { + return res.render("admin/create", { restaurant }); + } + ); + }, + + putRestaurant: (req, res) => { + if (!req.body.name) { + req.flash("error_message", "name didnt exit"); + return res.redirect("back"); + } + return Restaurant.findByPk(req.params.id).then((restaurant) => { + console.log(restaurant); + restaurant + .update({ + name: req.body.name, + tel: req.body.tel, + address: req.body.address, + opening_hours: req.body.opening_hours, + description: req.body.description, + }) + .then((restaurant) => { + req.flash( + "success_messages", + "restaurant was successfully to update" + ); + res.redirect("/admin/restaurants"); + }); + }); + }, }; module.exports = adminController; diff --git a/package-lock.json b/package-lock.json index 8ce60712..69dfda61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1475,6 +1475,27 @@ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" }, + "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==", + "requires": { + "debug": "3.1.0", + "methods": "~1.1.2", + "parseurl": "~1.3.2", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", diff --git a/package.json b/package.json index e858a526..33143fbb 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "express": "^4.17.1", "express-handlebars": "^5.3.4", "express-session": "^1.17.2", + "method-override": "^3.0.0", "mocha": "^8.2.0", "mysql2": "^2.2.5", "nodemon": "^2.0.12", diff --git a/routes/index.js b/routes/index.js index 2522ed7f..c58c0027 100644 --- a/routes/index.js +++ b/routes/index.js @@ -65,4 +65,14 @@ module.exports = (app) => { authenticatedAdmin, adminController.getRestaurant ); + app.get( + "/admin/restaurants/:id/edit", + authenticatedAdmin, + adminController.editRestaurant + ); + app.put( + "/admin/restaurants/:id", + authenticatedAdmin, + adminController.putRestaurant + ); }; diff --git a/views/admin/create.handlebars b/views/admin/create.handlebars index 43587ef7..e19a4762 100644 --- a/views/admin/create.handlebars +++ b/views/admin/create.handlebars @@ -1,57 +1,29 @@ -
-
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- - +{{#if restaurant}} + +{{else}} + +{{/if}} +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
\ No newline at end of file From 8d7626c1167672f3d6709b52f647349aeab147c0 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Wed, 10 Nov 2021 23:46:11 +0000 Subject: [PATCH 114/198] delete a restaurant --- controllers/adminController.js | 9 ++++++++- routes/index.js | 5 +++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/controllers/adminController.js b/controllers/adminController.js index ad40dbeb..42f2f86a 100644 --- a/controllers/adminController.js +++ b/controllers/adminController.js @@ -54,7 +54,6 @@ const adminController = { return res.redirect("back"); } return Restaurant.findByPk(req.params.id).then((restaurant) => { - console.log(restaurant); restaurant .update({ name: req.body.name, @@ -72,6 +71,14 @@ const adminController = { }); }); }, + + deleteRestaurant: (req, res) => { + return Restaurant.findByPk(req.params.id).then((restaurant) => { + restaurant.destroy().then((restaurant) => { + res.redirect("/admin/restaurants"); + }); + }); + }, }; module.exports = adminController; diff --git a/routes/index.js b/routes/index.js index c58c0027..a34eee6f 100644 --- a/routes/index.js +++ b/routes/index.js @@ -75,4 +75,9 @@ module.exports = (app) => { authenticatedAdmin, adminController.putRestaurant ); + app.delete( + "/admin/restaurants/:id", + authenticatedAdmin, + adminController.deleteRestaurant + ); }; From bc1dff52b6a67aeb1f88c7c9c985bd0ac2dc54ba Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Wed, 10 Nov 2021 23:46:52 +0000 Subject: [PATCH 115/198] correct styling --- app.js | 54 ++++++++++----------- controllers/adminController.js | 66 +++++++++++++------------- routes/index.js | 86 +++++++++++++++++----------------- 3 files changed, 103 insertions(+), 103 deletions(-) diff --git a/app.js b/app.js index 4a6cd741..66086aae 100644 --- a/app.js +++ b/app.js @@ -1,36 +1,36 @@ -const express = require("express"); -const handlebars = require("express-handlebars"); -const db = require("./models"); -const flash = require("connect-flash"); -const session = require("express-session"); -const passport = require("./config/passport"); -const methodOverride = require("method-override"); +const express = require('express') +const handlebars = require('express-handlebars') +const db = require('./models') +const flash = require('connect-flash') +const session = require('express-session') +const passport = require('./config/passport') +const methodOverride = require('method-override') -const app = express(); -const port = 3000; +const app = express() +const port = 3000 -app.engine("handlebars", handlebars({ defaultLayout: "main" })); -app.set("view engine", "handlebars"); -app.use(express.urlencoded({ extended: true })); -app.use(session({ secret: "secret", resave: false, saveUninitialized: false })); -app.use(passport.initialize()); -app.use(passport.session()); -app.use(flash()); -app.use(methodOverride("_method")); +app.engine('handlebars', handlebars({ defaultLayout: 'main' })) +app.set('view engine', 'handlebars') +app.use(express.urlencoded({ extended: true })) +app.use(session({ secret: 'secret', resave: false, saveUninitialized: false })) +app.use(passport.initialize()) +app.use(passport.session()) +app.use(flash()) +app.use(methodOverride('_method')) app.use((req, res, next) => { - res.locals.success_messages = req.flash("success_messages"); - res.locals.error_messages = req.flash("error_messages"); - res.locals.user = req.user; - next(); -}); + res.locals.success_messages = req.flash('success_messages') + res.locals.error_messages = req.flash('error_messages') + res.locals.user = req.user + next() +}) app.listen(port, () => { // what is this for? - db.sequelize.sync(); - console.log(`Example app listening at http://localhost:${port}`); -}); + db.sequelize.sync() + console.log(`Example app listening at http://localhost:${port}`) +}) -require("./routes")(app); +require('./routes')(app) -module.exports = app; +module.exports = app diff --git a/controllers/adminController.js b/controllers/adminController.js index 42f2f86a..d3dda2a9 100644 --- a/controllers/adminController.js +++ b/controllers/adminController.js @@ -1,57 +1,57 @@ -const db = require("../models"); -const Restaurant = db.Restaurant; +const db = require('../models') +const Restaurant = db.Restaurant const adminController = { getRestaurants: (req, res) => { return Restaurant.findAll({ raw: true, nest: true }).then((restaurants) => { - return res.render("admin/restaurants", { restaurants: restaurants }); - }); + return res.render('admin/restaurants', { restaurants: restaurants }) + }) }, createRestaurants: (req, res) => { - return res.render("admin/create"); + return res.render('admin/create') }, postRestaurants: (req, res) => { if (!req.body.name) { - req.flash("error_messages", "name didn't exist"); - return res.redirect("back"); + req.flash('error_messages', "name didn't exist") + return res.redirect('back') } - const { name, tel, address, opening_hours, description } = req.body; + const { name, tel, address, opening_hours, description } = req.body return Restaurant.create({ name, tel, address, opening_hours, - description, + description }).then((restaurant) => { - req.flash("success_messages", "restaurant was successfully created"); - return res.redirect("/admin/restaurants"); - }); + req.flash('success_messages', 'restaurant was successfully created') + return res.redirect('/admin/restaurants') + }) }, getRestaurant: (req, res) => { return Restaurant.findByPk(req.params.id, { raw: true, nest: true }).then( (restaurant) => { - return res.render("admin/restaurant", { - restaurant, - }); + return res.render('admin/restaurant', { + restaurant + }) } - ); + ) }, editRestaurant: (req, res) => { return Restaurant.findByPk(req.params.id, { raw: true, nest: true }).then( (restaurant) => { - return res.render("admin/create", { restaurant }); + return res.render('admin/create', { restaurant }) } - ); + ) }, putRestaurant: (req, res) => { if (!req.body.name) { - req.flash("error_message", "name didnt exit"); - return res.redirect("back"); + req.flash('error_message', 'name didnt exit') + return res.redirect('back') } return Restaurant.findByPk(req.params.id).then((restaurant) => { restaurant @@ -60,25 +60,25 @@ const adminController = { tel: req.body.tel, address: req.body.address, opening_hours: req.body.opening_hours, - description: req.body.description, + description: req.body.description }) .then((restaurant) => { req.flash( - "success_messages", - "restaurant was successfully to update" - ); - res.redirect("/admin/restaurants"); - }); - }); + 'success_messages', + 'restaurant was successfully to update' + ) + res.redirect('/admin/restaurants') + }) + }) }, deleteRestaurant: (req, res) => { return Restaurant.findByPk(req.params.id).then((restaurant) => { restaurant.destroy().then((restaurant) => { - res.redirect("/admin/restaurants"); - }); - }); - }, -}; + res.redirect('/admin/restaurants') + }) + }) + } +} -module.exports = adminController; +module.exports = adminController diff --git a/routes/index.js b/routes/index.js index a34eee6f..72e4bb06 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,83 +1,83 @@ -const restControllers = require("../controllers/restController"); -const adminController = require("../controllers/adminController"); -const userController = require("../controllers/userController"); -const passport = require("passport"); +const restControllers = require('../controllers/restController') +const adminController = require('../controllers/adminController') +const userController = require('../controllers/userController') +const passport = require('passport') module.exports = (app) => { const authenticated = (req, res, next) => { if (req.isAuthenticated()) { - return next(); + return next() } - res.redirect("/signin"); - }; + res.redirect('/signin') + } const authenticatedAdmin = (req, res, next) => { if (req.isAuthenticated()) { - console.log(req.user); + console.log(req.user) if (req.user.isAdmin) { - return next(); + return next() } - return res.redirect("/"); + return res.redirect('/') } - res.redirect("/signin"); - }; + res.redirect('/signin') + } // 如果使用者訪問首頁,就導向 /restaurants 的頁面 - app.get("/", authenticated, (req, res) => { - res.redirect("restaurants"); - }); - app.get("/restaurants", restControllers.getRestaurants); + app.get('/', authenticated, (req, res) => { + res.redirect('restaurants') + }) + app.get('/restaurants', restControllers.getRestaurants) - app.get("/admin", authenticatedAdmin, (req, res) => { - res.redirect("/admin/restaurants"); - }); + app.get('/admin', authenticatedAdmin, (req, res) => { + res.redirect('/admin/restaurants') + }) app.get( - "/admin/restaurants", + '/admin/restaurants', authenticatedAdmin, adminController.getRestaurants - ); + ) - app.get("/signup", userController.signUpPage); - app.post("/signup", userController.signUp); + app.get('/signup', userController.signUpPage) + app.post('/signup', userController.signUp) - app.get("/signin", userController.signInPage); + app.get('/signin', userController.signInPage) app.post( - "/signin", - passport.authenticate("local", { - failureRedirect: "/signin", - failureFlash: true, + '/signin', + passport.authenticate('local', { + failureRedirect: '/signin', + failureFlash: true }), userController.signIn - ); - app.get("/logout", userController.logout); + ) + app.get('/logout', userController.logout) app.get( - "/admin/restaurants/create", + '/admin/restaurants/create', authenticatedAdmin, adminController.createRestaurants - ); + ) app.post( - "/admin/restaurants", + '/admin/restaurants', authenticatedAdmin, adminController.postRestaurants - ); + ) app.get( - "/admin/restaurants/:id", + '/admin/restaurants/:id', authenticatedAdmin, adminController.getRestaurant - ); + ) app.get( - "/admin/restaurants/:id/edit", + '/admin/restaurants/:id/edit', authenticatedAdmin, adminController.editRestaurant - ); + ) app.put( - "/admin/restaurants/:id", + '/admin/restaurants/:id', authenticatedAdmin, adminController.putRestaurant - ); + ) app.delete( - "/admin/restaurants/:id", + '/admin/restaurants/:id', authenticatedAdmin, adminController.deleteRestaurant - ); -}; + ) +} From b75685ec7e4de92c0f8c34c76dbbafc9e2056d82 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Wed, 10 Nov 2021 23:49:14 +0000 Subject: [PATCH 116/198] add image column in db --- ...20211110234744-add-image-to-restaurants.js | 12 ++++++ models/restaurant.js | 38 ++++++++++--------- 2 files changed, 32 insertions(+), 18 deletions(-) create mode 100644 migrations/20211110234744-add-image-to-restaurants.js diff --git a/migrations/20211110234744-add-image-to-restaurants.js b/migrations/20211110234744-add-image-to-restaurants.js new file mode 100644 index 00000000..cbd104b9 --- /dev/null +++ b/migrations/20211110234744-add-image-to-restaurants.js @@ -0,0 +1,12 @@ +"use strict"; + +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.addColumn("Restaurants", "image", { + type: Sequelize.STRING, + }); + }, + down: async (queryInterface, Sequelize) => { + await queryInterface.removeColumn("Restaurants", "image"); + }, +}; diff --git a/models/restaurant.js b/models/restaurant.js index 193acdab..ef2746f5 100644 --- a/models/restaurant.js +++ b/models/restaurant.js @@ -1,7 +1,5 @@ -'use strict' -const { - Model -} = require('sequelize') +"use strict"; +const { Model } = require("sequelize"); module.exports = (sequelize, DataTypes) => { class Restaurant extends Model { /** @@ -9,19 +7,23 @@ module.exports = (sequelize, DataTypes) => { * This method is not a part of Sequelize lifecycle. * The `models/index` file will call this method automatically. */ - static associate (models) { + static associate(models) { // define association here } - }; - Restaurant.init({ - name: DataTypes.STRING, - tel: DataTypes.STRING, - address: DataTypes.STRING, - opening_hours: DataTypes.STRING, - description: DataTypes.TEXT - }, { - sequelize, - modelName: 'Restaurant' - }) - return Restaurant -} + } + Restaurant.init( + { + name: DataTypes.STRING, + tel: DataTypes.STRING, + address: DataTypes.STRING, + opening_hours: DataTypes.STRING, + description: DataTypes.TEXT, + image: DataTypes.STRING, + }, + { + sequelize, + modelName: "Restaurant", + } + ); + return Restaurant; +}; From 3391bb435f4cbad53d86e7f1c8cc6ff00000a7f1 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Thu, 11 Nov 2021 00:29:20 +0000 Subject: [PATCH 117/198] upload image for restaurant --- .gitignore | 1 + app.js | 55 +++++----- controllers/adminController.js | 154 +++++++++++++++++++--------- package-lock.json | 165 +++++++++++++++++++++++++++++- package.json | 1 + routes/index.js | 92 +++++++++-------- views/admin/create.handlebars | 8 +- views/admin/restaurant.handlebars | 6 +- 8 files changed, 356 insertions(+), 126 deletions(-) diff --git a/.gitignore b/.gitignore index 0de2a1f0..1c073410 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,6 @@ node_modules/ temp/ upload/ + .DS_Store .env \ No newline at end of file diff --git a/app.js b/app.js index 66086aae..52c8d91a 100644 --- a/app.js +++ b/app.js @@ -1,36 +1,37 @@ -const express = require('express') -const handlebars = require('express-handlebars') -const db = require('./models') -const flash = require('connect-flash') -const session = require('express-session') -const passport = require('./config/passport') -const methodOverride = require('method-override') +const express = require("express"); +const handlebars = require("express-handlebars"); +const db = require("./models"); +const flash = require("connect-flash"); +const session = require("express-session"); +const passport = require("./config/passport"); +const methodOverride = require("method-override"); -const app = express() -const port = 3000 +const app = express(); +const port = 3000; -app.engine('handlebars', handlebars({ defaultLayout: 'main' })) -app.set('view engine', 'handlebars') -app.use(express.urlencoded({ extended: true })) -app.use(session({ secret: 'secret', resave: false, saveUninitialized: false })) -app.use(passport.initialize()) -app.use(passport.session()) -app.use(flash()) -app.use(methodOverride('_method')) +app.engine("handlebars", handlebars({ defaultLayout: "main" })); +app.set("view engine", "handlebars"); +app.use(express.urlencoded({ extended: true })); +app.use(session({ secret: "secret", resave: false, saveUninitialized: false })); +app.use(passport.initialize()); +app.use(passport.session()); +app.use(flash()); +app.use(methodOverride("_method")); +app.use("/upload", express.static(__dirname + "/upload")); app.use((req, res, next) => { - res.locals.success_messages = req.flash('success_messages') - res.locals.error_messages = req.flash('error_messages') - res.locals.user = req.user - next() -}) + res.locals.success_messages = req.flash("success_messages"); + res.locals.error_messages = req.flash("error_messages"); + res.locals.user = req.user; + next(); +}); app.listen(port, () => { // what is this for? - db.sequelize.sync() - console.log(`Example app listening at http://localhost:${port}`) -}) + db.sequelize.sync(); + console.log(`Example app listening at http://localhost:${port}`); +}); -require('./routes')(app) +require("./routes")(app); -module.exports = app +module.exports = app; diff --git a/controllers/adminController.js b/controllers/adminController.js index d3dda2a9..a07cb0d7 100644 --- a/controllers/adminController.js +++ b/controllers/adminController.js @@ -1,84 +1,140 @@ -const db = require('../models') -const Restaurant = db.Restaurant +const db = require("../models"); +const Restaurant = db.Restaurant; +const fs = require("fs"); const adminController = { getRestaurants: (req, res) => { return Restaurant.findAll({ raw: true, nest: true }).then((restaurants) => { - return res.render('admin/restaurants', { restaurants: restaurants }) - }) + return res.render("admin/restaurants", { restaurants: restaurants }); + }); }, createRestaurants: (req, res) => { - return res.render('admin/create') + return res.render("admin/create"); }, postRestaurants: (req, res) => { if (!req.body.name) { - req.flash('error_messages', "name didn't exist") - return res.redirect('back') + req.flash("error_messages", "name didn't exist"); + return res.redirect("back"); + } + const { name, tel, address, opening_hours, description } = req.body; + const { file } = req; + if (file) { + fs.readFile(file.path, (err, data) => { + if (err) console.log("Error: ", err); + fs.writeFile(`upload/${file.originalname}`, data, () => { + return Restaurant.create({ + name, + tel, + address, + opening_hours, + description, + image: file ? `/upload/${file.originalname}` : null, + }).then((restaurant) => { + req.flash( + "success_messages", + "restaurant was successfully created" + ); + return res.redirect("/admin/restaurants"); + }); + }); + }); + } else { + return Restaurant.create({ + name, + tel, + address, + opening_hours, + description, + image: null, + }).then((restaurant) => { + req.flash("success_messages", "restaurant was successfully created"); + return res.redirect("/admin/restaurants"); + }); } - const { name, tel, address, opening_hours, description } = req.body - return Restaurant.create({ - name, - tel, - address, - opening_hours, - description - }).then((restaurant) => { - req.flash('success_messages', 'restaurant was successfully created') - return res.redirect('/admin/restaurants') - }) }, getRestaurant: (req, res) => { return Restaurant.findByPk(req.params.id, { raw: true, nest: true }).then( (restaurant) => { - return res.render('admin/restaurant', { - restaurant - }) + return res.render("admin/restaurant", { + restaurant, + }); } - ) + ); }, editRestaurant: (req, res) => { return Restaurant.findByPk(req.params.id, { raw: true, nest: true }).then( (restaurant) => { - return res.render('admin/create', { restaurant }) + return res.render("admin/create", { restaurant }); } - ) + ); }, putRestaurant: (req, res) => { + console.warn("put restaurant", req.file); if (!req.body.name) { - req.flash('error_message', 'name didnt exit') - return res.redirect('back') + req.flash("error_message", "name didnt exit"); + return res.redirect("back"); + } + + const { file } = req; + const { name, tel, address, opening_hours, description } = req.body; + if (file) { + fs.readFile(file.path, (err, data) => { + if (err) console.log("Error: ", err); + fs.writeFile(`upload/${file.originalname}`, data, () => { + return Restaurant.findByPk(req.params.id).then((restaurant) => { + restaurant + .update({ + name, + tel, + address, + opening_hours, + description, + image: file ? `/upload/${file.originalname}` : restaurant.image, + }) + .then((restaurant) => { + req.flash( + "success_messages", + "restaurant was successfully to update" + ); + res.redirect("/admin/restaurants"); + }); + }); + }); + }); + } else { + return Restaurant.findByPk(req.params.id).then((restaurant) => { + restaurant + .update({ + name, + tel, + address, + opening_hours, + description, + image: restaurant.image, + }) + .then((restaurant) => { + req.flash( + "success_messages", + "restaurant was successfully to update" + ); + res.redirect("/admin/restaurants"); + }); + }); } - return Restaurant.findByPk(req.params.id).then((restaurant) => { - restaurant - .update({ - name: req.body.name, - tel: req.body.tel, - address: req.body.address, - opening_hours: req.body.opening_hours, - description: req.body.description - }) - .then((restaurant) => { - req.flash( - 'success_messages', - 'restaurant was successfully to update' - ) - res.redirect('/admin/restaurants') - }) - }) }, deleteRestaurant: (req, res) => { return Restaurant.findByPk(req.params.id).then((restaurant) => { restaurant.destroy().then((restaurant) => { - res.redirect('/admin/restaurants') - }) - }) - } -} + res.redirect("/admin/restaurants"); + }); + }); + }, +}; -module.exports = adminController +module.exports = adminController; diff --git a/package-lock.json b/package-lock.json index 69dfda61..876954ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -161,6 +161,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": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY=" + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -335,6 +340,38 @@ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" }, + "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": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", + "requires": { + "dicer": "0.2.5", + "readable-stream": "1.1.x" + }, + "dependencies": { + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "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": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -518,6 +555,46 @@ "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" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "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==", + "requires": { + "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" + } + }, + "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==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "config-chain": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", @@ -574,6 +651,11 @@ "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", "dev": true }, + "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==" + }, "crypto-random-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", @@ -648,6 +730,33 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, + "dicer": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", + "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", + "requires": { + "readable-stream": "1.1.x", + "streamsearch": "0.1.2" + }, + "dependencies": { + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "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": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -1317,8 +1426,7 @@ "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, "isexe": { "version": "2.0.0", @@ -1651,6 +1759,31 @@ "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" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + } + } + }, "mysql2": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.2.5.tgz", @@ -1812,6 +1945,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": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -1944,6 +2082,11 @@ "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" }, + "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==" + }, "proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -2319,6 +2462,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": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" + }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -2506,6 +2654,11 @@ "mime-types": "~2.1.24" } }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, "typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -2601,8 +2754,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", @@ -2761,6 +2913,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.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", diff --git a/package.json b/package.json index 33143fbb..d17cbaf1 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "express-session": "^1.17.2", "method-override": "^3.0.0", "mocha": "^8.2.0", + "multer": "^1.4.3", "mysql2": "^2.2.5", "nodemon": "^2.0.12", "passport": "^0.5.0", diff --git a/routes/index.js b/routes/index.js index 72e4bb06..706eabd1 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,83 +1,89 @@ -const restControllers = require('../controllers/restController') -const adminController = require('../controllers/adminController') -const userController = require('../controllers/userController') -const passport = require('passport') +const restControllers = require("../controllers/restController"); +const adminController = require("../controllers/adminController"); +const userController = require("../controllers/userController"); +const passport = require("passport"); + +//multer setup +const multer = require("multer"); +const upload = multer({ dest: "temp/" }); module.exports = (app) => { const authenticated = (req, res, next) => { if (req.isAuthenticated()) { - return next() + return next(); } - res.redirect('/signin') - } + res.redirect("/signin"); + }; const authenticatedAdmin = (req, res, next) => { if (req.isAuthenticated()) { - console.log(req.user) + console.log(req.user); if (req.user.isAdmin) { - return next() + return next(); } - return res.redirect('/') + return res.redirect("/"); } - res.redirect('/signin') - } + res.redirect("/signin"); + }; // 如果使用者訪問首頁,就導向 /restaurants 的頁面 - app.get('/', authenticated, (req, res) => { - res.redirect('restaurants') - }) - app.get('/restaurants', restControllers.getRestaurants) + app.get("/", authenticated, (req, res) => { + res.redirect("restaurants"); + }); + app.get("/restaurants", restControllers.getRestaurants); - app.get('/admin', authenticatedAdmin, (req, res) => { - res.redirect('/admin/restaurants') - }) + app.get("/admin", authenticatedAdmin, (req, res) => { + res.redirect("/admin/restaurants"); + }); app.get( - '/admin/restaurants', + "/admin/restaurants", authenticatedAdmin, adminController.getRestaurants - ) + ); - app.get('/signup', userController.signUpPage) - app.post('/signup', userController.signUp) + app.get("/signup", userController.signUpPage); + app.post("/signup", userController.signUp); - app.get('/signin', userController.signInPage) + app.get("/signin", userController.signInPage); app.post( - '/signin', - passport.authenticate('local', { - failureRedirect: '/signin', - failureFlash: true + "/signin", + passport.authenticate("local", { + failureRedirect: "/signin", + failureFlash: true, }), userController.signIn - ) - app.get('/logout', userController.logout) + ); + app.get("/logout", userController.logout); app.get( - '/admin/restaurants/create', + "/admin/restaurants/create", authenticatedAdmin, adminController.createRestaurants - ) + ); app.post( - '/admin/restaurants', + "/admin/restaurants", authenticatedAdmin, + upload.single("image"), adminController.postRestaurants - ) + ); app.get( - '/admin/restaurants/:id', + "/admin/restaurants/:id", authenticatedAdmin, adminController.getRestaurant - ) + ); app.get( - '/admin/restaurants/:id/edit', + "/admin/restaurants/:id/edit", authenticatedAdmin, adminController.editRestaurant - ) + ); app.put( - '/admin/restaurants/:id', + "/admin/restaurants/:id", authenticatedAdmin, + upload.single("image"), adminController.putRestaurant - ) + ); app.delete( - '/admin/restaurants/:id', + "/admin/restaurants/:id", authenticatedAdmin, adminController.deleteRestaurant - ) -} + ); +}; diff --git a/views/admin/create.handlebars b/views/admin/create.handlebars index e19a4762..ae52437a 100644 --- a/views/admin/create.handlebars +++ b/views/admin/create.handlebars @@ -1,7 +1,7 @@ {{#if restaurant}} -
+ {{else}} - + {{/if}}
@@ -25,5 +25,9 @@
+
+ + +
\ No newline at end of file diff --git a/views/admin/restaurant.handlebars b/views/admin/restaurant.handlebars index 12b6cf30..02fb1be4 100644 --- a/views/admin/restaurant.handlebars +++ b/views/admin/restaurant.handlebars @@ -2,7 +2,11 @@

{{restaurant.name}}

- +

From 8df342a271dbadef007d8ef7d02b12f5a36e17a1 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Thu, 11 Nov 2021 00:36:05 +0000 Subject: [PATCH 118/198] add user seeder --- seeders/20211111003012-user-seed-file.js | 37 ++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 seeders/20211111003012-user-seed-file.js diff --git a/seeders/20211111003012-user-seed-file.js b/seeders/20211111003012-user-seed-file.js new file mode 100644 index 00000000..fe42b2a1 --- /dev/null +++ b/seeders/20211111003012-user-seed-file.js @@ -0,0 +1,37 @@ +"use strict"; +const bcrypt = require("bcryptjs"); +const { query } = require("express"); +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.bulkInsert("Users", [ + { + email: "root@example.com", + password: bcrypt.hashSync("12345678", bcrypt.genSaltSync(10), null), + isAdmin: true, + name: "root", + createdAt: new Date(), + updatedAt: new Date(), + }, + { + email: "user1@example.com", + password: bcrypt.hashSync("12345678", bcrypt.genSaltSync(10), null), + isAdmin: false, + name: "user1", + createdAt: new Date(), + updatedAt: new Date(), + }, + { + email: "user2@example.com", + password: bcrypt.hashSync("12345678", bcrypt.genSaltSync(10), null), + isAdmin: false, + name: "user2", + createdAt: new Date(), + updatedAt: new Date(), + }, + ]); + }, + + down: async (queryInterface, Sequelize) => { + await queryInterface.bulkDelete("Users", null, {}); + }, +}; From 5f414d4e747bb218d4366f175e1d379e851888a8 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Thu, 11 Nov 2021 00:44:35 +0000 Subject: [PATCH 119/198] set up restaurant seeder --- package-lock.json | 5 ++++ package.json | 1 + .../20211111003710-restaurants-seed-file.js | 27 +++++++++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 seeders/20211111003710-restaurants-seed-file.js diff --git a/package-lock.json b/package-lock.json index 876954ec..be5ff5c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1023,6 +1023,11 @@ } } }, + "faker": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/faker/-/faker-5.5.3.tgz", + "integrity": "sha512-wLTv2a28wjUyWkbnX7u/ABZBkUkIF2fCd73V6P2oFqEGEktDfzWx4UxrSqtPRw0xPRAcjeAOIiJWqZm3pP4u3g==" + }, "fast-safe-stringify": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", diff --git a/package.json b/package.json index d17cbaf1..4b883e4b 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "express": "^4.17.1", "express-handlebars": "^5.3.4", "express-session": "^1.17.2", + "faker": "^5.5.3", "method-override": "^3.0.0", "mocha": "^8.2.0", "multer": "^1.4.3", diff --git a/seeders/20211111003710-restaurants-seed-file.js b/seeders/20211111003710-restaurants-seed-file.js new file mode 100644 index 00000000..edbbfb26 --- /dev/null +++ b/seeders/20211111003710-restaurants-seed-file.js @@ -0,0 +1,27 @@ +"use strict"; +const faker = require("faker"); + +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.bulkInsert( + "Restaurants", + Array.from({ length: 50 }).map((d, i) => ({ + name: faker.name.findName(), + tel: faker.phone.phoneNumber(), + address: faker.address.streetAddress(), + opening_hours: "08:00", + image: `https://loremflickr.com/320/240/restaurant,food/?random=${ + Math.random() * 100 + }`, + description: faker.lorem.text(), + createdAt: new Date(), + updatedAt: new Date(), + })), + {} + ); + }, + + down: async (queryInterface, Sequelize) => { + await queryInterface.bulkDelete("Restaurants", null, {}); + }, +}; From 2532b2729f238cd55e83541e86e11089f815f528 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Thu, 11 Nov 2021 01:15:11 +0000 Subject: [PATCH 120/198] integrate with imgur api --- .env.example | 1 + app.js | 4 + controllers/adminController.js | 70 ++++++++-------- package-lock.json | 146 +++++++++++++++++++++++++++++++++ package.json | 2 + 5 files changed, 187 insertions(+), 36 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..2fc4c506 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +IMGUR_CLIENT_ID=SKIP \ No newline at end of file diff --git a/app.js b/app.js index 52c8d91a..61c9f7b0 100644 --- a/app.js +++ b/app.js @@ -6,6 +6,10 @@ const session = require("express-session"); const passport = require("./config/passport"); const methodOverride = require("method-override"); +if (process.env.NODE_ENV !== "production") { + require("dotenv").config(); +} + const app = express(); const port = 3000; diff --git a/controllers/adminController.js b/controllers/adminController.js index a07cb0d7..f8cd623c 100644 --- a/controllers/adminController.js +++ b/controllers/adminController.js @@ -1,6 +1,9 @@ const db = require("../models"); const Restaurant = db.Restaurant; const fs = require("fs"); +//imgur setup +const imgur = require("imgur-node-api"); +const IMGUR_CLIENT_ID = process.env.IMGUR_CLIENT_ID; const adminController = { getRestaurants: (req, res) => { @@ -21,23 +24,19 @@ const adminController = { const { name, tel, address, opening_hours, description } = req.body; const { file } = req; if (file) { - fs.readFile(file.path, (err, data) => { + imgur.setClientID(IMGUR_CLIENT_ID); + imgur.upload(file.path, (err, img) => { if (err) console.log("Error: ", err); - fs.writeFile(`upload/${file.originalname}`, data, () => { - return Restaurant.create({ - name, - tel, - address, - opening_hours, - description, - image: file ? `/upload/${file.originalname}` : null, - }).then((restaurant) => { - req.flash( - "success_messages", - "restaurant was successfully created" - ); - return res.redirect("/admin/restaurants"); - }); + return Restaurant.create({ + name, + tel, + address, + opening_hours, + description, + image: file ? img.data.link : null, + }).then((restaurant) => { + req.flash("success_messages", "restaurant was successfully created"); + return res.redirect("/admin/restaurants"); }); }); } else { @@ -83,27 +82,26 @@ const adminController = { const { file } = req; const { name, tel, address, opening_hours, description } = req.body; if (file) { - fs.readFile(file.path, (err, data) => { + imgur.setClientID(IMGUR_CLIENT_ID); + imgur.upload(file.path, (err, img) => { if (err) console.log("Error: ", err); - fs.writeFile(`upload/${file.originalname}`, data, () => { - return Restaurant.findByPk(req.params.id).then((restaurant) => { - restaurant - .update({ - name, - tel, - address, - opening_hours, - description, - image: file ? `/upload/${file.originalname}` : restaurant.image, - }) - .then((restaurant) => { - req.flash( - "success_messages", - "restaurant was successfully to update" - ); - res.redirect("/admin/restaurants"); - }); - }); + return Restaurant.findByPk(req.params.id).then((restaurant) => { + restaurant + .update({ + name, + tel, + address, + opening_hours, + description, + image: file ? img.data.link : restaurant.image, + }) + .then((restaurant) => { + req.flash( + "success_messages", + "restaurant was successfully to update" + ); + res.redirect("/admin/restaurants"); + }); }); }); } else { diff --git a/package-lock.json b/package-lock.json index be5ff5c1..1c5dc802 100644 --- a/package-lock.json +++ b/package-lock.json @@ -184,12 +184,22 @@ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==" }, + "async": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=" + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, + "aws-sign": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/aws-sign/-/aws-sign-0.2.0.tgz", + "integrity": "sha1-xVAThWyBlOyFSgy+yQqrWgTOOsU=" + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -232,6 +242,14 @@ "type-is": "~1.6.17" } }, + "boom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/boom/-/boom-0.3.8.tgz", + "integrity": "sha1-yM2wQUNZEnQWKMBE7Mcy0dF8Ceo=", + "requires": { + "hoek": "0.7.x" + } + }, "boxen": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", @@ -640,6 +658,11 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" }, + "cookie-jar": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/cookie-jar/-/cookie-jar-0.2.0.tgz", + "integrity": "sha1-ZOzAasl423leS1KQy+SLo3gUAPo=" + }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -656,6 +679,14 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, + "cryptiles": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-0.1.3.tgz", + "integrity": "sha1-GlVnNPBtJLo0hirpy55wmjr7/xw=", + "requires": { + "boom": "0.3.x" + } + }, "crypto-random-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", @@ -770,6 +801,11 @@ "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", @@ -1079,6 +1115,11 @@ "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==" }, + "forever-agent": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.2.0.tgz", + "integrity": "sha1-4cJcetROCcOPIzh2x2/MJP+EOx8=" + }, "form-data": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", @@ -1252,11 +1293,27 @@ "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==" }, + "hawk": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-0.10.2.tgz", + "integrity": "sha1-mzYd7pWpMWQObVBOBWCaj8OsRdI=", + "requires": { + "boom": "0.3.x", + "cryptiles": "0.1.x", + "hoek": "0.7.x", + "sntp": "0.1.x" + } + }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, + "hoek": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-0.7.6.tgz", + "integrity": "sha1-YPvZBFV1Qc0rh5Wr8wihs3cOFVo=" + }, "http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", @@ -1287,6 +1344,14 @@ "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=" }, + "imgur-node-api": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/imgur-node-api/-/imgur-node-api-0.1.0.tgz", + "integrity": "sha1-iJU25/x9/FyYXHtVeMGuwxyzwYY=", + "requires": { + "request": "~2.16.6" + } + }, "import-lazy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", @@ -1464,6 +1529,11 @@ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" }, + "json-stringify-safe": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-3.0.0.tgz", + "integrity": "sha1-nbew5TDH8onF6MhDKvGRwv91pbM=" + }, "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -1950,6 +2020,11 @@ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==" }, + "oauth-sign": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.2.0.tgz", + "integrity": "sha1-oOahcV2u0GLzIrYit/5a/RA1tuI=" + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -2231,6 +2306,64 @@ "rc": "^1.2.8" } }, + "request": { + "version": "2.16.6", + "resolved": "https://registry.npmjs.org/request/-/request-2.16.6.tgz", + "integrity": "sha1-hy/kRa5y3iZrN4edatfclI+gHK0=", + "requires": { + "aws-sign": "~0.2.0", + "cookie-jar": "~0.2.0", + "forever-agent": "~0.2.0", + "form-data": "~0.0.3", + "hawk": "~0.10.2", + "json-stringify-safe": "~3.0.0", + "mime": "~1.2.7", + "node-uuid": "~1.4.0", + "oauth-sign": "~0.2.0", + "qs": "~0.5.4", + "tunnel-agent": "~0.2.0" + }, + "dependencies": { + "combined-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", + "integrity": "sha1-ATfmV7qlp1QcV6w3rF/AfXO03B8=", + "requires": { + "delayed-stream": "0.0.5" + } + }, + "delayed-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz", + "integrity": "sha1-1LH0OpPoKW3+AmlPRoC8N6MTxz8=" + }, + "form-data": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.0.10.tgz", + "integrity": "sha1-2zRaU3jYau6x7V1VO4aawZLS9e0=", + "requires": { + "async": "~0.2.7", + "combined-stream": "~0.0.4", + "mime": "~1.2.2" + } + }, + "mime": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz", + "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=" + }, + "node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" + }, + "qs": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/qs/-/qs-0.5.6.tgz", + "integrity": "sha1-MbGtBYVnZRxSaSFQa5qHk5EaA4Q=" + } + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -2447,6 +2580,14 @@ "supports-color": "^7.1.0" } }, + "sntp": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-0.1.4.tgz", + "integrity": "sha1-XvSBuVGnspr/30r9fyaDj8ESD4Q=", + "requires": { + "hoek": "0.7.x" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -2635,6 +2776,11 @@ } } }, + "tunnel-agent": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.2.0.tgz", + "integrity": "sha1-aFPCr7GyEJ5FYp5JK9419Fnqaeg=" + }, "type": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", diff --git a/package.json b/package.json index 4b883e4b..70698e1f 100644 --- a/package.json +++ b/package.json @@ -17,10 +17,12 @@ "body-parser": "^1.19.0", "chai": "^4.2.0", "connect-flash": "^0.1.1", + "dotenv": "^10.0.0", "express": "^4.17.1", "express-handlebars": "^5.3.4", "express-session": "^1.17.2", "faker": "^5.5.3", + "imgur-node-api": "^0.1.0", "method-override": "^3.0.0", "mocha": "^8.2.0", "multer": "^1.4.3", From 157c68d8fbeb49e8293e88cdc67a080f3251a423 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Thu, 11 Nov 2021 01:29:57 +0000 Subject: [PATCH 121/198] Install pg and update production config --- config/config.json | 13 +++--- package-lock.json | 100 +++++++++++++++++++++++++++++++++++++++++++-- package.json | 1 + 3 files changed, 105 insertions(+), 9 deletions(-) diff --git a/config/config.json b/config/config.json index bbd3ff90..20546466 100644 --- a/config/config.json +++ b/config/config.json @@ -7,11 +7,14 @@ "dialect": "mysql" }, "production": { - "username": "root", - "password": null, - "database": "database_production", - "host": "127.0.0.1", - "dialect": "mysql" + "use_env_variable": "DATABASE_URL", + "dialect": "postgres", + "protocol": "postgres", + "dialectOptions": { + "ssl": { + "rejectUnauthorized": false + } + } }, "travis": { "username": "travis", diff --git a/package-lock.json b/package-lock.json index 1c5dc802..a8832131 100644 --- a/package-lock.json +++ b/package-lock.json @@ -363,6 +363,11 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, + "buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" + }, "busboy": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", @@ -2095,6 +2100,11 @@ } } }, + "packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -2152,11 +2162,88 @@ "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" }, + "pg": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.7.1.tgz", + "integrity": "sha512-7bdYcv7V6U3KAtWjpQJJBww0UEsWuh4yQ/EjNf2HeO/NnvKjpvhEIe/A/TleP6wtmSKnUnghs5A9jUoK6iDdkA==", + "requires": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.4.1", + "pg-protocol": "^1.5.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + } + }, + "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==" + }, + "pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + }, + "pg-pool": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.4.1.tgz", + "integrity": "sha512-TVHxR/gf3MeJRvchgNHxsYsTCHQ+4wm3VIHSS19z8NC0+gioEhq1okDY1sm/TYbfoP6JLFx01s0ShvZ3puP/iQ==" + }, + "pg-protocol": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", + "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" + }, + "pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "requires": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + } + }, + "pgpass": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.4.tgz", + "integrity": "sha512-YmuA56alyBq7M59vxVBfPJrGSozru8QAdoNlWuW3cz8l+UX3cWge0vTvjKhsSHSJpo3Bom8/Mm6hf0TR5GY0+w==", + "requires": { + "split2": "^3.1.1" + } + }, "picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" }, + "postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + }, + "postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=" + }, + "postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==" + }, + "postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "requires": { + "xtend": "^4.0.0" + } + }, "prepend-http": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", @@ -2275,7 +2362,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -2593,6 +2679,14 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, + "split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "requires": { + "readable-stream": "^3.0.0" + } + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -2627,7 +2721,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, "requires": { "safe-buffer": "~5.2.0" }, @@ -2635,8 +2728,7 @@ "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==", - "dev": true + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" } } }, diff --git a/package.json b/package.json index 70698e1f..9e98e7b6 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "nodemon": "^2.0.12", "passport": "^0.5.0", "passport-local": "^1.0.0", + "pg": "^8.7.1", "sequelize": "^6.3.5", "sequelize-cli": "^6.2.0" }, From 5c4a3829227bb638221dd469e1d7b60c43b5f5a8 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Thu, 11 Nov 2021 01:51:11 +0000 Subject: [PATCH 122/198] update port and procfile --- Procfile | 1 + app.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 Procfile diff --git a/Procfile b/Procfile new file mode 100644 index 00000000..6feca7ec --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: NODE_ENV=production node app.js \ No newline at end of file diff --git a/app.js b/app.js index 61c9f7b0..1bd9936d 100644 --- a/app.js +++ b/app.js @@ -11,7 +11,7 @@ if (process.env.NODE_ENV !== "production") { } const app = express(); -const port = 3000; +const port = process.env.PORT || 3000; app.engine("handlebars", handlebars({ defaultLayout: "main" })); app.set("view engine", "handlebars"); From 41e606099b72bbef263a6cb5918d2b29d5201c38 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Thu, 11 Nov 2021 01:56:22 +0000 Subject: [PATCH 123/198] heroku PG switch to ClearDB mysql --- config/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.json b/config/config.json index 20546466..f614dc28 100644 --- a/config/config.json +++ b/config/config.json @@ -7,7 +7,7 @@ "dialect": "mysql" }, "production": { - "use_env_variable": "DATABASE_URL", + "use_env_variable": "CLEARDB_DATABASE_URL", "dialect": "postgres", "protocol": "postgres", "dialectOptions": { From e8f98a4806d3ea35efbeacd044725a01e3f8865b Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Thu, 11 Nov 2021 23:49:37 +0000 Subject: [PATCH 124/198] amend code with helpers --- controllers/adminController.js | 1 + models/index.js | 7 ++++--- package-lock.json | 2 +- package.json | 2 +- routes/index.js | 8 ++++---- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/controllers/adminController.js b/controllers/adminController.js index f8cd623c..07c55afc 100644 --- a/controllers/adminController.js +++ b/controllers/adminController.js @@ -26,6 +26,7 @@ const adminController = { if (file) { imgur.setClientID(IMGUR_CLIENT_ID); imgur.upload(file.path, (err, img) => { + console.log("here"); if (err) console.log("Error: ", err); return Restaurant.create({ name, diff --git a/models/index.js b/models/index.js index a69939c1..d4167896 100644 --- a/models/index.js +++ b/models/index.js @@ -20,7 +20,6 @@ if (config.use_env_variable) { ); } -// 動態引入其他 models fs.readdirSync(__dirname) .filter((file) => { return ( @@ -28,11 +27,13 @@ fs.readdirSync(__dirname) ); }) .forEach((file) => { - const model = sequelize["import"](path.join(__dirname, file)); + const model = require(path.join(__dirname, file))( + sequelize, + Sequelize.DataTypes + ); db[model.name] = model; }); -// 設定 Models 之間的關聯 Object.keys(db).forEach((modelName) => { if (db[modelName].associate) { db[modelName].associate(db); diff --git a/package-lock.json b/package-lock.json index 9ce9e91f..a8832131 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3221,4 +3221,4 @@ } } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 0f227c4c..9e98e7b6 100644 --- a/package.json +++ b/package.json @@ -42,4 +42,4 @@ "sinon": "^9.2.0", "supertest": "^5.0.0" } -} \ No newline at end of file +} diff --git a/routes/index.js b/routes/index.js index 706eabd1..3ff8a911 100644 --- a/routes/index.js +++ b/routes/index.js @@ -2,6 +2,7 @@ const restControllers = require("../controllers/restController"); const adminController = require("../controllers/adminController"); const userController = require("../controllers/userController"); const passport = require("passport"); +const helpers = require("../_helpers"); //multer setup const multer = require("multer"); @@ -9,16 +10,15 @@ const upload = multer({ dest: "temp/" }); module.exports = (app) => { const authenticated = (req, res, next) => { - if (req.isAuthenticated()) { + if (helpers.ensureAuthenticated()) { return next(); } res.redirect("/signin"); }; const authenticatedAdmin = (req, res, next) => { - if (req.isAuthenticated()) { - console.log(req.user); - if (req.user.isAdmin) { + if (helpers.ensureAuthenticated()) { + if (helpse.getUser(req).isAdmin) { return next(); } return res.redirect("/"); From f38bb34e99d826193044ef1969545deb4458ba43 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Thu, 11 Nov 2021 23:52:24 +0000 Subject: [PATCH 125/198] correct the error using helpers --- routes/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routes/index.js b/routes/index.js index 3ff8a911..c65b7265 100644 --- a/routes/index.js +++ b/routes/index.js @@ -10,14 +10,14 @@ const upload = multer({ dest: "temp/" }); module.exports = (app) => { const authenticated = (req, res, next) => { - if (helpers.ensureAuthenticated()) { + if (helpers.ensureAuthenticated(req)) { return next(); } res.redirect("/signin"); }; const authenticatedAdmin = (req, res, next) => { - if (helpers.ensureAuthenticated()) { + if (helpers.ensureAuthenticated(req)) { if (helpse.getUser(req).isAdmin) { return next(); } From 4d56bbf9346634f5fc9ec21313de57448b1cdf4b Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Fri, 12 Nov 2021 01:24:16 +0000 Subject: [PATCH 126/198] add change user permission --- _helpers.js | 12 +- app.js | 60 +++++----- controllers/adminController.js | 27 ++++- controllers/userController.js | 47 ++++---- helpers/unitTestHelpers.js | 48 ++++---- ...20211110234744-add-image-to-restaurants.js | 14 +-- models/index.js | 42 +++---- models/restaurant.js | 16 +-- routes/index.js | 104 +++++++++--------- seeders/20211111003012-user-seed-file.js | 42 +++---- .../20211111003710-restaurants-seed-file.js | 18 +-- tests/R01.test.js | 36 +++--- views/admin/restaurants.handlebars | 6 +- views/admin/users.handlebars | 44 ++++++++ 14 files changed, 295 insertions(+), 221 deletions(-) create mode 100644 views/admin/users.handlebars diff --git a/_helpers.js b/_helpers.js index f4f1481a..bff0b66b 100644 --- a/_helpers.js +++ b/_helpers.js @@ -1,12 +1,12 @@ -function ensureAuthenticated(req) { - return req.isAuthenticated(); +function ensureAuthenticated (req) { + return req.isAuthenticated() } -function getUser(req) { - return req.user; +function getUser (req) { + return req.user } module.exports = { ensureAuthenticated, - getUser, -}; + getUser +} diff --git a/app.js b/app.js index 1bd9936d..56b3cf6b 100644 --- a/app.js +++ b/app.js @@ -1,41 +1,41 @@ -const express = require("express"); -const handlebars = require("express-handlebars"); -const db = require("./models"); -const flash = require("connect-flash"); -const session = require("express-session"); -const passport = require("./config/passport"); -const methodOverride = require("method-override"); +const express = require('express') +const handlebars = require('express-handlebars') +const db = require('./models') +const flash = require('connect-flash') +const session = require('express-session') +const passport = require('./config/passport') +const methodOverride = require('method-override') -if (process.env.NODE_ENV !== "production") { - require("dotenv").config(); +if (process.env.NODE_ENV !== 'production') { + require('dotenv').config() } -const app = express(); -const port = process.env.PORT || 3000; +const app = express() +const port = process.env.PORT || 3000 -app.engine("handlebars", handlebars({ defaultLayout: "main" })); -app.set("view engine", "handlebars"); -app.use(express.urlencoded({ extended: true })); -app.use(session({ secret: "secret", resave: false, saveUninitialized: false })); -app.use(passport.initialize()); -app.use(passport.session()); -app.use(flash()); -app.use(methodOverride("_method")); -app.use("/upload", express.static(__dirname + "/upload")); +app.engine('handlebars', handlebars({ defaultLayout: 'main' })) +app.set('view engine', 'handlebars') +app.use(express.urlencoded({ extended: true })) +app.use(session({ secret: 'secret', resave: false, saveUninitialized: false })) +app.use(passport.initialize()) +app.use(passport.session()) +app.use(flash()) +app.use(methodOverride('_method')) +app.use('/upload', express.static(__dirname + '/upload')) app.use((req, res, next) => { - res.locals.success_messages = req.flash("success_messages"); - res.locals.error_messages = req.flash("error_messages"); - res.locals.user = req.user; - next(); -}); + res.locals.success_messages = req.flash('success_messages') + res.locals.error_messages = req.flash('error_messages') + res.locals.user = req.user + next() +}) app.listen(port, () => { // what is this for? - db.sequelize.sync(); - console.log(`Example app listening at http://localhost:${port}`); -}); + db.sequelize.sync() + console.log(`Example app listening at http://localhost:${port}`) +}) -require("./routes")(app); +require('./routes')(app) -module.exports = app; +module.exports = app diff --git a/controllers/adminController.js b/controllers/adminController.js index 07c55afc..4f191c7f 100644 --- a/controllers/adminController.js +++ b/controllers/adminController.js @@ -1,7 +1,7 @@ const db = require("../models"); const Restaurant = db.Restaurant; -const fs = require("fs"); -//imgur setup +const User = db.User; +// imgur setup const imgur = require("imgur-node-api"); const IMGUR_CLIENT_ID = process.env.IMGUR_CLIENT_ID; @@ -26,7 +26,6 @@ const adminController = { if (file) { imgur.setClientID(IMGUR_CLIENT_ID); imgur.upload(file.path, (err, img) => { - console.log("here"); if (err) console.log("Error: ", err); return Restaurant.create({ name, @@ -134,6 +133,28 @@ const adminController = { }); }); }, + + getUsers: (req, res) => { + console.log("i am in get users"); + return User.findAll({ raw: true, nest: true }).then((users) => { + return res.render("admin/users", { users }); + }); + }, + + toggleAdmin: (req, res) => { + return User.findByPk(req.params.id).then((user) => { + const { isAdmin } = user.toJSON(); + if (isAdmin) { + req.flash("error_messages", "禁止變更管理者權限"); + res.redirect("back"); + } else { + user.update({ isAdmin: !isAdmin }).then((user) => { + req.flash("success_messages", "使用者權限變更成功"); + res.redirect("/admin/users"); + }); + } + }); + }, }; module.exports = adminController; diff --git a/controllers/userController.js b/controllers/userController.js index a2e3298a..d60c7546 100644 --- a/controllers/userController.js +++ b/controllers/userController.js @@ -1,51 +1,50 @@ -const bcrypt = require('bcrypt-nodejs') -const db = require('../models') -const User = db.User +const bcrypt = require("bcrypt-nodejs"); +const db = require("../models"); +const User = db.User; const userController = { signUpPage: (req, res) => { - return res.render('signup') + return res.render("signup"); }, signUp: (req, res) => { - const { name, email, password, passwordCheck } = req.body + const { name, email, password, passwordCheck } = req.body; if (password !== passwordCheck) { - req.flash('error_messages', '兩次密碼輸入不同') - return res.redirect('/signup') + req.flash("error_messages", "兩次密碼輸入不同"); + return res.redirect("/signup"); } else { User.findOne({ where: { email } }).then((user) => { - console.log(user) if (user) { - req.flash('error_messages', '信箱已經註冊過') - return res.redirect('/signup') + req.flash("error_messages", "信箱已經註冊過"); + return res.redirect("/signup"); } else { User.create({ name, email, - password: bcrypt.hashSync(password, bcrypt.genSaltSync(10), null) + password: bcrypt.hashSync(password, bcrypt.genSaltSync(10), null), }).then((user) => { - req.flash('success_messages', '成功註冊帳號') - return res.redirect('/signin') - }) + req.flash("success_messages", "成功註冊帳號"); + return res.redirect("/signin"); + }); } - }) + }); } }, signInPage: (req, res) => { - return res.render('signin') + return res.render("signin"); }, signIn: (req, res) => { - req.flash('success_messages', '成功登入!') - res.redirect('/restaurants') + req.flash("success_messages", "成功登入!"); + res.redirect("/restaurants"); }, logout: (req, res) => { - req.flash('success_message', '登出成功!') - req.logout() - res.redirect('/signin') - } -} + req.flash("success_message", "登出成功!"); + req.logout(); + res.redirect("/signin"); + }, +}; -module.exports = userController +module.exports = userController; diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js index 4a777f4c..1913c2ff 100644 --- a/helpers/unitTestHelpers.js +++ b/helpers/unitTestHelpers.js @@ -8,15 +8,15 @@ const createModelMock = (name, defaultValue, data, joinedTableName) => { const mockModel = dbMock.define(name, defaultValue, { instanceMethods: { update: (changes) => { - mockModel._defaults = {...changes} + mockModel._defaults = { ...changes } return Promise.resolve() } } - }); + }) // 模擬 Sequelize 行為 // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) - mockModel.findByPk = (id) => mockModel.findOne({where: {id: id}}) + mockModel.findByPk = (id) => mockModel.findOne({ where: { id: id } }) // 將 count 的 function 預設回傳假資料數目 1 mockModel.count = () => 1 // 因為 mock 中的 create 有問題,因此指向 upsert function, 這樣可以在 useHandler 中取得 create 呼叫 @@ -27,75 +27,75 @@ const createModelMock = (name, defaultValue, data, joinedTableName) => { mockModel.$queryInterface.$useHandler((query, queryOptions) => { if (query === 'upsert') { // 新增 joinTable 資料到模擬資料 - const {UserId, RestaurantId} = queryOptions[0]; + const { UserId, RestaurantId } = queryOptions[0] const restaurant = data.find(d => d.id === RestaurantId) - restaurant[joinedTableName].push({UserId: UserId}); + restaurant[joinedTableName].push({ UserId: UserId }) return Promise.resolve(data.map(d => mockModel.build(d))) } else if (query === 'findAll') { // 回傳模擬資料 if (!data) { - return mockModel.build([defaultValue]); + return mockModel.build([defaultValue]) } - return Promise.resolve( data ? data.map(d => mockModel.build(d)) : []) - }else if (query === 'destroy') { + return Promise.resolve(data ? data.map(d => mockModel.build(d)) : []) + } else if (query === 'destroy') { // destroy 可以從 where 取得要刪除的資料 // 因此就可以模擬將模擬資料中的資料刪除 // 刪除模擬資料中的某一筆 joinTable 資料 - const {UserId, RestaurantId} = queryOptions[0].where; + const { UserId, RestaurantId } = queryOptions[0].where const restaurant = data.find(d => d.id === RestaurantId) restaurant[joinedTableName] = restaurant[joinedTableName].filter(d => !(d.UserId === UserId)) return Promise.resolve(data.map(d => mockModel.build(d))) } - }); + }) } else { - mockModel.$queryInterface.$useHandler((query, queryOptions,done) => { + mockModel.$queryInterface.$useHandler((query, queryOptions, done) => { if (query === 'upsert') { // create 時會帶 userId 跟 restaurantId (ex: Like.create({ userId: 1, restaurantId: 2})) - const {UserId, RestaurantId} = queryOptions[0] - + const { UserId, RestaurantId } = queryOptions[0] + // 新增這個 Like 的資訊到模擬資料裡 data.push({ UserId, RestaurantId }) - + // 回傳模擬資料 return Promise.resolve(mockModel.build(data)) } else if (query === 'findAll') { // 回傳模擬資料 if (!data) { - return mockModel.build([defaultValue]); + return mockModel.build([defaultValue]) } return Promise.resolve(data ? data.map(d => mockModel.build(d)) : []) } else if (query === 'destroy') { // destroy 可以從 where 取得要刪除的資料 // 因此就可以模擬將模擬資料中的資料刪除 - const {UserId, RestaurantId} = queryOptions[0].where + const { UserId, RestaurantId } = queryOptions[0].where data = data.filter(d => !(d.UserId === UserId && d.RestaurantId === RestaurantId)) - + return Promise.resolve(mockModel.build(data)) } - }); + }) } - return mockModel; + return mockModel } const createControllerProxy = (path, model) => { const controller = proxyquire(path, { '../models': model - }); + }) - return controller; + return controller } const mockRequest = (query) => { return { ...query, - flash: sinon.spy(), + flash: sinon.spy() } } const mockResponse = () => { return { redirect: sinon.spy(), - render: sinon.spy(), + render: sinon.spy() } } @@ -104,4 +104,4 @@ module.exports = { createControllerProxy, mockRequest, mockResponse -} \ No newline at end of file +} diff --git a/migrations/20211110234744-add-image-to-restaurants.js b/migrations/20211110234744-add-image-to-restaurants.js index cbd104b9..8c798e99 100644 --- a/migrations/20211110234744-add-image-to-restaurants.js +++ b/migrations/20211110234744-add-image-to-restaurants.js @@ -1,12 +1,12 @@ -"use strict"; +'use strict' module.exports = { up: async (queryInterface, Sequelize) => { - await queryInterface.addColumn("Restaurants", "image", { - type: Sequelize.STRING, - }); + await queryInterface.addColumn('Restaurants', 'image', { + type: Sequelize.STRING + }) }, down: async (queryInterface, Sequelize) => { - await queryInterface.removeColumn("Restaurants", "image"); - }, -}; + await queryInterface.removeColumn('Restaurants', 'image') + } +} diff --git a/models/index.js b/models/index.js index d4167896..b2edcde4 100644 --- a/models/index.js +++ b/models/index.js @@ -1,46 +1,46 @@ -"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 - ); + ) } fs.readdirSync(__dirname) .filter((file) => { return ( - file.indexOf(".") !== 0 && file !== basename && file.slice(-3) === ".js" - ); + 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; - }); + ) + 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/restaurant.js b/models/restaurant.js index ef2746f5..d50ccd29 100644 --- a/models/restaurant.js +++ b/models/restaurant.js @@ -1,5 +1,5 @@ -"use strict"; -const { Model } = require("sequelize"); +'use strict' +const { Model } = require('sequelize') module.exports = (sequelize, DataTypes) => { class Restaurant extends Model { /** @@ -7,7 +7,7 @@ module.exports = (sequelize, DataTypes) => { * This method is not a part of Sequelize lifecycle. * The `models/index` file will call this method automatically. */ - static associate(models) { + static associate (models) { // define association here } } @@ -18,12 +18,12 @@ module.exports = (sequelize, DataTypes) => { address: DataTypes.STRING, opening_hours: DataTypes.STRING, description: DataTypes.TEXT, - image: DataTypes.STRING, + image: DataTypes.STRING }, { sequelize, - modelName: "Restaurant", + modelName: 'Restaurant' } - ); - return Restaurant; -}; + ) + return Restaurant +} diff --git a/routes/index.js b/routes/index.js index c65b7265..c4b15f9a 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,89 +1,95 @@ -const restControllers = require("../controllers/restController"); -const adminController = require("../controllers/adminController"); -const userController = require("../controllers/userController"); -const passport = require("passport"); -const helpers = require("../_helpers"); +const restControllers = require('../controllers/restController') +const adminController = require('../controllers/adminController') +const userController = require('../controllers/userController') +const passport = require('passport') +const helpers = require('../_helpers') -//multer setup -const multer = require("multer"); -const upload = multer({ dest: "temp/" }); +// multer setup +const multer = require('multer') +const upload = multer({ dest: 'temp/' }) module.exports = (app) => { const authenticated = (req, res, next) => { if (helpers.ensureAuthenticated(req)) { - return next(); + return next() } - res.redirect("/signin"); - }; + res.redirect('/signin') + } const authenticatedAdmin = (req, res, next) => { if (helpers.ensureAuthenticated(req)) { - if (helpse.getUser(req).isAdmin) { - return next(); + if (helpers.getUser(req).isAdmin) { + return next() } - return res.redirect("/"); + return res.redirect('/') } - res.redirect("/signin"); - }; + res.redirect('/signin') + } // 如果使用者訪問首頁,就導向 /restaurants 的頁面 - app.get("/", authenticated, (req, res) => { - res.redirect("restaurants"); - }); - app.get("/restaurants", restControllers.getRestaurants); + app.get('/', authenticated, (req, res) => { + res.redirect('restaurants') + }) + app.get('/restaurants', restControllers.getRestaurants) - app.get("/admin", authenticatedAdmin, (req, res) => { - res.redirect("/admin/restaurants"); - }); + app.get('/admin', authenticatedAdmin, (req, res) => { + res.redirect('/admin/restaurants') + }) app.get( - "/admin/restaurants", + '/admin/restaurants', authenticatedAdmin, adminController.getRestaurants - ); + ) - app.get("/signup", userController.signUpPage); - app.post("/signup", userController.signUp); + app.get('/signup', userController.signUpPage) + app.post('/signup', userController.signUp) - app.get("/signin", userController.signInPage); + app.get('/signin', userController.signInPage) app.post( - "/signin", - passport.authenticate("local", { - failureRedirect: "/signin", - failureFlash: true, + '/signin', + passport.authenticate('local', { + failureRedirect: '/signin', + failureFlash: true }), userController.signIn - ); - app.get("/logout", userController.logout); + ) + app.get('/logout', userController.logout) app.get( - "/admin/restaurants/create", + '/admin/restaurants/create', authenticatedAdmin, adminController.createRestaurants - ); + ) app.post( - "/admin/restaurants", + '/admin/restaurants', authenticatedAdmin, - upload.single("image"), + upload.single('image'), adminController.postRestaurants - ); + ) app.get( - "/admin/restaurants/:id", + '/admin/restaurants/:id', authenticatedAdmin, adminController.getRestaurant - ); + ) app.get( - "/admin/restaurants/:id/edit", + '/admin/restaurants/:id/edit', authenticatedAdmin, adminController.editRestaurant - ); + ) app.put( - "/admin/restaurants/:id", + '/admin/restaurants/:id', authenticatedAdmin, - upload.single("image"), + upload.single('image'), adminController.putRestaurant - ); + ) app.delete( - "/admin/restaurants/:id", + '/admin/restaurants/:id', authenticatedAdmin, adminController.deleteRestaurant - ); -}; + ) + app.get('/admin/users', authenticatedAdmin, adminController.getUsers) + app.put( + '/admin/users/:id/toggleAdmin', + authenticatedAdmin, + adminController.toggleAdmin + ) +} diff --git a/seeders/20211111003012-user-seed-file.js b/seeders/20211111003012-user-seed-file.js index fe42b2a1..b147ab70 100644 --- a/seeders/20211111003012-user-seed-file.js +++ b/seeders/20211111003012-user-seed-file.js @@ -1,37 +1,37 @@ -"use strict"; -const bcrypt = require("bcryptjs"); -const { query } = require("express"); +'use strict' +const bcrypt = require('bcryptjs') +const { query } = require('express') module.exports = { up: async (queryInterface, Sequelize) => { - await queryInterface.bulkInsert("Users", [ + await queryInterface.bulkInsert('Users', [ { - email: "root@example.com", - password: bcrypt.hashSync("12345678", bcrypt.genSaltSync(10), null), + email: 'root@example.com', + password: bcrypt.hashSync('12345678', bcrypt.genSaltSync(10), null), isAdmin: true, - name: "root", + name: 'root', createdAt: new Date(), - updatedAt: new Date(), + updatedAt: new Date() }, { - email: "user1@example.com", - password: bcrypt.hashSync("12345678", bcrypt.genSaltSync(10), null), + email: 'user1@example.com', + password: bcrypt.hashSync('12345678', bcrypt.genSaltSync(10), null), isAdmin: false, - name: "user1", + name: 'user1', createdAt: new Date(), - updatedAt: new Date(), + updatedAt: new Date() }, { - email: "user2@example.com", - password: bcrypt.hashSync("12345678", bcrypt.genSaltSync(10), null), + email: 'user2@example.com', + password: bcrypt.hashSync('12345678', bcrypt.genSaltSync(10), null), isAdmin: false, - name: "user2", + name: 'user2', createdAt: new Date(), - updatedAt: new Date(), - }, - ]); + updatedAt: new Date() + } + ]) }, down: async (queryInterface, Sequelize) => { - await queryInterface.bulkDelete("Users", null, {}); - }, -}; + await queryInterface.bulkDelete('Users', null, {}) + } +} diff --git a/seeders/20211111003710-restaurants-seed-file.js b/seeders/20211111003710-restaurants-seed-file.js index edbbfb26..e75658b8 100644 --- a/seeders/20211111003710-restaurants-seed-file.js +++ b/seeders/20211111003710-restaurants-seed-file.js @@ -1,27 +1,27 @@ -"use strict"; -const faker = require("faker"); +'use strict' +const faker = require('faker') module.exports = { up: async (queryInterface, Sequelize) => { await queryInterface.bulkInsert( - "Restaurants", + 'Restaurants', Array.from({ length: 50 }).map((d, i) => ({ name: faker.name.findName(), tel: faker.phone.phoneNumber(), address: faker.address.streetAddress(), - opening_hours: "08:00", + opening_hours: '08:00', image: `https://loremflickr.com/320/240/restaurant,food/?random=${ Math.random() * 100 }`, description: faker.lorem.text(), createdAt: new Date(), - updatedAt: new Date(), + updatedAt: new Date() })), {} - ); + ) }, down: async (queryInterface, Sequelize) => { - await queryInterface.bulkDelete("Restaurants", null, {}); - }, -}; + await queryInterface.bulkDelete('Restaurants', null, {}) + } +} diff --git a/tests/R01.test.js b/tests/R01.test.js index 2765ccfb..f543dedd 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -3,13 +3,13 @@ const request = require('supertest') const should = chai.should() const app = require('../app') -const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers'); +const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers') describe('# R01', () => { - describe('登入測試: POST /signin', function(){ + describe('登入測試: POST /signin', function () { // 以下測試會發出請求,測試資料庫內是否有作業指定的使用者資料 // 測試資料的來源是真實的資料庫 - it('#1 密碼錯誤', function(done){ + it('#1 密碼錯誤', function (done) { request(app) // 對 POST /signin 發出請求,參數是錯誤的密碼 .post('/signin') @@ -20,7 +20,7 @@ describe('# R01', () => { .expect(302, done) }) - it('#2 帳號錯誤', function(done){ + it('#2 帳號錯誤', function (done) { request(app) // 對 POST /signin 發出請求,參數是錯誤的帳號 .post('/signin') @@ -31,17 +31,17 @@ describe('# R01', () => { .expect(302, done) }) - it('#3 成功登入', function(done){ + it('#3 成功登入', function (done) { request(app) // 對 POST /signin 發出請求,參數是作業指定的使用者帳號密碼 .post('/signin') .type('urlencoded') .send('email=root@example.com&password=12345678') - // 期待登入驗證成功,重新導向 /restaurants + // 期待登入驗證成功,重新導向 /restaurants .expect('Location', '/restaurants') .expect(302, done) }) - }); + }) describe('# R01: 使用者權限管理', function () { // 前置準備 @@ -52,13 +52,13 @@ describe('# R01', () => { id: 1, email: 'root@example.com', name: 'admin', - isAdmin: false, + isAdmin: false }) // 修改 adminController 中的資料庫連線設定,由連向真實的資料庫 -> 改為連向模擬的 User table this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) }) - + // 開始測試 context('# [顯示使用者清單]', () => { it(' GET /admin/users ', async () => { @@ -81,15 +81,15 @@ describe('# R01', () => { // 製作假資料 // 本 context 會用這筆資料進行測試 this.UserMock = createModelMock( - 'User', + 'User', { id: 1, email: 'root@example.com', name: 'admin', - isAdmin: true, // 是管理者 + isAdmin: true // 是管理者 } ) - + // 將 adminController 中的 User db 取代成 User mock db this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) }) @@ -99,14 +99,14 @@ describe('# R01', () => { const req = mockRequest({ params: { id: 1 } }) // 帶入 params.id = 1,對 PUT /admin/users/1/toggleAdmin 發出請求 const res = mockResponse() - // 測試作業指定的 adminController.toggleAdmin 函式 + // 測試作業指定的 adminController.toggleAdmin 函式 await this.adminController.toggleAdmin(req, res) // toggleAdmin 正確執行的話,應呼叫 req.flash // req.flash 的參數應該要與下列字串一致 - req.flash.calledWith('error_messages','禁止變更管理者權限').should.be.true + req.flash.calledWith('error_messages', '禁止變更管理者權限').should.be.true - // toggleAdmin 執行完畢後,應呼叫 res.redirect 並重新導向上一頁 + // toggleAdmin 執行完畢後,應呼叫 res.redirect 並重新導向上一頁 res.redirect.calledWith('back').should.be.true }) }) @@ -121,7 +121,7 @@ describe('# R01', () => { id: 1, email: 'user@example.com', name: 'user', - isAdmin: false, // 非管理者 + isAdmin: false // 非管理者 } ) // 將 adminController 中的 User db 取代成 User mock db @@ -136,9 +136,9 @@ describe('# R01', () => { // 測試作業指定的 adminController.toggleAdmin 函式 await this.adminController.toggleAdmin(req, res) - // toggleAdmin 正確執行的話,應呼叫 req.flash + // toggleAdmin 正確執行的話,應呼叫 req.flash // req.flash 的參數應與下列字串一致 - req.flash.calledWith('success_messages','使用者權限變更成功').should.be.true + req.flash.calledWith('success_messages', '使用者權限變更成功').should.be.true // toggleAdmin 執行完畢後,應呼叫 res.redirect 並重新導向 /admin/users res.redirect.calledWith('/admin/users').should.be.true diff --git a/views/admin/restaurants.handlebars b/views/admin/restaurants.handlebars index fecfcebd..52cf15c5 100644 --- a/views/admin/restaurants.handlebars +++ b/views/admin/restaurants.handlebars @@ -1,6 +1,10 @@

餐廳後台

- +Restaurant + | +User + + diff --git a/views/admin/users.handlebars b/views/admin/users.handlebars new file mode 100644 index 00000000..d150f5ff --- /dev/null +++ b/views/admin/users.handlebars @@ -0,0 +1,44 @@ +

餐廳後台

+ +Restaurant + | +User + +
+
+ + + + + + + + + + + + + {{#each users}} + + + + + + + + {{/each}} + +
#NameEmailRole#
{{this.id}}{{this.name}}{{this.email}}{{#if this.isAdmin}}admin{{else}}user{{/if}} +
+ +
+
\ No newline at end of file From 1043690220695b7af302adb297a08a2723c4c3db Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Fri, 12 Nov 2021 01:32:12 +0000 Subject: [PATCH 127/198] remove console.log --- controllers/adminController.js | 1 - 1 file changed, 1 deletion(-) diff --git a/controllers/adminController.js b/controllers/adminController.js index 4f191c7f..d6553696 100644 --- a/controllers/adminController.js +++ b/controllers/adminController.js @@ -135,7 +135,6 @@ const adminController = { }, getUsers: (req, res) => { - console.log("i am in get users"); return User.findAll({ raw: true, nest: true }).then((users) => { return res.render("admin/users", { users }); }); From 12f8169113783f9072c690bac29cd36bd008bef0 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Fri, 12 Nov 2021 02:29:56 +0000 Subject: [PATCH 128/198] correct logic only block root user admin access change --- controllers/adminController.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/controllers/adminController.js b/controllers/adminController.js index d6553696..7324bd86 100644 --- a/controllers/adminController.js +++ b/controllers/adminController.js @@ -142,10 +142,10 @@ const adminController = { toggleAdmin: (req, res) => { return User.findByPk(req.params.id).then((user) => { - const { isAdmin } = user.toJSON(); - if (isAdmin) { + const { isAdmin, email } = user.toJSON(); + if (email === "root@example.com") { req.flash("error_messages", "禁止變更管理者權限"); - res.redirect("back"); + return res.redirect("back"); } else { user.update({ isAdmin: !isAdmin }).then((user) => { req.flash("success_messages", "使用者權限變更成功"); From 1ac970d90a98a3e0af1b8761e747e4615484c042 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Sun, 14 Nov 2021 22:23:54 +0000 Subject: [PATCH 129/198] add category table and associate cateogry id to restaurant --- migrations/20211114220125-create-category.js | 27 +++++++++++++++++++ ...1114220312-add-categoryId-to-restaurant.js | 18 +++++++++++++ models/category.js | 20 ++++++++++++++ models/restaurant.js | 18 ++++++------- 4 files changed, 74 insertions(+), 9 deletions(-) create mode 100644 migrations/20211114220125-create-category.js create mode 100644 migrations/20211114220312-add-categoryId-to-restaurant.js create mode 100644 models/category.js diff --git a/migrations/20211114220125-create-category.js b/migrations/20211114220125-create-category.js new file mode 100644 index 00000000..ab0ee89a --- /dev/null +++ b/migrations/20211114220125-create-category.js @@ -0,0 +1,27 @@ +'use strict'; +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.createTable('Categories', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + name: { + type: Sequelize.STRING + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }); + }, + down: async (queryInterface, Sequelize) => { + await queryInterface.dropTable('Categories'); + } +}; \ No newline at end of file diff --git a/migrations/20211114220312-add-categoryId-to-restaurant.js b/migrations/20211114220312-add-categoryId-to-restaurant.js new file mode 100644 index 00000000..780d3c95 --- /dev/null +++ b/migrations/20211114220312-add-categoryId-to-restaurant.js @@ -0,0 +1,18 @@ +"use strict"; + +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.addColumn("Restaurants", "CategoryId", { + type: Sequelize.INTEGER, + allowNull: false, + references: { + model: "Categories", + key: "id", + }, + }); + }, + + down: async (queryInterface, Sequelize) => { + await queryInterface.removeColumn("Restaurants", "CategoryId"); + }, +}; diff --git a/models/category.js b/models/category.js new file mode 100644 index 00000000..814531d2 --- /dev/null +++ b/models/category.js @@ -0,0 +1,20 @@ +"use strict"; +const { Model } = require("sequelize"); +module.exports = (sequelize, DataTypes) => { + class Category extends Model { + static associate(models) { + Category.hasMany(models.Restaurant); + // define association here + } + } + Category.init( + { + name: DataTypes.STRING, + }, + { + sequelize, + modelName: "Category", + } + ); + return Category; +}; diff --git a/models/restaurant.js b/models/restaurant.js index d50ccd29..cddc174f 100644 --- a/models/restaurant.js +++ b/models/restaurant.js @@ -1,5 +1,5 @@ -'use strict' -const { Model } = require('sequelize') +"use strict"; +const { Model } = require("sequelize"); module.exports = (sequelize, DataTypes) => { class Restaurant extends Model { /** @@ -7,8 +7,8 @@ module.exports = (sequelize, DataTypes) => { * This method is not a part of Sequelize lifecycle. * The `models/index` file will call this method automatically. */ - static associate (models) { - // define association here + static associate(models) { + Restaurant.belongsTo(models.CAtegory); } } Restaurant.init( @@ -18,12 +18,12 @@ module.exports = (sequelize, DataTypes) => { address: DataTypes.STRING, opening_hours: DataTypes.STRING, description: DataTypes.TEXT, - image: DataTypes.STRING + image: DataTypes.STRING, }, { sequelize, - modelName: 'Restaurant' + modelName: "Restaurant", } - ) - return Restaurant -} + ); + return Restaurant; +}; From c29f21af146babe04410854bae65d92121a0fef1 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Mon, 15 Nov 2021 23:58:35 +0800 Subject: [PATCH 130/198] change restaurantId,userId to UserId, RestaurantId --- tests/R04.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/R04.test.js b/tests/R04.test.js index d736b2f3..fecfb969 100644 --- a/tests/R04.test.js +++ b/tests/R04.test.js @@ -10,8 +10,8 @@ const { createModelMock, createControllerProxy, mockRequest, mockResponse } = re // 建立模擬的 Like 資料 let mockLikeData = [ { - userId: 1, - restaurantId: 2, + UserId: 1, + RestaurantId: 2, }, ] From 85385f2f0fe48d9fbbc4bcb484d0e3f5c354dab3 Mon Sep 17 00:00:00 2001 From: Amber Yan Date: Tue, 16 Nov 2021 01:11:54 +0800 Subject: [PATCH 131/198] fixed typo --- tests/R05.test.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/R05.test.js b/tests/R05.test.js index 3709204f..58e46c06 100644 --- a/tests/R05.test.js +++ b/tests/R05.test.js @@ -16,7 +16,7 @@ let mockRestaurantData = [ address: 'address', opening_hours: 'opening_hours', description: 'test description', - favoritedUsers: [ + FavoritedUsers: [ { userId: 1, }, @@ -30,7 +30,7 @@ let mockRestaurantData = [ opening_hours: 'opening_hours', description: 'description', categoryId: 1, - favoritedUsers: [], + FavoritedUsers: [], }, ] @@ -44,7 +44,7 @@ describe('# R05: TOP 10 人氣餐廳 ', function () { // 模擬 getUser 函式,負責取得使用者資料 this.getUser = sinon .stub(helpers, 'getUser') - .returns({ id: 1, followings: [], favoritedRestaurants: [] }) + .returns({ id: 1, Followings: [], FavoritedRestaurants: [] }) }) it(' GET /restaurants/top ', (done) => { @@ -78,7 +78,7 @@ describe('# R05: TOP 10 人氣餐廳 ', function () { // 模擬 getUser 函式,負責取得使用者資料 this.getUser = sinon .stub(helpers, 'getUser') - .returns({ id: 1, followings: [], favoritedRestaurants: [] }) + .returns({ id: 1, Followings: [], FavoritedRestaurants: [] }) // 建立了一個模擬的 Restaurant table,裡面放入 2 間餐廳資料 this.restaurantMock = createModelMock('Restaurant', null, mockRestaurantData) @@ -99,7 +99,7 @@ describe('# R05: TOP 10 人氣餐廳 ', function () { // 模擬 request & response // 對 POST /favorite/2 發出請求,夾帶 user.favoritedRestaurants const req = mockRequest({ - user: { favoritedRestaurants: [] }, + user: { FavoritedRestaurants: [] }, params: { restaurantId: 2 }, }) const res = mockResponse() @@ -120,7 +120,7 @@ describe('# R05: TOP 10 人氣餐廳 ', function () { // 模擬 request & response // 對 DELETE /favorite/1 發出請求,夾入 user.favoritedRestaurants const req = mockRequest({ - user: { favoritedRestaurants: [] }, + user: { FavoritedRestaurants: [] }, params: { restaurantId: 1 }, }) const res = mockResponse() From d9c545072796318156b88165aa0436604d183080 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Mon, 15 Nov 2021 23:01:48 +0000 Subject: [PATCH 132/198] set up seeder file --- .../20211114222527-categories-seed-file.js | 28 +++++++++++++++++++ ...> 20211115230049-restaurants-seed-file.js} | 19 +++++++------ 2 files changed, 38 insertions(+), 9 deletions(-) create mode 100644 seeders/20211114222527-categories-seed-file.js rename seeders/{20211111003710-restaurants-seed-file.js => 20211115230049-restaurants-seed-file.js} (66%) diff --git a/seeders/20211114222527-categories-seed-file.js b/seeders/20211114222527-categories-seed-file.js new file mode 100644 index 00000000..274ee9f9 --- /dev/null +++ b/seeders/20211114222527-categories-seed-file.js @@ -0,0 +1,28 @@ +"use strict"; + +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.bulkInsert( + "Categories", + [ + "中式料理", + "日本料理", + "義大利料理", + "墨西哥料理", + "素食料理", + "美式料理", + "複合式料理", + ].map((item, index) => ({ + id: index * 10 + 1, + name: item, + createdAt: new Date(), + updatedAt: new Date(), + })), + {} + ); + }, + + down: async (queryInterface, Sequelize) => { + await queryInterface.bulkDelete("Categories", null, {}); + }, +}; diff --git a/seeders/20211111003710-restaurants-seed-file.js b/seeders/20211115230049-restaurants-seed-file.js similarity index 66% rename from seeders/20211111003710-restaurants-seed-file.js rename to seeders/20211115230049-restaurants-seed-file.js index e75658b8..064da8cb 100644 --- a/seeders/20211111003710-restaurants-seed-file.js +++ b/seeders/20211115230049-restaurants-seed-file.js @@ -1,27 +1,28 @@ -'use strict' -const faker = require('faker') +"use strict"; +const faker = require("faker"); module.exports = { up: async (queryInterface, Sequelize) => { await queryInterface.bulkInsert( - 'Restaurants', + "Restaurants", Array.from({ length: 50 }).map((d, i) => ({ name: faker.name.findName(), tel: faker.phone.phoneNumber(), address: faker.address.streetAddress(), - opening_hours: '08:00', + opening_hours: "08:00", image: `https://loremflickr.com/320/240/restaurant,food/?random=${ Math.random() * 100 }`, description: faker.lorem.text(), createdAt: new Date(), - updatedAt: new Date() + updatedAt: new Date(), + CategoryId: Math.floor(Math.random() * 6) * 10 + 1, })), {} - ) + ); }, down: async (queryInterface, Sequelize) => { - await queryInterface.bulkDelete('Restaurants', null, {}) - } -} + await queryInterface.bulkDelete("Restaurants", null, {}); + }, +}; From 7e2b869626681cafea3a11dacd2f78e34da17d06 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Mon, 15 Nov 2021 23:03:02 +0000 Subject: [PATCH 133/198] javascript styling fix --- controllers/adminController.js | 134 +++++++++--------- controllers/userController.js | 46 +++--- migrations/20211114220125-create-category.js | 8 +- ...1114220312-add-categoryId-to-restaurant.js | 18 +-- models/category.js | 18 +-- models/restaurant.js | 18 +-- .../20211114222527-categories-seed-file.js | 28 ++-- .../20211115230049-restaurants-seed-file.js | 18 +-- 8 files changed, 144 insertions(+), 144 deletions(-) diff --git a/controllers/adminController.js b/controllers/adminController.js index 7324bd86..b49c3ff2 100644 --- a/controllers/adminController.js +++ b/controllers/adminController.js @@ -1,44 +1,44 @@ -const db = require("../models"); -const Restaurant = db.Restaurant; -const User = db.User; +const db = require('../models') +const Restaurant = db.Restaurant +const User = db.User // imgur setup -const imgur = require("imgur-node-api"); -const IMGUR_CLIENT_ID = process.env.IMGUR_CLIENT_ID; +const imgur = require('imgur-node-api') +const IMGUR_CLIENT_ID = process.env.IMGUR_CLIENT_ID const adminController = { getRestaurants: (req, res) => { return Restaurant.findAll({ raw: true, nest: true }).then((restaurants) => { - return res.render("admin/restaurants", { restaurants: restaurants }); - }); + return res.render('admin/restaurants', { restaurants: restaurants }) + }) }, createRestaurants: (req, res) => { - return res.render("admin/create"); + return res.render('admin/create') }, postRestaurants: (req, res) => { if (!req.body.name) { - req.flash("error_messages", "name didn't exist"); - return res.redirect("back"); + req.flash('error_messages', "name didn't exist") + return res.redirect('back') } - const { name, tel, address, opening_hours, description } = req.body; - const { file } = req; + const { name, tel, address, opening_hours, description } = req.body + const { file } = req if (file) { - imgur.setClientID(IMGUR_CLIENT_ID); + imgur.setClientID(IMGUR_CLIENT_ID) imgur.upload(file.path, (err, img) => { - if (err) console.log("Error: ", err); + if (err) console.log('Error: ', err) return Restaurant.create({ name, tel, address, opening_hours, description, - image: file ? img.data.link : null, + image: file ? img.data.link : null }).then((restaurant) => { - req.flash("success_messages", "restaurant was successfully created"); - return res.redirect("/admin/restaurants"); - }); - }); + req.flash('success_messages', 'restaurant was successfully created') + return res.redirect('/admin/restaurants') + }) + }) } else { return Restaurant.create({ name, @@ -46,45 +46,45 @@ const adminController = { address, opening_hours, description, - image: null, + image: null }).then((restaurant) => { - req.flash("success_messages", "restaurant was successfully created"); - return res.redirect("/admin/restaurants"); - }); + req.flash('success_messages', 'restaurant was successfully created') + return res.redirect('/admin/restaurants') + }) } }, getRestaurant: (req, res) => { return Restaurant.findByPk(req.params.id, { raw: true, nest: true }).then( (restaurant) => { - return res.render("admin/restaurant", { - restaurant, - }); + return res.render('admin/restaurant', { + restaurant + }) } - ); + ) }, editRestaurant: (req, res) => { return Restaurant.findByPk(req.params.id, { raw: true, nest: true }).then( (restaurant) => { - return res.render("admin/create", { restaurant }); + return res.render('admin/create', { restaurant }) } - ); + ) }, putRestaurant: (req, res) => { - console.warn("put restaurant", req.file); + console.warn('put restaurant', req.file) if (!req.body.name) { - req.flash("error_message", "name didnt exit"); - return res.redirect("back"); + req.flash('error_message', 'name didnt exit') + return res.redirect('back') } - const { file } = req; - const { name, tel, address, opening_hours, description } = req.body; + const { file } = req + const { name, tel, address, opening_hours, description } = req.body if (file) { - imgur.setClientID(IMGUR_CLIENT_ID); + imgur.setClientID(IMGUR_CLIENT_ID) imgur.upload(file.path, (err, img) => { - if (err) console.log("Error: ", err); + if (err) console.log('Error: ', err) return Restaurant.findByPk(req.params.id).then((restaurant) => { restaurant .update({ @@ -93,17 +93,17 @@ const adminController = { address, opening_hours, description, - image: file ? img.data.link : restaurant.image, + image: file ? img.data.link : restaurant.image }) .then((restaurant) => { req.flash( - "success_messages", - "restaurant was successfully to update" - ); - res.redirect("/admin/restaurants"); - }); - }); - }); + 'success_messages', + 'restaurant was successfully to update' + ) + res.redirect('/admin/restaurants') + }) + }) + }) } else { return Restaurant.findByPk(req.params.id).then((restaurant) => { restaurant @@ -113,47 +113,47 @@ const adminController = { address, opening_hours, description, - image: restaurant.image, + image: restaurant.image }) .then((restaurant) => { req.flash( - "success_messages", - "restaurant was successfully to update" - ); - res.redirect("/admin/restaurants"); - }); - }); + 'success_messages', + 'restaurant was successfully to update' + ) + res.redirect('/admin/restaurants') + }) + }) } }, deleteRestaurant: (req, res) => { return Restaurant.findByPk(req.params.id).then((restaurant) => { restaurant.destroy().then((restaurant) => { - res.redirect("/admin/restaurants"); - }); - }); + res.redirect('/admin/restaurants') + }) + }) }, getUsers: (req, res) => { return User.findAll({ raw: true, nest: true }).then((users) => { - return res.render("admin/users", { users }); - }); + return res.render('admin/users', { users }) + }) }, toggleAdmin: (req, res) => { return User.findByPk(req.params.id).then((user) => { - const { isAdmin, email } = user.toJSON(); - if (email === "root@example.com") { - req.flash("error_messages", "禁止變更管理者權限"); - return res.redirect("back"); + const { isAdmin, email } = user.toJSON() + if (email === 'root@example.com') { + req.flash('error_messages', '禁止變更管理者權限') + return res.redirect('back') } else { user.update({ isAdmin: !isAdmin }).then((user) => { - req.flash("success_messages", "使用者權限變更成功"); - res.redirect("/admin/users"); - }); + req.flash('success_messages', '使用者權限變更成功') + res.redirect('/admin/users') + }) } - }); - }, -}; + }) + } +} -module.exports = adminController; +module.exports = adminController diff --git a/controllers/userController.js b/controllers/userController.js index d60c7546..b5fec9a4 100644 --- a/controllers/userController.js +++ b/controllers/userController.js @@ -1,50 +1,50 @@ -const bcrypt = require("bcrypt-nodejs"); -const db = require("../models"); -const User = db.User; +const bcrypt = require('bcrypt-nodejs') +const db = require('../models') +const User = db.User const userController = { signUpPage: (req, res) => { - return res.render("signup"); + return res.render('signup') }, signUp: (req, res) => { - const { name, email, password, passwordCheck } = req.body; + const { name, email, password, passwordCheck } = req.body if (password !== passwordCheck) { - req.flash("error_messages", "兩次密碼輸入不同"); - return res.redirect("/signup"); + req.flash('error_messages', '兩次密碼輸入不同') + return res.redirect('/signup') } else { User.findOne({ where: { email } }).then((user) => { if (user) { - req.flash("error_messages", "信箱已經註冊過"); - return res.redirect("/signup"); + req.flash('error_messages', '信箱已經註冊過') + return res.redirect('/signup') } else { User.create({ name, email, - password: bcrypt.hashSync(password, bcrypt.genSaltSync(10), null), + password: bcrypt.hashSync(password, bcrypt.genSaltSync(10), null) }).then((user) => { - req.flash("success_messages", "成功註冊帳號"); - return res.redirect("/signin"); - }); + req.flash('success_messages', '成功註冊帳號') + return res.redirect('/signin') + }) } - }); + }) } }, signInPage: (req, res) => { - return res.render("signin"); + return res.render('signin') }, signIn: (req, res) => { - req.flash("success_messages", "成功登入!"); - res.redirect("/restaurants"); + req.flash('success_messages', '成功登入!') + res.redirect('/restaurants') }, logout: (req, res) => { - req.flash("success_message", "登出成功!"); - req.logout(); - res.redirect("/signin"); - }, -}; + req.flash('success_message', '登出成功!') + req.logout() + res.redirect('/signin') + } +} -module.exports = userController; +module.exports = userController diff --git a/migrations/20211114220125-create-category.js b/migrations/20211114220125-create-category.js index ab0ee89a..d88e60cd 100644 --- a/migrations/20211114220125-create-category.js +++ b/migrations/20211114220125-create-category.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' module.exports = { up: async (queryInterface, Sequelize) => { await queryInterface.createTable('Categories', { @@ -19,9 +19,9 @@ module.exports = { allowNull: false, type: Sequelize.DATE } - }); + }) }, down: async (queryInterface, Sequelize) => { - await queryInterface.dropTable('Categories'); + await queryInterface.dropTable('Categories') } -}; \ No newline at end of file +} diff --git a/migrations/20211114220312-add-categoryId-to-restaurant.js b/migrations/20211114220312-add-categoryId-to-restaurant.js index 780d3c95..3cb7a30a 100644 --- a/migrations/20211114220312-add-categoryId-to-restaurant.js +++ b/migrations/20211114220312-add-categoryId-to-restaurant.js @@ -1,18 +1,18 @@ -"use strict"; +'use strict' module.exports = { up: async (queryInterface, Sequelize) => { - await queryInterface.addColumn("Restaurants", "CategoryId", { + await queryInterface.addColumn('Restaurants', 'CategoryId', { type: Sequelize.INTEGER, allowNull: false, references: { - model: "Categories", - key: "id", - }, - }); + model: 'Categories', + key: 'id' + } + }) }, down: async (queryInterface, Sequelize) => { - await queryInterface.removeColumn("Restaurants", "CategoryId"); - }, -}; + await queryInterface.removeColumn('Restaurants', 'CategoryId') + } +} diff --git a/models/category.js b/models/category.js index 814531d2..36a9ec54 100644 --- a/models/category.js +++ b/models/category.js @@ -1,20 +1,20 @@ -"use strict"; -const { Model } = require("sequelize"); +'use strict' +const { Model } = require('sequelize') module.exports = (sequelize, DataTypes) => { class Category extends Model { - static associate(models) { - Category.hasMany(models.Restaurant); + static associate (models) { + Category.hasMany(models.Restaurant) // define association here } } Category.init( { - name: DataTypes.STRING, + name: DataTypes.STRING }, { sequelize, - modelName: "Category", + modelName: 'Category' } - ); - return Category; -}; + ) + return Category +} diff --git a/models/restaurant.js b/models/restaurant.js index cddc174f..56268267 100644 --- a/models/restaurant.js +++ b/models/restaurant.js @@ -1,5 +1,5 @@ -"use strict"; -const { Model } = require("sequelize"); +'use strict' +const { Model } = require('sequelize') module.exports = (sequelize, DataTypes) => { class Restaurant extends Model { /** @@ -7,8 +7,8 @@ module.exports = (sequelize, DataTypes) => { * This method is not a part of Sequelize lifecycle. * The `models/index` file will call this method automatically. */ - static associate(models) { - Restaurant.belongsTo(models.CAtegory); + static associate (models) { + Restaurant.belongsTo(models.CAtegory) } } Restaurant.init( @@ -18,12 +18,12 @@ module.exports = (sequelize, DataTypes) => { address: DataTypes.STRING, opening_hours: DataTypes.STRING, description: DataTypes.TEXT, - image: DataTypes.STRING, + image: DataTypes.STRING }, { sequelize, - modelName: "Restaurant", + modelName: 'Restaurant' } - ); - return Restaurant; -}; + ) + return Restaurant +} diff --git a/seeders/20211114222527-categories-seed-file.js b/seeders/20211114222527-categories-seed-file.js index 274ee9f9..8b85992e 100644 --- a/seeders/20211114222527-categories-seed-file.js +++ b/seeders/20211114222527-categories-seed-file.js @@ -1,28 +1,28 @@ -"use strict"; +'use strict' module.exports = { up: async (queryInterface, Sequelize) => { await queryInterface.bulkInsert( - "Categories", + 'Categories', [ - "中式料理", - "日本料理", - "義大利料理", - "墨西哥料理", - "素食料理", - "美式料理", - "複合式料理", + '中式料理', + '日本料理', + '義大利料理', + '墨西哥料理', + '素食料理', + '美式料理', + '複合式料理' ].map((item, index) => ({ id: index * 10 + 1, name: item, createdAt: new Date(), - updatedAt: new Date(), + updatedAt: new Date() })), {} - ); + ) }, down: async (queryInterface, Sequelize) => { - await queryInterface.bulkDelete("Categories", null, {}); - }, -}; + await queryInterface.bulkDelete('Categories', null, {}) + } +} diff --git a/seeders/20211115230049-restaurants-seed-file.js b/seeders/20211115230049-restaurants-seed-file.js index 064da8cb..1c8f18e8 100644 --- a/seeders/20211115230049-restaurants-seed-file.js +++ b/seeders/20211115230049-restaurants-seed-file.js @@ -1,28 +1,28 @@ -"use strict"; -const faker = require("faker"); +'use strict' +const faker = require('faker') module.exports = { up: async (queryInterface, Sequelize) => { await queryInterface.bulkInsert( - "Restaurants", + 'Restaurants', Array.from({ length: 50 }).map((d, i) => ({ name: faker.name.findName(), tel: faker.phone.phoneNumber(), address: faker.address.streetAddress(), - opening_hours: "08:00", + opening_hours: '08:00', image: `https://loremflickr.com/320/240/restaurant,food/?random=${ Math.random() * 100 }`, description: faker.lorem.text(), createdAt: new Date(), updatedAt: new Date(), - CategoryId: Math.floor(Math.random() * 6) * 10 + 1, + CategoryId: Math.floor(Math.random() * 6) * 10 + 1 })), {} - ); + ) }, down: async (queryInterface, Sequelize) => { - await queryInterface.bulkDelete("Restaurants", null, {}); - }, -}; + await queryInterface.bulkDelete('Restaurants', null, {}) + } +} From b97773b90860ad36f82cbd99b3201e3012bcebbf Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Mon, 15 Nov 2021 23:09:49 +0000 Subject: [PATCH 134/198] render category info in restaurant lists --- controllers/adminController.js | 147 +++++++++++++++-------------- models/restaurant.js | 18 ++-- views/admin/restaurant.handlebars | 1 + views/admin/restaurants.handlebars | 2 + 4 files changed, 88 insertions(+), 80 deletions(-) diff --git a/controllers/adminController.js b/controllers/adminController.js index b49c3ff2..32027f78 100644 --- a/controllers/adminController.js +++ b/controllers/adminController.js @@ -1,44 +1,49 @@ -const db = require('../models') -const Restaurant = db.Restaurant -const User = db.User +const db = require("../models"); +const Category = db.Category; +const Restaurant = db.Restaurant; +const User = db.User; // imgur setup -const imgur = require('imgur-node-api') -const IMGUR_CLIENT_ID = process.env.IMGUR_CLIENT_ID +const imgur = require("imgur-node-api"); +const IMGUR_CLIENT_ID = process.env.IMGUR_CLIENT_ID; const adminController = { getRestaurants: (req, res) => { - return Restaurant.findAll({ raw: true, nest: true }).then((restaurants) => { - return res.render('admin/restaurants', { restaurants: restaurants }) - }) + return Restaurant.findAll({ + raw: true, + nest: true, + include: [Category], + }).then((restaurants) => { + return res.render("admin/restaurants", { restaurants: restaurants }); + }); }, createRestaurants: (req, res) => { - return res.render('admin/create') + return res.render("admin/create"); }, postRestaurants: (req, res) => { if (!req.body.name) { - req.flash('error_messages', "name didn't exist") - return res.redirect('back') + req.flash("error_messages", "name didn't exist"); + return res.redirect("back"); } - const { name, tel, address, opening_hours, description } = req.body - const { file } = req + const { name, tel, address, opening_hours, description } = req.body; + const { file } = req; if (file) { - imgur.setClientID(IMGUR_CLIENT_ID) + imgur.setClientID(IMGUR_CLIENT_ID); imgur.upload(file.path, (err, img) => { - if (err) console.log('Error: ', err) + if (err) console.log("Error: ", err); return Restaurant.create({ name, tel, address, opening_hours, description, - image: file ? img.data.link : null + image: file ? img.data.link : null, }).then((restaurant) => { - req.flash('success_messages', 'restaurant was successfully created') - return res.redirect('/admin/restaurants') - }) - }) + req.flash("success_messages", "restaurant was successfully created"); + return res.redirect("/admin/restaurants"); + }); + }); } else { return Restaurant.create({ name, @@ -46,45 +51,45 @@ const adminController = { address, opening_hours, description, - image: null + image: null, }).then((restaurant) => { - req.flash('success_messages', 'restaurant was successfully created') - return res.redirect('/admin/restaurants') - }) + req.flash("success_messages", "restaurant was successfully created"); + return res.redirect("/admin/restaurants"); + }); } }, getRestaurant: (req, res) => { - return Restaurant.findByPk(req.params.id, { raw: true, nest: true }).then( - (restaurant) => { - return res.render('admin/restaurant', { - restaurant - }) - } - ) + return Restaurant.findByPk(req.params.id, { + include: [Category], + }).then((restaurant) => { + return res.render("admin/restaurant", { + restaurant: restaurant.toJSON(), + }); + }); }, editRestaurant: (req, res) => { return Restaurant.findByPk(req.params.id, { raw: true, nest: true }).then( (restaurant) => { - return res.render('admin/create', { restaurant }) + return res.render("admin/create", { restaurant }); } - ) + ); }, putRestaurant: (req, res) => { - console.warn('put restaurant', req.file) + console.warn("put restaurant", req.file); if (!req.body.name) { - req.flash('error_message', 'name didnt exit') - return res.redirect('back') + req.flash("error_message", "name didnt exit"); + return res.redirect("back"); } - const { file } = req - const { name, tel, address, opening_hours, description } = req.body + const { file } = req; + const { name, tel, address, opening_hours, description } = req.body; if (file) { - imgur.setClientID(IMGUR_CLIENT_ID) + imgur.setClientID(IMGUR_CLIENT_ID); imgur.upload(file.path, (err, img) => { - if (err) console.log('Error: ', err) + if (err) console.log("Error: ", err); return Restaurant.findByPk(req.params.id).then((restaurant) => { restaurant .update({ @@ -93,17 +98,17 @@ const adminController = { address, opening_hours, description, - image: file ? img.data.link : restaurant.image + image: file ? img.data.link : restaurant.image, }) .then((restaurant) => { req.flash( - 'success_messages', - 'restaurant was successfully to update' - ) - res.redirect('/admin/restaurants') - }) - }) - }) + "success_messages", + "restaurant was successfully to update" + ); + res.redirect("/admin/restaurants"); + }); + }); + }); } else { return Restaurant.findByPk(req.params.id).then((restaurant) => { restaurant @@ -113,47 +118,47 @@ const adminController = { address, opening_hours, description, - image: restaurant.image + image: restaurant.image, }) .then((restaurant) => { req.flash( - 'success_messages', - 'restaurant was successfully to update' - ) - res.redirect('/admin/restaurants') - }) - }) + "success_messages", + "restaurant was successfully to update" + ); + res.redirect("/admin/restaurants"); + }); + }); } }, deleteRestaurant: (req, res) => { return Restaurant.findByPk(req.params.id).then((restaurant) => { restaurant.destroy().then((restaurant) => { - res.redirect('/admin/restaurants') - }) - }) + res.redirect("/admin/restaurants"); + }); + }); }, getUsers: (req, res) => { return User.findAll({ raw: true, nest: true }).then((users) => { - return res.render('admin/users', { users }) - }) + return res.render("admin/users", { users }); + }); }, toggleAdmin: (req, res) => { return User.findByPk(req.params.id).then((user) => { - const { isAdmin, email } = user.toJSON() - if (email === 'root@example.com') { - req.flash('error_messages', '禁止變更管理者權限') - return res.redirect('back') + const { isAdmin, email } = user.toJSON(); + if (email === "root@example.com") { + req.flash("error_messages", "禁止變更管理者權限"); + return res.redirect("back"); } else { user.update({ isAdmin: !isAdmin }).then((user) => { - req.flash('success_messages', '使用者權限變更成功') - res.redirect('/admin/users') - }) + req.flash("success_messages", "使用者權限變更成功"); + res.redirect("/admin/users"); + }); } - }) - } -} + }); + }, +}; -module.exports = adminController +module.exports = adminController; diff --git a/models/restaurant.js b/models/restaurant.js index 56268267..7bec8178 100644 --- a/models/restaurant.js +++ b/models/restaurant.js @@ -1,5 +1,5 @@ -'use strict' -const { Model } = require('sequelize') +"use strict"; +const { Model } = require("sequelize"); module.exports = (sequelize, DataTypes) => { class Restaurant extends Model { /** @@ -7,8 +7,8 @@ module.exports = (sequelize, DataTypes) => { * This method is not a part of Sequelize lifecycle. * The `models/index` file will call this method automatically. */ - static associate (models) { - Restaurant.belongsTo(models.CAtegory) + static associate(models) { + Restaurant.belongsTo(models.Category); } } Restaurant.init( @@ -18,12 +18,12 @@ module.exports = (sequelize, DataTypes) => { address: DataTypes.STRING, opening_hours: DataTypes.STRING, description: DataTypes.TEXT, - image: DataTypes.STRING + image: DataTypes.STRING, }, { sequelize, - modelName: 'Restaurant' + modelName: "Restaurant", } - ) - return Restaurant -} + ); + return Restaurant; +}; diff --git a/views/admin/restaurant.handlebars b/views/admin/restaurant.handlebars index 02fb1be4..8887abf9 100644 --- a/views/admin/restaurant.handlebars +++ b/views/admin/restaurant.handlebars @@ -1,6 +1,7 @@

{{restaurant.name}}

+

[{{restaurant.Category.name}}]

# + Category Name # @@ -23,6 +24,7 @@ {{#each restaurants}} {{this.id}} + {{this.Category.name}} {{this.name}}
+
+ + +
From 093f8cebac736f6db28ebbbef2bd54c4244247f9 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Mon, 15 Nov 2021 23:27:59 +0000 Subject: [PATCH 136/198] add helpers to set selected category --- app.js | 66 +++++++++++++++++++---------------- config/handlebars-helpers.js | 8 +++++ views/admin/create.handlebars | 2 +- 3 files changed, 45 insertions(+), 31 deletions(-) create mode 100644 config/handlebars-helpers.js diff --git a/app.js b/app.js index 56b3cf6b..9329210e 100644 --- a/app.js +++ b/app.js @@ -1,41 +1,47 @@ -const express = require('express') -const handlebars = require('express-handlebars') -const db = require('./models') -const flash = require('connect-flash') -const session = require('express-session') -const passport = require('./config/passport') -const methodOverride = require('method-override') +const express = require("express"); +const handlebars = require("express-handlebars"); +const db = require("./models"); +const flash = require("connect-flash"); +const session = require("express-session"); +const passport = require("./config/passport"); +const methodOverride = require("method-override"); -if (process.env.NODE_ENV !== 'production') { - require('dotenv').config() +if (process.env.NODE_ENV !== "production") { + require("dotenv").config(); } -const app = express() -const port = process.env.PORT || 3000 +const app = express(); +const port = process.env.PORT || 3000; -app.engine('handlebars', handlebars({ defaultLayout: 'main' })) -app.set('view engine', 'handlebars') -app.use(express.urlencoded({ extended: true })) -app.use(session({ secret: 'secret', resave: false, saveUninitialized: false })) -app.use(passport.initialize()) -app.use(passport.session()) -app.use(flash()) -app.use(methodOverride('_method')) -app.use('/upload', express.static(__dirname + '/upload')) +app.engine( + "handlebars", + handlebars({ + defaultLayout: "main", + helpers: require("./config/handlebars-helpers"), + }) +); +app.set("view engine", "handlebars"); +app.use(express.urlencoded({ extended: true })); +app.use(session({ secret: "secret", resave: false, saveUninitialized: false })); +app.use(passport.initialize()); +app.use(passport.session()); +app.use(flash()); +app.use(methodOverride("_method")); +app.use("/upload", express.static(__dirname + "/upload")); app.use((req, res, next) => { - res.locals.success_messages = req.flash('success_messages') - res.locals.error_messages = req.flash('error_messages') - res.locals.user = req.user - next() -}) + res.locals.success_messages = req.flash("success_messages"); + res.locals.error_messages = req.flash("error_messages"); + res.locals.user = req.user; + next(); +}); app.listen(port, () => { // what is this for? - db.sequelize.sync() - console.log(`Example app listening at http://localhost:${port}`) -}) + db.sequelize.sync(); + console.log(`Example app listening at http://localhost:${port}`); +}); -require('./routes')(app) +require("./routes")(app); -module.exports = app +module.exports = app; diff --git a/config/handlebars-helpers.js b/config/handlebars-helpers.js new file mode 100644 index 00000000..02c1497e --- /dev/null +++ b/config/handlebars-helpers.js @@ -0,0 +1,8 @@ +module.exports = { + ifCond: function (a, b, options) { + if (a === b) { + return options.fn(this); + } + return options.inverse(this); + }, +}; diff --git a/views/admin/create.handlebars b/views/admin/create.handlebars index 43b44c39..2d90d6ce 100644 --- a/views/admin/create.handlebars +++ b/views/admin/create.handlebars @@ -11,7 +11,7 @@
From f79b72f8bc5e5487393ac231f3d888517b75c4fe Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Tue, 16 Nov 2021 00:07:40 +0000 Subject: [PATCH 137/198] add admin/categories and getCategories function in categoryController --- controllers/categoryController.js | 14 ++++ routes/index.js | 106 +++++++++++++++-------------- views/admin/categories.handlebars | 49 +++++++++++++ views/admin/restaurants.handlebars | 2 + 4 files changed, 121 insertions(+), 50 deletions(-) create mode 100644 controllers/categoryController.js create mode 100644 views/admin/categories.handlebars diff --git a/controllers/categoryController.js b/controllers/categoryController.js new file mode 100644 index 00000000..24b541ec --- /dev/null +++ b/controllers/categoryController.js @@ -0,0 +1,14 @@ +const db = require("../models"); +const Category = db.Category; +let categoryController = { + getCategories: (req, res) => { + return Category.findAll({ + raw: true, + nest: true, + }).then((categories) => { + return res.render("admin/categories", { categories }); + }); + }, +}; + +module.exports = categoryController; diff --git a/routes/index.js b/routes/index.js index c4b15f9a..04055590 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,95 +1,101 @@ -const restControllers = require('../controllers/restController') -const adminController = require('../controllers/adminController') -const userController = require('../controllers/userController') -const passport = require('passport') -const helpers = require('../_helpers') +const restControllers = require("../controllers/restController"); +const adminController = require("../controllers/adminController"); +const userController = require("../controllers/userController"); +const passport = require("passport"); +const helpers = require("../_helpers"); // multer setup -const multer = require('multer') -const upload = multer({ dest: 'temp/' }) +const multer = require("multer"); +const categoryController = require("../controllers/categoryController"); +const upload = multer({ dest: "temp/" }); module.exports = (app) => { const authenticated = (req, res, next) => { if (helpers.ensureAuthenticated(req)) { - return next() + return next(); } - res.redirect('/signin') - } + res.redirect("/signin"); + }; const authenticatedAdmin = (req, res, next) => { if (helpers.ensureAuthenticated(req)) { if (helpers.getUser(req).isAdmin) { - return next() + return next(); } - return res.redirect('/') + return res.redirect("/"); } - res.redirect('/signin') - } + res.redirect("/signin"); + }; // 如果使用者訪問首頁,就導向 /restaurants 的頁面 - app.get('/', authenticated, (req, res) => { - res.redirect('restaurants') - }) - app.get('/restaurants', restControllers.getRestaurants) + app.get("/", authenticated, (req, res) => { + res.redirect("restaurants"); + }); + app.get("/restaurants", restControllers.getRestaurants); - app.get('/admin', authenticatedAdmin, (req, res) => { - res.redirect('/admin/restaurants') - }) + app.get("/admin", authenticatedAdmin, (req, res) => { + res.redirect("/admin/restaurants"); + }); app.get( - '/admin/restaurants', + "/admin/restaurants", authenticatedAdmin, adminController.getRestaurants - ) + ); - app.get('/signup', userController.signUpPage) - app.post('/signup', userController.signUp) + app.get("/signup", userController.signUpPage); + app.post("/signup", userController.signUp); - app.get('/signin', userController.signInPage) + app.get("/signin", userController.signInPage); app.post( - '/signin', - passport.authenticate('local', { - failureRedirect: '/signin', - failureFlash: true + "/signin", + passport.authenticate("local", { + failureRedirect: "/signin", + failureFlash: true, }), userController.signIn - ) - app.get('/logout', userController.logout) + ); + app.get("/logout", userController.logout); app.get( - '/admin/restaurants/create', + "/admin/restaurants/create", authenticatedAdmin, adminController.createRestaurants - ) + ); app.post( - '/admin/restaurants', + "/admin/restaurants", authenticatedAdmin, - upload.single('image'), + upload.single("image"), adminController.postRestaurants - ) + ); app.get( - '/admin/restaurants/:id', + "/admin/restaurants/:id", authenticatedAdmin, adminController.getRestaurant - ) + ); app.get( - '/admin/restaurants/:id/edit', + "/admin/restaurants/:id/edit", authenticatedAdmin, adminController.editRestaurant - ) + ); app.put( - '/admin/restaurants/:id', + "/admin/restaurants/:id", authenticatedAdmin, - upload.single('image'), + upload.single("image"), adminController.putRestaurant - ) + ); app.delete( - '/admin/restaurants/:id', + "/admin/restaurants/:id", authenticatedAdmin, adminController.deleteRestaurant - ) - app.get('/admin/users', authenticatedAdmin, adminController.getUsers) + ); + app.get("/admin/users", authenticatedAdmin, adminController.getUsers); app.put( - '/admin/users/:id/toggleAdmin', + "/admin/users/:id/toggleAdmin", authenticatedAdmin, adminController.toggleAdmin - ) -} + ); + app.get( + "/admin/categories", + authenticatedAdmin, + categoryController.getCategories + ); +}; diff --git a/views/admin/categories.handlebars b/views/admin/categories.handlebars new file mode 100644 index 00000000..a757e896 --- /dev/null +++ b/views/admin/categories.handlebars @@ -0,0 +1,49 @@ +

餐廳後台

+Restaurants +| +Categories +
+
+
+
+ + +
+
+
+
+ + + + + + + + + + {{#each categories}} + + + + + + {{/each}} + +
#Category Name#
{{this.id}}{{this.name}} + +
+ +
+
+
+
\ No newline at end of file diff --git a/views/admin/restaurants.handlebars b/views/admin/restaurants.handlebars index 483592f0..0825e798 100644 --- a/views/admin/restaurants.handlebars +++ b/views/admin/restaurants.handlebars @@ -3,6 +3,8 @@ Restaurant | User + | +Category From 5d9b9dfab6282da1e7151c8ae7e7ed892df1273c Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Tue, 16 Nov 2021 00:11:43 +0000 Subject: [PATCH 138/198] add post category feature --- controllers/categoryController.js | 10 ++++++++++ routes/index.js | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/controllers/categoryController.js b/controllers/categoryController.js index 24b541ec..d560c37e 100644 --- a/controllers/categoryController.js +++ b/controllers/categoryController.js @@ -9,6 +9,16 @@ let categoryController = { return res.render("admin/categories", { categories }); }); }, + postCategory: (req, res) => { + if (!req.body.name) { + req.flash("error_messages", "name didn't exist"); + return res.redirect("back"); + } else { + return Category.create({ name: req.body.name }).then((category) => { + re.redirect("/admin/categories"); + }); + } + }, }; module.exports = categoryController; diff --git a/routes/index.js b/routes/index.js index 04055590..a60aa1b3 100644 --- a/routes/index.js +++ b/routes/index.js @@ -98,4 +98,9 @@ module.exports = (app) => { authenticatedAdmin, categoryController.getCategories ); + app.put( + "/admin/categories", + authenticatedAdmin, + categoryController.postCategory + ); }; From 73e2a62d02035d6984a7dd707dc4600d5fef0f1b Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Tue, 16 Nov 2021 20:52:54 +0000 Subject: [PATCH 139/198] update category --- controllers/categoryController.js | 25 ++++++++++++- routes/index.js | 12 ++++++- views/admin/categories.handlebars | 59 ++++++++++++++++--------------- views/admin/users.handlebars | 2 ++ 4 files changed, 68 insertions(+), 30 deletions(-) diff --git a/controllers/categoryController.js b/controllers/categoryController.js index d560c37e..4562deb5 100644 --- a/controllers/categoryController.js +++ b/controllers/categoryController.js @@ -6,7 +6,18 @@ let categoryController = { raw: true, nest: true, }).then((categories) => { - return res.render("admin/categories", { categories }); + if (req.params.id) { + Category.findByPk(req.params.id).then((category) => { + if (category) { + return res.render("admin/categories", { + categories, + category: category.toJSON(), + }); + } + }); + } else { + return res.render("admin/categories", { categories }); + } }); }, postCategory: (req, res) => { @@ -19,6 +30,18 @@ let categoryController = { }); } }, + putCategory: (req, res) => { + if (!req.body.name) { + req.flash("error_messages", "name didn't exist"); + return res.redirect("back"); + } else { + return Category.findByPk(req.params.id).then((category) => { + category + .update(req.body) + .then((category) => res.redirect("/admin/categories")); + }); + } + }, }; module.exports = categoryController; diff --git a/routes/index.js b/routes/index.js index a60aa1b3..0d11ca42 100644 --- a/routes/index.js +++ b/routes/index.js @@ -98,9 +98,19 @@ module.exports = (app) => { authenticatedAdmin, categoryController.getCategories ); - app.put( + app.post( "/admin/categories", authenticatedAdmin, categoryController.postCategory ); + app.get( + "/admin/categories/:id", + authenticatedAdmin, + categoryController.getCategories + ); + app.put( + "/admin/categories/:id", + authenticatedAdmin, + categoryController.putCategory + ); }; diff --git a/views/admin/categories.handlebars b/views/admin/categories.handlebars index a757e896..effcba74 100644 --- a/views/admin/categories.handlebars +++ b/views/admin/categories.handlebars @@ -1,16 +1,23 @@

餐廳後台

-
Restaurants -| -Categories +Restaurant + | +User + | +Category

-
-
- - -
+
+ {{#if category}} +
+ + + or Create New + {{else}} + + + +
+ {{/if}}


@@ -24,24 +31,20 @@ {{#each categories}} - - {{this.id}} - {{this.name}} - - -
- -
- - + + {{this.id}} + {{this.name}} + + +
+ +
+ + {{/each}} diff --git a/views/admin/users.handlebars b/views/admin/users.handlebars index d150f5ff..53a4113d 100644 --- a/views/admin/users.handlebars +++ b/views/admin/users.handlebars @@ -3,6 +3,8 @@ Restaurant | User + | +Category

From 01a236a3ad71509f8cb107871d7b2d78a83db7d3 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Tue, 16 Nov 2021 20:56:03 +0000 Subject: [PATCH 140/198] delete category --- controllers/categoryController.js | 7 +++++++ routes/index.js | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/controllers/categoryController.js b/controllers/categoryController.js index 4562deb5..4cf7082c 100644 --- a/controllers/categoryController.js +++ b/controllers/categoryController.js @@ -42,6 +42,13 @@ let categoryController = { }); } }, + deletCategory: (req, res) => { + return Category.findByPk(req.params.id).then((category) => { + category.destroy().then((category) => { + res.redirect("/admin/categories"); + }); + }); + }, }; module.exports = categoryController; diff --git a/routes/index.js b/routes/index.js index 0d11ca42..6b45f137 100644 --- a/routes/index.js +++ b/routes/index.js @@ -113,4 +113,9 @@ module.exports = (app) => { authenticatedAdmin, categoryController.putCategory ); + app.delete( + "/admin/categories/:id", + authenticatedAdmin, + categoryController.deletCategory + ); }; From 8b8cb28c79fc65df9b36958cf52a0695e0ddfb9a Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Tue, 16 Nov 2021 21:22:23 +0000 Subject: [PATCH 141/198] get all restaurants for normal user --- controllers/restController.js | 22 ++++++++++++++++++---- views/restaurants.handlebars | 29 ++++++++++++++++++++++++++--- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/controllers/restController.js b/controllers/restController.js index aedaa885..1e1b6b57 100644 --- a/controllers/restController.js +++ b/controllers/restController.js @@ -1,7 +1,21 @@ +const db = require("../models"); +const Restaurant = db.Restaurant; +const Category = db.Category; + const restControllers = { getRestaurants: (req, res) => { - return res.render('restaurants') - } -} + Restaurant.findAll({ raw: true, nest: true, include: Category }).then( + (restaurants) => { + console.log(restaurants); + const data = restaurants.map((r) => ({ + ...r, + description: r.description.substring(0, 50), + categoryName: r.Category.name, + })); + return res.render("restaurants", { restaurants: data }); + } + ); + }, +}; -module.exports = restControllers +module.exports = restControllers; diff --git a/views/restaurants.handlebars b/views/restaurants.handlebars index 97b6e76e..53bac5eb 100644 --- a/views/restaurants.handlebars +++ b/views/restaurants.handlebars @@ -1,3 +1,26 @@ -
- restaurants -
\ No newline at end of file +
+ {{#each restaurants}} +
+
+ Card image cap +
+

+ + {{this.name}} + +

+ {{this.categoryName}} +

{{this.description}}

+
+
+
+ {{/each}} +
+
+
\ No newline at end of file From 0b4d87a97fe9c1f8459054d1fa33959ebf597f12 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Tue, 16 Nov 2021 21:36:51 +0000 Subject: [PATCH 142/198] show restaurant detail --- controllers/restController.js | 10 +++++++++- routes/index.js | 5 +++-- views/restaurant.handlebars | 27 +++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 views/restaurant.handlebars diff --git a/controllers/restController.js b/controllers/restController.js index 1e1b6b57..b2aa9dae 100644 --- a/controllers/restController.js +++ b/controllers/restController.js @@ -6,7 +6,6 @@ const restControllers = { getRestaurants: (req, res) => { Restaurant.findAll({ raw: true, nest: true, include: Category }).then( (restaurants) => { - console.log(restaurants); const data = restaurants.map((r) => ({ ...r, description: r.description.substring(0, 50), @@ -16,6 +15,15 @@ const restControllers = { } ); }, + getRestaurant: (req, res) => { + return Restaurant.findByPk(req.params.id, { + include: Category, + }).then((restaurant) => { + return res.render("restaurant", { + restaurant: restaurant.toJSON(), + }); + }); + }, }; module.exports = restControllers; diff --git a/routes/index.js b/routes/index.js index 6b45f137..27d163a7 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,4 +1,4 @@ -const restControllers = require("../controllers/restController"); +const restController = require("../controllers/restController"); const adminController = require("../controllers/adminController"); const userController = require("../controllers/userController"); const passport = require("passport"); @@ -31,7 +31,7 @@ module.exports = (app) => { app.get("/", authenticated, (req, res) => { res.redirect("restaurants"); }); - app.get("/restaurants", restControllers.getRestaurants); + app.get("/restaurants", restController.getRestaurants); app.get("/admin", authenticatedAdmin, (req, res) => { res.redirect("/admin/restaurants"); @@ -118,4 +118,5 @@ module.exports = (app) => { authenticatedAdmin, categoryController.deletCategory ); + app.get("/restaurants/:id", restController.getRestaurant); }; diff --git a/views/restaurant.handlebars b/views/restaurant.handlebars new file mode 100644 index 00000000..0f893ce3 --- /dev/null +++ b/views/restaurant.handlebars @@ -0,0 +1,27 @@ +
+
+

{{restaurant.name}}

+

[{{restaurant.Category.name}}]

+
+
+ +
+
    +
  • Opening Hour: {{restaurant.opening_hours}}
  • +
  • Tel: {{restaurant.tel}}
  • +
  • Address: {{restaurant.address}}
  • +
+
+
+
+

{{restaurant.description}}

+
+
+
+
+
+回上一頁 \ No newline at end of file From 2800f4d9f1a4509cd7ad989ce509f295f6c21e5d Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Tue, 16 Nov 2021 21:37:32 +0000 Subject: [PATCH 143/198] fix javascript styling --- app.js | 66 +++++++------- config/handlebars-helpers.js | 8 +- controllers/adminController.js | 144 +++++++++++++++--------------- controllers/categoryController.js | 48 +++++----- controllers/restController.js | 30 +++---- models/restaurant.js | 18 ++-- routes/index.js | 124 ++++++++++++------------- 7 files changed, 219 insertions(+), 219 deletions(-) diff --git a/app.js b/app.js index 9329210e..860ee3b5 100644 --- a/app.js +++ b/app.js @@ -1,47 +1,47 @@ -const express = require("express"); -const handlebars = require("express-handlebars"); -const db = require("./models"); -const flash = require("connect-flash"); -const session = require("express-session"); -const passport = require("./config/passport"); -const methodOverride = require("method-override"); +const express = require('express') +const handlebars = require('express-handlebars') +const db = require('./models') +const flash = require('connect-flash') +const session = require('express-session') +const passport = require('./config/passport') +const methodOverride = require('method-override') -if (process.env.NODE_ENV !== "production") { - require("dotenv").config(); +if (process.env.NODE_ENV !== 'production') { + require('dotenv').config() } -const app = express(); -const port = process.env.PORT || 3000; +const app = express() +const port = process.env.PORT || 3000 app.engine( - "handlebars", + 'handlebars', handlebars({ - defaultLayout: "main", - helpers: require("./config/handlebars-helpers"), + defaultLayout: 'main', + helpers: require('./config/handlebars-helpers') }) -); -app.set("view engine", "handlebars"); -app.use(express.urlencoded({ extended: true })); -app.use(session({ secret: "secret", resave: false, saveUninitialized: false })); -app.use(passport.initialize()); -app.use(passport.session()); -app.use(flash()); -app.use(methodOverride("_method")); -app.use("/upload", express.static(__dirname + "/upload")); +) +app.set('view engine', 'handlebars') +app.use(express.urlencoded({ extended: true })) +app.use(session({ secret: 'secret', resave: false, saveUninitialized: false })) +app.use(passport.initialize()) +app.use(passport.session()) +app.use(flash()) +app.use(methodOverride('_method')) +app.use('/upload', express.static(__dirname + '/upload')) app.use((req, res, next) => { - res.locals.success_messages = req.flash("success_messages"); - res.locals.error_messages = req.flash("error_messages"); - res.locals.user = req.user; - next(); -}); + res.locals.success_messages = req.flash('success_messages') + res.locals.error_messages = req.flash('error_messages') + res.locals.user = req.user + next() +}) app.listen(port, () => { // what is this for? - db.sequelize.sync(); - console.log(`Example app listening at http://localhost:${port}`); -}); + db.sequelize.sync() + console.log(`Example app listening at http://localhost:${port}`) +}) -require("./routes")(app); +require('./routes')(app) -module.exports = app; +module.exports = app diff --git a/config/handlebars-helpers.js b/config/handlebars-helpers.js index 02c1497e..c667f781 100644 --- a/config/handlebars-helpers.js +++ b/config/handlebars-helpers.js @@ -1,8 +1,8 @@ module.exports = { ifCond: function (a, b, options) { if (a === b) { - return options.fn(this); + return options.fn(this) } - return options.inverse(this); - }, -}; + return options.inverse(this) + } +} diff --git a/controllers/adminController.js b/controllers/adminController.js index 2b488833..6db44d29 100644 --- a/controllers/adminController.js +++ b/controllers/adminController.js @@ -1,40 +1,40 @@ -const db = require("../models"); -const Category = db.Category; -const Restaurant = db.Restaurant; -const User = db.User; +const db = require('../models') +const Category = db.Category +const Restaurant = db.Restaurant +const User = db.User // imgur setup -const imgur = require("imgur-node-api"); -const IMGUR_CLIENT_ID = process.env.IMGUR_CLIENT_ID; +const imgur = require('imgur-node-api') +const IMGUR_CLIENT_ID = process.env.IMGUR_CLIENT_ID const adminController = { getRestaurants: (req, res) => { return Restaurant.findAll({ raw: true, nest: true, - include: [Category], + include: [Category] }).then((restaurants) => { - return res.render("admin/restaurants", { restaurants: restaurants }); - }); + return res.render('admin/restaurants', { restaurants: restaurants }) + }) }, createRestaurants: (req, res) => { Category.findAll({ raw: true, nest: true }).then((categories) => { - return res.render("admin/create", { categories }); - }); + return res.render('admin/create', { categories }) + }) }, postRestaurants: (req, res) => { if (!req.body.name) { - req.flash("error_messages", "name didn't exist"); - return res.redirect("back"); + req.flash('error_messages', "name didn't exist") + return res.redirect('back') } const { name, tel, address, opening_hours, description, categoryId } = - req.body; - const { file } = req; + req.body + const { file } = req if (file) { - imgur.setClientID(IMGUR_CLIENT_ID); + imgur.setClientID(IMGUR_CLIENT_ID) imgur.upload(file.path, (err, img) => { - if (err) console.log("Error: ", err); + if (err) console.log('Error: ', err) return Restaurant.create({ name, tel, @@ -42,12 +42,12 @@ const adminController = { opening_hours, description, image: file ? img.data.link : null, - categoryId, + categoryId }).then((restaurant) => { - req.flash("success_messages", "restaurant was successfully created"); - return res.redirect("/admin/restaurants"); - }); - }); + req.flash('success_messages', 'restaurant was successfully created') + return res.redirect('/admin/restaurants') + }) + }) } else { return Restaurant.create({ name, @@ -55,48 +55,48 @@ const adminController = { address, opening_hours, description, - image: null, + image: null }).then((restaurant) => { - req.flash("success_messages", "restaurant was successfully created"); - return res.redirect("/admin/restaurants"); - }); + req.flash('success_messages', 'restaurant was successfully created') + return res.redirect('/admin/restaurants') + }) } }, getRestaurant: (req, res) => { return Restaurant.findByPk(req.params.id, { - include: [Category], + include: [Category] }).then((restaurant) => { - return res.render("admin/restaurant", { - restaurant: restaurant.toJSON(), - }); - }); + return res.render('admin/restaurant', { + restaurant: restaurant.toJSON() + }) + }) }, editRestaurant: (req, res) => { Category.findAll({ raw: true, nest: true }).then((categories) => { return Restaurant.findByPk(req.params.id, { raw: true, nest: true }).then( (restaurant) => { - return res.render("admin/create", { restaurant, categories }); + return res.render('admin/create', { restaurant, categories }) } - ); - }); + ) + }) }, putRestaurant: (req, res) => { - console.warn("put restaurant", req.file); + console.warn('put restaurant', req.file) if (!req.body.name) { - req.flash("error_message", "name didnt exit"); - return res.redirect("back"); + req.flash('error_message', 'name didnt exit') + return res.redirect('back') } - const { file } = req; + const { file } = req const { name, tel, address, opening_hours, description, categoryId } = - req.body; + req.body if (file) { - imgur.setClientID(IMGUR_CLIENT_ID); + imgur.setClientID(IMGUR_CLIENT_ID) imgur.upload(file.path, (err, img) => { - if (err) console.log("Error: ", err); + if (err) console.log('Error: ', err) return Restaurant.findByPk(req.params.id).then((restaurant) => { restaurant .update({ @@ -106,17 +106,17 @@ const adminController = { opening_hours, description, image: file ? img.data.link : restaurant.image, - categoryId, + categoryId }) .then((restaurant) => { req.flash( - "success_messages", - "restaurant was successfully to update" - ); - res.redirect("/admin/restaurants"); - }); - }); - }); + 'success_messages', + 'restaurant was successfully to update' + ) + res.redirect('/admin/restaurants') + }) + }) + }) } else { return Restaurant.findByPk(req.params.id).then((restaurant) => { restaurant @@ -126,47 +126,47 @@ const adminController = { address, opening_hours, description, - image: restaurant.image, + image: restaurant.image }) .then((restaurant) => { req.flash( - "success_messages", - "restaurant was successfully to update" - ); - res.redirect("/admin/restaurants"); - }); - }); + 'success_messages', + 'restaurant was successfully to update' + ) + res.redirect('/admin/restaurants') + }) + }) } }, deleteRestaurant: (req, res) => { return Restaurant.findByPk(req.params.id).then((restaurant) => { restaurant.destroy().then((restaurant) => { - res.redirect("/admin/restaurants"); - }); - }); + res.redirect('/admin/restaurants') + }) + }) }, getUsers: (req, res) => { return User.findAll({ raw: true, nest: true }).then((users) => { - return res.render("admin/users", { users }); - }); + return res.render('admin/users', { users }) + }) }, toggleAdmin: (req, res) => { return User.findByPk(req.params.id).then((user) => { - const { isAdmin, email } = user.toJSON(); - if (email === "root@example.com") { - req.flash("error_messages", "禁止變更管理者權限"); - return res.redirect("back"); + const { isAdmin, email } = user.toJSON() + if (email === 'root@example.com') { + req.flash('error_messages', '禁止變更管理者權限') + return res.redirect('back') } else { user.update({ isAdmin: !isAdmin }).then((user) => { - req.flash("success_messages", "使用者權限變更成功"); - res.redirect("/admin/users"); - }); + req.flash('success_messages', '使用者權限變更成功') + res.redirect('/admin/users') + }) } - }); - }, -}; + }) + } +} -module.exports = adminController; +module.exports = adminController diff --git a/controllers/categoryController.js b/controllers/categoryController.js index 4cf7082c..288b56e0 100644 --- a/controllers/categoryController.js +++ b/controllers/categoryController.js @@ -1,54 +1,54 @@ -const db = require("../models"); -const Category = db.Category; -let categoryController = { +const db = require('../models') +const Category = db.Category +const categoryController = { getCategories: (req, res) => { return Category.findAll({ raw: true, - nest: true, + nest: true }).then((categories) => { if (req.params.id) { Category.findByPk(req.params.id).then((category) => { if (category) { - return res.render("admin/categories", { + return res.render('admin/categories', { categories, - category: category.toJSON(), - }); + category: category.toJSON() + }) } - }); + }) } else { - return res.render("admin/categories", { categories }); + return res.render('admin/categories', { categories }) } - }); + }) }, postCategory: (req, res) => { if (!req.body.name) { - req.flash("error_messages", "name didn't exist"); - return res.redirect("back"); + req.flash('error_messages', "name didn't exist") + return res.redirect('back') } else { return Category.create({ name: req.body.name }).then((category) => { - re.redirect("/admin/categories"); - }); + re.redirect('/admin/categories') + }) } }, putCategory: (req, res) => { if (!req.body.name) { - req.flash("error_messages", "name didn't exist"); - return res.redirect("back"); + req.flash('error_messages', "name didn't exist") + return res.redirect('back') } else { return Category.findByPk(req.params.id).then((category) => { category .update(req.body) - .then((category) => res.redirect("/admin/categories")); - }); + .then((category) => res.redirect('/admin/categories')) + }) } }, deletCategory: (req, res) => { return Category.findByPk(req.params.id).then((category) => { category.destroy().then((category) => { - res.redirect("/admin/categories"); - }); - }); - }, -}; + res.redirect('/admin/categories') + }) + }) + } +} -module.exports = categoryController; +module.exports = categoryController diff --git a/controllers/restController.js b/controllers/restController.js index b2aa9dae..11af0cc2 100644 --- a/controllers/restController.js +++ b/controllers/restController.js @@ -1,6 +1,6 @@ -const db = require("../models"); -const Restaurant = db.Restaurant; -const Category = db.Category; +const db = require('../models') +const Restaurant = db.Restaurant +const Category = db.Category const restControllers = { getRestaurants: (req, res) => { @@ -9,21 +9,21 @@ const restControllers = { const data = restaurants.map((r) => ({ ...r, description: r.description.substring(0, 50), - categoryName: r.Category.name, - })); - return res.render("restaurants", { restaurants: data }); + categoryName: r.Category.name + })) + return res.render('restaurants', { restaurants: data }) } - ); + ) }, getRestaurant: (req, res) => { return Restaurant.findByPk(req.params.id, { - include: Category, + include: Category }).then((restaurant) => { - return res.render("restaurant", { - restaurant: restaurant.toJSON(), - }); - }); - }, -}; + return res.render('restaurant', { + restaurant: restaurant.toJSON() + }) + }) + } +} -module.exports = restControllers; +module.exports = restControllers diff --git a/models/restaurant.js b/models/restaurant.js index 7bec8178..efa6694b 100644 --- a/models/restaurant.js +++ b/models/restaurant.js @@ -1,5 +1,5 @@ -"use strict"; -const { Model } = require("sequelize"); +'use strict' +const { Model } = require('sequelize') module.exports = (sequelize, DataTypes) => { class Restaurant extends Model { /** @@ -7,8 +7,8 @@ module.exports = (sequelize, DataTypes) => { * This method is not a part of Sequelize lifecycle. * The `models/index` file will call this method automatically. */ - static associate(models) { - Restaurant.belongsTo(models.Category); + static associate (models) { + Restaurant.belongsTo(models.Category) } } Restaurant.init( @@ -18,12 +18,12 @@ module.exports = (sequelize, DataTypes) => { address: DataTypes.STRING, opening_hours: DataTypes.STRING, description: DataTypes.TEXT, - image: DataTypes.STRING, + image: DataTypes.STRING }, { sequelize, - modelName: "Restaurant", + modelName: 'Restaurant' } - ); - return Restaurant; -}; + ) + return Restaurant +} diff --git a/routes/index.js b/routes/index.js index 27d163a7..46b4af10 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,122 +1,122 @@ -const restController = require("../controllers/restController"); -const adminController = require("../controllers/adminController"); -const userController = require("../controllers/userController"); -const passport = require("passport"); -const helpers = require("../_helpers"); +const restController = require('../controllers/restController') +const adminController = require('../controllers/adminController') +const userController = require('../controllers/userController') +const passport = require('passport') +const helpers = require('../_helpers') // multer setup -const multer = require("multer"); -const categoryController = require("../controllers/categoryController"); -const upload = multer({ dest: "temp/" }); +const multer = require('multer') +const categoryController = require('../controllers/categoryController') +const upload = multer({ dest: 'temp/' }) module.exports = (app) => { const authenticated = (req, res, next) => { if (helpers.ensureAuthenticated(req)) { - return next(); + return next() } - res.redirect("/signin"); - }; + res.redirect('/signin') + } const authenticatedAdmin = (req, res, next) => { if (helpers.ensureAuthenticated(req)) { if (helpers.getUser(req).isAdmin) { - return next(); + return next() } - return res.redirect("/"); + return res.redirect('/') } - res.redirect("/signin"); - }; + res.redirect('/signin') + } // 如果使用者訪問首頁,就導向 /restaurants 的頁面 - app.get("/", authenticated, (req, res) => { - res.redirect("restaurants"); - }); - app.get("/restaurants", restController.getRestaurants); + app.get('/', authenticated, (req, res) => { + res.redirect('restaurants') + }) + app.get('/restaurants', restController.getRestaurants) - app.get("/admin", authenticatedAdmin, (req, res) => { - res.redirect("/admin/restaurants"); - }); + app.get('/admin', authenticatedAdmin, (req, res) => { + res.redirect('/admin/restaurants') + }) app.get( - "/admin/restaurants", + '/admin/restaurants', authenticatedAdmin, adminController.getRestaurants - ); + ) - app.get("/signup", userController.signUpPage); - app.post("/signup", userController.signUp); + app.get('/signup', userController.signUpPage) + app.post('/signup', userController.signUp) - app.get("/signin", userController.signInPage); + app.get('/signin', userController.signInPage) app.post( - "/signin", - passport.authenticate("local", { - failureRedirect: "/signin", - failureFlash: true, + '/signin', + passport.authenticate('local', { + failureRedirect: '/signin', + failureFlash: true }), userController.signIn - ); - app.get("/logout", userController.logout); + ) + app.get('/logout', userController.logout) app.get( - "/admin/restaurants/create", + '/admin/restaurants/create', authenticatedAdmin, adminController.createRestaurants - ); + ) app.post( - "/admin/restaurants", + '/admin/restaurants', authenticatedAdmin, - upload.single("image"), + upload.single('image'), adminController.postRestaurants - ); + ) app.get( - "/admin/restaurants/:id", + '/admin/restaurants/:id', authenticatedAdmin, adminController.getRestaurant - ); + ) app.get( - "/admin/restaurants/:id/edit", + '/admin/restaurants/:id/edit', authenticatedAdmin, adminController.editRestaurant - ); + ) app.put( - "/admin/restaurants/:id", + '/admin/restaurants/:id', authenticatedAdmin, - upload.single("image"), + upload.single('image'), adminController.putRestaurant - ); + ) app.delete( - "/admin/restaurants/:id", + '/admin/restaurants/:id', authenticatedAdmin, adminController.deleteRestaurant - ); - app.get("/admin/users", authenticatedAdmin, adminController.getUsers); + ) + app.get('/admin/users', authenticatedAdmin, adminController.getUsers) app.put( - "/admin/users/:id/toggleAdmin", + '/admin/users/:id/toggleAdmin', authenticatedAdmin, adminController.toggleAdmin - ); + ) app.get( - "/admin/categories", + '/admin/categories', authenticatedAdmin, categoryController.getCategories - ); + ) app.post( - "/admin/categories", + '/admin/categories', authenticatedAdmin, categoryController.postCategory - ); + ) app.get( - "/admin/categories/:id", + '/admin/categories/:id', authenticatedAdmin, categoryController.getCategories - ); + ) app.put( - "/admin/categories/:id", + '/admin/categories/:id', authenticatedAdmin, categoryController.putCategory - ); + ) app.delete( - "/admin/categories/:id", + '/admin/categories/:id', authenticatedAdmin, categoryController.deletCategory - ); - app.get("/restaurants/:id", restController.getRestaurant); -}; + ) + app.get('/restaurants/:id', restController.getRestaurant) +} From 7ed0de0397a740550c821b46eb59ef158bb25b4a Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Tue, 16 Nov 2021 23:26:31 +0000 Subject: [PATCH 144/198] filter restaurant list --- config/handlebars-helpers.js | 8 ++--- controllers/restController.js | 57 ++++++++++++++++++++++------------- views/restaurants.handlebars | 13 ++++++++ 3 files changed, 53 insertions(+), 25 deletions(-) diff --git a/config/handlebars-helpers.js b/config/handlebars-helpers.js index c667f781..02c1497e 100644 --- a/config/handlebars-helpers.js +++ b/config/handlebars-helpers.js @@ -1,8 +1,8 @@ module.exports = { ifCond: function (a, b, options) { if (a === b) { - return options.fn(this) + return options.fn(this); } - return options.inverse(this) - } -} + return options.inverse(this); + }, +}; diff --git a/controllers/restController.js b/controllers/restController.js index 11af0cc2..906c8ad3 100644 --- a/controllers/restController.js +++ b/controllers/restController.js @@ -1,29 +1,44 @@ -const db = require('../models') -const Restaurant = db.Restaurant -const Category = db.Category +const db = require("../models"); +const Restaurant = db.Restaurant; +const Category = db.Category; const restControllers = { getRestaurants: (req, res) => { - Restaurant.findAll({ raw: true, nest: true, include: Category }).then( - (restaurants) => { - const data = restaurants.map((r) => ({ - ...r, - description: r.description.substring(0, 50), - categoryName: r.Category.name - })) - return res.render('restaurants', { restaurants: data }) - } - ) + const whereQuery = {}; + let categoryId = ""; + if (req.query.categoryId) { + categoryId = Number(req.query.categoryId); + whereQuery.CategoryId = categoryId; + } + Restaurant.findAll({ + raw: true, + nest: true, + include: Category, + where: whereQuery, + }).then((restaurants) => { + const data = restaurants.map((r) => ({ + ...r, + description: r.description.substring(0, 50), + categoryName: r.Category.name, + })); + Category.findAll({ raw: true, nest: true }).then((categories) => { + return res.render("restaurants", { + restaurants: data, + categories, + categoryId, + }); + }); + }); }, getRestaurant: (req, res) => { return Restaurant.findByPk(req.params.id, { - include: Category + include: Category, }).then((restaurant) => { - return res.render('restaurant', { - restaurant: restaurant.toJSON() - }) - }) - } -} + return res.render("restaurant", { + restaurant: restaurant.toJSON(), + }); + }); + }, +}; -module.exports = restControllers +module.exports = restControllers; diff --git a/views/restaurants.handlebars b/views/restaurants.handlebars index 53bac5eb..5891bd4a 100644 --- a/views/restaurants.handlebars +++ b/views/restaurants.handlebars @@ -1,3 +1,16 @@ + + +
{{#each restaurants}}
From eeaaa2d3c5122ba514a67dc48f35ee2b9ae47c20 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Tue, 16 Nov 2021 23:58:03 +0000 Subject: [PATCH 145/198] add pagination feature --- controllers/restController.js | 37 ++++++++++++++++++++++++++++------- views/restaurants.handlebars | 24 +++++++++++++++++++++-- 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/controllers/restController.js b/controllers/restController.js index 906c8ad3..e5a86ee7 100644 --- a/controllers/restController.js +++ b/controllers/restController.js @@ -2,34 +2,57 @@ const db = require("../models"); const Restaurant = db.Restaurant; const Category = db.Category; +const pageLimit = 10; + const restControllers = { getRestaurants: (req, res) => { + let offset = 0; const whereQuery = {}; let categoryId = ""; + if (req.query.categoryId) { categoryId = Number(req.query.categoryId); whereQuery.CategoryId = categoryId; } - Restaurant.findAll({ - raw: true, - nest: true, + if (req.query.page) { + offset = (req.query.page - 1) * pageLimit; + } + + Restaurant.findAndCountAll({ include: Category, where: whereQuery, + offset: offset, + limit: pageLimit, }).then((restaurants) => { - const data = restaurants.map((r) => ({ - ...r, - description: r.description.substring(0, 50), - categoryName: r.Category.name, + //data for pagination + const page = Number(req.query.page) || 1; + const pages = Math.ceil(restaurants.count / pageLimit); + const totalPage = Array.from({ length: pages }).map( + (item, index) => index + 1 + ); + + const prev = page - 1 < 1 ? 1 : page - 1; + const next = page + 1 > pages ? pages : page + 1; + + const data = restaurants.rows.map((r) => ({ + ...r.dataValues, + description: r.dataValues.description.substring(0, 50), + categoryName: r.dataValues.Category.name, })); Category.findAll({ raw: true, nest: true }).then((categories) => { return res.render("restaurants", { restaurants: data, categories, categoryId, + page, + totalPage, + prev, + next, }); }); }); }, + getRestaurant: (req, res) => { return Restaurant.findByPk(req.params.id, { include: Category, diff --git a/views/restaurants.handlebars b/views/restaurants.handlebars index 5891bd4a..bcb2d387 100644 --- a/views/restaurants.handlebars +++ b/views/restaurants.handlebars @@ -5,7 +5,6 @@ {{#each categories}} {{/each}} @@ -36,4 +35,25 @@ {{/each}}

-
\ No newline at end of file +
+ \ No newline at end of file From 0b89e2bfcbf8950ae1fa869fdc5a7d0fe517ce01 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Wed, 17 Nov 2021 00:02:31 +0000 Subject: [PATCH 146/198] center pagination component --- views/restaurants.handlebars | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/restaurants.handlebars b/views/restaurants.handlebars index bcb2d387..d54556a0 100644 --- a/views/restaurants.handlebars +++ b/views/restaurants.handlebars @@ -37,7 +37,7 @@

- +
  • Opening Hour: {{restaurant.opening_hours}}
  • @@ -24,4 +20,12 @@


+
+
+ + +
+ + +
回上一頁 \ No newline at end of file From 724e82653b27ca2022175ec7149ba18fc7b76fa8 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Wed, 17 Nov 2021 23:54:14 +0000 Subject: [PATCH 152/198] show comment in restaurant page --- config/handlebars-helpers.js | 14 +++++--- controllers/restController.js | 65 ++++++++++++++++++----------------- package.json | 1 + views/restaurant.handlebars | 12 +++++-- 4 files changed, 55 insertions(+), 37 deletions(-) diff --git a/config/handlebars-helpers.js b/config/handlebars-helpers.js index c667f781..de45c1b6 100644 --- a/config/handlebars-helpers.js +++ b/config/handlebars-helpers.js @@ -1,8 +1,14 @@ +const moment = require("moment"); + module.exports = { ifCond: function (a, b, options) { if (a === b) { - return options.fn(this) + return options.fn(this); } - return options.inverse(this) - } -} + return options.inverse(this); + }, + + moment: function (a) { + return moment(a).fromNow(); + }, +}; diff --git a/controllers/restController.js b/controllers/restController.js index bd56a454..ddac9960 100644 --- a/controllers/restController.js +++ b/controllers/restController.js @@ -1,67 +1,70 @@ -const db = require('../models') -const Restaurant = db.Restaurant -const Category = db.Category +const db = require("../models"); +const Restaurant = db.Restaurant; +const Category = db.Category; +const User = db.User; +const Comment = db.Comment; -const pageLimit = 10 +const pageLimit = 10; const restControllers = { getRestaurants: (req, res) => { - let offset = 0 - const whereQuery = {} - let categoryId = '' + let offset = 0; + const whereQuery = {}; + let categoryId = ""; if (req.query.categoryId) { - categoryId = Number(req.query.categoryId) - whereQuery.CategoryId = categoryId + categoryId = Number(req.query.categoryId); + whereQuery.CategoryId = categoryId; } if (req.query.page) { - offset = (req.query.page - 1) * pageLimit + offset = (req.query.page - 1) * pageLimit; } Restaurant.findAndCountAll({ include: Category, where: whereQuery, offset: offset, - limit: pageLimit + limit: pageLimit, }).then((restaurants) => { // data for pagination - const page = Number(req.query.page) || 1 - const pages = Math.ceil(restaurants.count / pageLimit) + const page = Number(req.query.page) || 1; + const pages = Math.ceil(restaurants.count / pageLimit); const totalPage = Array.from({ length: pages }).map( (item, index) => index + 1 - ) + ); - const prev = page - 1 < 1 ? 1 : page - 1 - const next = page + 1 > pages ? pages : page + 1 + const prev = page - 1 < 1 ? 1 : page - 1; + const next = page + 1 > pages ? pages : page + 1; const data = restaurants.rows.map((r) => ({ ...r.dataValues, description: r.dataValues.description.substring(0, 50), - categoryName: r.dataValues.Category.name - })) + categoryName: r.dataValues.Category.name, + })); Category.findAll({ raw: true, nest: true }).then((categories) => { - return res.render('restaurants', { + return res.render("restaurants", { restaurants: data, categories, categoryId, page, totalPage, prev, - next - }) - }) - }) + next, + }); + }); + }); }, getRestaurant: (req, res) => { return Restaurant.findByPk(req.params.id, { - include: Category + include: [Category, { model: Comment, include: User }], }).then((restaurant) => { - return res.render('restaurant', { - restaurant: restaurant.toJSON() - }) - }) - } -} + console.log(restaurant.Comments[0].dataValues); + return res.render("restaurant", { + restaurant: restaurant.toJSON(), + }); + }); + }, +}; -module.exports = restControllers +module.exports = restControllers; diff --git a/package.json b/package.json index 9e98e7b6..f505f57f 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "imgur-node-api": "^0.1.0", "method-override": "^3.0.0", "mocha": "^8.2.0", + "moment": "^2.29.1", "multer": "^1.4.3", "mysql2": "^2.2.5", "nodemon": "^2.0.12", diff --git a/views/restaurant.handlebars b/views/restaurant.handlebars index 521c25f7..6e7b9d27 100644 --- a/views/restaurant.handlebars +++ b/views/restaurant.handlebars @@ -18,8 +18,16 @@

-
-
+

+

所有評論:

+{{#each restaurant.Comments}} +
+

{{this.User.name}}

+

{{this.text}}

+
{{moment this.createdAt}}
+
+
+{{/each}}
From 567dae8c0864eabd9bda9aa36205b2cb49716664 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Thu, 18 Nov 2021 00:06:04 +0000 Subject: [PATCH 153/198] admin user to delete comment --- controllers/commentController.js | 9 ++++++++- controllers/restController.js | 1 - routes/index.js | 1 + views/restaurant.handlebars | 5 +++++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/controllers/commentController.js b/controllers/commentController.js index df6ea6f5..00e62786 100644 --- a/controllers/commentController.js +++ b/controllers/commentController.js @@ -8,7 +8,14 @@ const CommentController = { RestaurantId: req.body.restaurantId, UserId: req.user.id, }).then((comment) => { - res.redirect("/restaurants/${req.body.restaurantId}"); + res.redirect(`/restaurants/${req.body.restaurantId}`); + }); + }, + deleteComment: (req, res) => { + return Comment.findByPk(req.params.id).then((comment) => { + comment.destroy().then((comment) => { + res.redirect(`/restaurants/${comment.RestaurantId}`); + }); }); }, }; diff --git a/controllers/restController.js b/controllers/restController.js index ddac9960..badd7959 100644 --- a/controllers/restController.js +++ b/controllers/restController.js @@ -59,7 +59,6 @@ const restControllers = { return Restaurant.findByPk(req.params.id, { include: [Category, { model: Comment, include: User }], }).then((restaurant) => { - console.log(restaurant.Comments[0].dataValues); return res.render("restaurant", { restaurant: restaurant.toJSON(), }); diff --git a/routes/index.js b/routes/index.js index 2f416901..078d1a6e 100644 --- a/routes/index.js +++ b/routes/index.js @@ -121,4 +121,5 @@ module.exports = (app) => { ); app.get("/restaurants/:id", restController.getRestaurant); app.post("/comments", authenticated, commentController.postComment); + app.delete("/comments/:id", authenticated, commentController.deleteComment); }; diff --git a/views/restaurant.handlebars b/views/restaurant.handlebars index 6e7b9d27..59147240 100644 --- a/views/restaurant.handlebars +++ b/views/restaurant.handlebars @@ -21,6 +21,11 @@

所有評論:

{{#each restaurant.Comments}} + {{#if ../user.isAdmin}} + + + + {{/if}}

{{this.User.name}}

{{this.text}}

From 85479b3893579c0edea62e036d3b38a039764882 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Thu, 18 Nov 2021 00:19:56 +0000 Subject: [PATCH 154/198] merge with upstream/R02-test --- helpers/unitTestHelpers.js | 7 ------- package-lock.json | 5 +---- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js index fee28b10..e0c15d34 100644 --- a/helpers/unitTestHelpers.js +++ b/helpers/unitTestHelpers.js @@ -34,17 +34,10 @@ const createModelMock = (name, defaultValue, data, joinedTableName) => { } else if (query === 'findAll') { // 回傳模擬資料 if (!data) { -<<<<<<< HEAD return mockModel.build([defaultValue]) } return Promise.resolve(data ? data.map(d => mockModel.build(d)) : []) } else if (query === 'destroy') { -======= - return mockModel.build([defaultValue]); - } - return Promise.resolve( data ? data.map(d => mockModel.build(d)) : []) - }else if (query === 'destroy') { ->>>>>>> upstream/R02-test // destroy 可以從 where 取得要刪除的資料 // 因此就可以模擬將模擬資料中的資料刪除 // 刪除模擬資料中的某一筆 joinTable 資料 diff --git a/package-lock.json b/package-lock.json index bea12912..a8832131 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2596,7 +2596,6 @@ "yargs": "^13.1.0" } }, -<<<<<<< HEAD "sequelize-mock": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/sequelize-mock/-/sequelize-mock-0.10.2.tgz", @@ -2608,8 +2607,6 @@ "lodash": "^4.16.4" } }, -======= ->>>>>>> upstream/R02-test "sequelize-pool": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-6.1.0.tgz", @@ -3224,4 +3221,4 @@ } } } -} \ No newline at end of file +} From 9bd41611265da4bfef129b2fb211b1ced7f702f6 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Thu, 18 Nov 2021 00:20:51 +0000 Subject: [PATCH 155/198] remove merge conflict duplciation --- helpers/unitTestHelpers.js | 118 ++++++++++++++++--------------------- 1 file changed, 52 insertions(+), 66 deletions(-) diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js index e0c15d34..4b38644b 100644 --- a/helpers/unitTestHelpers.js +++ b/helpers/unitTestHelpers.js @@ -1,124 +1,110 @@ -const SequelizeMock = require('sequelize-mock') -const proxyquire = require('proxyquire') -const sinon = require('sinon') +const SequelizeMock = require("sequelize-mock"); +const proxyquire = require("proxyquire"); +const sinon = require("sinon"); -const dbMock = new SequelizeMock() +const dbMock = new SequelizeMock(); const createModelMock = (name, defaultValue, data, joinedTableName) => { const mockModel = dbMock.define(name, defaultValue, { instanceMethods: { update: (changes) => { - mockModel._defaults = { ...changes } - return Promise.resolve() - } - } - }) + mockModel._defaults = { ...changes }; + return Promise.resolve(); + }, + }, + }); // 模擬 Sequelize 行為 // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) - mockModel.findByPk = (id) => mockModel.findOne({ where: { id: id } }) + mockModel.findByPk = (id) => mockModel.findOne({ where: { id: id } }); // 將 count 的 function 預設回傳假資料數目 1 - mockModel.count = () => 1 + mockModel.count = () => 1; // 因為 mock 中的 create 有問題,因此指向 upsert function, 這樣可以在 useHandler 中取得 create 呼叫 - mockModel.create = mockModel.upsert + mockModel.create = mockModel.upsert; // modify middleware if (joinedTableName) { mockModel.$queryInterface.$useHandler((query, queryOptions) => { - if (query === 'upsert') { + if (query === "upsert") { // 新增 joinTable 資料到模擬資料 - const { UserId, RestaurantId } = queryOptions[0] - const restaurant = data.find(d => d.id === RestaurantId) - restaurant[joinedTableName].push({ UserId: UserId }) - return Promise.resolve(data.map(d => mockModel.build(d))) - } else if (query === 'findAll') { + const { UserId, RestaurantId } = queryOptions[0]; + const restaurant = data.find((d) => d.id === RestaurantId); + restaurant[joinedTableName].push({ UserId: UserId }); + return Promise.resolve(data.map((d) => mockModel.build(d))); + } else if (query === "findAll") { // 回傳模擬資料 if (!data) { - return mockModel.build([defaultValue]) } - return Promise.resolve(data ? data.map(d => mockModel.build(d)) : []) - } else if (query === 'destroy') { + return Promise.resolve(data ? data.map((d) => mockModel.build(d)) : []); + } else if (query === "destroy") { // destroy 可以從 where 取得要刪除的資料 // 因此就可以模擬將模擬資料中的資料刪除 // 刪除模擬資料中的某一筆 joinTable 資料 - const { UserId, RestaurantId } = queryOptions[0].where - const restaurant = data.find(d => d.id === RestaurantId) - restaurant[joinedTableName] = restaurant[joinedTableName].filter(d => !(d.UserId === UserId)) - return Promise.resolve(data.map(d => mockModel.build(d))) + const { UserId, RestaurantId } = queryOptions[0].where; + const restaurant = data.find((d) => d.id === RestaurantId); + restaurant[joinedTableName] = restaurant[joinedTableName].filter( + (d) => !(d.UserId === UserId) + ); + return Promise.resolve(data.map((d) => mockModel.build(d))); } - }) + }); } else { mockModel.$queryInterface.$useHandler((query, queryOptions, done) => { - if (query === 'upsert') { + if (query === "upsert") { // create 時會帶 userId 跟 restaurantId (ex: Like.create({ userId: 1, restaurantId: 2})) - const { UserId, RestaurantId } = queryOptions[0] + const { UserId, RestaurantId } = queryOptions[0]; // 新增這個 Like 的資訊到模擬資料裡 - data.push({ UserId, RestaurantId }) + data.push({ UserId, RestaurantId }); // 回傳模擬資料 - return Promise.resolve(mockModel.build(data)) - } else if (query === 'findAll') { + return Promise.resolve(mockModel.build(data)); + } else if (query === "findAll") { // 回傳模擬資料 if (!data) { return mockModel.build([defaultValue]); } - return Promise.resolve(data ? data.map(d => mockModel.build(d)) : []) - } else if (query === 'destroy') { + return Promise.resolve(data ? data.map((d) => mockModel.build(d)) : []); + } else if (query === "destroy") { // destroy 可以從 where 取得要刪除的資料 // 因此就可以模擬將模擬資料中的資料刪除 - const { UserId, RestaurantId } = queryOptions[0].where - data = data.filter(d => !(d.UserId === UserId && d.RestaurantId === RestaurantId)) + const { UserId, RestaurantId } = queryOptions[0].where; + data = data.filter( + (d) => !(d.UserId === UserId && d.RestaurantId === RestaurantId) + ); - return Promise.resolve(mockModel.build(data)) + return Promise.resolve(mockModel.build(data)); } - }) + }); } - return mockModel -} + return mockModel; +}; const createControllerProxy = (path, model) => { const controller = proxyquire(path, { - '../models': model - }) - - return controller -} + "../models": model, + }); -const mockRequest = (query) => { - return { - ...query, - flash: sinon.spy() - } -} -const mockResponse = () => { - return { - redirect: sinon.spy(), - render: sinon.spy() - } -} + return controller; +}; const mockRequest = (query) => { return { ...query, flash: sinon.spy(), - } -} + }; +}; const mockResponse = () => { return { redirect: sinon.spy(), render: sinon.spy(), - } -} + }; +}; module.exports = { createModelMock, createControllerProxy, mockRequest, - mockResponse -<<<<<<< HEAD -} -======= -} ->>>>>>> upstream/R02-test + mockResponse, +}; From b84a195199c7bc2c3bb4d14192ff55d8f91df05b Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Thu, 18 Nov 2021 00:49:52 +0000 Subject: [PATCH 156/198] fix javascript coding style --- config/handlebars-helpers.js | 12 +- controllers/commentController.js | 22 ++-- controllers/restController.js | 66 +++++----- helpers/unitTestHelpers.js | 102 +++++++-------- migrations/20211117222631-create-comment.js | 26 ++-- models/comment.js | 20 +-- models/restaurant.js | 20 +-- models/user.js | 18 +-- routes/index.js | 130 ++++++++++---------- tests/R01.test.js | 4 +- tests/R02.test.js | 32 ++--- views/profile.handlebars | 12 ++ 12 files changed, 238 insertions(+), 226 deletions(-) create mode 100644 views/profile.handlebars diff --git a/config/handlebars-helpers.js b/config/handlebars-helpers.js index de45c1b6..1111fca2 100644 --- a/config/handlebars-helpers.js +++ b/config/handlebars-helpers.js @@ -1,14 +1,14 @@ -const moment = require("moment"); +const moment = require('moment') module.exports = { ifCond: function (a, b, options) { if (a === b) { - return options.fn(this); + return options.fn(this) } - return options.inverse(this); + return options.inverse(this) }, moment: function (a) { - return moment(a).fromNow(); - }, -}; + return moment(a).fromNow() + } +} diff --git a/controllers/commentController.js b/controllers/commentController.js index 00e62786..da5a583f 100644 --- a/controllers/commentController.js +++ b/controllers/commentController.js @@ -1,23 +1,23 @@ -const db = require("../models"); -const Comment = db.Comment; +const db = require('../models') +const Comment = db.Comment const CommentController = { postComment: (req, res) => { return Comment.create({ text: req.body.text, RestaurantId: req.body.restaurantId, - UserId: req.user.id, + UserId: req.user.id }).then((comment) => { - res.redirect(`/restaurants/${req.body.restaurantId}`); - }); + res.redirect(`/restaurants/${req.body.restaurantId}`) + }) }, deleteComment: (req, res) => { return Comment.findByPk(req.params.id).then((comment) => { comment.destroy().then((comment) => { - res.redirect(`/restaurants/${comment.RestaurantId}`); - }); - }); - }, -}; + res.redirect(`/restaurants/${comment.RestaurantId}`) + }) + }) + } +} -module.exports = CommentController; +module.exports = CommentController diff --git a/controllers/restController.js b/controllers/restController.js index badd7959..650f40f9 100644 --- a/controllers/restController.js +++ b/controllers/restController.js @@ -1,69 +1,69 @@ -const db = require("../models"); -const Restaurant = db.Restaurant; -const Category = db.Category; -const User = db.User; -const Comment = db.Comment; +const db = require('../models') +const Restaurant = db.Restaurant +const Category = db.Category +const User = db.User +const Comment = db.Comment -const pageLimit = 10; +const pageLimit = 10 const restControllers = { getRestaurants: (req, res) => { - let offset = 0; - const whereQuery = {}; - let categoryId = ""; + let offset = 0 + const whereQuery = {} + let categoryId = '' if (req.query.categoryId) { - categoryId = Number(req.query.categoryId); - whereQuery.CategoryId = categoryId; + categoryId = Number(req.query.categoryId) + whereQuery.CategoryId = categoryId } if (req.query.page) { - offset = (req.query.page - 1) * pageLimit; + offset = (req.query.page - 1) * pageLimit } Restaurant.findAndCountAll({ include: Category, where: whereQuery, offset: offset, - limit: pageLimit, + limit: pageLimit }).then((restaurants) => { // data for pagination - const page = Number(req.query.page) || 1; - const pages = Math.ceil(restaurants.count / pageLimit); + const page = Number(req.query.page) || 1 + const pages = Math.ceil(restaurants.count / pageLimit) const totalPage = Array.from({ length: pages }).map( (item, index) => index + 1 - ); + ) - const prev = page - 1 < 1 ? 1 : page - 1; - const next = page + 1 > pages ? pages : page + 1; + const prev = page - 1 < 1 ? 1 : page - 1 + const next = page + 1 > pages ? pages : page + 1 const data = restaurants.rows.map((r) => ({ ...r.dataValues, description: r.dataValues.description.substring(0, 50), - categoryName: r.dataValues.Category.name, - })); + categoryName: r.dataValues.Category.name + })) Category.findAll({ raw: true, nest: true }).then((categories) => { - return res.render("restaurants", { + return res.render('restaurants', { restaurants: data, categories, categoryId, page, totalPage, prev, - next, - }); - }); - }); + next + }) + }) + }) }, getRestaurant: (req, res) => { return Restaurant.findByPk(req.params.id, { - include: [Category, { model: Comment, include: User }], + include: [Category, { model: Comment, include: User }] }).then((restaurant) => { - return res.render("restaurant", { - restaurant: restaurant.toJSON(), - }); - }); - }, -}; + return res.render('restaurant', { + restaurant: restaurant.toJSON() + }) + }) + } +} -module.exports = restControllers; +module.exports = restControllers diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js index 4b38644b..586d239f 100644 --- a/helpers/unitTestHelpers.js +++ b/helpers/unitTestHelpers.js @@ -1,110 +1,110 @@ -const SequelizeMock = require("sequelize-mock"); -const proxyquire = require("proxyquire"); -const sinon = require("sinon"); +const SequelizeMock = require('sequelize-mock') +const proxyquire = require('proxyquire') +const sinon = require('sinon') -const dbMock = new SequelizeMock(); +const dbMock = new SequelizeMock() const createModelMock = (name, defaultValue, data, joinedTableName) => { const mockModel = dbMock.define(name, defaultValue, { instanceMethods: { update: (changes) => { - mockModel._defaults = { ...changes }; - return Promise.resolve(); - }, - }, - }); + mockModel._defaults = { ...changes } + return Promise.resolve() + } + } + }) // 模擬 Sequelize 行為 // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) - mockModel.findByPk = (id) => mockModel.findOne({ where: { id: id } }); + mockModel.findByPk = (id) => mockModel.findOne({ where: { id: id } }) // 將 count 的 function 預設回傳假資料數目 1 - mockModel.count = () => 1; + mockModel.count = () => 1 // 因為 mock 中的 create 有問題,因此指向 upsert function, 這樣可以在 useHandler 中取得 create 呼叫 - mockModel.create = mockModel.upsert; + mockModel.create = mockModel.upsert // modify middleware if (joinedTableName) { mockModel.$queryInterface.$useHandler((query, queryOptions) => { - if (query === "upsert") { + if (query === 'upsert') { // 新增 joinTable 資料到模擬資料 - const { UserId, RestaurantId } = queryOptions[0]; - const restaurant = data.find((d) => d.id === RestaurantId); - restaurant[joinedTableName].push({ UserId: UserId }); - return Promise.resolve(data.map((d) => mockModel.build(d))); - } else if (query === "findAll") { + const { UserId, RestaurantId } = queryOptions[0] + const restaurant = data.find((d) => d.id === RestaurantId) + restaurant[joinedTableName].push({ UserId: UserId }) + return Promise.resolve(data.map((d) => mockModel.build(d))) + } else if (query === 'findAll') { // 回傳模擬資料 if (!data) { } - return Promise.resolve(data ? data.map((d) => mockModel.build(d)) : []); - } else if (query === "destroy") { + return Promise.resolve(data ? data.map((d) => mockModel.build(d)) : []) + } else if (query === 'destroy') { // destroy 可以從 where 取得要刪除的資料 // 因此就可以模擬將模擬資料中的資料刪除 // 刪除模擬資料中的某一筆 joinTable 資料 - const { UserId, RestaurantId } = queryOptions[0].where; - const restaurant = data.find((d) => d.id === RestaurantId); + const { UserId, RestaurantId } = queryOptions[0].where + const restaurant = data.find((d) => d.id === RestaurantId) restaurant[joinedTableName] = restaurant[joinedTableName].filter( (d) => !(d.UserId === UserId) - ); - return Promise.resolve(data.map((d) => mockModel.build(d))); + ) + return Promise.resolve(data.map((d) => mockModel.build(d))) } - }); + }) } else { mockModel.$queryInterface.$useHandler((query, queryOptions, done) => { - if (query === "upsert") { + if (query === 'upsert') { // create 時會帶 userId 跟 restaurantId (ex: Like.create({ userId: 1, restaurantId: 2})) - const { UserId, RestaurantId } = queryOptions[0]; + const { UserId, RestaurantId } = queryOptions[0] // 新增這個 Like 的資訊到模擬資料裡 - data.push({ UserId, RestaurantId }); + data.push({ UserId, RestaurantId }) // 回傳模擬資料 - return Promise.resolve(mockModel.build(data)); - } else if (query === "findAll") { + return Promise.resolve(mockModel.build(data)) + } else if (query === 'findAll') { // 回傳模擬資料 if (!data) { - return mockModel.build([defaultValue]); + return mockModel.build([defaultValue]) } - return Promise.resolve(data ? data.map((d) => mockModel.build(d)) : []); - } else if (query === "destroy") { + return Promise.resolve(data ? data.map((d) => mockModel.build(d)) : []) + } else if (query === 'destroy') { // destroy 可以從 where 取得要刪除的資料 // 因此就可以模擬將模擬資料中的資料刪除 - const { UserId, RestaurantId } = queryOptions[0].where; + const { UserId, RestaurantId } = queryOptions[0].where data = data.filter( (d) => !(d.UserId === UserId && d.RestaurantId === RestaurantId) - ); + ) - return Promise.resolve(mockModel.build(data)); + return Promise.resolve(mockModel.build(data)) } - }); + }) } - return mockModel; -}; + return mockModel +} const createControllerProxy = (path, model) => { const controller = proxyquire(path, { - "../models": model, - }); + '../models': model + }) - return controller; -}; + return controller +} const mockRequest = (query) => { return { ...query, - flash: sinon.spy(), - }; -}; + flash: sinon.spy() + } +} const mockResponse = () => { return { redirect: sinon.spy(), - render: sinon.spy(), - }; -}; + render: sinon.spy() + } +} module.exports = { createModelMock, createControllerProxy, mockRequest, - mockResponse, -}; + mockResponse +} diff --git a/migrations/20211117222631-create-comment.js b/migrations/20211117222631-create-comment.js index 091ae87d..6967d04b 100644 --- a/migrations/20211117222631-create-comment.js +++ b/migrations/20211117222631-create-comment.js @@ -1,35 +1,35 @@ -"use strict"; +'use strict' module.exports = { up: async (queryInterface, Sequelize) => { - await queryInterface.createTable("Comments", { + await queryInterface.createTable('Comments', { id: { allowNull: false, autoIncrement: true, primaryKey: true, - type: Sequelize.INTEGER, + type: Sequelize.INTEGER }, text: { - type: Sequelize.STRING, + type: Sequelize.STRING }, UserId: { type: Sequelize.INTEGER, - references: { model: "users", key: "id" }, + references: { model: 'users', key: 'id' } }, RestaurantId: { type: Sequelize.INTEGER, - references: { model: "restaurants", key: "id" }, + references: { model: 'restaurants', key: 'id' } }, createdAt: { allowNull: false, - type: Sequelize.DATE, + type: Sequelize.DATE }, updatedAt: { allowNull: false, - type: Sequelize.DATE, - }, - }); + type: Sequelize.DATE + } + }) }, down: async (queryInterface, Sequelize) => { - await queryInterface.dropTable("Comments"); - }, -}; + await queryInterface.dropTable('Comments') + } +} diff --git a/models/comment.js b/models/comment.js index e71b43dd..3b99d12e 100644 --- a/models/comment.js +++ b/models/comment.js @@ -1,5 +1,5 @@ -"use strict"; -const { Model } = require("sequelize"); +'use strict' +const { Model } = require('sequelize') module.exports = (sequelize, DataTypes) => { class Comment extends Model { /** @@ -7,21 +7,21 @@ module.exports = (sequelize, DataTypes) => { * This method is not a part of Sequelize lifecycle. * The `models/index` file will call this method automatically. */ - static associate(models) { - Comment.belongsTo(models.Restaurant); - Comment.belongsTo(models.User); + static associate (models) { + Comment.belongsTo(models.Restaurant) + Comment.belongsTo(models.User) } } Comment.init( { text: DataTypes.STRING, UserId: DataTypes.INTEGER, - RestaurantId: DataTypes.INTEGER, + RestaurantId: DataTypes.INTEGER }, { sequelize, - modelName: "Comment", + modelName: 'Comment' } - ); - return Comment; -}; + ) + return Comment +} diff --git a/models/restaurant.js b/models/restaurant.js index 8bf44d80..920ee227 100644 --- a/models/restaurant.js +++ b/models/restaurant.js @@ -1,5 +1,5 @@ -"use strict"; -const { Model } = require("sequelize"); +'use strict' +const { Model } = require('sequelize') module.exports = (sequelize, DataTypes) => { class Restaurant extends Model { /** @@ -7,9 +7,9 @@ module.exports = (sequelize, DataTypes) => { * This method is not a part of Sequelize lifecycle. * The `models/index` file will call this method automatically. */ - static associate(models) { - Restaurant.belongsTo(models.Category); - Restaurant.hasMany(models.Comment); + static associate (models) { + Restaurant.belongsTo(models.Category) + Restaurant.hasMany(models.Comment) } } Restaurant.init( @@ -19,12 +19,12 @@ module.exports = (sequelize, DataTypes) => { address: DataTypes.STRING, opening_hours: DataTypes.STRING, description: DataTypes.TEXT, - image: DataTypes.STRING, + image: DataTypes.STRING }, { sequelize, - modelName: "Restaurant", + modelName: 'Restaurant' } - ); - return Restaurant; -}; + ) + return Restaurant +} diff --git a/models/user.js b/models/user.js index c1de30be..6b940ec1 100644 --- a/models/user.js +++ b/models/user.js @@ -1,5 +1,5 @@ -"use strict"; -const { Model } = require("sequelize"); +'use strict' +const { Model } = require('sequelize') module.exports = (sequelize, DataTypes) => { class User extends Model { /** @@ -7,8 +7,8 @@ module.exports = (sequelize, DataTypes) => { * This method is not a part of Sequelize lifecycle. * The `models/index` file will call this method automatically. */ - static associate(models) { - User.hasMany(models.Comment); + static associate (models) { + User.hasMany(models.Comment) } } User.init( @@ -16,12 +16,12 @@ module.exports = (sequelize, DataTypes) => { name: DataTypes.STRING, email: DataTypes.STRING, password: DataTypes.STRING, - isAdmin: DataTypes.BOOLEAN, + isAdmin: DataTypes.BOOLEAN }, { sequelize, - modelName: "User", + modelName: 'User' } - ); - return User; -}; + ) + return User +} diff --git a/routes/index.js b/routes/index.js index 078d1a6e..a861fdc9 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,125 +1,125 @@ -const restController = require("../controllers/restController"); -const adminController = require("../controllers/adminController"); -const userController = require("../controllers/userController"); -const commentController = require("../controllers/commentController"); -const passport = require("passport"); -const helpers = require("../_helpers"); +const restController = require('../controllers/restController') +const adminController = require('../controllers/adminController') +const userController = require('../controllers/userController') +const commentController = require('../controllers/commentController') +const passport = require('passport') +const helpers = require('../_helpers') // multer setup -const multer = require("multer"); -const categoryController = require("../controllers/categoryController"); -const upload = multer({ dest: "temp/" }); +const multer = require('multer') +const categoryController = require('../controllers/categoryController') +const upload = multer({ dest: 'temp/' }) module.exports = (app) => { const authenticated = (req, res, next) => { if (helpers.ensureAuthenticated(req)) { - return next(); + return next() } - res.redirect("/signin"); - }; + res.redirect('/signin') + } const authenticatedAdmin = (req, res, next) => { if (helpers.ensureAuthenticated(req)) { if (helpers.getUser(req).isAdmin) { - return next(); + return next() } - return res.redirect("/"); + return res.redirect('/') } - res.redirect("/signin"); - }; + res.redirect('/signin') + } // 如果使用者訪問首頁,就導向 /restaurants 的頁面 - app.get("/", authenticated, (req, res) => { - res.redirect("restaurants"); - }); - app.get("/restaurants", restController.getRestaurants); + app.get('/', authenticated, (req, res) => { + res.redirect('restaurants') + }) + app.get('/restaurants', restController.getRestaurants) - app.get("/admin", authenticatedAdmin, (req, res) => { - res.redirect("/admin/restaurants"); - }); + app.get('/admin', authenticatedAdmin, (req, res) => { + res.redirect('/admin/restaurants') + }) app.get( - "/admin/restaurants", + '/admin/restaurants', authenticatedAdmin, adminController.getRestaurants - ); + ) - app.get("/signup", userController.signUpPage); - app.post("/signup", userController.signUp); + app.get('/signup', userController.signUpPage) + app.post('/signup', userController.signUp) - app.get("/signin", userController.signInPage); + app.get('/signin', userController.signInPage) app.post( - "/signin", - passport.authenticate("local", { - failureRedirect: "/signin", - failureFlash: true, + '/signin', + passport.authenticate('local', { + failureRedirect: '/signin', + failureFlash: true }), userController.signIn - ); - app.get("/logout", userController.logout); + ) + app.get('/logout', userController.logout) app.get( - "/admin/restaurants/create", + '/admin/restaurants/create', authenticatedAdmin, adminController.createRestaurants - ); + ) app.post( - "/admin/restaurants", + '/admin/restaurants', authenticatedAdmin, - upload.single("image"), + upload.single('image'), adminController.postRestaurants - ); + ) app.get( - "/admin/restaurants/:id", + '/admin/restaurants/:id', authenticatedAdmin, adminController.getRestaurant - ); + ) app.get( - "/admin/restaurants/:id/edit", + '/admin/restaurants/:id/edit', authenticatedAdmin, adminController.editRestaurant - ); + ) app.put( - "/admin/restaurants/:id", + '/admin/restaurants/:id', authenticatedAdmin, - upload.single("image"), + upload.single('image'), adminController.putRestaurant - ); + ) app.delete( - "/admin/restaurants/:id", + '/admin/restaurants/:id', authenticatedAdmin, adminController.deleteRestaurant - ); - app.get("/admin/users", authenticatedAdmin, adminController.getUsers); + ) + app.get('/admin/users', authenticatedAdmin, adminController.getUsers) app.put( - "/admin/users/:id/toggleAdmin", + '/admin/users/:id/toggleAdmin', authenticatedAdmin, adminController.toggleAdmin - ); + ) app.get( - "/admin/categories", + '/admin/categories', authenticatedAdmin, categoryController.getCategories - ); + ) app.post( - "/admin/categories", + '/admin/categories', authenticatedAdmin, categoryController.postCategory - ); + ) app.get( - "/admin/categories/:id", + '/admin/categories/:id', authenticatedAdmin, categoryController.getCategories - ); + ) app.put( - "/admin/categories/:id", + '/admin/categories/:id', authenticatedAdmin, categoryController.putCategory - ); + ) app.delete( - "/admin/categories/:id", + '/admin/categories/:id', authenticatedAdmin, categoryController.deletCategory - ); - app.get("/restaurants/:id", restController.getRestaurant); - app.post("/comments", authenticated, commentController.postComment); - app.delete("/comments/:id", authenticated, commentController.deleteComment); -}; + ) + app.get('/restaurants/:id', restController.getRestaurant) + app.post('/comments', authenticated, commentController.postComment) + app.delete('/comments/:id', authenticated, commentController.deleteComment) +} diff --git a/tests/R01.test.js b/tests/R01.test.js index cd57cf53..f543dedd 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -3,10 +3,10 @@ const request = require('supertest') const should = chai.should() const app = require('../app') -const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers'); +const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers') describe('# R01', () => { - describe('登入測試: POST /signin', function(){ + describe('登入測試: POST /signin', function () { // 以下測試會發出請求,測試資料庫內是否有作業指定的使用者資料 // 測試資料的來源是真實的資料庫 it('#1 密碼錯誤', function (done) { diff --git a/tests/R02.test.js b/tests/R02.test.js index 26c927b3..8f2a16ff 100644 --- a/tests/R02.test.js +++ b/tests/R02.test.js @@ -3,9 +3,9 @@ const request = require('supertest') const sinon = require('sinon') const should = chai.should() -const helpers = require('../_helpers'); +const helpers = require('../_helpers') -const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers'); +const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers') describe('# R02', () => { describe('# R02: 建立 User Profile', function () { @@ -17,13 +17,13 @@ describe('# R02', () => { .stub(helpers, 'ensureAuthenticated') .returns(true) this.getUser = sinon.stub(helpers, 'getUser').returns({ id: 1 }) - // 製作假資料 - // 本 context 會用這筆資料進行測試 + // 製作假資料 + // 本 context 會用這筆資料進行測試 this.UserMock = createModelMock('User', { id: 1, email: 'root@example.com', name: 'admin', - isAdmin: false, + isAdmin: false }) // 修改 userController 中的資料庫連線設定,由連向真實的資料庫 -> 改為連向模擬的 User table @@ -40,12 +40,12 @@ describe('# R02', () => { await this.userController.getUser(req, res) // toggleAdmin 執行完畢後,應呼叫 res.render - // res.render 的第 1 個參數要是 'profile' + // res.render 的第 1 個參數要是 'profile' // res.render 的第 2 個參數要是 user,其 name 屬性的值應是 'admin' res.render.getCall(0).args[0].should.equal('profile') res.render.getCall(0).args[1].user.name.should.equal('admin') }) - + // 測試完畢,清除資料 after(async () => { // 清除模擬驗證資料 @@ -68,7 +68,7 @@ describe('# R02', () => { id: 1, email: 'root@example.com', name: 'admin', - isAdmin: false, + isAdmin: false }) // 連向模擬的 User table @@ -84,7 +84,7 @@ describe('# R02', () => { await this.userController.editUser(req, res) // editUser 執行完畢後,應呼叫 res.render - // res.render 的第 1 個參數要是 'edit' + // res.render 的第 1 個參數要是 'edit' // res.render 的第 2 個參數要是 user,其 name 屬性的值應是 'admin' res.render.getCall(0).args[0].should.equal('edit') res.render.getCall(0).args[1].user.name.should.equal('admin') @@ -112,7 +112,7 @@ describe('# R02', () => { id: 1, email: 'root@example.com', name: 'admin', - isAdmin: false, + isAdmin: false } ) @@ -121,23 +121,23 @@ describe('# R02', () => { }) it(' PUT /users/:id ', async () => { - // 模擬 request & response + // 模擬 request & response // 對 PUT /users/1 發出 request,並夾帶 body.name = amdin2, body.email = admin_test@gmail.com const req = mockRequest({ params: { id: 1 }, - body: { name: 'admin2', email: 'admin_test@gmail.com' }, - }) + body: { name: 'admin2', email: 'admin_test@gmail.com' } + }) const res = mockResponse() // 測試作業指定的 userController.putUser 函式 await this.userController.putUser(req, res) - // putUser 正確執行的話,應呼叫 req.flash + // putUser 正確執行的話,應呼叫 req.flash // req.flash 的參數應與下列字串一致 - req.flash.calledWith('success_messages','使用者資料編輯成功').should.be.true + req.flash.calledWith('success_messages', '使用者資料編輯成功').should.be.true // putUser 執行完畢後,應呼叫 res.redirect 並重新導向 /users/1 res.redirect.calledWith('/users/1').should.be.true - // putUser 執行完畢後,id:1 使用者的 name 和 email 應該已被修改 + // putUser 執行完畢後,id:1 使用者的 name 和 email 應該已被修改 // 將假資料撈出,比對確認有成功修改到 const user = await this.UserMock.findOne({ where: { id: 1 } }) user.name.should.equal('admin2') diff --git a/views/profile.handlebars b/views/profile.handlebars new file mode 100644 index 00000000..41d54123 --- /dev/null +++ b/views/profile.handlebars @@ -0,0 +1,12 @@ +
+
+
+ Card image cap +
+
+
{{user.name}}
+

{{user.email}}

+ edit +
+
+
\ No newline at end of file From a317a44868a5967861b226e822ea7315e842859b Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Fri, 19 Nov 2021 01:35:29 +0000 Subject: [PATCH 157/198] user profile feature --- controllers/userController.js | 52 +++++++++++++++++++ helpers/unitTestHelpers.js | 1 + .../20211119005510-add-image-to-user.js | 13 +++++ models/user.js | 3 +- routes/index.js | 8 +++ views/edit.handlebars | 22 ++++++++ views/profile.handlebars | 4 +- 7 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 migrations/20211119005510-add-image-to-user.js create mode 100644 views/edit.handlebars diff --git a/controllers/userController.js b/controllers/userController.js index b5fec9a4..0d3a5462 100644 --- a/controllers/userController.js +++ b/controllers/userController.js @@ -1,6 +1,9 @@ const bcrypt = require('bcrypt-nodejs') const db = require('../models') const User = db.User +// imgur setup +const imgur = require('imgur-node-api') +const IMGUR_CLIENT_ID = process.env.IMGUR_CLIENT_ID const userController = { signUpPage: (req, res) => { @@ -44,6 +47,55 @@ const userController = { req.flash('success_message', '登出成功!') req.logout() res.redirect('/signin') + }, + getUser: (req, res) => { + return User.findByPk(req.params.id).then((user) => { + return res.render('profile', { user: user.toJSON() }) + }) + }, + editUser: (req, res) => { + return User.findByPk(req.params.id).then((user) => { + return res.render('edit', { user: user.toJSON() }) + }) + }, + putUser: (req, res) => { + const { name, email } = req.body + const { file } = req + + if (!name || !email || !file) { + req.flash('error_message', 'You havent put in any value') + } + + if (file) { + imgur.setClientID(IMGUR_CLIENT_ID) + imgur.upload(file.path, (err, img) => { + if (err) console.log('Error: ', err) + return User.findByPk(req.params.id).then((user) => { + user + .update({ + name, + email, + image: file ? img.data.link : user.image + }) + .then((user) => { + req.flash('success_messages', '使用者資料編輯成功') + return res.redirect(`/users/${req.params.id}`) + }) + }) + }) + } else { + return User.findByPk(req.params.id).then((user) => { + user + .update({ + name, + email + }) + .then((user) => { + req.flash('success_messages', '使用者資料編輯成功') + return res.redirect(`/users/${req.params.id}`) + }) + }) + } } } diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js index 586d239f..b13887cf 100644 --- a/helpers/unitTestHelpers.js +++ b/helpers/unitTestHelpers.js @@ -34,6 +34,7 @@ const createModelMock = (name, defaultValue, data, joinedTableName) => { } else if (query === 'findAll') { // 回傳模擬資料 if (!data) { + return mockModel.build([defaultValue]) } return Promise.resolve(data ? data.map((d) => mockModel.build(d)) : []) } else if (query === 'destroy') { diff --git a/migrations/20211119005510-add-image-to-user.js b/migrations/20211119005510-add-image-to-user.js new file mode 100644 index 00000000..899de31e --- /dev/null +++ b/migrations/20211119005510-add-image-to-user.js @@ -0,0 +1,13 @@ +'use strict' + +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.addColumn('Users', 'image', { + type: Sequelize.STRING + }) + }, + + down: async (queryInterface, Sequelize) => { + await queryInterface.removeColumn('Users', 'image') + } +} diff --git a/models/user.js b/models/user.js index 6b940ec1..9fb333d0 100644 --- a/models/user.js +++ b/models/user.js @@ -16,7 +16,8 @@ module.exports = (sequelize, DataTypes) => { name: DataTypes.STRING, email: DataTypes.STRING, password: DataTypes.STRING, - isAdmin: DataTypes.BOOLEAN + isAdmin: DataTypes.BOOLEAN, + image: DataTypes.STRING }, { sequelize, diff --git a/routes/index.js b/routes/index.js index a861fdc9..713d6a0d 100644 --- a/routes/index.js +++ b/routes/index.js @@ -122,4 +122,12 @@ module.exports = (app) => { app.get('/restaurants/:id', restController.getRestaurant) app.post('/comments', authenticated, commentController.postComment) app.delete('/comments/:id', authenticated, commentController.deleteComment) + app.get('/users/:id', authenticated, userController.getUser) + app.get('/users/:id/edit', authenticated, userController.editUser) + app.put( + '/users/:id', + authenticated, + upload.single('image'), + userController.putUser + ) } diff --git a/views/edit.handlebars b/views/edit.handlebars new file mode 100644 index 00000000..565a3e71 --- /dev/null +++ b/views/edit.handlebars @@ -0,0 +1,22 @@ +
+
+ + +
+
+ + +
+ +
\ No newline at end of file diff --git a/views/profile.handlebars b/views/profile.handlebars index 41d54123..bfaec25b 100644 --- a/views/profile.handlebars +++ b/views/profile.handlebars @@ -1,12 +1,12 @@
- Card image cap +
{{user.name}}

{{user.email}}

- edit + edit
\ No newline at end of file From 39ba4ffbff6b6bf748a673d62471cd86e6486c1c Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Fri, 19 Nov 2021 11:15:13 +0000 Subject: [PATCH 158/198] fix production connection --- config/config.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/config/config.json b/config/config.json index 7eefcc2b..c32bfca9 100644 --- a/config/config.json +++ b/config/config.json @@ -8,8 +8,6 @@ }, "production": { "use_env_variable": "CLEARDB_DATABASE_URL", - "username": "root", - "password": null, "database": "database_production", "host": "127.0.0.1", "dialect": "mysql" From c043d41cd1fd61a4444a5069b20dccecf72e88bf Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Fri, 19 Nov 2021 20:29:03 +0000 Subject: [PATCH 159/198] display user's commit on profile page --- config/handlebars-helpers.js | 4 +++ controllers/userController.js | 24 ++++++++++++--- migrations/20211117222631-create-comment.js | 6 ++-- seeders/20211119201043-comment-seed-file.js | 21 +++++++++++++ views/profile.handlebars | 33 +++++++++++++++++++-- 5 files changed, 78 insertions(+), 10 deletions(-) create mode 100644 seeders/20211119201043-comment-seed-file.js diff --git a/config/handlebars-helpers.js b/config/handlebars-helpers.js index 1111fca2..d39deb4b 100644 --- a/config/handlebars-helpers.js +++ b/config/handlebars-helpers.js @@ -10,5 +10,9 @@ module.exports = { moment: function (a) { return moment(a).fromNow() + }, + + count: function (a) { + return a.length } } diff --git a/controllers/userController.js b/controllers/userController.js index 0d3a5462..5b6b0f02 100644 --- a/controllers/userController.js +++ b/controllers/userController.js @@ -1,6 +1,8 @@ const bcrypt = require('bcrypt-nodejs') const db = require('../models') const User = db.User +const Restaurant = db.Restaurant +const Comment = db.Comment // imgur setup const imgur = require('imgur-node-api') const IMGUR_CLIENT_ID = process.env.IMGUR_CLIENT_ID @@ -48,10 +50,24 @@ const userController = { req.logout() res.redirect('/signin') }, - getUser: (req, res) => { - return User.findByPk(req.params.id).then((user) => { - return res.render('profile', { user: user.toJSON() }) - }) + getUser: async (req, res) => { + try { + const user = await User.findByPk(req.params.id, { + raw: true, + nest: true + }) + + const comments = await Comment.findAll({ + raw: true, + nest: true, + where: { UserId: req.params.id }, + include: [Restaurant] + }) + + return res.render('profile', { user: user, comments: comments }) + } catch (eror) { + console.log(error) + } }, editUser: (req, res) => { return User.findByPk(req.params.id).then((user) => { diff --git a/migrations/20211117222631-create-comment.js b/migrations/20211117222631-create-comment.js index 6967d04b..801b3f31 100644 --- a/migrations/20211117222631-create-comment.js +++ b/migrations/20211117222631-create-comment.js @@ -12,12 +12,10 @@ module.exports = { type: Sequelize.STRING }, UserId: { - type: Sequelize.INTEGER, - references: { model: 'users', key: 'id' } + type: Sequelize.INTEGER }, RestaurantId: { - type: Sequelize.INTEGER, - references: { model: 'restaurants', key: 'id' } + type: Sequelize.INTEGER }, createdAt: { allowNull: false, diff --git a/seeders/20211119201043-comment-seed-file.js b/seeders/20211119201043-comment-seed-file.js new file mode 100644 index 00000000..82718dc5 --- /dev/null +++ b/seeders/20211119201043-comment-seed-file.js @@ -0,0 +1,21 @@ +'use strict' + +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.bulkInsert( + 'Comments', + Array.from({ length: 50 }).map((d, i) => ({ + text: 'comment-seeder', + UserId: Math.floor(Math.random() * 2) + 1, + RestaurantId: Math.floor(Math.random() * 50) + 1, + createdAt: new Date(), + updatedAt: new Date() + })), + {} + ) + }, + + down: async (queryInterface, Sequelize) => { + await queryInterface.bulkDelete('Comments', null, {}) + } +} diff --git a/views/profile.handlebars b/views/profile.handlebars index bfaec25b..148d9ebc 100644 --- a/views/profile.handlebars +++ b/views/profile.handlebars @@ -1,12 +1,41 @@
-
- +
+
{{user.name}}

{{user.email}}

+

{{#count comments}}{{/count}} 已評論餐廳

edit
+
+
+
+ {{#count comments}}{{/count}} + 已評論餐廳 +
+
+ {{#each comments}} + + + + + {{/each}} +
\ No newline at end of file From 6a31e46340c51aff155525171f49271872ceb0ed Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Fri, 19 Nov 2021 21:02:33 +0000 Subject: [PATCH 160/198] fix missing graph and fix seeder id generate --- seeders/20211111003012-user-seed-file.js | 45 ++++++++++--------- .../20211114222527-categories-seed-file.js | 28 ++++++------ .../20211115230049-restaurants-seed-file.js | 19 ++++---- views/layouts/main.handlebars | 4 ++ views/profile.handlebars | 16 ++++--- 5 files changed, 63 insertions(+), 49 deletions(-) diff --git a/seeders/20211111003012-user-seed-file.js b/seeders/20211111003012-user-seed-file.js index b147ab70..ab1b077c 100644 --- a/seeders/20211111003012-user-seed-file.js +++ b/seeders/20211111003012-user-seed-file.js @@ -1,37 +1,40 @@ -'use strict' -const bcrypt = require('bcryptjs') -const { query } = require('express') +"use strict"; +const bcrypt = require("bcryptjs"); +const { query } = require("express"); module.exports = { up: async (queryInterface, Sequelize) => { - await queryInterface.bulkInsert('Users', [ + await queryInterface.bulkInsert("Users", [ { - email: 'root@example.com', - password: bcrypt.hashSync('12345678', bcrypt.genSaltSync(10), null), + id: 1, + email: "root@example.com", + password: bcrypt.hashSync("12345678", bcrypt.genSaltSync(10), null), isAdmin: true, - name: 'root', + name: "root", createdAt: new Date(), - updatedAt: new Date() + updatedAt: new Date(), }, { - email: 'user1@example.com', - password: bcrypt.hashSync('12345678', bcrypt.genSaltSync(10), null), + id: 2, + email: "user1@example.com", + password: bcrypt.hashSync("12345678", bcrypt.genSaltSync(10), null), isAdmin: false, - name: 'user1', + name: "user1", createdAt: new Date(), - updatedAt: new Date() + updatedAt: new Date(), }, { - email: 'user2@example.com', - password: bcrypt.hashSync('12345678', bcrypt.genSaltSync(10), null), + id: 3, + email: "user2@example.com", + password: bcrypt.hashSync("12345678", bcrypt.genSaltSync(10), null), isAdmin: false, - name: 'user2', + name: "user2", createdAt: new Date(), - updatedAt: new Date() - } - ]) + updatedAt: new Date(), + }, + ]); }, down: async (queryInterface, Sequelize) => { - await queryInterface.bulkDelete('Users', null, {}) - } -} + await queryInterface.bulkDelete("Users", null, {}); + }, +}; diff --git a/seeders/20211114222527-categories-seed-file.js b/seeders/20211114222527-categories-seed-file.js index 8b85992e..274ee9f9 100644 --- a/seeders/20211114222527-categories-seed-file.js +++ b/seeders/20211114222527-categories-seed-file.js @@ -1,28 +1,28 @@ -'use strict' +"use strict"; module.exports = { up: async (queryInterface, Sequelize) => { await queryInterface.bulkInsert( - 'Categories', + "Categories", [ - '中式料理', - '日本料理', - '義大利料理', - '墨西哥料理', - '素食料理', - '美式料理', - '複合式料理' + "中式料理", + "日本料理", + "義大利料理", + "墨西哥料理", + "素食料理", + "美式料理", + "複合式料理", ].map((item, index) => ({ id: index * 10 + 1, name: item, createdAt: new Date(), - updatedAt: new Date() + updatedAt: new Date(), })), {} - ) + ); }, down: async (queryInterface, Sequelize) => { - await queryInterface.bulkDelete('Categories', null, {}) - } -} + await queryInterface.bulkDelete("Categories", null, {}); + }, +}; diff --git a/seeders/20211115230049-restaurants-seed-file.js b/seeders/20211115230049-restaurants-seed-file.js index 1c8f18e8..6349718e 100644 --- a/seeders/20211115230049-restaurants-seed-file.js +++ b/seeders/20211115230049-restaurants-seed-file.js @@ -1,28 +1,29 @@ -'use strict' -const faker = require('faker') +"use strict"; +const faker = require("faker"); module.exports = { up: async (queryInterface, Sequelize) => { await queryInterface.bulkInsert( - 'Restaurants', + "Restaurants", Array.from({ length: 50 }).map((d, i) => ({ + id: i + 1, name: faker.name.findName(), tel: faker.phone.phoneNumber(), address: faker.address.streetAddress(), - opening_hours: '08:00', + opening_hours: "08:00", image: `https://loremflickr.com/320/240/restaurant,food/?random=${ Math.random() * 100 }`, description: faker.lorem.text(), createdAt: new Date(), updatedAt: new Date(), - CategoryId: Math.floor(Math.random() * 6) * 10 + 1 + CategoryId: Math.floor(Math.random() * 6) * 10 + 1, })), {} - ) + ); }, down: async (queryInterface, Sequelize) => { - await queryInterface.bulkDelete('Restaurants', null, {}) - } -} + await queryInterface.bulkDelete("Restaurants", null, {}); + }, +}; diff --git a/views/layouts/main.handlebars b/views/layouts/main.handlebars index 3c1e9829..0acf6e92 100644 --- a/views/layouts/main.handlebars +++ b/views/layouts/main.handlebars @@ -82,4 +82,8 @@ integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous" > + \ No newline at end of file diff --git a/views/profile.handlebars b/views/profile.handlebars index 148d9ebc..08eeb8f5 100644 --- a/views/profile.handlebars +++ b/views/profile.handlebars @@ -1,11 +1,17 @@
- + {{#if user.image}} + + {{else}} +
+ +
+ {{/if}}
{{user.name}}
From a2bca88c06464bfd6e86358fe864647d9e3ef06e Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Sat, 20 Nov 2021 08:03:34 +0000 Subject: [PATCH 161/198] add feeds page --- controllers/restController.js | 97 +++++++++++++++-------- routes/index.js | 141 +++++++++++++++++----------------- views/feeds.handlebars | 55 +++++++++++++ views/restaurants.handlebars | 10 ++- 4 files changed, 199 insertions(+), 104 deletions(-) create mode 100644 views/feeds.handlebars diff --git a/controllers/restController.js b/controllers/restController.js index 650f40f9..837ab47f 100644 --- a/controllers/restController.js +++ b/controllers/restController.js @@ -1,69 +1,100 @@ -const db = require('../models') -const Restaurant = db.Restaurant -const Category = db.Category -const User = db.User -const Comment = db.Comment +const db = require("../models"); +const Restaurant = db.Restaurant; +const Category = db.Category; +const User = db.User; +const Comment = db.Comment; -const pageLimit = 10 +const pageLimit = 10; const restControllers = { getRestaurants: (req, res) => { - let offset = 0 - const whereQuery = {} - let categoryId = '' + let offset = 0; + const whereQuery = {}; + let categoryId = ""; if (req.query.categoryId) { - categoryId = Number(req.query.categoryId) - whereQuery.CategoryId = categoryId + categoryId = Number(req.query.categoryId); + whereQuery.CategoryId = categoryId; } if (req.query.page) { - offset = (req.query.page - 1) * pageLimit + offset = (req.query.page - 1) * pageLimit; } Restaurant.findAndCountAll({ include: Category, where: whereQuery, offset: offset, - limit: pageLimit + limit: pageLimit, }).then((restaurants) => { // data for pagination - const page = Number(req.query.page) || 1 - const pages = Math.ceil(restaurants.count / pageLimit) + const page = Number(req.query.page) || 1; + const pages = Math.ceil(restaurants.count / pageLimit); const totalPage = Array.from({ length: pages }).map( (item, index) => index + 1 - ) + ); - const prev = page - 1 < 1 ? 1 : page - 1 - const next = page + 1 > pages ? pages : page + 1 + const prev = page - 1 < 1 ? 1 : page - 1; + const next = page + 1 > pages ? pages : page + 1; const data = restaurants.rows.map((r) => ({ ...r.dataValues, description: r.dataValues.description.substring(0, 50), - categoryName: r.dataValues.Category.name - })) + categoryName: r.dataValues.Category.name, + })); Category.findAll({ raw: true, nest: true }).then((categories) => { - return res.render('restaurants', { + return res.render("restaurants", { restaurants: data, categories, categoryId, page, totalPage, prev, - next - }) - }) - }) + next, + }); + }); + }); }, getRestaurant: (req, res) => { return Restaurant.findByPk(req.params.id, { - include: [Category, { model: Comment, include: User }] + include: [Category, { model: Comment, include: User }], }).then((restaurant) => { - return res.render('restaurant', { - restaurant: restaurant.toJSON() - }) - }) - } -} + return res.render("restaurant", { + restaurant: restaurant.toJSON(), + }); + }); + }, + + getFeeds: async (req, res) => { + //get restaurant + + try { + const restaurants = await Restaurant.findAll({ + limit: 10, + raw: true, + nest: true, + order: [["createdAt", "DESC"]], + include: [Category], + }); + + const comments = await Comment.findAll({ + limit: 10, + raw: true, + nest: true, + order: [["createdAt", "DESC"]], + include: [User, Restaurant], + }); + + return res.render("feeds", { + restaurants, + comments, + }); + } catch (error) { + console.log("Error: ", error); + } + + //get restaurnat comment + }, +}; -module.exports = restControllers +module.exports = restControllers; diff --git a/routes/index.js b/routes/index.js index 713d6a0d..08126ad0 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,133 +1,134 @@ -const restController = require('../controllers/restController') -const adminController = require('../controllers/adminController') -const userController = require('../controllers/userController') -const commentController = require('../controllers/commentController') -const passport = require('passport') -const helpers = require('../_helpers') +const restController = require("../controllers/restController"); +const adminController = require("../controllers/adminController"); +const userController = require("../controllers/userController"); +const commentController = require("../controllers/commentController"); +const passport = require("passport"); +const helpers = require("../_helpers"); // multer setup -const multer = require('multer') -const categoryController = require('../controllers/categoryController') -const upload = multer({ dest: 'temp/' }) +const multer = require("multer"); +const categoryController = require("../controllers/categoryController"); +const upload = multer({ dest: "temp/" }); module.exports = (app) => { const authenticated = (req, res, next) => { if (helpers.ensureAuthenticated(req)) { - return next() + return next(); } - res.redirect('/signin') - } + res.redirect("/signin"); + }; const authenticatedAdmin = (req, res, next) => { if (helpers.ensureAuthenticated(req)) { if (helpers.getUser(req).isAdmin) { - return next() + return next(); } - return res.redirect('/') + return res.redirect("/"); } - res.redirect('/signin') - } + res.redirect("/signin"); + }; // 如果使用者訪問首頁,就導向 /restaurants 的頁面 - app.get('/', authenticated, (req, res) => { - res.redirect('restaurants') - }) - app.get('/restaurants', restController.getRestaurants) + app.get("/", authenticated, (req, res) => { + res.redirect("restaurants"); + }); + app.get("/restaurants", restController.getRestaurants); - app.get('/admin', authenticatedAdmin, (req, res) => { - res.redirect('/admin/restaurants') - }) + app.get("/admin", authenticatedAdmin, (req, res) => { + res.redirect("/admin/restaurants"); + }); app.get( - '/admin/restaurants', + "/admin/restaurants", authenticatedAdmin, adminController.getRestaurants - ) + ); - app.get('/signup', userController.signUpPage) - app.post('/signup', userController.signUp) + app.get("/signup", userController.signUpPage); + app.post("/signup", userController.signUp); - app.get('/signin', userController.signInPage) + app.get("/signin", userController.signInPage); app.post( - '/signin', - passport.authenticate('local', { - failureRedirect: '/signin', - failureFlash: true + "/signin", + passport.authenticate("local", { + failureRedirect: "/signin", + failureFlash: true, }), userController.signIn - ) - app.get('/logout', userController.logout) + ); + app.get("/logout", userController.logout); app.get( - '/admin/restaurants/create', + "/admin/restaurants/create", authenticatedAdmin, adminController.createRestaurants - ) + ); app.post( - '/admin/restaurants', + "/admin/restaurants", authenticatedAdmin, - upload.single('image'), + upload.single("image"), adminController.postRestaurants - ) + ); app.get( - '/admin/restaurants/:id', + "/admin/restaurants/:id", authenticatedAdmin, adminController.getRestaurant - ) + ); app.get( - '/admin/restaurants/:id/edit', + "/admin/restaurants/:id/edit", authenticatedAdmin, adminController.editRestaurant - ) + ); app.put( - '/admin/restaurants/:id', + "/admin/restaurants/:id", authenticatedAdmin, - upload.single('image'), + upload.single("image"), adminController.putRestaurant - ) + ); app.delete( - '/admin/restaurants/:id', + "/admin/restaurants/:id", authenticatedAdmin, adminController.deleteRestaurant - ) - app.get('/admin/users', authenticatedAdmin, adminController.getUsers) + ); + app.get("/admin/users", authenticatedAdmin, adminController.getUsers); app.put( - '/admin/users/:id/toggleAdmin', + "/admin/users/:id/toggleAdmin", authenticatedAdmin, adminController.toggleAdmin - ) + ); app.get( - '/admin/categories', + "/admin/categories", authenticatedAdmin, categoryController.getCategories - ) + ); app.post( - '/admin/categories', + "/admin/categories", authenticatedAdmin, categoryController.postCategory - ) + ); app.get( - '/admin/categories/:id', + "/admin/categories/:id", authenticatedAdmin, categoryController.getCategories - ) + ); app.put( - '/admin/categories/:id', + "/admin/categories/:id", authenticatedAdmin, categoryController.putCategory - ) + ); app.delete( - '/admin/categories/:id', + "/admin/categories/:id", authenticatedAdmin, categoryController.deletCategory - ) - app.get('/restaurants/:id', restController.getRestaurant) - app.post('/comments', authenticated, commentController.postComment) - app.delete('/comments/:id', authenticated, commentController.deleteComment) - app.get('/users/:id', authenticated, userController.getUser) - app.get('/users/:id/edit', authenticated, userController.editUser) + ); + app.get("/restaurants/feeds", authenticated, restController.getFeeds); + app.get("/restaurants/:id", authenticated, restController.getRestaurant); + app.post("/comments", authenticated, commentController.postComment); + app.delete("/comments/:id", authenticated, commentController.deleteComment); + app.get("/users/:id", authenticated, userController.getUser); + app.get("/users/:id/edit", authenticated, userController.editUser); app.put( - '/users/:id', + "/users/:id", authenticated, - upload.single('image'), + upload.single("image"), userController.putUser - ) -} + ); +}; diff --git a/views/feeds.handlebars b/views/feeds.handlebars new file mode 100644 index 00000000..2e7d2728 --- /dev/null +++ b/views/feeds.handlebars @@ -0,0 +1,55 @@ + +

+
+
+
+
+ 最新餐廳 +
+
+ {{#each restaurants}} +
+

+ {{this.name}} + {{this.Category.name}} +

+

{{this.description}}

+ {{moment this.createdAt}} +
+
+ {{/each}} +
+
+
+
+
+
+ 最新評論 +
+
+ {{#each comments}} +
+

+ {{this.Restaurant.name}} +

+

{{this.text}}

+ by + {{this.User.name}} + at + {{moment this.createdAt}} +
+
+ {{/each}} +
+
+
+
\ No newline at end of file diff --git a/views/restaurants.handlebars b/views/restaurants.handlebars index d54556a0..0fcd0d18 100644 --- a/views/restaurants.handlebars +++ b/views/restaurants.handlebars @@ -1,4 +1,12 @@ - + +

{{restaurant.description}}

+



所有評論:

{{#each restaurant.Comments}} - {{#if ../user.isAdmin}} -
- -
- {{/if}} -
-

{{this.User.name}}

-

{{this.text}}

-
{{moment this.createdAt}}
-
-
+{{#if ../user.isAdmin}} +
+ +
+{{/if}} +
+

{{this.User.name}}

+

{{this.text}}

+
{{moment this.createdAt}}
+
+
{{/each}}
From c97de3fdc84c6752ce0b36045c4cd9d949ecfd95 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Sat, 20 Nov 2021 16:58:24 +0000 Subject: [PATCH 171/198] fix styling --- controllers/restController.js | 106 ++++++------- helpers/unitTestHelpers.js | 104 ++++++------ ...1120142551-add-viewCounts-to-restaurant.js | 14 ++ routes/index.js | 148 +++++++++--------- seeders/20211111003012-user-seed-file.js | 40 ++--- 5 files changed, 213 insertions(+), 199 deletions(-) create mode 100644 migrations/20211120142551-add-viewCounts-to-restaurant.js diff --git a/controllers/restController.js b/controllers/restController.js index 19809c06..6c7a3ec0 100644 --- a/controllers/restController.js +++ b/controllers/restController.js @@ -1,68 +1,68 @@ -const db = require("../models"); -const Restaurant = db.Restaurant; -const Category = db.Category; -const User = db.User; -const Comment = db.Comment; +const db = require('../models') +const Restaurant = db.Restaurant +const Category = db.Category +const User = db.User +const Comment = db.Comment -const pageLimit = 10; +const pageLimit = 10 const restControllers = { getRestaurants: (req, res) => { - let offset = 0; - const whereQuery = {}; - let categoryId = ""; + let offset = 0 + const whereQuery = {} + let categoryId = '' if (req.query.categoryId) { - categoryId = Number(req.query.categoryId); - whereQuery.CategoryId = categoryId; + categoryId = Number(req.query.categoryId) + whereQuery.CategoryId = categoryId } if (req.query.page) { - offset = (req.query.page - 1) * pageLimit; + offset = (req.query.page - 1) * pageLimit } Restaurant.findAndCountAll({ include: Category, where: whereQuery, offset: offset, - limit: pageLimit, + limit: pageLimit }).then((restaurants) => { // data for pagination - const page = Number(req.query.page) || 1; - const pages = Math.ceil(restaurants.count / pageLimit); + const page = Number(req.query.page) || 1 + const pages = Math.ceil(restaurants.count / pageLimit) const totalPage = Array.from({ length: pages }).map( (item, index) => index + 1 - ); + ) - const prev = page - 1 < 1 ? 1 : page - 1; - const next = page + 1 > pages ? pages : page + 1; + const prev = page - 1 < 1 ? 1 : page - 1 + const next = page + 1 > pages ? pages : page + 1 const data = restaurants.rows.map((r) => ({ ...r.dataValues, description: r.dataValues.description.substring(0, 50), - categoryName: r.dataValues.Category.name, - })); + categoryName: r.dataValues.Category.name + })) Category.findAll({ raw: true, nest: true }).then((categories) => { - return res.render("restaurants", { + return res.render('restaurants', { restaurants: data, categories, categoryId, page, totalPage, prev, - next, - }); - }); - }); + next + }) + }) + }) }, getRestaurant: (req, res) => { return Restaurant.findByPk(req.params.id, { - include: [Category, { model: Comment, include: User }], + include: [Category, { model: Comment, include: User }] }).then((restaurant) => { - return res.render("restaurant", { - restaurant: restaurant.toJSON(), - }); - }); + return res.render('restaurant', { + restaurant: restaurant.toJSON() + }) + }) }, getFeeds: async (req, res) => { @@ -71,45 +71,45 @@ const restControllers = { limit: 10, raw: true, nest: true, - order: [["createdAt", "DESC"]], - include: [Category], - }); + order: [['createdAt', 'DESC']], + include: [Category] + }) const comments = await Comment.findAll({ limit: 10, raw: true, nest: true, - order: [["createdAt", "DESC"]], - include: [User, Restaurant], - }); + order: [['createdAt', 'DESC']], + include: [User, Restaurant] + }) - return res.render("feeds", { + return res.render('feeds', { restaurants, - comments, - }); + comments + }) } catch (error) { - console.log("Error: ", error); + console.log('Error: ', error) } }, getDashBoard: async (req, res) => { - const restaurantId = req.params.id; + const restaurantId = req.params.id const restaurant = await Restaurant.findByPk(restaurantId, { - include: Category, - }); + include: Category + }) const comment = await Comment.findAndCountAll({ - where: { RestaurantId: restaurantId }, - }); + where: { RestaurantId: restaurantId } + }) - console.log(comment.count); + console.log(comment.count) - return res.render("dashboard", { + return res.render('dashboard', { restaurant: { ...restaurant.toJSON(), - commentCount: comment.count, - }, - }); - }, -}; + commentCount: comment.count + } + }) + } +} -module.exports = restControllers; +module.exports = restControllers diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js index 263c893d..b13887cf 100644 --- a/helpers/unitTestHelpers.js +++ b/helpers/unitTestHelpers.js @@ -1,111 +1,111 @@ -const SequelizeMock = require("sequelize-mock"); -const proxyquire = require("proxyquire"); -const sinon = require("sinon"); +const SequelizeMock = require('sequelize-mock') +const proxyquire = require('proxyquire') +const sinon = require('sinon') -const dbMock = new SequelizeMock(); +const dbMock = new SequelizeMock() const createModelMock = (name, defaultValue, data, joinedTableName) => { const mockModel = dbMock.define(name, defaultValue, { instanceMethods: { update: (changes) => { - mockModel._defaults = { ...changes }; - return Promise.resolve(); - }, - }, - }); + mockModel._defaults = { ...changes } + return Promise.resolve() + } + } + }) // 模擬 Sequelize 行為 // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) - mockModel.findByPk = (id) => mockModel.findOne({ where: { id: id } }); + mockModel.findByPk = (id) => mockModel.findOne({ where: { id: id } }) // 將 count 的 function 預設回傳假資料數目 1 - mockModel.count = () => 1; + mockModel.count = () => 1 // 因為 mock 中的 create 有問題,因此指向 upsert function, 這樣可以在 useHandler 中取得 create 呼叫 - mockModel.create = mockModel.upsert; + mockModel.create = mockModel.upsert // modify middleware if (joinedTableName) { mockModel.$queryInterface.$useHandler((query, queryOptions) => { - if (query === "upsert") { + if (query === 'upsert') { // 新增 joinTable 資料到模擬資料 - const { UserId, RestaurantId } = queryOptions[0]; - const restaurant = data.find((d) => d.id === RestaurantId); - restaurant[joinedTableName].push({ UserId: UserId }); - return Promise.resolve(data.map((d) => mockModel.build(d))); - } else if (query === "findAll") { + const { UserId, RestaurantId } = queryOptions[0] + const restaurant = data.find((d) => d.id === RestaurantId) + restaurant[joinedTableName].push({ UserId: UserId }) + return Promise.resolve(data.map((d) => mockModel.build(d))) + } else if (query === 'findAll') { // 回傳模擬資料 if (!data) { - return mockModel.build([defaultValue]); + return mockModel.build([defaultValue]) } - return Promise.resolve(data ? data.map((d) => mockModel.build(d)) : []); - } else if (query === "destroy") { + return Promise.resolve(data ? data.map((d) => mockModel.build(d)) : []) + } else if (query === 'destroy') { // destroy 可以從 where 取得要刪除的資料 // 因此就可以模擬將模擬資料中的資料刪除 // 刪除模擬資料中的某一筆 joinTable 資料 - const { UserId, RestaurantId } = queryOptions[0].where; - const restaurant = data.find((d) => d.id === RestaurantId); + const { UserId, RestaurantId } = queryOptions[0].where + const restaurant = data.find((d) => d.id === RestaurantId) restaurant[joinedTableName] = restaurant[joinedTableName].filter( (d) => !(d.UserId === UserId) - ); - return Promise.resolve(data.map((d) => mockModel.build(d))); + ) + return Promise.resolve(data.map((d) => mockModel.build(d))) } - }); + }) } else { mockModel.$queryInterface.$useHandler((query, queryOptions, done) => { - if (query === "upsert") { + if (query === 'upsert') { // create 時會帶 userId 跟 restaurantId (ex: Like.create({ userId: 1, restaurantId: 2})) - const { UserId, RestaurantId } = queryOptions[0]; + const { UserId, RestaurantId } = queryOptions[0] // 新增這個 Like 的資訊到模擬資料裡 - data.push({ UserId, RestaurantId }); + data.push({ UserId, RestaurantId }) // 回傳模擬資料 - return Promise.resolve(mockModel.build(data)); - } else if (query === "findAll") { + return Promise.resolve(mockModel.build(data)) + } else if (query === 'findAll') { // 回傳模擬資料 if (!data) { - return mockModel.build([defaultValue]); + return mockModel.build([defaultValue]) } - return Promise.resolve(data ? data.map((d) => mockModel.build(d)) : []); - } else if (query === "destroy") { + return Promise.resolve(data ? data.map((d) => mockModel.build(d)) : []) + } else if (query === 'destroy') { // destroy 可以從 where 取得要刪除的資料 // 因此就可以模擬將模擬資料中的資料刪除 - const { UserId, RestaurantId } = queryOptions[0].where; + const { UserId, RestaurantId } = queryOptions[0].where data = data.filter( (d) => !(d.UserId === UserId && d.RestaurantId === RestaurantId) - ); + ) - return Promise.resolve(mockModel.build(data)); + return Promise.resolve(mockModel.build(data)) } - }); + }) } - return mockModel; -}; + return mockModel +} const createControllerProxy = (path, model) => { const controller = proxyquire(path, { - "../models": model, - }); + '../models': model + }) - return controller; -}; + return controller +} const mockRequest = (query) => { return { ...query, - flash: sinon.spy(), - }; -}; + flash: sinon.spy() + } +} const mockResponse = () => { return { redirect: sinon.spy(), - render: sinon.spy(), - }; -}; + render: sinon.spy() + } +} module.exports = { createModelMock, createControllerProxy, mockRequest, - mockResponse, -}; + mockResponse +} diff --git a/migrations/20211120142551-add-viewCounts-to-restaurant.js b/migrations/20211120142551-add-viewCounts-to-restaurant.js new file mode 100644 index 00000000..e95291b2 --- /dev/null +++ b/migrations/20211120142551-add-viewCounts-to-restaurant.js @@ -0,0 +1,14 @@ +'use strict' + +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.addColumn('Restaurants', 'viewCounts', { + type: Sequelize.INTEGER, + defaultValue: 0 + }) + }, + + down: async (queryInterface, Sequelize) => { + await queryInterface.removeColumn('Restaurants', 'viewCounts') + } +} diff --git a/routes/index.js b/routes/index.js index 06a41305..d1ecff02 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,140 +1,140 @@ -const restController = require("../controllers/restController"); -const adminController = require("../controllers/adminController"); -const userController = require("../controllers/userController"); -const commentController = require("../controllers/commentController"); -const passport = require("passport"); -const helpers = require("../_helpers"); +const restController = require('../controllers/restController') +const adminController = require('../controllers/adminController') +const userController = require('../controllers/userController') +const commentController = require('../controllers/commentController') +const passport = require('passport') +const helpers = require('../_helpers') // multer setup -const multer = require("multer"); -const categoryController = require("../controllers/categoryController"); -const restControllers = require("../controllers/restController"); -const upload = multer({ dest: "temp/" }); +const multer = require('multer') +const categoryController = require('../controllers/categoryController') +const restControllers = require('../controllers/restController') +const upload = multer({ dest: 'temp/' }) module.exports = (app) => { const authenticated = (req, res, next) => { if (helpers.ensureAuthenticated(req)) { - return next(); + return next() } - res.redirect("/signin"); - }; + res.redirect('/signin') + } const authenticatedAdmin = (req, res, next) => { if (helpers.ensureAuthenticated(req)) { if (helpers.getUser(req).isAdmin) { - return next(); + return next() } - return res.redirect("/"); + return res.redirect('/') } - res.redirect("/signin"); - }; + res.redirect('/signin') + } // 如果使用者訪問首頁,就導向 /restaurants 的頁面 - app.get("/", authenticated, (req, res) => { - res.redirect("restaurants"); - }); - app.get("/restaurants", restController.getRestaurants); + app.get('/', authenticated, (req, res) => { + res.redirect('restaurants') + }) + app.get('/restaurants', restController.getRestaurants) - app.get("/admin", authenticatedAdmin, (req, res) => { - res.redirect("/admin/restaurants"); - }); + app.get('/admin', authenticatedAdmin, (req, res) => { + res.redirect('/admin/restaurants') + }) app.get( - "/admin/restaurants", + '/admin/restaurants', authenticatedAdmin, adminController.getRestaurants - ); + ) - app.get("/signup", userController.signUpPage); - app.post("/signup", userController.signUp); + app.get('/signup', userController.signUpPage) + app.post('/signup', userController.signUp) - app.get("/signin", userController.signInPage); + app.get('/signin', userController.signInPage) app.post( - "/signin", - passport.authenticate("local", { - failureRedirect: "/signin", - failureFlash: true, + '/signin', + passport.authenticate('local', { + failureRedirect: '/signin', + failureFlash: true }), userController.signIn - ); - app.get("/logout", userController.logout); + ) + app.get('/logout', userController.logout) app.get( - "/admin/restaurants/create", + '/admin/restaurants/create', authenticatedAdmin, adminController.createRestaurants - ); + ) app.post( - "/admin/restaurants", + '/admin/restaurants', authenticatedAdmin, - upload.single("image"), + upload.single('image'), adminController.postRestaurants - ); + ) app.get( - "/admin/restaurants/:id", + '/admin/restaurants/:id', authenticatedAdmin, adminController.getRestaurant - ); + ) app.get( - "/admin/restaurants/:id/edit", + '/admin/restaurants/:id/edit', authenticatedAdmin, adminController.editRestaurant - ); + ) app.put( - "/admin/restaurants/:id", + '/admin/restaurants/:id', authenticatedAdmin, - upload.single("image"), + upload.single('image'), adminController.putRestaurant - ); + ) app.delete( - "/admin/restaurants/:id", + '/admin/restaurants/:id', authenticatedAdmin, adminController.deleteRestaurant - ); - app.get("/admin/users", authenticatedAdmin, adminController.getUsers); + ) + app.get('/admin/users', authenticatedAdmin, adminController.getUsers) app.put( - "/admin/users/:id/toggleAdmin", + '/admin/users/:id/toggleAdmin', authenticatedAdmin, adminController.toggleAdmin - ); + ) app.get( - "/admin/categories", + '/admin/categories', authenticatedAdmin, categoryController.getCategories - ); + ) app.post( - "/admin/categories", + '/admin/categories', authenticatedAdmin, categoryController.postCategory - ); + ) app.get( - "/admin/categories/:id", + '/admin/categories/:id', authenticatedAdmin, categoryController.getCategories - ); + ) app.put( - "/admin/categories/:id", + '/admin/categories/:id', authenticatedAdmin, categoryController.putCategory - ); + ) app.delete( - "/admin/categories/:id", + '/admin/categories/:id', authenticatedAdmin, categoryController.deletCategory - ); - app.get("/restaurants/feeds", authenticated, restController.getFeeds); - app.get("/restaurants/:id", authenticated, restController.getRestaurant); - app.post("/comments", authenticated, commentController.postComment); - app.delete("/comments/:id", authenticated, commentController.deleteComment); - app.get("/users/:id", authenticated, userController.getUser); - app.get("/users/:id/edit", authenticated, userController.editUser); + ) + app.get('/restaurants/feeds', authenticated, restController.getFeeds) + app.get('/restaurants/:id', authenticated, restController.getRestaurant) + app.post('/comments', authenticated, commentController.postComment) + app.delete('/comments/:id', authenticated, commentController.deleteComment) + app.get('/users/:id', authenticated, userController.getUser) + app.get('/users/:id/edit', authenticated, userController.editUser) app.put( - "/users/:id", + '/users/:id', authenticated, - upload.single("image"), + upload.single('image'), userController.putUser - ); + ) app.get( - "/restaurants/:id/dashboard", + '/restaurants/:id/dashboard', authenticated, restControllers.getDashBoard - ); -}; + ) +} diff --git a/seeders/20211111003012-user-seed-file.js b/seeders/20211111003012-user-seed-file.js index 1168c7d9..22ae1b97 100644 --- a/seeders/20211111003012-user-seed-file.js +++ b/seeders/20211111003012-user-seed-file.js @@ -1,39 +1,39 @@ -"use strict"; -const bcrypt = require("bcryptjs"); +'use strict' +const bcrypt = require('bcryptjs') module.exports = { up: async (queryInterface, Sequelize) => { - await queryInterface.bulkInsert("Users", [ + await queryInterface.bulkInsert('Users', [ { id: 1, - email: "root@example.com", - password: bcrypt.hashSync("12345678", bcrypt.genSaltSync(10), null), + email: 'root@example.com', + password: bcrypt.hashSync('12345678', bcrypt.genSaltSync(10), null), isAdmin: true, - name: "root", + name: 'root', createdAt: new Date(), - updatedAt: new Date(), + updatedAt: new Date() }, { id: 2, - email: "user1@example.com", - password: bcrypt.hashSync("12345678", bcrypt.genSaltSync(10), null), + email: 'user1@example.com', + password: bcrypt.hashSync('12345678', bcrypt.genSaltSync(10), null), isAdmin: false, - name: "user1", + name: 'user1', createdAt: new Date(), - updatedAt: new Date(), + updatedAt: new Date() }, { id: 3, - email: "user2@example.com", - password: bcrypt.hashSync("12345678", bcrypt.genSaltSync(10), null), + email: 'user2@example.com', + password: bcrypt.hashSync('12345678', bcrypt.genSaltSync(10), null), isAdmin: false, - name: "user2", + name: 'user2', createdAt: new Date(), - updatedAt: new Date(), - }, - ]); + updatedAt: new Date() + } + ]) }, down: async (queryInterface, Sequelize) => { - await queryInterface.bulkDelete("Users", null, {}); - }, -}; + await queryInterface.bulkDelete('Users', null, {}) + } +} From b642c0b3962fc90ed565a53d4213b0e9ee273cb7 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Sat, 20 Nov 2021 16:59:35 +0000 Subject: [PATCH 172/198] add dashboard view to show comment --- controllers/restController.js | 4 ++++ routes/index.js | 6 ++++++ views/dashboard.handlebars | 15 --------------- views/restaurant.handlebars | 23 +++++++++++------------ 4 files changed, 21 insertions(+), 27 deletions(-) delete mode 100644 views/dashboard.handlebars diff --git a/controllers/restController.js b/controllers/restController.js index 6c7a3ec0..86b88053 100644 --- a/controllers/restController.js +++ b/controllers/restController.js @@ -91,6 +91,7 @@ const restControllers = { console.log('Error: ', error) } }, +<<<<<<< HEAD getDashBoard: async (req, res) => { const restaurantId = req.params.id @@ -111,5 +112,8 @@ const restControllers = { }) } } +======= +}; +>>>>>>> parent of a49b72c... add dashboard view to show comment module.exports = restControllers diff --git a/routes/index.js b/routes/index.js index d1ecff02..8d55578c 100644 --- a/routes/index.js +++ b/routes/index.js @@ -8,7 +8,10 @@ const helpers = require('../_helpers') // multer setup const multer = require('multer') const categoryController = require('../controllers/categoryController') +<<<<<<< HEAD const restControllers = require('../controllers/restController') +======= +>>>>>>> parent of a49b72c... add dashboard view to show comment const upload = multer({ dest: 'temp/' }) module.exports = (app) => { @@ -132,9 +135,12 @@ module.exports = (app) => { upload.single('image'), userController.putUser ) +<<<<<<< HEAD app.get( '/restaurants/:id/dashboard', authenticated, restControllers.getDashBoard ) +======= +>>>>>>> parent of a49b72c... add dashboard view to show comment } diff --git a/views/dashboard.handlebars b/views/dashboard.handlebars deleted file mode 100644 index 66515f1f..00000000 --- a/views/dashboard.handlebars +++ /dev/null @@ -1,15 +0,0 @@ -
-
-

{{restaurant.name}}

-

[{{restaurant.Category.name}}]

-
-
-
-
    -
  • 評論數:{{restaurant.commentCount}}
  • -
  • 瀏覽次數:
  • -
  • 收藏數:
  • -
-
-
-回上一頁 \ No newline at end of file diff --git a/views/restaurant.handlebars b/views/restaurant.handlebars index b3bcddb0..59147240 100644 --- a/views/restaurant.handlebars +++ b/views/restaurant.handlebars @@ -15,24 +15,23 @@

{{restaurant.description}}

-



所有評論:

{{#each restaurant.Comments}} -{{#if ../user.isAdmin}} - - - -{{/if}} -
-

{{this.User.name}}

-

{{this.text}}

-
{{moment this.createdAt}}
-
-
+ {{#if ../user.isAdmin}} +
+ +
+ {{/if}} +
+

{{this.User.name}}

+

{{this.text}}

+
{{moment this.createdAt}}
+
+
{{/each}}
From 6f18312567ac228a5b6307624076d702eafc773d Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Sat, 20 Nov 2021 17:10:31 +0000 Subject: [PATCH 173/198] fix styling and merge conflict --- controllers/restController.js | 109 +++++++++++------------- routes/index.js | 154 ++++++++++++++++------------------ tests/R02.test.js | 53 ------------ 3 files changed, 123 insertions(+), 193 deletions(-) diff --git a/controllers/restController.js b/controllers/restController.js index 86b88053..018e8406 100644 --- a/controllers/restController.js +++ b/controllers/restController.js @@ -1,68 +1,69 @@ -const db = require('../models') -const Restaurant = db.Restaurant -const Category = db.Category -const User = db.User -const Comment = db.Comment +const db = require("../models"); +const Restaurant = db.Restaurant; +const Category = db.Category; +const User = db.User; +const Comment = db.Comment; -const pageLimit = 10 +const pageLimit = 10; const restControllers = { getRestaurants: (req, res) => { - let offset = 0 - const whereQuery = {} - let categoryId = '' + let offset = 0; + const whereQuery = {}; + let categoryId = ""; if (req.query.categoryId) { - categoryId = Number(req.query.categoryId) - whereQuery.CategoryId = categoryId + categoryId = Number(req.query.categoryId); + whereQuery.CategoryId = categoryId; } if (req.query.page) { - offset = (req.query.page - 1) * pageLimit + offset = (req.query.page - 1) * pageLimit; } Restaurant.findAndCountAll({ include: Category, where: whereQuery, offset: offset, - limit: pageLimit + limit: pageLimit, }).then((restaurants) => { // data for pagination - const page = Number(req.query.page) || 1 - const pages = Math.ceil(restaurants.count / pageLimit) + const page = Number(req.query.page) || 1; + const pages = Math.ceil(restaurants.count / pageLimit); const totalPage = Array.from({ length: pages }).map( (item, index) => index + 1 - ) + ); - const prev = page - 1 < 1 ? 1 : page - 1 - const next = page + 1 > pages ? pages : page + 1 + const prev = page - 1 < 1 ? 1 : page - 1; + const next = page + 1 > pages ? pages : page + 1; const data = restaurants.rows.map((r) => ({ ...r.dataValues, description: r.dataValues.description.substring(0, 50), - categoryName: r.dataValues.Category.name - })) + categoryName: r.dataValues.Category.name, + })); Category.findAll({ raw: true, nest: true }).then((categories) => { - return res.render('restaurants', { + return res.render("restaurants", { restaurants: data, categories, categoryId, page, totalPage, prev, - next - }) - }) - }) + next, + }); + }); + }); }, getRestaurant: (req, res) => { return Restaurant.findByPk(req.params.id, { - include: [Category, { model: Comment, include: User }] + include: [Category, { model: Comment, include: User }], }).then((restaurant) => { - return res.render('restaurant', { - restaurant: restaurant.toJSON() - }) - }) + restaurant.increment("viewCounts"); + return res.render("restaurant", { + restaurant: restaurant.toJSON(), + }); + }); }, getFeeds: async (req, res) => { @@ -71,49 +72,37 @@ const restControllers = { limit: 10, raw: true, nest: true, - order: [['createdAt', 'DESC']], - include: [Category] - }) + order: [["createdAt", "DESC"]], + include: [Category], + }); const comments = await Comment.findAll({ limit: 10, raw: true, nest: true, - order: [['createdAt', 'DESC']], - include: [User, Restaurant] - }) + order: [["createdAt", "DESC"]], + include: [User, Restaurant], + }); - return res.render('feeds', { + return res.render("feeds", { restaurants, - comments - }) + comments, + }); } catch (error) { - console.log('Error: ', error) + console.log("Error: ", error); } }, -<<<<<<< HEAD getDashBoard: async (req, res) => { - const restaurantId = req.params.id + const restaurantId = req.params.id; const restaurant = await Restaurant.findByPk(restaurantId, { - include: Category - }) + include: [Category, { model: Comment }], + }); const comment = await Comment.findAndCountAll({ - where: { RestaurantId: restaurantId } - }) - - console.log(comment.count) - - return res.render('dashboard', { - restaurant: { - ...restaurant.toJSON(), - commentCount: comment.count - } - }) - } -} -======= + where: { RestaurantId: restaurantId }, + }); + return res.render("dashboard", { restaurant: restaurant.toJSON() }); + }, }; ->>>>>>> parent of a49b72c... add dashboard view to show comment -module.exports = restControllers +module.exports = restControllers; diff --git a/routes/index.js b/routes/index.js index 8d55578c..06a41305 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,146 +1,140 @@ -const restController = require('../controllers/restController') -const adminController = require('../controllers/adminController') -const userController = require('../controllers/userController') -const commentController = require('../controllers/commentController') -const passport = require('passport') -const helpers = require('../_helpers') +const restController = require("../controllers/restController"); +const adminController = require("../controllers/adminController"); +const userController = require("../controllers/userController"); +const commentController = require("../controllers/commentController"); +const passport = require("passport"); +const helpers = require("../_helpers"); // multer setup -const multer = require('multer') -const categoryController = require('../controllers/categoryController') -<<<<<<< HEAD -const restControllers = require('../controllers/restController') -======= ->>>>>>> parent of a49b72c... add dashboard view to show comment -const upload = multer({ dest: 'temp/' }) +const multer = require("multer"); +const categoryController = require("../controllers/categoryController"); +const restControllers = require("../controllers/restController"); +const upload = multer({ dest: "temp/" }); module.exports = (app) => { const authenticated = (req, res, next) => { if (helpers.ensureAuthenticated(req)) { - return next() + return next(); } - res.redirect('/signin') - } + res.redirect("/signin"); + }; const authenticatedAdmin = (req, res, next) => { if (helpers.ensureAuthenticated(req)) { if (helpers.getUser(req).isAdmin) { - return next() + return next(); } - return res.redirect('/') + return res.redirect("/"); } - res.redirect('/signin') - } + res.redirect("/signin"); + }; // 如果使用者訪問首頁,就導向 /restaurants 的頁面 - app.get('/', authenticated, (req, res) => { - res.redirect('restaurants') - }) - app.get('/restaurants', restController.getRestaurants) + app.get("/", authenticated, (req, res) => { + res.redirect("restaurants"); + }); + app.get("/restaurants", restController.getRestaurants); - app.get('/admin', authenticatedAdmin, (req, res) => { - res.redirect('/admin/restaurants') - }) + app.get("/admin", authenticatedAdmin, (req, res) => { + res.redirect("/admin/restaurants"); + }); app.get( - '/admin/restaurants', + "/admin/restaurants", authenticatedAdmin, adminController.getRestaurants - ) + ); - app.get('/signup', userController.signUpPage) - app.post('/signup', userController.signUp) + app.get("/signup", userController.signUpPage); + app.post("/signup", userController.signUp); - app.get('/signin', userController.signInPage) + app.get("/signin", userController.signInPage); app.post( - '/signin', - passport.authenticate('local', { - failureRedirect: '/signin', - failureFlash: true + "/signin", + passport.authenticate("local", { + failureRedirect: "/signin", + failureFlash: true, }), userController.signIn - ) - app.get('/logout', userController.logout) + ); + app.get("/logout", userController.logout); app.get( - '/admin/restaurants/create', + "/admin/restaurants/create", authenticatedAdmin, adminController.createRestaurants - ) + ); app.post( - '/admin/restaurants', + "/admin/restaurants", authenticatedAdmin, - upload.single('image'), + upload.single("image"), adminController.postRestaurants - ) + ); app.get( - '/admin/restaurants/:id', + "/admin/restaurants/:id", authenticatedAdmin, adminController.getRestaurant - ) + ); app.get( - '/admin/restaurants/:id/edit', + "/admin/restaurants/:id/edit", authenticatedAdmin, adminController.editRestaurant - ) + ); app.put( - '/admin/restaurants/:id', + "/admin/restaurants/:id", authenticatedAdmin, - upload.single('image'), + upload.single("image"), adminController.putRestaurant - ) + ); app.delete( - '/admin/restaurants/:id', + "/admin/restaurants/:id", authenticatedAdmin, adminController.deleteRestaurant - ) - app.get('/admin/users', authenticatedAdmin, adminController.getUsers) + ); + app.get("/admin/users", authenticatedAdmin, adminController.getUsers); app.put( - '/admin/users/:id/toggleAdmin', + "/admin/users/:id/toggleAdmin", authenticatedAdmin, adminController.toggleAdmin - ) + ); app.get( - '/admin/categories', + "/admin/categories", authenticatedAdmin, categoryController.getCategories - ) + ); app.post( - '/admin/categories', + "/admin/categories", authenticatedAdmin, categoryController.postCategory - ) + ); app.get( - '/admin/categories/:id', + "/admin/categories/:id", authenticatedAdmin, categoryController.getCategories - ) + ); app.put( - '/admin/categories/:id', + "/admin/categories/:id", authenticatedAdmin, categoryController.putCategory - ) + ); app.delete( - '/admin/categories/:id', + "/admin/categories/:id", authenticatedAdmin, categoryController.deletCategory - ) - app.get('/restaurants/feeds', authenticated, restController.getFeeds) - app.get('/restaurants/:id', authenticated, restController.getRestaurant) - app.post('/comments', authenticated, commentController.postComment) - app.delete('/comments/:id', authenticated, commentController.deleteComment) - app.get('/users/:id', authenticated, userController.getUser) - app.get('/users/:id/edit', authenticated, userController.editUser) + ); + app.get("/restaurants/feeds", authenticated, restController.getFeeds); + app.get("/restaurants/:id", authenticated, restController.getRestaurant); + app.post("/comments", authenticated, commentController.postComment); + app.delete("/comments/:id", authenticated, commentController.deleteComment); + app.get("/users/:id", authenticated, userController.getUser); + app.get("/users/:id/edit", authenticated, userController.editUser); app.put( - '/users/:id', + "/users/:id", authenticated, - upload.single('image'), + upload.single("image"), userController.putUser - ) -<<<<<<< HEAD + ); app.get( - '/restaurants/:id/dashboard', + "/restaurants/:id/dashboard", authenticated, restControllers.getDashBoard - ) -======= ->>>>>>> parent of a49b72c... add dashboard view to show comment -} + ); +}; diff --git a/tests/R02.test.js b/tests/R02.test.js index 1c9c6e47..a9473a27 100644 --- a/tests/R02.test.js +++ b/tests/R02.test.js @@ -1,38 +1,20 @@ -<<<<<<< HEAD -const chai = require('chai') -const request = require('supertest') -const sinon = require('sinon') -const should = chai.should() - -const helpers = require('../_helpers') -======= const chai = require("chai"); const request = require("supertest"); const sinon = require("sinon"); const should = chai.should(); const helpers = require("../_helpers"); ->>>>>>> main const { createModelMock, createControllerProxy, mockRequest, -<<<<<<< HEAD - mockResponse -} = require('../helpers/unitTestHelpers') - -describe('# R02', () => { - describe('# R02: 建立 User Profile', function () { - context('# [瀏覽 Profile]', () => { -======= mockResponse, } = require("../helpers/unitTestHelpers"); describe("# R02", () => { describe("# R02: 建立 User Profile", function () { context("# [瀏覽 Profile]", () => { ->>>>>>> main // 前置準備 before(() => { // 模擬登入驗證 @@ -51,17 +33,10 @@ describe("# R02", () => { // 修改 userController 中的資料庫連線設定,由連向真實的資料庫 -> 改為連向模擬的 User table this.userController = createControllerProxy( -<<<<<<< HEAD - '../controllers/userController', - { User: this.UserMock } - ) - }) -======= "../controllers/userController", { User: this.UserMock } ); }); ->>>>>>> main // 開始測試 it(" GET /users/:id ", async () => { @@ -106,17 +81,10 @@ describe("# R02", () => { // 連向模擬的 User table this.userController = createControllerProxy( -<<<<<<< HEAD - '../controllers/userController', - { User: this.UserMock } - ) - }) -======= "../controllers/userController", { User: this.UserMock } ); }); ->>>>>>> main it(" GET /users/:id/edit ", async () => { // 模擬 request & response @@ -149,21 +117,6 @@ describe("# R02", () => { this.getUser = sinon.stub(helpers, "getUser").returns({ id: 1 }); // 製作假資料 // 本 context 會用這筆資料進行測試 -<<<<<<< HEAD - this.UserMock = createModelMock('User', { - id: 1, - email: 'root@example.com', - name: 'admin', - isAdmin: false - }) - - // 連向模擬的 User table - this.userController = createControllerProxy( - '../controllers/userController', - { User: this.UserMock } - ) - }) -======= this.UserMock = createModelMock("User", { id: 1, email: "root@example.com", @@ -177,7 +130,6 @@ describe("# R02", () => { { User: this.UserMock } ); }); ->>>>>>> main it(" PUT /users/:id ", async () => { // 模擬 request & response @@ -193,13 +145,8 @@ describe("# R02", () => { // putUser 正確執行的話,應呼叫 req.flash // req.flash 的參數應與下列字串一致 -<<<<<<< HEAD - req.flash.calledWith('success_messages', '使用者資料編輯成功').should.be - .true -======= req.flash.calledWith("success_messages", "使用者資料編輯成功").should.be .true; ->>>>>>> main // putUser 執行完畢後,應呼叫 res.redirect 並重新導向 /users/1 res.redirect.calledWith("/users/1").should.be.true; // putUser 執行完畢後,id:1 使用者的 name 和 email 應該已被修改 From ee1e8e69b741239eb6c287d4eb3e42f0fb683ce7 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Sat, 20 Nov 2021 17:11:51 +0000 Subject: [PATCH 174/198] fix styling and merge conflict --- controllers/restController.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/controllers/restController.js b/controllers/restController.js index 018e8406..f1317d52 100644 --- a/controllers/restController.js +++ b/controllers/restController.js @@ -92,17 +92,6 @@ const restControllers = { console.log("Error: ", error); } }, - - getDashBoard: async (req, res) => { - const restaurantId = req.params.id; - const restaurant = await Restaurant.findByPk(restaurantId, { - include: [Category, { model: Comment }], - }); - const comment = await Comment.findAndCountAll({ - where: { RestaurantId: restaurantId }, - }); - return res.render("dashboard", { restaurant: restaurant.toJSON() }); - }, }; module.exports = restControllers; From 7e661a8758982ab4c89d4441f99a918a814c5a0a Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Sat, 20 Nov 2021 17:13:25 +0000 Subject: [PATCH 175/198] add view counts --- controllers/restController.js | 11 +++++++++ ...1120142551-add-viewCounts-to-restaurant.js | 14 +++++------ models/restaurant.js | 3 ++- views/dashboard.handlebars | 15 ++++++++++++ views/restaurant.handlebars | 23 ++++++++++--------- 5 files changed, 47 insertions(+), 19 deletions(-) create mode 100644 views/dashboard.handlebars diff --git a/controllers/restController.js b/controllers/restController.js index f1317d52..018e8406 100644 --- a/controllers/restController.js +++ b/controllers/restController.js @@ -92,6 +92,17 @@ const restControllers = { console.log("Error: ", error); } }, + + getDashBoard: async (req, res) => { + const restaurantId = req.params.id; + const restaurant = await Restaurant.findByPk(restaurantId, { + include: [Category, { model: Comment }], + }); + const comment = await Comment.findAndCountAll({ + where: { RestaurantId: restaurantId }, + }); + return res.render("dashboard", { restaurant: restaurant.toJSON() }); + }, }; module.exports = restControllers; diff --git a/migrations/20211120142551-add-viewCounts-to-restaurant.js b/migrations/20211120142551-add-viewCounts-to-restaurant.js index e95291b2..964619cf 100644 --- a/migrations/20211120142551-add-viewCounts-to-restaurant.js +++ b/migrations/20211120142551-add-viewCounts-to-restaurant.js @@ -1,14 +1,14 @@ -'use strict' +"use strict"; module.exports = { up: async (queryInterface, Sequelize) => { - await queryInterface.addColumn('Restaurants', 'viewCounts', { + await queryInterface.addColumn("Restaurants", "viewCounts", { type: Sequelize.INTEGER, - defaultValue: 0 - }) + defaultValue: 0, + }); }, down: async (queryInterface, Sequelize) => { - await queryInterface.removeColumn('Restaurants', 'viewCounts') - } -} + await queryInterface.removeColumn("Restaurants", "viewCounts"); + }, +}; diff --git a/models/restaurant.js b/models/restaurant.js index 920ee227..d6899a92 100644 --- a/models/restaurant.js +++ b/models/restaurant.js @@ -19,7 +19,8 @@ module.exports = (sequelize, DataTypes) => { address: DataTypes.STRING, opening_hours: DataTypes.STRING, description: DataTypes.TEXT, - image: DataTypes.STRING + image: DataTypes.STRING, + viewCounts: DataTypes.INTEGER }, { sequelize, diff --git a/views/dashboard.handlebars b/views/dashboard.handlebars new file mode 100644 index 00000000..45027a54 --- /dev/null +++ b/views/dashboard.handlebars @@ -0,0 +1,15 @@ +
+
+

{{restaurant.name}}

+

[{{restaurant.Category.name}}]

+
+
+
+
    +
  • 評論數:{{restaurant.Comments.length}}
  • +
  • 瀏覽次數:{{restaurant.viewCounts}}
  • +
  • 收藏數:
  • +
+
+
+回上一頁 \ No newline at end of file diff --git a/views/restaurant.handlebars b/views/restaurant.handlebars index 59147240..b3bcddb0 100644 --- a/views/restaurant.handlebars +++ b/views/restaurant.handlebars @@ -15,23 +15,24 @@

{{restaurant.description}}

+



所有評論:

{{#each restaurant.Comments}} - {{#if ../user.isAdmin}} - - - - {{/if}} -
-

{{this.User.name}}

-

{{this.text}}

-
{{moment this.createdAt}}
-
-
+{{#if ../user.isAdmin}} +
+ +
+{{/if}} +
+

{{this.User.name}}

+

{{this.text}}

+
{{moment this.createdAt}}
+
+
{{/each}}
From 160e7589d8d7d8a83ce8f17da416eaf6002fdcd9 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Sat, 20 Nov 2021 17:25:28 +0000 Subject: [PATCH 176/198] hide edit button when viewing other's profile --- controllers/userController.js | 124 ++++++++++++++++++---------------- views/profile.handlebars | 4 +- 2 files changed, 68 insertions(+), 60 deletions(-) diff --git a/controllers/userController.js b/controllers/userController.js index 74a315d5..b02647d1 100644 --- a/controllers/userController.js +++ b/controllers/userController.js @@ -1,133 +1,139 @@ -const bcrypt = require('bcrypt-nodejs') -const db = require('../models') -const User = db.User -const Restaurant = db.Restaurant -const Comment = db.Comment -const helpers = require('../_helpers') +const bcrypt = require("bcrypt-nodejs"); +const db = require("../models"); +const User = db.User; +const Restaurant = db.Restaurant; +const Comment = db.Comment; +const helpers = require("../_helpers"); // imgur setup -const imgur = require('imgur-node-api') -const IMGUR_CLIENT_ID = process.env.IMGUR_CLIENT_ID +const imgur = require("imgur-node-api"); +const IMGUR_CLIENT_ID = process.env.IMGUR_CLIENT_ID; const userController = { signUpPage: (req, res) => { - return res.render('signup') + return res.render("signup"); }, signUp: (req, res) => { - const { name, email, password, passwordCheck } = req.body + const { name, email, password, passwordCheck } = req.body; if (password !== passwordCheck) { - req.flash('error_messages', '兩次密碼輸入不同') - return res.redirect('/signup') + req.flash("error_messages", "兩次密碼輸入不同"); + return res.redirect("/signup"); } else { User.findOne({ where: { email } }).then((user) => { if (user) { - req.flash('error_messages', '信箱已經註冊過') - return res.redirect('/signup') + req.flash("error_messages", "信箱已經註冊過"); + return res.redirect("/signup"); } else { User.create({ name, email, - password: bcrypt.hashSync(password, bcrypt.genSaltSync(10), null) + password: bcrypt.hashSync(password, bcrypt.genSaltSync(10), null), }).then((user) => { - req.flash('success_messages', '成功註冊帳號') - return res.redirect('/signin') - }) + req.flash("success_messages", "成功註冊帳號"); + return res.redirect("/signin"); + }); } - }) + }); } }, signInPage: (req, res) => { - return res.render('signin') + return res.render("signin"); }, signIn: (req, res) => { - req.flash('success_messages', '成功登入!') - res.redirect('/restaurants') + req.flash("success_messages", "成功登入!"); + res.redirect("/restaurants"); }, logout: (req, res) => { - req.flash('success_message', '登出成功!') - req.logout() - res.redirect('/signin') + req.flash("success_message", "登出成功!"); + req.logout(); + res.redirect("/signin"); }, getUser: async (req, res) => { try { + const isEditable = + helpers.getUser(req).id === Number(req.params.id) || false; + const user = await User.findByPk(req.params.id, { raw: true, - nest: true - }) + nest: true, + }); const comments = await Comment.findAll({ raw: true, nest: true, where: { UserId: req.params.id }, - include: [Restaurant] - }) + include: [Restaurant], + }); - return res.render('profile', { user: user, comments: comments }) + return res.render("profile", { + user: { ...user, isEditable }, + comments: comments, + }); } catch (error) { - console.log(error) + console.log(error); } }, editUser: (req, res) => { // can only edit your own profile if (helpers.getUser(req).id !== Number(req.params.id)) { req.flash( - 'error_messages', - '無法更改其他使用者資料,只可以瀏覽其他使用者資料' - ) - return res.redirect(`/users/${req.params.id}`) + "error_messages", + "無法更改其他使用者資料,只可以瀏覽其他使用者資料" + ); + return res.redirect(`/users/${req.params.id}`); } return User.findByPk(req.params.id).then((user) => { - return res.render('edit', { user: user.toJSON() }) - }) + return res.render("edit", { user: user.toJSON() }); + }); }, putUser: (req, res) => { - const { name, email } = req.body - const { file } = req + const { name, email } = req.body; + const { file } = req; if (helpers.getUser(req).id !== Number(req.params.id)) { - req.flash('error_messages', '無法更改其他使用者資料,請重新編輯你的資料') - return res.redirect('back') + req.flash("error_messages", "無法更改其他使用者資料,請重新編輯你的資料"); + return res.redirect("back"); } if (!name || !email) { - req.flash('error_messages', '使用者資料沒有變更') - return res.redirect('back') + req.flash("error_messages", "使用者資料沒有變更"); + return res.redirect("back"); } if (file) { - imgur.setClientID(IMGUR_CLIENT_ID) + imgur.setClientID(IMGUR_CLIENT_ID); imgur.upload(file.path, (err, img) => { - if (err) console.log('Error: ', err) + if (err) console.log("Error: ", err); return User.findByPk(req.params.id).then((user) => { user .update({ name, email, - image: file ? img.data.link : user.image + image: file ? img.data.link : user.image, }) .then((user) => { - req.flash('success_messages', '使用者資料編輯成功') - return res.redirect(`/users/${req.params.id}`) - }) - }) - }) + req.flash("success_messages", "使用者資料編輯成功"); + return res.redirect(`/users/${req.params.id}`); + }); + }); + }); } else { return User.findByPk(req.params.id).then((user) => { user .update({ name, - email + email, }) .then((user) => { - req.flash('success_messages', '使用者資料編輯成功') - return res.redirect(`/users/${req.params.id}`) - }) - }) + req.flash("success_messages", "使用者資料編輯成功"); + return res.redirect(`/users/${req.params.id}`); + }); + }); } - } -} + }, +}; -module.exports = userController +module.exports = userController; diff --git a/views/profile.handlebars b/views/profile.handlebars index 08eeb8f5..2fe498e3 100644 --- a/views/profile.handlebars +++ b/views/profile.handlebars @@ -17,7 +17,9 @@
{{user.name}}

{{user.email}}

{{#count comments}}{{/count}} 已評論餐廳

- edit + {{#if user.isEditable}} + edit + {{/if}}
From 0ba3289e806ca0858ae6a0e3134c9dbcdee7d464 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Sat, 20 Nov 2021 17:28:10 +0000 Subject: [PATCH 177/198] fix styling --- controllers/restController.js | 100 ++++++------ controllers/userController.js | 124 +++++++-------- ...1120142551-add-viewCounts-to-restaurant.js | 14 +- routes/index.js | 148 +++++++++--------- 4 files changed, 193 insertions(+), 193 deletions(-) diff --git a/controllers/restController.js b/controllers/restController.js index 018e8406..b5b6fe6c 100644 --- a/controllers/restController.js +++ b/controllers/restController.js @@ -1,69 +1,69 @@ -const db = require("../models"); -const Restaurant = db.Restaurant; -const Category = db.Category; -const User = db.User; -const Comment = db.Comment; +const db = require('../models') +const Restaurant = db.Restaurant +const Category = db.Category +const User = db.User +const Comment = db.Comment -const pageLimit = 10; +const pageLimit = 10 const restControllers = { getRestaurants: (req, res) => { - let offset = 0; - const whereQuery = {}; - let categoryId = ""; + let offset = 0 + const whereQuery = {} + let categoryId = '' if (req.query.categoryId) { - categoryId = Number(req.query.categoryId); - whereQuery.CategoryId = categoryId; + categoryId = Number(req.query.categoryId) + whereQuery.CategoryId = categoryId } if (req.query.page) { - offset = (req.query.page - 1) * pageLimit; + offset = (req.query.page - 1) * pageLimit } Restaurant.findAndCountAll({ include: Category, where: whereQuery, offset: offset, - limit: pageLimit, + limit: pageLimit }).then((restaurants) => { // data for pagination - const page = Number(req.query.page) || 1; - const pages = Math.ceil(restaurants.count / pageLimit); + const page = Number(req.query.page) || 1 + const pages = Math.ceil(restaurants.count / pageLimit) const totalPage = Array.from({ length: pages }).map( (item, index) => index + 1 - ); + ) - const prev = page - 1 < 1 ? 1 : page - 1; - const next = page + 1 > pages ? pages : page + 1; + const prev = page - 1 < 1 ? 1 : page - 1 + const next = page + 1 > pages ? pages : page + 1 const data = restaurants.rows.map((r) => ({ ...r.dataValues, description: r.dataValues.description.substring(0, 50), - categoryName: r.dataValues.Category.name, - })); + categoryName: r.dataValues.Category.name + })) Category.findAll({ raw: true, nest: true }).then((categories) => { - return res.render("restaurants", { + return res.render('restaurants', { restaurants: data, categories, categoryId, page, totalPage, prev, - next, - }); - }); - }); + next + }) + }) + }) }, getRestaurant: (req, res) => { return Restaurant.findByPk(req.params.id, { - include: [Category, { model: Comment, include: User }], + include: [Category, { model: Comment, include: User }] }).then((restaurant) => { - restaurant.increment("viewCounts"); - return res.render("restaurant", { - restaurant: restaurant.toJSON(), - }); - }); + restaurant.increment('viewCounts') + return res.render('restaurant', { + restaurant: restaurant.toJSON() + }) + }) }, getFeeds: async (req, res) => { @@ -72,37 +72,37 @@ const restControllers = { limit: 10, raw: true, nest: true, - order: [["createdAt", "DESC"]], - include: [Category], - }); + order: [['createdAt', 'DESC']], + include: [Category] + }) const comments = await Comment.findAll({ limit: 10, raw: true, nest: true, - order: [["createdAt", "DESC"]], - include: [User, Restaurant], - }); + order: [['createdAt', 'DESC']], + include: [User, Restaurant] + }) - return res.render("feeds", { + return res.render('feeds', { restaurants, - comments, - }); + comments + }) } catch (error) { - console.log("Error: ", error); + console.log('Error: ', error) } }, getDashBoard: async (req, res) => { - const restaurantId = req.params.id; + const restaurantId = req.params.id const restaurant = await Restaurant.findByPk(restaurantId, { - include: [Category, { model: Comment }], - }); + include: [Category, { model: Comment }] + }) const comment = await Comment.findAndCountAll({ - where: { RestaurantId: restaurantId }, - }); - return res.render("dashboard", { restaurant: restaurant.toJSON() }); - }, -}; + where: { RestaurantId: restaurantId } + }) + return res.render('dashboard', { restaurant: restaurant.toJSON() }) + } +} -module.exports = restControllers; +module.exports = restControllers diff --git a/controllers/userController.js b/controllers/userController.js index b02647d1..61c44059 100644 --- a/controllers/userController.js +++ b/controllers/userController.js @@ -1,139 +1,139 @@ -const bcrypt = require("bcrypt-nodejs"); -const db = require("../models"); -const User = db.User; -const Restaurant = db.Restaurant; -const Comment = db.Comment; -const helpers = require("../_helpers"); +const bcrypt = require('bcrypt-nodejs') +const db = require('../models') +const User = db.User +const Restaurant = db.Restaurant +const Comment = db.Comment +const helpers = require('../_helpers') // imgur setup -const imgur = require("imgur-node-api"); -const IMGUR_CLIENT_ID = process.env.IMGUR_CLIENT_ID; +const imgur = require('imgur-node-api') +const IMGUR_CLIENT_ID = process.env.IMGUR_CLIENT_ID const userController = { signUpPage: (req, res) => { - return res.render("signup"); + return res.render('signup') }, signUp: (req, res) => { - const { name, email, password, passwordCheck } = req.body; + const { name, email, password, passwordCheck } = req.body if (password !== passwordCheck) { - req.flash("error_messages", "兩次密碼輸入不同"); - return res.redirect("/signup"); + req.flash('error_messages', '兩次密碼輸入不同') + return res.redirect('/signup') } else { User.findOne({ where: { email } }).then((user) => { if (user) { - req.flash("error_messages", "信箱已經註冊過"); - return res.redirect("/signup"); + req.flash('error_messages', '信箱已經註冊過') + return res.redirect('/signup') } else { User.create({ name, email, - password: bcrypt.hashSync(password, bcrypt.genSaltSync(10), null), + password: bcrypt.hashSync(password, bcrypt.genSaltSync(10), null) }).then((user) => { - req.flash("success_messages", "成功註冊帳號"); - return res.redirect("/signin"); - }); + req.flash('success_messages', '成功註冊帳號') + return res.redirect('/signin') + }) } - }); + }) } }, signInPage: (req, res) => { - return res.render("signin"); + return res.render('signin') }, signIn: (req, res) => { - req.flash("success_messages", "成功登入!"); - res.redirect("/restaurants"); + req.flash('success_messages', '成功登入!') + res.redirect('/restaurants') }, logout: (req, res) => { - req.flash("success_message", "登出成功!"); - req.logout(); - res.redirect("/signin"); + req.flash('success_message', '登出成功!') + req.logout() + res.redirect('/signin') }, getUser: async (req, res) => { try { const isEditable = - helpers.getUser(req).id === Number(req.params.id) || false; + helpers.getUser(req).id === Number(req.params.id) || false const user = await User.findByPk(req.params.id, { raw: true, - nest: true, - }); + nest: true + }) const comments = await Comment.findAll({ raw: true, nest: true, where: { UserId: req.params.id }, - include: [Restaurant], - }); + include: [Restaurant] + }) - return res.render("profile", { + return res.render('profile', { user: { ...user, isEditable }, - comments: comments, - }); + comments: comments + }) } catch (error) { - console.log(error); + console.log(error) } }, editUser: (req, res) => { // can only edit your own profile if (helpers.getUser(req).id !== Number(req.params.id)) { req.flash( - "error_messages", - "無法更改其他使用者資料,只可以瀏覽其他使用者資料" - ); - return res.redirect(`/users/${req.params.id}`); + 'error_messages', + '無法更改其他使用者資料,只可以瀏覽其他使用者資料' + ) + return res.redirect(`/users/${req.params.id}`) } return User.findByPk(req.params.id).then((user) => { - return res.render("edit", { user: user.toJSON() }); - }); + return res.render('edit', { user: user.toJSON() }) + }) }, putUser: (req, res) => { - const { name, email } = req.body; - const { file } = req; + const { name, email } = req.body + const { file } = req if (helpers.getUser(req).id !== Number(req.params.id)) { - req.flash("error_messages", "無法更改其他使用者資料,請重新編輯你的資料"); - return res.redirect("back"); + req.flash('error_messages', '無法更改其他使用者資料,請重新編輯你的資料') + return res.redirect('back') } if (!name || !email) { - req.flash("error_messages", "使用者資料沒有變更"); - return res.redirect("back"); + req.flash('error_messages', '使用者資料沒有變更') + return res.redirect('back') } if (file) { - imgur.setClientID(IMGUR_CLIENT_ID); + imgur.setClientID(IMGUR_CLIENT_ID) imgur.upload(file.path, (err, img) => { - if (err) console.log("Error: ", err); + if (err) console.log('Error: ', err) return User.findByPk(req.params.id).then((user) => { user .update({ name, email, - image: file ? img.data.link : user.image, + image: file ? img.data.link : user.image }) .then((user) => { - req.flash("success_messages", "使用者資料編輯成功"); - return res.redirect(`/users/${req.params.id}`); - }); - }); - }); + req.flash('success_messages', '使用者資料編輯成功') + return res.redirect(`/users/${req.params.id}`) + }) + }) + }) } else { return User.findByPk(req.params.id).then((user) => { user .update({ name, - email, + email }) .then((user) => { - req.flash("success_messages", "使用者資料編輯成功"); - return res.redirect(`/users/${req.params.id}`); - }); - }); + req.flash('success_messages', '使用者資料編輯成功') + return res.redirect(`/users/${req.params.id}`) + }) + }) } - }, -}; + } +} -module.exports = userController; +module.exports = userController diff --git a/migrations/20211120142551-add-viewCounts-to-restaurant.js b/migrations/20211120142551-add-viewCounts-to-restaurant.js index 964619cf..e95291b2 100644 --- a/migrations/20211120142551-add-viewCounts-to-restaurant.js +++ b/migrations/20211120142551-add-viewCounts-to-restaurant.js @@ -1,14 +1,14 @@ -"use strict"; +'use strict' module.exports = { up: async (queryInterface, Sequelize) => { - await queryInterface.addColumn("Restaurants", "viewCounts", { + await queryInterface.addColumn('Restaurants', 'viewCounts', { type: Sequelize.INTEGER, - defaultValue: 0, - }); + defaultValue: 0 + }) }, down: async (queryInterface, Sequelize) => { - await queryInterface.removeColumn("Restaurants", "viewCounts"); - }, -}; + await queryInterface.removeColumn('Restaurants', 'viewCounts') + } +} diff --git a/routes/index.js b/routes/index.js index 06a41305..d1ecff02 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,140 +1,140 @@ -const restController = require("../controllers/restController"); -const adminController = require("../controllers/adminController"); -const userController = require("../controllers/userController"); -const commentController = require("../controllers/commentController"); -const passport = require("passport"); -const helpers = require("../_helpers"); +const restController = require('../controllers/restController') +const adminController = require('../controllers/adminController') +const userController = require('../controllers/userController') +const commentController = require('../controllers/commentController') +const passport = require('passport') +const helpers = require('../_helpers') // multer setup -const multer = require("multer"); -const categoryController = require("../controllers/categoryController"); -const restControllers = require("../controllers/restController"); -const upload = multer({ dest: "temp/" }); +const multer = require('multer') +const categoryController = require('../controllers/categoryController') +const restControllers = require('../controllers/restController') +const upload = multer({ dest: 'temp/' }) module.exports = (app) => { const authenticated = (req, res, next) => { if (helpers.ensureAuthenticated(req)) { - return next(); + return next() } - res.redirect("/signin"); - }; + res.redirect('/signin') + } const authenticatedAdmin = (req, res, next) => { if (helpers.ensureAuthenticated(req)) { if (helpers.getUser(req).isAdmin) { - return next(); + return next() } - return res.redirect("/"); + return res.redirect('/') } - res.redirect("/signin"); - }; + res.redirect('/signin') + } // 如果使用者訪問首頁,就導向 /restaurants 的頁面 - app.get("/", authenticated, (req, res) => { - res.redirect("restaurants"); - }); - app.get("/restaurants", restController.getRestaurants); + app.get('/', authenticated, (req, res) => { + res.redirect('restaurants') + }) + app.get('/restaurants', restController.getRestaurants) - app.get("/admin", authenticatedAdmin, (req, res) => { - res.redirect("/admin/restaurants"); - }); + app.get('/admin', authenticatedAdmin, (req, res) => { + res.redirect('/admin/restaurants') + }) app.get( - "/admin/restaurants", + '/admin/restaurants', authenticatedAdmin, adminController.getRestaurants - ); + ) - app.get("/signup", userController.signUpPage); - app.post("/signup", userController.signUp); + app.get('/signup', userController.signUpPage) + app.post('/signup', userController.signUp) - app.get("/signin", userController.signInPage); + app.get('/signin', userController.signInPage) app.post( - "/signin", - passport.authenticate("local", { - failureRedirect: "/signin", - failureFlash: true, + '/signin', + passport.authenticate('local', { + failureRedirect: '/signin', + failureFlash: true }), userController.signIn - ); - app.get("/logout", userController.logout); + ) + app.get('/logout', userController.logout) app.get( - "/admin/restaurants/create", + '/admin/restaurants/create', authenticatedAdmin, adminController.createRestaurants - ); + ) app.post( - "/admin/restaurants", + '/admin/restaurants', authenticatedAdmin, - upload.single("image"), + upload.single('image'), adminController.postRestaurants - ); + ) app.get( - "/admin/restaurants/:id", + '/admin/restaurants/:id', authenticatedAdmin, adminController.getRestaurant - ); + ) app.get( - "/admin/restaurants/:id/edit", + '/admin/restaurants/:id/edit', authenticatedAdmin, adminController.editRestaurant - ); + ) app.put( - "/admin/restaurants/:id", + '/admin/restaurants/:id', authenticatedAdmin, - upload.single("image"), + upload.single('image'), adminController.putRestaurant - ); + ) app.delete( - "/admin/restaurants/:id", + '/admin/restaurants/:id', authenticatedAdmin, adminController.deleteRestaurant - ); - app.get("/admin/users", authenticatedAdmin, adminController.getUsers); + ) + app.get('/admin/users', authenticatedAdmin, adminController.getUsers) app.put( - "/admin/users/:id/toggleAdmin", + '/admin/users/:id/toggleAdmin', authenticatedAdmin, adminController.toggleAdmin - ); + ) app.get( - "/admin/categories", + '/admin/categories', authenticatedAdmin, categoryController.getCategories - ); + ) app.post( - "/admin/categories", + '/admin/categories', authenticatedAdmin, categoryController.postCategory - ); + ) app.get( - "/admin/categories/:id", + '/admin/categories/:id', authenticatedAdmin, categoryController.getCategories - ); + ) app.put( - "/admin/categories/:id", + '/admin/categories/:id', authenticatedAdmin, categoryController.putCategory - ); + ) app.delete( - "/admin/categories/:id", + '/admin/categories/:id', authenticatedAdmin, categoryController.deletCategory - ); - app.get("/restaurants/feeds", authenticated, restController.getFeeds); - app.get("/restaurants/:id", authenticated, restController.getRestaurant); - app.post("/comments", authenticated, commentController.postComment); - app.delete("/comments/:id", authenticated, commentController.deleteComment); - app.get("/users/:id", authenticated, userController.getUser); - app.get("/users/:id/edit", authenticated, userController.editUser); + ) + app.get('/restaurants/feeds', authenticated, restController.getFeeds) + app.get('/restaurants/:id', authenticated, restController.getRestaurant) + app.post('/comments', authenticated, commentController.postComment) + app.delete('/comments/:id', authenticated, commentController.deleteComment) + app.get('/users/:id', authenticated, userController.getUser) + app.get('/users/:id/edit', authenticated, userController.editUser) app.put( - "/users/:id", + '/users/:id', authenticated, - upload.single("image"), + upload.single('image'), userController.putUser - ); + ) app.get( - "/restaurants/:id/dashboard", + '/restaurants/:id/dashboard', authenticated, restControllers.getDashBoard - ); -}; + ) +} From 730040462ec2336230c0283534b7741d55610ddf Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Sat, 20 Nov 2021 18:03:12 +0000 Subject: [PATCH 178/198] create favorite model and set association between restaurant and user --- migrations/20211120175539-create-favorite.js | 32 ++++++++++++++++++++ models/favorite.js | 24 +++++++++++++++ models/user.js | 23 ++++++++------ 3 files changed, 70 insertions(+), 9 deletions(-) create mode 100644 migrations/20211120175539-create-favorite.js create mode 100644 models/favorite.js diff --git a/migrations/20211120175539-create-favorite.js b/migrations/20211120175539-create-favorite.js new file mode 100644 index 00000000..6b21d2fa --- /dev/null +++ b/migrations/20211120175539-create-favorite.js @@ -0,0 +1,32 @@ +"use strict"; +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.createTable("Favorites", { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER, + }, + UserId: { + type: Sequelize.INTEGER, + references: { model: "Users", key: "id" }, + }, + RestaurantId: { + type: Sequelize.INTEGER, + references: { model: "Restaurants", key: "id" }, + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE, + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE, + }, + }); + }, + down: async (queryInterface, Sequelize) => { + await queryInterface.dropTable("Favorites"); + }, +}; diff --git a/models/favorite.js b/models/favorite.js new file mode 100644 index 00000000..3def4705 --- /dev/null +++ b/models/favorite.js @@ -0,0 +1,24 @@ +'use strict'; +const { + Model +} = require('sequelize'); +module.exports = (sequelize, DataTypes) => { + class Favorite extends Model { + /** + * Helper method for defining associations. + * This method is not a part of Sequelize lifecycle. + * The `models/index` file will call this method automatically. + */ + static associate(models) { + // define association here + } + }; + Favorite.init({ + UserId: DataTypes.INTEGER, + RestaurantId: DataTypes.INTEGER + }, { + sequelize, + modelName: 'Favorite', + }); + return Favorite; +}; \ No newline at end of file diff --git a/models/user.js b/models/user.js index 9fb333d0..80c6520e 100644 --- a/models/user.js +++ b/models/user.js @@ -1,5 +1,5 @@ -'use strict' -const { Model } = require('sequelize') +"use strict"; +const { Model } = require("sequelize"); module.exports = (sequelize, DataTypes) => { class User extends Model { /** @@ -7,8 +7,13 @@ module.exports = (sequelize, DataTypes) => { * This method is not a part of Sequelize lifecycle. * The `models/index` file will call this method automatically. */ - static associate (models) { - User.hasMany(models.Comment) + static associate(models) { + User.hasMany(models.Comment); + User.belongsToMany(model.Restaurant, { + through: model.Favorite, + foreignKey: "UserId", + as: "FavoritedRestaurants", + }); } } User.init( @@ -17,12 +22,12 @@ module.exports = (sequelize, DataTypes) => { email: DataTypes.STRING, password: DataTypes.STRING, isAdmin: DataTypes.BOOLEAN, - image: DataTypes.STRING + image: DataTypes.STRING, }, { sequelize, - modelName: 'User' + modelName: "User", } - ) - return User -} + ); + return User; +}; From 55964f37a374495a84cc34b21ecb230fb430b492 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Sat, 20 Nov 2021 18:43:50 +0000 Subject: [PATCH 179/198] Add favorite/unfavorite feature --- controllers/userController.js | 148 ++++++++++++++++++------------- models/restaurant.js | 25 +++--- models/user.js | 4 +- routes/index.js | 158 ++++++++++++++++++---------------- views/restaurants.handlebars | 58 ++++++------- 5 files changed, 216 insertions(+), 177 deletions(-) diff --git a/controllers/userController.js b/controllers/userController.js index 61c44059..e26eb176 100644 --- a/controllers/userController.js +++ b/controllers/userController.js @@ -1,139 +1,163 @@ -const bcrypt = require('bcrypt-nodejs') -const db = require('../models') -const User = db.User -const Restaurant = db.Restaurant -const Comment = db.Comment -const helpers = require('../_helpers') +const bcrypt = require("bcrypt-nodejs"); +const db = require("../models"); +const User = db.User; +const Restaurant = db.Restaurant; +const Comment = db.Comment; +const Favorite = db.Favorite; +const helpers = require("../_helpers"); // imgur setup -const imgur = require('imgur-node-api') -const IMGUR_CLIENT_ID = process.env.IMGUR_CLIENT_ID +const imgur = require("imgur-node-api"); +const restaurant = require("../models/restaurant"); +const IMGUR_CLIENT_ID = process.env.IMGUR_CLIENT_ID; const userController = { signUpPage: (req, res) => { - return res.render('signup') + return res.render("signup"); }, signUp: (req, res) => { - const { name, email, password, passwordCheck } = req.body + const { name, email, password, passwordCheck } = req.body; if (password !== passwordCheck) { - req.flash('error_messages', '兩次密碼輸入不同') - return res.redirect('/signup') + req.flash("error_messages", "兩次密碼輸入不同"); + return res.redirect("/signup"); } else { User.findOne({ where: { email } }).then((user) => { if (user) { - req.flash('error_messages', '信箱已經註冊過') - return res.redirect('/signup') + req.flash("error_messages", "信箱已經註冊過"); + return res.redirect("/signup"); } else { User.create({ name, email, - password: bcrypt.hashSync(password, bcrypt.genSaltSync(10), null) + password: bcrypt.hashSync(password, bcrypt.genSaltSync(10), null), }).then((user) => { - req.flash('success_messages', '成功註冊帳號') - return res.redirect('/signin') - }) + req.flash("success_messages", "成功註冊帳號"); + return res.redirect("/signin"); + }); } - }) + }); } }, signInPage: (req, res) => { - return res.render('signin') + return res.render("signin"); }, signIn: (req, res) => { - req.flash('success_messages', '成功登入!') - res.redirect('/restaurants') + req.flash("success_messages", "成功登入!"); + res.redirect("/restaurants"); }, logout: (req, res) => { - req.flash('success_message', '登出成功!') - req.logout() - res.redirect('/signin') + req.flash("success_message", "登出成功!"); + req.logout(); + res.redirect("/signin"); }, getUser: async (req, res) => { try { const isEditable = - helpers.getUser(req).id === Number(req.params.id) || false + helpers.getUser(req).id === Number(req.params.id) || false; const user = await User.findByPk(req.params.id, { raw: true, - nest: true - }) + nest: true, + }); const comments = await Comment.findAll({ raw: true, nest: true, where: { UserId: req.params.id }, - include: [Restaurant] - }) + include: [Restaurant], + }); - return res.render('profile', { + return res.render("profile", { user: { ...user, isEditable }, - comments: comments - }) + comments: comments, + }); } catch (error) { - console.log(error) + console.log(error); } }, editUser: (req, res) => { // can only edit your own profile if (helpers.getUser(req).id !== Number(req.params.id)) { req.flash( - 'error_messages', - '無法更改其他使用者資料,只可以瀏覽其他使用者資料' - ) - return res.redirect(`/users/${req.params.id}`) + "error_messages", + "無法更改其他使用者資料,只可以瀏覽其他使用者資料" + ); + return res.redirect(`/users/${req.params.id}`); } return User.findByPk(req.params.id).then((user) => { - return res.render('edit', { user: user.toJSON() }) - }) + return res.render("edit", { user: user.toJSON() }); + }); }, putUser: (req, res) => { - const { name, email } = req.body - const { file } = req + const { name, email } = req.body; + const { file } = req; if (helpers.getUser(req).id !== Number(req.params.id)) { - req.flash('error_messages', '無法更改其他使用者資料,請重新編輯你的資料') - return res.redirect('back') + req.flash("error_messages", "無法更改其他使用者資料,請重新編輯你的資料"); + return res.redirect("back"); } if (!name || !email) { - req.flash('error_messages', '使用者資料沒有變更') - return res.redirect('back') + req.flash("error_messages", "使用者資料沒有變更"); + return res.redirect("back"); } if (file) { - imgur.setClientID(IMGUR_CLIENT_ID) + imgur.setClientID(IMGUR_CLIENT_ID); imgur.upload(file.path, (err, img) => { - if (err) console.log('Error: ', err) + if (err) console.log("Error: ", err); return User.findByPk(req.params.id).then((user) => { user .update({ name, email, - image: file ? img.data.link : user.image + image: file ? img.data.link : user.image, }) .then((user) => { - req.flash('success_messages', '使用者資料編輯成功') - return res.redirect(`/users/${req.params.id}`) - }) - }) - }) + req.flash("success_messages", "使用者資料編輯成功"); + return res.redirect(`/users/${req.params.id}`); + }); + }); + }); } else { return User.findByPk(req.params.id).then((user) => { user .update({ name, - email + email, }) .then((user) => { - req.flash('success_messages', '使用者資料編輯成功') - return res.redirect(`/users/${req.params.id}`) - }) - }) + req.flash("success_messages", "使用者資料編輯成功"); + return res.redirect(`/users/${req.params.id}`); + }); + }); } - } -} + }, + addFavorite: (req, res) => { + return Favorite.create({ + UserId: helpers.getUser(req).id, + RestaurantId: req.params.restaurantId, + }).then((resaurant) => { + return res.redirect("back"); + }); + }, + removeFavorite: (req, res) => { + return Favorite.findOne({ + where: { + UserId: helpers.getUser(req).id, + RestaurantId: req.params.restaurantId, + }, + }).then((favorite) => { + if (favorite) { + favorite.destroy().then((restaurant) => { + return res.redirect("back"); + }); + } + }); + }, +}; -module.exports = userController +module.exports = userController; diff --git a/models/restaurant.js b/models/restaurant.js index d6899a92..49d1324d 100644 --- a/models/restaurant.js +++ b/models/restaurant.js @@ -1,5 +1,5 @@ -'use strict' -const { Model } = require('sequelize') +"use strict"; +const { Model } = require("sequelize"); module.exports = (sequelize, DataTypes) => { class Restaurant extends Model { /** @@ -7,9 +7,14 @@ module.exports = (sequelize, DataTypes) => { * This method is not a part of Sequelize lifecycle. * The `models/index` file will call this method automatically. */ - static associate (models) { - Restaurant.belongsTo(models.Category) - Restaurant.hasMany(models.Comment) + static associate(models) { + Restaurant.belongsTo(models.Category); + Restaurant.hasMany(models.Comment); + Restaurant.belongsToMany(models.User, { + through: models.Favorite, + foreignKey: "RestaurantId", + as: "FavoriteUser", + }); } } Restaurant.init( @@ -20,12 +25,12 @@ module.exports = (sequelize, DataTypes) => { opening_hours: DataTypes.STRING, description: DataTypes.TEXT, image: DataTypes.STRING, - viewCounts: DataTypes.INTEGER + viewCounts: DataTypes.INTEGER, }, { sequelize, - modelName: 'Restaurant' + modelName: "Restaurant", } - ) - return Restaurant -} + ); + return Restaurant; +}; diff --git a/models/user.js b/models/user.js index 80c6520e..69dd582e 100644 --- a/models/user.js +++ b/models/user.js @@ -9,8 +9,8 @@ module.exports = (sequelize, DataTypes) => { */ static associate(models) { User.hasMany(models.Comment); - User.belongsToMany(model.Restaurant, { - through: model.Favorite, + User.belongsToMany(models.Restaurant, { + through: models.Favorite, foreignKey: "UserId", as: "FavoritedRestaurants", }); diff --git a/routes/index.js b/routes/index.js index d1ecff02..d527258b 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,140 +1,150 @@ -const restController = require('../controllers/restController') -const adminController = require('../controllers/adminController') -const userController = require('../controllers/userController') -const commentController = require('../controllers/commentController') -const passport = require('passport') -const helpers = require('../_helpers') +const restController = require("../controllers/restController"); +const adminController = require("../controllers/adminController"); +const userController = require("../controllers/userController"); +const commentController = require("../controllers/commentController"); +const passport = require("passport"); +const helpers = require("../_helpers"); // multer setup -const multer = require('multer') -const categoryController = require('../controllers/categoryController') -const restControllers = require('../controllers/restController') -const upload = multer({ dest: 'temp/' }) +const multer = require("multer"); +const categoryController = require("../controllers/categoryController"); +const restControllers = require("../controllers/restController"); +const upload = multer({ dest: "temp/" }); module.exports = (app) => { const authenticated = (req, res, next) => { if (helpers.ensureAuthenticated(req)) { - return next() + return next(); } - res.redirect('/signin') - } + res.redirect("/signin"); + }; const authenticatedAdmin = (req, res, next) => { if (helpers.ensureAuthenticated(req)) { if (helpers.getUser(req).isAdmin) { - return next() + return next(); } - return res.redirect('/') + return res.redirect("/"); } - res.redirect('/signin') - } + res.redirect("/signin"); + }; // 如果使用者訪問首頁,就導向 /restaurants 的頁面 - app.get('/', authenticated, (req, res) => { - res.redirect('restaurants') - }) - app.get('/restaurants', restController.getRestaurants) + app.get("/", authenticated, (req, res) => { + res.redirect("restaurants"); + }); + app.get("/restaurants", restController.getRestaurants); - app.get('/admin', authenticatedAdmin, (req, res) => { - res.redirect('/admin/restaurants') - }) + app.get("/admin", authenticatedAdmin, (req, res) => { + res.redirect("/admin/restaurants"); + }); app.get( - '/admin/restaurants', + "/admin/restaurants", authenticatedAdmin, adminController.getRestaurants - ) + ); - app.get('/signup', userController.signUpPage) - app.post('/signup', userController.signUp) + app.get("/signup", userController.signUpPage); + app.post("/signup", userController.signUp); - app.get('/signin', userController.signInPage) + app.get("/signin", userController.signInPage); app.post( - '/signin', - passport.authenticate('local', { - failureRedirect: '/signin', - failureFlash: true + "/signin", + passport.authenticate("local", { + failureRedirect: "/signin", + failureFlash: true, }), userController.signIn - ) - app.get('/logout', userController.logout) + ); + app.get("/logout", userController.logout); app.get( - '/admin/restaurants/create', + "/admin/restaurants/create", authenticatedAdmin, adminController.createRestaurants - ) + ); app.post( - '/admin/restaurants', + "/admin/restaurants", authenticatedAdmin, - upload.single('image'), + upload.single("image"), adminController.postRestaurants - ) + ); app.get( - '/admin/restaurants/:id', + "/admin/restaurants/:id", authenticatedAdmin, adminController.getRestaurant - ) + ); app.get( - '/admin/restaurants/:id/edit', + "/admin/restaurants/:id/edit", authenticatedAdmin, adminController.editRestaurant - ) + ); app.put( - '/admin/restaurants/:id', + "/admin/restaurants/:id", authenticatedAdmin, - upload.single('image'), + upload.single("image"), adminController.putRestaurant - ) + ); app.delete( - '/admin/restaurants/:id', + "/admin/restaurants/:id", authenticatedAdmin, adminController.deleteRestaurant - ) - app.get('/admin/users', authenticatedAdmin, adminController.getUsers) + ); + app.get("/admin/users", authenticatedAdmin, adminController.getUsers); app.put( - '/admin/users/:id/toggleAdmin', + "/admin/users/:id/toggleAdmin", authenticatedAdmin, adminController.toggleAdmin - ) + ); app.get( - '/admin/categories', + "/admin/categories", authenticatedAdmin, categoryController.getCategories - ) + ); app.post( - '/admin/categories', + "/admin/categories", authenticatedAdmin, categoryController.postCategory - ) + ); app.get( - '/admin/categories/:id', + "/admin/categories/:id", authenticatedAdmin, categoryController.getCategories - ) + ); app.put( - '/admin/categories/:id', + "/admin/categories/:id", authenticatedAdmin, categoryController.putCategory - ) + ); app.delete( - '/admin/categories/:id', + "/admin/categories/:id", authenticatedAdmin, categoryController.deletCategory - ) - app.get('/restaurants/feeds', authenticated, restController.getFeeds) - app.get('/restaurants/:id', authenticated, restController.getRestaurant) - app.post('/comments', authenticated, commentController.postComment) - app.delete('/comments/:id', authenticated, commentController.deleteComment) - app.get('/users/:id', authenticated, userController.getUser) - app.get('/users/:id/edit', authenticated, userController.editUser) + ); + app.get("/restaurants/feeds", authenticated, restController.getFeeds); + app.get("/restaurants/:id", authenticated, restController.getRestaurant); + app.post("/comments", authenticated, commentController.postComment); + app.delete("/comments/:id", authenticated, commentController.deleteComment); + app.get("/users/:id", authenticated, userController.getUser); + app.get("/users/:id/edit", authenticated, userController.editUser); app.put( - '/users/:id', + "/users/:id", authenticated, - upload.single('image'), + upload.single("image"), userController.putUser - ) + ); app.get( - '/restaurants/:id/dashboard', + "/restaurants/:id/dashboard", authenticated, restControllers.getDashBoard - ) -} + ); + app.post( + "/favorite/:restaurantId", + authenticated, + userController.addFavorite + ); + app.delete( + "/favorite/:restaurantId", + authenticated, + userController.removeFavorite + ); +}; diff --git a/views/restaurants.handlebars b/views/restaurants.handlebars index 0fcd0d18..18c20f6a 100644 --- a/views/restaurants.handlebars +++ b/views/restaurants.handlebars @@ -12,34 +12,33 @@ 全部 {{#each categories}} - + {{/each}}
{{#each restaurants}} -
-
- Card image cap -
-

- - {{this.name}} - -

- {{this.categoryName}} -

{{this.description}}

-
+
+
+ Card image cap +
+

+ + {{this.name}} + +

+ {{this.categoryName}} +

{{this.description}}

+ +
+
+
{{/each}}

@@ -52,16 +51,17 @@ {{#each totalPage}} - {{#ifCond this ../page}} -
  • {{this}}
  • + {{#ifCond this ../page}} +
  • {{this}}
  • {{else}} -
  • {{this}}
  • - {{/ifCond}} +
  • {{this}}
  • + {{/ifCond}} {{/each}} -
  • - - +
  • + +
  • \ No newline at end of file From 7e0fd2a9ec7b5991d841e227880f9102fd93cda2 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Sat, 20 Nov 2021 20:47:10 +0000 Subject: [PATCH 180/198] add add/remove favorite button to restaurant single page and restaurant index page --- config/passport.js | 51 ++++++++-------- controllers/restController.js | 111 +++++++++++++++++++--------------- models/restaurant.js | 2 +- routes/index.js | 2 +- views/restaurant.handlebars | 7 +++ views/restaurants.handlebars | 4 ++ 6 files changed, 101 insertions(+), 76 deletions(-) diff --git a/config/passport.js b/config/passport.js index 51cae323..447da34d 100644 --- a/config/passport.js +++ b/config/passport.js @@ -1,17 +1,18 @@ -const passport = require('passport') -const LocalStrategy = require('passport-local') -const bcrypt = require('bcryptjs') -const db = require('../models') -const User = db.User +const passport = require("passport"); +const LocalStrategy = require("passport-local"); +const bcrypt = require("bcryptjs"); +const db = require("../models"); +const User = db.User; +const Restaurant = db.Restaurant; // setup passport strategy passport.use( new LocalStrategy( // customize user field { - usernameField: 'email', - passwordField: 'password', - passReqToCallback: true + usernameField: "email", + passwordField: "password", + passReqToCallback: true, }, // authenticate user (req, username, password, cb) => { @@ -20,32 +21,34 @@ passport.use( return cb( null, false, - req.flash('error_messages', '帳號或密碼輸入錯誤') - ) + req.flash("error_messages", "帳號或密碼輸入錯誤") + ); } if (!bcrypt.compareSync(password, user.password)) { return cb( null, false, - req.flash('error_messages', '帳號或密碼輸入錯誤') - ) + req.flash("error_messages", "帳號或密碼輸入錯誤") + ); } - user = user.toJSON() - return cb(null, user) - }) + user = user.toJSON(); + return cb(null, user); + }); } ) -) +); // serialize and deserialize user. 只存id不存物件以便省空間 passport.serializeUser((user, cb) => { - cb(null, user.id) -}) + cb(null, user.id); +}); passport.deserializeUser((id, cb) => { - User.findByPk(id).then((user) => { - user = user.toJSON() - return cb(null, user) - }) -}) + User.findByPk(id, { + include: [{ model: Restaurant, as: "FavoritedRestaurants" }], + }).then((user) => { + user = user.toJSON(); + return cb(null, user); + }); +}); -module.exports = passport +module.exports = passport; diff --git a/controllers/restController.js b/controllers/restController.js index b5b6fe6c..811b3398 100644 --- a/controllers/restController.js +++ b/controllers/restController.js @@ -1,69 +1,80 @@ -const db = require('../models') -const Restaurant = db.Restaurant -const Category = db.Category -const User = db.User -const Comment = db.Comment +const db = require("../models"); +const Restaurant = db.Restaurant; +const Category = db.Category; +const User = db.User; +const Comment = db.Comment; -const pageLimit = 10 +const pageLimit = 10; const restControllers = { getRestaurants: (req, res) => { - let offset = 0 - const whereQuery = {} - let categoryId = '' + let offset = 0; + const whereQuery = {}; + let categoryId = ""; if (req.query.categoryId) { - categoryId = Number(req.query.categoryId) - whereQuery.CategoryId = categoryId + categoryId = Number(req.query.categoryId); + whereQuery.CategoryId = categoryId; } if (req.query.page) { - offset = (req.query.page - 1) * pageLimit + offset = (req.query.page - 1) * pageLimit; } Restaurant.findAndCountAll({ include: Category, where: whereQuery, offset: offset, - limit: pageLimit + limit: pageLimit, }).then((restaurants) => { // data for pagination - const page = Number(req.query.page) || 1 - const pages = Math.ceil(restaurants.count / pageLimit) + const page = Number(req.query.page) || 1; + const pages = Math.ceil(restaurants.count / pageLimit); const totalPage = Array.from({ length: pages }).map( (item, index) => index + 1 - ) + ); - const prev = page - 1 < 1 ? 1 : page - 1 - const next = page + 1 > pages ? pages : page + 1 + const prev = page - 1 < 1 ? 1 : page - 1; + const next = page + 1 > pages ? pages : page + 1; const data = restaurants.rows.map((r) => ({ ...r.dataValues, description: r.dataValues.description.substring(0, 50), - categoryName: r.dataValues.Category.name - })) + categoryName: r.dataValues.Category.name, + isFavorited: req.user.FavoritedRestaurants.map( + (restaurant) => restaurant.id + ).includes(r.id), //narrow down from all favorited restaurants to current restaurant + })); Category.findAll({ raw: true, nest: true }).then((categories) => { - return res.render('restaurants', { + return res.render("restaurants", { restaurants: data, categories, categoryId, page, totalPage, prev, - next - }) - }) - }) + next, + }); + }); + }); }, getRestaurant: (req, res) => { return Restaurant.findByPk(req.params.id, { - include: [Category, { model: Comment, include: User }] + include: [ + Category, + { model: Comment, include: [User] }, + { model: User, as: "FavoritedUsers" }, + ], }).then((restaurant) => { - restaurant.increment('viewCounts') - return res.render('restaurant', { - restaurant: restaurant.toJSON() - }) - }) + const isFavorited = restaurant.FavoritedUsers.map( + (user) => user.id + ).includes(req.user.id); + restaurant.increment("viewCounts"); + return res.render("restaurant", { + restaurant: restaurant.toJSON(), + isFavorited: isFavorited, + }); + }); }, getFeeds: async (req, res) => { @@ -72,37 +83,37 @@ const restControllers = { limit: 10, raw: true, nest: true, - order: [['createdAt', 'DESC']], - include: [Category] - }) + order: [["createdAt", "DESC"]], + include: [Category], + }); const comments = await Comment.findAll({ limit: 10, raw: true, nest: true, - order: [['createdAt', 'DESC']], - include: [User, Restaurant] - }) + order: [["createdAt", "DESC"]], + include: [User, Restaurant], + }); - return res.render('feeds', { + return res.render("feeds", { restaurants, - comments - }) + comments, + }); } catch (error) { - console.log('Error: ', error) + console.log("Error: ", error); } }, getDashBoard: async (req, res) => { - const restaurantId = req.params.id + const restaurantId = req.params.id; const restaurant = await Restaurant.findByPk(restaurantId, { - include: [Category, { model: Comment }] - }) + include: [Category, { model: Comment }], + }); const comment = await Comment.findAndCountAll({ - where: { RestaurantId: restaurantId } - }) - return res.render('dashboard', { restaurant: restaurant.toJSON() }) - } -} + where: { RestaurantId: restaurantId }, + }); + return res.render("dashboard", { restaurant: restaurant.toJSON() }); + }, +}; -module.exports = restControllers +module.exports = restControllers; diff --git a/models/restaurant.js b/models/restaurant.js index 49d1324d..eb7111c3 100644 --- a/models/restaurant.js +++ b/models/restaurant.js @@ -13,7 +13,7 @@ module.exports = (sequelize, DataTypes) => { Restaurant.belongsToMany(models.User, { through: models.Favorite, foreignKey: "RestaurantId", - as: "FavoriteUser", + as: "FavoritedUsers", }); } } diff --git a/routes/index.js b/routes/index.js index d527258b..49f314d9 100644 --- a/routes/index.js +++ b/routes/index.js @@ -33,7 +33,7 @@ module.exports = (app) => { app.get("/", authenticated, (req, res) => { res.redirect("restaurants"); }); - app.get("/restaurants", restController.getRestaurants); + app.get("/restaurants", authenticated, restController.getRestaurants); app.get("/admin", authenticatedAdmin, (req, res) => { res.redirect("/admin/restaurants"); diff --git a/views/restaurant.handlebars b/views/restaurant.handlebars index b3bcddb0..a71fcf17 100644 --- a/views/restaurant.handlebars +++ b/views/restaurant.handlebars @@ -16,6 +16,13 @@

    {{restaurant.description}}

    + {{#if isFavorited }} +
    + {{else}} +
    + {{/if}}

    diff --git a/views/restaurants.handlebars b/views/restaurants.handlebars index 18c20f6a..2e36fc83 100644 --- a/views/restaurants.handlebars +++ b/views/restaurants.handlebars @@ -32,10 +32,14 @@

    {{this.categoryName}}

    {{this.description}}

    + {{#if this.isFavorited }}
    + {{else}}
    + {{/if}} +
    From f1cf40f5a103a9347a40e3958e8c45984f27a5df Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Sat, 20 Nov 2021 20:47:57 +0000 Subject: [PATCH 181/198] fix styling --- config/passport.js | 50 +++--- controllers/restController.js | 5 +- controllers/userController.js | 1 - migrations/20211120175539-create-favorite.js | 24 +-- models/favorite.js | 14 +- models/restaurant.js | 26 ++-- models/user.js | 24 +-- routes/index.js | 156 +++++++++---------- 8 files changed, 148 insertions(+), 152 deletions(-) diff --git a/config/passport.js b/config/passport.js index 447da34d..29f674d4 100644 --- a/config/passport.js +++ b/config/passport.js @@ -1,18 +1,18 @@ -const passport = require("passport"); -const LocalStrategy = require("passport-local"); -const bcrypt = require("bcryptjs"); -const db = require("../models"); -const User = db.User; -const Restaurant = db.Restaurant; +const passport = require('passport') +const LocalStrategy = require('passport-local') +const bcrypt = require('bcryptjs') +const db = require('../models') +const User = db.User +const Restaurant = db.Restaurant // setup passport strategy passport.use( new LocalStrategy( // customize user field { - usernameField: "email", - passwordField: "password", - passReqToCallback: true, + usernameField: 'email', + passwordField: 'password', + passReqToCallback: true }, // authenticate user (req, username, password, cb) => { @@ -21,34 +21,34 @@ passport.use( return cb( null, false, - req.flash("error_messages", "帳號或密碼輸入錯誤") - ); + req.flash('error_messages', '帳號或密碼輸入錯誤') + ) } if (!bcrypt.compareSync(password, user.password)) { return cb( null, false, - req.flash("error_messages", "帳號或密碼輸入錯誤") - ); + req.flash('error_messages', '帳號或密碼輸入錯誤') + ) } - user = user.toJSON(); - return cb(null, user); - }); + user = user.toJSON() + return cb(null, user) + }) } ) -); +) // serialize and deserialize user. 只存id不存物件以便省空間 passport.serializeUser((user, cb) => { - cb(null, user.id); -}); + cb(null, user.id) +}) passport.deserializeUser((id, cb) => { User.findByPk(id, { - include: [{ model: Restaurant, as: "FavoritedRestaurants" }], + include: [{ model: Restaurant, as: 'FavoritedRestaurants' }] }).then((user) => { - user = user.toJSON(); - return cb(null, user); - }); -}); + user = user.toJSON() + return cb(null, user) + }) +}) -module.exports = passport; +module.exports = passport diff --git a/controllers/restController.js b/controllers/restController.js index 811b3398..b42e53cb 100644 --- a/controllers/restController.js +++ b/controllers/restController.js @@ -42,7 +42,7 @@ const restControllers = { categoryName: r.dataValues.Category.name, isFavorited: req.user.FavoritedRestaurants.map( (restaurant) => restaurant.id - ).includes(r.id), //narrow down from all favorited restaurants to current restaurant + ).includes(r.id), // narrow down from all favorited restaurants to current restaurant })); Category.findAll({ raw: true, nest: true }).then((categories) => { return res.render("restaurants", { @@ -109,9 +109,6 @@ const restControllers = { const restaurant = await Restaurant.findByPk(restaurantId, { include: [Category, { model: Comment }], }); - const comment = await Comment.findAndCountAll({ - where: { RestaurantId: restaurantId }, - }); return res.render("dashboard", { restaurant: restaurant.toJSON() }); }, }; diff --git a/controllers/userController.js b/controllers/userController.js index e26eb176..2647077e 100644 --- a/controllers/userController.js +++ b/controllers/userController.js @@ -7,7 +7,6 @@ const Favorite = db.Favorite; const helpers = require("../_helpers"); // imgur setup const imgur = require("imgur-node-api"); -const restaurant = require("../models/restaurant"); const IMGUR_CLIENT_ID = process.env.IMGUR_CLIENT_ID; const userController = { diff --git a/migrations/20211120175539-create-favorite.js b/migrations/20211120175539-create-favorite.js index 6b21d2fa..a677d9ea 100644 --- a/migrations/20211120175539-create-favorite.js +++ b/migrations/20211120175539-create-favorite.js @@ -1,32 +1,32 @@ -"use strict"; +'use strict' module.exports = { up: async (queryInterface, Sequelize) => { - await queryInterface.createTable("Favorites", { + await queryInterface.createTable('Favorites', { id: { allowNull: false, autoIncrement: true, primaryKey: true, - type: Sequelize.INTEGER, + type: Sequelize.INTEGER }, UserId: { type: Sequelize.INTEGER, - references: { model: "Users", key: "id" }, + references: { model: 'Users', key: 'id' } }, RestaurantId: { type: Sequelize.INTEGER, - references: { model: "Restaurants", key: "id" }, + references: { model: 'Restaurants', key: 'id' } }, createdAt: { allowNull: false, - type: Sequelize.DATE, + type: Sequelize.DATE }, updatedAt: { allowNull: false, - type: Sequelize.DATE, - }, - }); + type: Sequelize.DATE + } + }) }, down: async (queryInterface, Sequelize) => { - await queryInterface.dropTable("Favorites"); - }, -}; + await queryInterface.dropTable('Favorites') + } +} diff --git a/models/favorite.js b/models/favorite.js index 3def4705..b86c0f68 100644 --- a/models/favorite.js +++ b/models/favorite.js @@ -1,7 +1,7 @@ -'use strict'; +'use strict' const { Model -} = require('sequelize'); +} = require('sequelize') module.exports = (sequelize, DataTypes) => { class Favorite extends Model { /** @@ -9,7 +9,7 @@ module.exports = (sequelize, DataTypes) => { * This method is not a part of Sequelize lifecycle. * The `models/index` file will call this method automatically. */ - static associate(models) { + static associate (models) { // define association here } }; @@ -18,7 +18,7 @@ module.exports = (sequelize, DataTypes) => { RestaurantId: DataTypes.INTEGER }, { sequelize, - modelName: 'Favorite', - }); - return Favorite; -}; \ No newline at end of file + modelName: 'Favorite' + }) + return Favorite +} diff --git a/models/restaurant.js b/models/restaurant.js index eb7111c3..b8519550 100644 --- a/models/restaurant.js +++ b/models/restaurant.js @@ -1,5 +1,5 @@ -"use strict"; -const { Model } = require("sequelize"); +'use strict' +const { Model } = require('sequelize') module.exports = (sequelize, DataTypes) => { class Restaurant extends Model { /** @@ -7,14 +7,14 @@ module.exports = (sequelize, DataTypes) => { * This method is not a part of Sequelize lifecycle. * The `models/index` file will call this method automatically. */ - static associate(models) { - Restaurant.belongsTo(models.Category); - Restaurant.hasMany(models.Comment); + static associate (models) { + Restaurant.belongsTo(models.Category) + Restaurant.hasMany(models.Comment) Restaurant.belongsToMany(models.User, { through: models.Favorite, - foreignKey: "RestaurantId", - as: "FavoritedUsers", - }); + foreignKey: 'RestaurantId', + as: 'FavoritedUsers' + }) } } Restaurant.init( @@ -25,12 +25,12 @@ module.exports = (sequelize, DataTypes) => { opening_hours: DataTypes.STRING, description: DataTypes.TEXT, image: DataTypes.STRING, - viewCounts: DataTypes.INTEGER, + viewCounts: DataTypes.INTEGER }, { sequelize, - modelName: "Restaurant", + modelName: 'Restaurant' } - ); - return Restaurant; -}; + ) + return Restaurant +} diff --git a/models/user.js b/models/user.js index 69dd582e..0b59cd67 100644 --- a/models/user.js +++ b/models/user.js @@ -1,5 +1,5 @@ -"use strict"; -const { Model } = require("sequelize"); +'use strict' +const { Model } = require('sequelize') module.exports = (sequelize, DataTypes) => { class User extends Model { /** @@ -7,13 +7,13 @@ module.exports = (sequelize, DataTypes) => { * This method is not a part of Sequelize lifecycle. * The `models/index` file will call this method automatically. */ - static associate(models) { - User.hasMany(models.Comment); + static associate (models) { + User.hasMany(models.Comment) User.belongsToMany(models.Restaurant, { through: models.Favorite, - foreignKey: "UserId", - as: "FavoritedRestaurants", - }); + foreignKey: 'UserId', + as: 'FavoritedRestaurants' + }) } } User.init( @@ -22,12 +22,12 @@ module.exports = (sequelize, DataTypes) => { email: DataTypes.STRING, password: DataTypes.STRING, isAdmin: DataTypes.BOOLEAN, - image: DataTypes.STRING, + image: DataTypes.STRING }, { sequelize, - modelName: "User", + modelName: 'User' } - ); - return User; -}; + ) + return User +} diff --git a/routes/index.js b/routes/index.js index 49f314d9..b21fd9c7 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,150 +1,150 @@ -const restController = require("../controllers/restController"); -const adminController = require("../controllers/adminController"); -const userController = require("../controllers/userController"); -const commentController = require("../controllers/commentController"); -const passport = require("passport"); -const helpers = require("../_helpers"); +const restController = require('../controllers/restController') +const adminController = require('../controllers/adminController') +const userController = require('../controllers/userController') +const commentController = require('../controllers/commentController') +const passport = require('passport') +const helpers = require('../_helpers') // multer setup -const multer = require("multer"); -const categoryController = require("../controllers/categoryController"); -const restControllers = require("../controllers/restController"); -const upload = multer({ dest: "temp/" }); +const multer = require('multer') +const categoryController = require('../controllers/categoryController') +const restControllers = require('../controllers/restController') +const upload = multer({ dest: 'temp/' }) module.exports = (app) => { const authenticated = (req, res, next) => { if (helpers.ensureAuthenticated(req)) { - return next(); + return next() } - res.redirect("/signin"); - }; + res.redirect('/signin') + } const authenticatedAdmin = (req, res, next) => { if (helpers.ensureAuthenticated(req)) { if (helpers.getUser(req).isAdmin) { - return next(); + return next() } - return res.redirect("/"); + return res.redirect('/') } - res.redirect("/signin"); - }; + res.redirect('/signin') + } // 如果使用者訪問首頁,就導向 /restaurants 的頁面 - app.get("/", authenticated, (req, res) => { - res.redirect("restaurants"); - }); - app.get("/restaurants", authenticated, restController.getRestaurants); + app.get('/', authenticated, (req, res) => { + res.redirect('restaurants') + }) + app.get('/restaurants', authenticated, restController.getRestaurants) - app.get("/admin", authenticatedAdmin, (req, res) => { - res.redirect("/admin/restaurants"); - }); + app.get('/admin', authenticatedAdmin, (req, res) => { + res.redirect('/admin/restaurants') + }) app.get( - "/admin/restaurants", + '/admin/restaurants', authenticatedAdmin, adminController.getRestaurants - ); + ) - app.get("/signup", userController.signUpPage); - app.post("/signup", userController.signUp); + app.get('/signup', userController.signUpPage) + app.post('/signup', userController.signUp) - app.get("/signin", userController.signInPage); + app.get('/signin', userController.signInPage) app.post( - "/signin", - passport.authenticate("local", { - failureRedirect: "/signin", - failureFlash: true, + '/signin', + passport.authenticate('local', { + failureRedirect: '/signin', + failureFlash: true }), userController.signIn - ); - app.get("/logout", userController.logout); + ) + app.get('/logout', userController.logout) app.get( - "/admin/restaurants/create", + '/admin/restaurants/create', authenticatedAdmin, adminController.createRestaurants - ); + ) app.post( - "/admin/restaurants", + '/admin/restaurants', authenticatedAdmin, - upload.single("image"), + upload.single('image'), adminController.postRestaurants - ); + ) app.get( - "/admin/restaurants/:id", + '/admin/restaurants/:id', authenticatedAdmin, adminController.getRestaurant - ); + ) app.get( - "/admin/restaurants/:id/edit", + '/admin/restaurants/:id/edit', authenticatedAdmin, adminController.editRestaurant - ); + ) app.put( - "/admin/restaurants/:id", + '/admin/restaurants/:id', authenticatedAdmin, - upload.single("image"), + upload.single('image'), adminController.putRestaurant - ); + ) app.delete( - "/admin/restaurants/:id", + '/admin/restaurants/:id', authenticatedAdmin, adminController.deleteRestaurant - ); - app.get("/admin/users", authenticatedAdmin, adminController.getUsers); + ) + app.get('/admin/users', authenticatedAdmin, adminController.getUsers) app.put( - "/admin/users/:id/toggleAdmin", + '/admin/users/:id/toggleAdmin', authenticatedAdmin, adminController.toggleAdmin - ); + ) app.get( - "/admin/categories", + '/admin/categories', authenticatedAdmin, categoryController.getCategories - ); + ) app.post( - "/admin/categories", + '/admin/categories', authenticatedAdmin, categoryController.postCategory - ); + ) app.get( - "/admin/categories/:id", + '/admin/categories/:id', authenticatedAdmin, categoryController.getCategories - ); + ) app.put( - "/admin/categories/:id", + '/admin/categories/:id', authenticatedAdmin, categoryController.putCategory - ); + ) app.delete( - "/admin/categories/:id", + '/admin/categories/:id', authenticatedAdmin, categoryController.deletCategory - ); - app.get("/restaurants/feeds", authenticated, restController.getFeeds); - app.get("/restaurants/:id", authenticated, restController.getRestaurant); - app.post("/comments", authenticated, commentController.postComment); - app.delete("/comments/:id", authenticated, commentController.deleteComment); - app.get("/users/:id", authenticated, userController.getUser); - app.get("/users/:id/edit", authenticated, userController.editUser); + ) + app.get('/restaurants/feeds', authenticated, restController.getFeeds) + app.get('/restaurants/:id', authenticated, restController.getRestaurant) + app.post('/comments', authenticated, commentController.postComment) + app.delete('/comments/:id', authenticated, commentController.deleteComment) + app.get('/users/:id', authenticated, userController.getUser) + app.get('/users/:id/edit', authenticated, userController.editUser) app.put( - "/users/:id", + '/users/:id', authenticated, - upload.single("image"), + upload.single('image'), userController.putUser - ); + ) app.get( - "/restaurants/:id/dashboard", + '/restaurants/:id/dashboard', authenticated, restControllers.getDashBoard - ); + ) app.post( - "/favorite/:restaurantId", + '/favorite/:restaurantId', authenticated, userController.addFavorite - ); + ) app.delete( - "/favorite/:restaurantId", + '/favorite/:restaurantId', authenticated, userController.removeFavorite - ); -}; + ) +} From d7ce5e609c2912b605737673011d17b507eaf3cc Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Sat, 20 Nov 2021 20:52:28 +0000 Subject: [PATCH 182/198] merge with R04-test --- config/config.json | 5 -- controllers/restController.js | 100 ++++++++++++------------ controllers/userController.js | 142 +++++++++++++++++----------------- helpers/unitTestHelpers.js | 125 ++++++++++++++---------------- package-lock.json | 78 +------------------ tests/R01.test.js | 2 +- 6 files changed, 179 insertions(+), 273 deletions(-) diff --git a/config/config.json b/config/config.json index 3e311ffa..c32bfca9 100644 --- a/config/config.json +++ b/config/config.json @@ -7,12 +7,7 @@ "dialect": "mysql" }, "production": { -<<<<<<< HEAD "use_env_variable": "CLEARDB_DATABASE_URL", -======= - "username": "root", - "password": null, ->>>>>>> origin/R04-test "database": "database_production", "host": "127.0.0.1", "dialect": "mysql" diff --git a/controllers/restController.js b/controllers/restController.js index b42e53cb..296e85d3 100644 --- a/controllers/restController.js +++ b/controllers/restController.js @@ -1,40 +1,40 @@ -const db = require("../models"); -const Restaurant = db.Restaurant; -const Category = db.Category; -const User = db.User; -const Comment = db.Comment; +const db = require('../models') +const Restaurant = db.Restaurant +const Category = db.Category +const User = db.User +const Comment = db.Comment -const pageLimit = 10; +const pageLimit = 10 const restControllers = { getRestaurants: (req, res) => { - let offset = 0; - const whereQuery = {}; - let categoryId = ""; + let offset = 0 + const whereQuery = {} + let categoryId = '' if (req.query.categoryId) { - categoryId = Number(req.query.categoryId); - whereQuery.CategoryId = categoryId; + categoryId = Number(req.query.categoryId) + whereQuery.CategoryId = categoryId } if (req.query.page) { - offset = (req.query.page - 1) * pageLimit; + offset = (req.query.page - 1) * pageLimit } Restaurant.findAndCountAll({ include: Category, where: whereQuery, offset: offset, - limit: pageLimit, + limit: pageLimit }).then((restaurants) => { // data for pagination - const page = Number(req.query.page) || 1; - const pages = Math.ceil(restaurants.count / pageLimit); + const page = Number(req.query.page) || 1 + const pages = Math.ceil(restaurants.count / pageLimit) const totalPage = Array.from({ length: pages }).map( (item, index) => index + 1 - ); + ) - const prev = page - 1 < 1 ? 1 : page - 1; - const next = page + 1 > pages ? pages : page + 1; + const prev = page - 1 < 1 ? 1 : page - 1 + const next = page + 1 > pages ? pages : page + 1 const data = restaurants.rows.map((r) => ({ ...r.dataValues, @@ -42,20 +42,20 @@ const restControllers = { categoryName: r.dataValues.Category.name, isFavorited: req.user.FavoritedRestaurants.map( (restaurant) => restaurant.id - ).includes(r.id), // narrow down from all favorited restaurants to current restaurant - })); + ).includes(r.id) // narrow down from all favorited restaurants to current restaurant + })) Category.findAll({ raw: true, nest: true }).then((categories) => { - return res.render("restaurants", { + return res.render('restaurants', { restaurants: data, categories, categoryId, page, totalPage, prev, - next, - }); - }); - }); + next + }) + }) + }) }, getRestaurant: (req, res) => { @@ -63,18 +63,18 @@ const restControllers = { include: [ Category, { model: Comment, include: [User] }, - { model: User, as: "FavoritedUsers" }, - ], + { model: User, as: 'FavoritedUsers' } + ] }).then((restaurant) => { const isFavorited = restaurant.FavoritedUsers.map( (user) => user.id - ).includes(req.user.id); - restaurant.increment("viewCounts"); - return res.render("restaurant", { + ).includes(req.user.id) + restaurant.increment('viewCounts') + return res.render('restaurant', { restaurant: restaurant.toJSON(), - isFavorited: isFavorited, - }); - }); + isFavorited: isFavorited + }) + }) }, getFeeds: async (req, res) => { @@ -83,34 +83,34 @@ const restControllers = { limit: 10, raw: true, nest: true, - order: [["createdAt", "DESC"]], - include: [Category], - }); + order: [['createdAt', 'DESC']], + include: [Category] + }) const comments = await Comment.findAll({ limit: 10, raw: true, nest: true, - order: [["createdAt", "DESC"]], - include: [User, Restaurant], - }); + order: [['createdAt', 'DESC']], + include: [User, Restaurant] + }) - return res.render("feeds", { + return res.render('feeds', { restaurants, - comments, - }); + comments + }) } catch (error) { - console.log("Error: ", error); + console.log('Error: ', error) } }, getDashBoard: async (req, res) => { - const restaurantId = req.params.id; + const restaurantId = req.params.id const restaurant = await Restaurant.findByPk(restaurantId, { - include: [Category, { model: Comment }], - }); - return res.render("dashboard", { restaurant: restaurant.toJSON() }); - }, -}; + include: [Category, { model: Comment }] + }) + return res.render('dashboard', { restaurant: restaurant.toJSON() }) + } +} -module.exports = restControllers; +module.exports = restControllers diff --git a/controllers/userController.js b/controllers/userController.js index 2647077e..1929ee2c 100644 --- a/controllers/userController.js +++ b/controllers/userController.js @@ -1,162 +1,162 @@ -const bcrypt = require("bcrypt-nodejs"); -const db = require("../models"); -const User = db.User; -const Restaurant = db.Restaurant; -const Comment = db.Comment; -const Favorite = db.Favorite; -const helpers = require("../_helpers"); +const bcrypt = require('bcrypt-nodejs') +const db = require('../models') +const User = db.User +const Restaurant = db.Restaurant +const Comment = db.Comment +const Favorite = db.Favorite +const helpers = require('../_helpers') // imgur setup -const imgur = require("imgur-node-api"); -const IMGUR_CLIENT_ID = process.env.IMGUR_CLIENT_ID; +const imgur = require('imgur-node-api') +const IMGUR_CLIENT_ID = process.env.IMGUR_CLIENT_ID const userController = { signUpPage: (req, res) => { - return res.render("signup"); + return res.render('signup') }, signUp: (req, res) => { - const { name, email, password, passwordCheck } = req.body; + const { name, email, password, passwordCheck } = req.body if (password !== passwordCheck) { - req.flash("error_messages", "兩次密碼輸入不同"); - return res.redirect("/signup"); + req.flash('error_messages', '兩次密碼輸入不同') + return res.redirect('/signup') } else { User.findOne({ where: { email } }).then((user) => { if (user) { - req.flash("error_messages", "信箱已經註冊過"); - return res.redirect("/signup"); + req.flash('error_messages', '信箱已經註冊過') + return res.redirect('/signup') } else { User.create({ name, email, - password: bcrypt.hashSync(password, bcrypt.genSaltSync(10), null), + password: bcrypt.hashSync(password, bcrypt.genSaltSync(10), null) }).then((user) => { - req.flash("success_messages", "成功註冊帳號"); - return res.redirect("/signin"); - }); + req.flash('success_messages', '成功註冊帳號') + return res.redirect('/signin') + }) } - }); + }) } }, signInPage: (req, res) => { - return res.render("signin"); + return res.render('signin') }, signIn: (req, res) => { - req.flash("success_messages", "成功登入!"); - res.redirect("/restaurants"); + req.flash('success_messages', '成功登入!') + res.redirect('/restaurants') }, logout: (req, res) => { - req.flash("success_message", "登出成功!"); - req.logout(); - res.redirect("/signin"); + req.flash('success_message', '登出成功!') + req.logout() + res.redirect('/signin') }, getUser: async (req, res) => { try { const isEditable = - helpers.getUser(req).id === Number(req.params.id) || false; + helpers.getUser(req).id === Number(req.params.id) || false const user = await User.findByPk(req.params.id, { raw: true, - nest: true, - }); + nest: true + }) const comments = await Comment.findAll({ raw: true, nest: true, where: { UserId: req.params.id }, - include: [Restaurant], - }); + include: [Restaurant] + }) - return res.render("profile", { + return res.render('profile', { user: { ...user, isEditable }, - comments: comments, - }); + comments: comments + }) } catch (error) { - console.log(error); + console.log(error) } }, editUser: (req, res) => { // can only edit your own profile if (helpers.getUser(req).id !== Number(req.params.id)) { req.flash( - "error_messages", - "無法更改其他使用者資料,只可以瀏覽其他使用者資料" - ); - return res.redirect(`/users/${req.params.id}`); + 'error_messages', + '無法更改其他使用者資料,只可以瀏覽其他使用者資料' + ) + return res.redirect(`/users/${req.params.id}`) } return User.findByPk(req.params.id).then((user) => { - return res.render("edit", { user: user.toJSON() }); - }); + return res.render('edit', { user: user.toJSON() }) + }) }, putUser: (req, res) => { - const { name, email } = req.body; - const { file } = req; + const { name, email } = req.body + const { file } = req if (helpers.getUser(req).id !== Number(req.params.id)) { - req.flash("error_messages", "無法更改其他使用者資料,請重新編輯你的資料"); - return res.redirect("back"); + req.flash('error_messages', '無法更改其他使用者資料,請重新編輯你的資料') + return res.redirect('back') } if (!name || !email) { - req.flash("error_messages", "使用者資料沒有變更"); - return res.redirect("back"); + req.flash('error_messages', '使用者資料沒有變更') + return res.redirect('back') } if (file) { - imgur.setClientID(IMGUR_CLIENT_ID); + imgur.setClientID(IMGUR_CLIENT_ID) imgur.upload(file.path, (err, img) => { - if (err) console.log("Error: ", err); + if (err) console.log('Error: ', err) return User.findByPk(req.params.id).then((user) => { user .update({ name, email, - image: file ? img.data.link : user.image, + image: file ? img.data.link : user.image }) .then((user) => { - req.flash("success_messages", "使用者資料編輯成功"); - return res.redirect(`/users/${req.params.id}`); - }); - }); - }); + req.flash('success_messages', '使用者資料編輯成功') + return res.redirect(`/users/${req.params.id}`) + }) + }) + }) } else { return User.findByPk(req.params.id).then((user) => { user .update({ name, - email, + email }) .then((user) => { - req.flash("success_messages", "使用者資料編輯成功"); - return res.redirect(`/users/${req.params.id}`); - }); - }); + req.flash('success_messages', '使用者資料編輯成功') + return res.redirect(`/users/${req.params.id}`) + }) + }) } }, addFavorite: (req, res) => { return Favorite.create({ UserId: helpers.getUser(req).id, - RestaurantId: req.params.restaurantId, + RestaurantId: req.params.restaurantId }).then((resaurant) => { - return res.redirect("back"); - }); + return res.redirect('back') + }) }, removeFavorite: (req, res) => { return Favorite.findOne({ where: { UserId: helpers.getUser(req).id, - RestaurantId: req.params.restaurantId, - }, + RestaurantId: req.params.restaurantId + } }).then((favorite) => { if (favorite) { favorite.destroy().then((restaurant) => { - return res.redirect("back"); - }); + return res.redirect('back') + }) } - }); - }, -}; + }) + } +} -module.exports = userController; +module.exports = userController diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js index 33bfa589..fa4baf8c 100644 --- a/helpers/unitTestHelpers.js +++ b/helpers/unitTestHelpers.js @@ -1,137 +1,124 @@ -const SequelizeMock = require('sequelize-mock') -const proxyquire = require('proxyquire') -const sinon = require('sinon') +const SequelizeMock = require("sequelize-mock"); +const proxyquire = require("proxyquire"); +const sinon = require("sinon"); -const dbMock = new SequelizeMock() +const dbMock = new SequelizeMock(); const createModelMock = (name, defaultValue, data, joinedTableName) => { const mockModel = dbMock.define(name, defaultValue, { instanceMethods: { update: (changes) => { - mockModel._defaults = { ...changes } - return Promise.resolve() - } - } - }) + mockModel._defaults = { ...changes }; + return Promise.resolve(); + }, + }, + }); // 模擬 Sequelize 行為 // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) - mockModel.findByPk = (id) => mockModel.findOne({ where: { id: id } }) + mockModel.findByPk = (id) => mockModel.findOne({ where: { id: id } }); // 將 count 的 function 預設回傳假資料數目 1 - mockModel.count = () => 1 + mockModel.count = () => 1; // 因為 mock 中的 create 有問題,因此指向 upsert function, 這樣可以在 useHandler 中取得 create 呼叫 - mockModel.create = mockModel.upsert + mockModel.create = mockModel.upsert; // modify middleware if (joinedTableName) { mockModel.$queryInterface.$useHandler((query, queryOptions) => { - if (query === 'upsert') { + if (query === "upsert") { // 新增 joinTable 資料到模擬資料 - const { UserId, RestaurantId } = queryOptions[0] - const restaurant = data.find((d) => d.id === RestaurantId) - restaurant[joinedTableName].push({ UserId: UserId }) - return Promise.resolve(data.map((d) => mockModel.build(d))) - } else if (query === 'findAll') { + const { UserId, RestaurantId } = queryOptions[0]; + const restaurant = data.find((d) => d.id === RestaurantId); + restaurant[joinedTableName].push({ UserId: UserId }); + return Promise.resolve(data.map((d) => mockModel.build(d))); + } else if (query === "findAll") { // 回傳模擬資料 -<<<<<<< HEAD if (!data) { - return mockModel.build([defaultValue]) + return mockModel.build([defaultValue]); } - return Promise.resolve(data ? data.map((d) => mockModel.build(d)) : []) - } else if (query === 'destroy') { -======= - return Promise.resolve( data ? data.map(d => mockModel.build(d)) : []) - }else if (query === 'destroy') { ->>>>>>> origin/R04-test + return Promise.resolve(data ? data.map((d) => mockModel.build(d)) : []); + } else if (query === "destroy") { // destroy 可以從 where 取得要刪除的資料 // 因此就可以模擬將模擬資料中的資料刪除 // 刪除模擬資料中的某一筆 joinTable 資料 - const { UserId, RestaurantId } = queryOptions[0].where - const restaurant = data.find((d) => d.id === RestaurantId) + const { UserId, RestaurantId } = queryOptions[0].where; + const restaurant = data.find((d) => d.id === RestaurantId); restaurant[joinedTableName] = restaurant[joinedTableName].filter( (d) => !(d.UserId === UserId) - ) - return Promise.resolve(data.map((d) => mockModel.build(d))) + ); + return Promise.resolve(data.map((d) => mockModel.build(d))); } - }) + }); } else { mockModel.$queryInterface.$useHandler((query, queryOptions, done) => { - if (query === 'upsert') { + if (query === "upsert") { // create 時會帶 userId 跟 restaurantId (ex: Like.create({ userId: 1, restaurantId: 2})) - const { UserId, RestaurantId } = queryOptions[0] + const { UserId, RestaurantId } = queryOptions[0]; // 新增這個 Like 的資訊到模擬資料裡 - data.push({ UserId, RestaurantId }) + data.push({ UserId, RestaurantId }); // 回傳模擬資料 - return Promise.resolve(mockModel.build(data)) - } else if (query === 'findAll') { + return Promise.resolve(mockModel.build(data)); + } else if (query === "findAll") { // 回傳模擬資料 -<<<<<<< HEAD if (!data) { - return mockModel.build([defaultValue]) + return mockModel.build([defaultValue]); } - return Promise.resolve(data ? data.map((d) => mockModel.build(d)) : []) -======= - return Promise.resolve(data ? data.map(d => mockModel.build(d)) : []) ->>>>>>> origin/R04-test - } else if (query === 'destroy') { + return Promise.resolve(data ? data.map((d) => mockModel.build(d)) : []); + } else if (query === "destroy") { // destroy 可以從 where 取得要刪除的資料 // 因此就可以模擬將模擬資料中的資料刪除 - const { UserId, RestaurantId } = queryOptions[0].where + const { UserId, RestaurantId } = queryOptions[0].where; data = data.filter( (d) => !(d.UserId === UserId && d.RestaurantId === RestaurantId) - ) + ); - return Promise.resolve(mockModel.build(data)) + return Promise.resolve(mockModel.build(data)); } - }) + }); } - return mockModel -} + return mockModel; +}; const createControllerProxy = (path, model) => { const controller = proxyquire(path, { - '../models': model - }) + "../models": model, + }); - return controller -} + return controller; +}; const mockRequest = (query) => { return { ...query, - flash: sinon.spy() - } -} + flash: sinon.spy(), + }; +}; const mockResponse = () => { return { redirect: sinon.spy(), - render: sinon.spy() - } -} + render: sinon.spy(), + }; +}; const mockRequest = (query) => { return { ...query, flash: sinon.spy(), - } -} + }; +}; const mockResponse = () => { return { redirect: sinon.spy(), render: sinon.spy(), - } -} + }; +}; module.exports = { createModelMock, createControllerProxy, mockRequest, - mockResponse -<<<<<<< HEAD -} -======= -} ->>>>>>> origin/R04-test + mockResponse, +}; diff --git a/package-lock.json b/package-lock.json index 822ed099..e570880f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,4 @@ { -<<<<<<< HEAD "name": "forum-express-grading", "version": "2.0.0", "lockfileVersion": 1, @@ -105,21 +104,11 @@ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" }, -======= - "name": "forum-express-workspace", - "version": "1.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { ->>>>>>> origin/R04-test "@sinonjs/commons": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", -<<<<<<< HEAD "dev": true, -======= ->>>>>>> origin/R04-test "requires": { "type-detect": "4.0.8" } @@ -128,10 +117,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", -<<<<<<< HEAD "dev": true, -======= ->>>>>>> origin/R04-test "requires": { "@sinonjs/commons": "^1.7.0" } @@ -140,10 +126,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-5.0.1.tgz", "integrity": "sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==", -<<<<<<< HEAD "dev": true, -======= ->>>>>>> origin/R04-test "requires": { "@sinonjs/commons": "^1", "@sinonjs/samsam": "^5.0.2" @@ -153,10 +136,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.2.0.tgz", "integrity": "sha512-CaIcyX5cDsjcW/ab7HposFWzV1kC++4HNsfnEdFJa7cP1QIuILAKV+BgfeqRXhcnSAc76r/Rh/O5C+300BwUIw==", -<<<<<<< HEAD "dev": true, -======= ->>>>>>> origin/R04-test "requires": { "@sinonjs/commons": "^1.6.0", "lodash.get": "^4.4.2", @@ -166,7 +146,6 @@ "@sinonjs/text-encoding": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", -<<<<<<< HEAD "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, @@ -183,9 +162,6 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true -======= - "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==" ->>>>>>> origin/R04-test }, "@types/node": { "version": "14.14.5", @@ -211,7 +187,6 @@ "negotiator": "0.6.2" } }, -<<<<<<< HEAD "acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", @@ -279,8 +254,6 @@ } } }, -======= ->>>>>>> origin/R04-test "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -313,14 +286,11 @@ "picomatch": "^2.0.4" } }, -<<<<<<< HEAD "append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", "integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY=" }, -======= ->>>>>>> origin/R04-test "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -334,7 +304,6 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, -<<<<<<< HEAD "array-includes": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", @@ -370,14 +339,11 @@ "es-abstract": "^1.19.0" } }, -======= ->>>>>>> origin/R04-test "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==" }, -<<<<<<< HEAD "astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -399,19 +365,12 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/aws-sign/-/aws-sign-0.2.0.tgz", "integrity": "sha1-xVAThWyBlOyFSgy+yQqrWgTOOsU=" -======= - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" ->>>>>>> origin/R04-test }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, -<<<<<<< HEAD "bcrypt-nodejs": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/bcrypt-nodejs/-/bcrypt-nodejs-0.0.3.tgz", @@ -422,8 +381,6 @@ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" }, -======= ->>>>>>> origin/R04-test "binary-extensions": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", @@ -451,7 +408,6 @@ "type-is": "~1.6.17" } }, -<<<<<<< HEAD "boom": { "version": "0.3.8", "resolved": "https://registry.npmjs.org/boom/-/boom-0.3.8.tgz", @@ -546,8 +502,6 @@ } } }, -======= ->>>>>>> origin/R04-test "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -570,7 +524,6 @@ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" }, -<<<<<<< HEAD "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -608,14 +561,11 @@ } } }, -======= ->>>>>>> origin/R04-test "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" }, -<<<<<<< HEAD "cacheable-request": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", @@ -661,13 +611,10 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, -======= ->>>>>>> origin/R04-test "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" -<<<<<<< HEAD }, "chai": { "version": "4.2.0", @@ -1340,8 +1287,6 @@ "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-16.0.3.tgz", "integrity": "sha512-x4fmJL5hGqNJKGHSjnLdgA6U6h1YW/G2dW9fA+cyVur4SK6lyue8+UgNKWlZtUDTXvgKDD/Oa3GQjmB5kjtVvg==", "dev": true -======= ->>>>>>> origin/R04-test }, "eslint-config-standard-jsx": { "version": "10.0.0", @@ -1932,7 +1877,6 @@ "flat-cache": "^3.0.4" } }, -<<<<<<< HEAD "fill-keys": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", @@ -1943,8 +1887,6 @@ "merge-descriptors": "~1.0.0" } }, -======= ->>>>>>> origin/R04-test "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -2483,7 +2425,6 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, -<<<<<<< HEAD "is-number-object": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", @@ -2509,8 +2450,6 @@ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==" }, -======= ->>>>>>> origin/R04-test "is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -2976,15 +2915,12 @@ } } }, -<<<<<<< HEAD "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 }, -======= ->>>>>>> origin/R04-test "moment": { "version": "2.29.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", @@ -3747,7 +3683,6 @@ "ipaddr.js": "1.9.1" } }, -<<<<<<< HEAD "proxyquire": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-2.1.3.tgz", @@ -3759,8 +3694,6 @@ "resolve": "^1.11.1" } }, -======= ->>>>>>> origin/R04-test "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -4193,7 +4126,6 @@ "yargs": "^13.1.0" } }, -<<<<<<< HEAD "sequelize-mock": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/sequelize-mock/-/sequelize-mock-0.10.2.tgz", @@ -4205,8 +4137,6 @@ "lodash": "^4.16.4" } }, -======= ->>>>>>> origin/R04-test "sequelize-pool": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-6.1.0.tgz", @@ -4431,14 +4361,11 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, -<<<<<<< HEAD "streamsearch": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" }, -======= ->>>>>>> origin/R04-test "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -4449,7 +4376,6 @@ "strip-ansi": "^5.1.0" } }, -<<<<<<< HEAD "string.prototype.matchall": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.6.tgz", @@ -4486,8 +4412,6 @@ "define-properties": "^1.1.3" } }, -======= ->>>>>>> origin/R04-test "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -5152,4 +5076,4 @@ } } } -} \ No newline at end of file +} diff --git a/tests/R01.test.js b/tests/R01.test.js index a98ae62a..427a55f1 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -14,7 +14,7 @@ describe('# R01', () => { describe('登入測試: POST /signin', function () { // 以下測試會發出請求,測試資料庫內是否有作業指定的使用者資料 // 測試資料的來源是真實的資料庫 - it('#1 密碼錯誤', function (done) { + it('#1 密碼錯誤', function (done) { request(app) // 對 POST /signin 發出請求,參數是錯誤的密碼 .post('/signin') From 87cb316846acc3fb78333315f1b3713182379610 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Sat, 20 Nov 2021 20:53:52 +0000 Subject: [PATCH 183/198] merge with R04-test --- tests/R02.test.js | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/tests/R02.test.js b/tests/R02.test.js index 61981ccb..a9473a27 100644 --- a/tests/R02.test.js +++ b/tests/R02.test.js @@ -32,16 +32,11 @@ describe("# R02", () => { }); // 修改 userController 中的資料庫連線設定,由連向真實的資料庫 -> 改為連向模擬的 User table -<<<<<<< HEAD this.userController = createControllerProxy( "../controllers/userController", { User: this.UserMock } ); }); -======= - this.userController = createControllerProxy('../controllers/userController', { User: this.UserMock }) - }) ->>>>>>> origin/R04-test // 開始測試 it(" GET /users/:id ", async () => { @@ -85,16 +80,11 @@ describe("# R02", () => { }); // 連向模擬的 User table -<<<<<<< HEAD this.userController = createControllerProxy( "../controllers/userController", { User: this.UserMock } ); }); -======= - this.userController = createControllerProxy('../controllers/userController', { User: this.UserMock }) - }) ->>>>>>> origin/R04-test it(" GET /users/:id/edit ", async () => { // 模擬 request & response @@ -135,16 +125,11 @@ describe("# R02", () => { }); // 連向模擬的 User table -<<<<<<< HEAD this.userController = createControllerProxy( "../controllers/userController", { User: this.UserMock } ); }); -======= - this.userController = createControllerProxy('../controllers/userController', { User: this.UserMock }) - }) ->>>>>>> origin/R04-test it(" PUT /users/:id ", async () => { // 模擬 request & response From 4c5f6ce07c5524a54bc215cdd26eea8041c89042 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Sat, 20 Nov 2021 20:54:52 +0000 Subject: [PATCH 184/198] merge with R04-test --- tests/R01.test.js | 186 ++++++++++++++++++++++------------------------ 1 file changed, 87 insertions(+), 99 deletions(-) diff --git a/tests/R01.test.js b/tests/R01.test.js index 427a55f1..9d1d67a3 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -1,174 +1,162 @@ -const chai = require('chai') -const request = require('supertest') -const should = chai.should() +const chai = require("chai"); +const request = require("supertest"); +const should = chai.should(); -const app = require('../app') +const app = require("../app"); const { createModelMock, createControllerProxy, mockRequest, - mockResponse -} = require('../helpers/unitTestHelpers') + mockResponse, +} = require("../helpers/unitTestHelpers"); -describe('# R01', () => { - describe('登入測試: POST /signin', function () { +describe("# R01", () => { + describe("登入測試: POST /signin", function () { // 以下測試會發出請求,測試資料庫內是否有作業指定的使用者資料 // 測試資料的來源是真實的資料庫 - it('#1 密碼錯誤', function (done) { + it("#1 密碼錯誤", function (done) { request(app) // 對 POST /signin 發出請求,參數是錯誤的密碼 - .post('/signin') - .type('urlencoded') - .send('email=root@example.com&password=123') + .post("/signin") + .type("urlencoded") + .send("email=root@example.com&password=123") // 期待登入驗證回應失敗,重新導向 /signin - .expect('Location', '/signin') - .expect(302, done) - }) + .expect("Location", "/signin") + .expect(302, done); + }); - it('#2 帳號錯誤', function (done) { + it("#2 帳號錯誤", function (done) { request(app) // 對 POST /signin 發出請求,參數是錯誤的帳號 - .post('/signin') - .type('urlencoded') - .send('email=tu&password=12345678') + .post("/signin") + .type("urlencoded") + .send("email=tu&password=12345678") // 期待登入驗證回應失敗,重新導向 /signin - .expect('Location', '/signin') - .expect(302, done) - }) + .expect("Location", "/signin") + .expect(302, done); + }); - it('#3 成功登入', function (done) { + it("#3 成功登入", function (done) { request(app) // 對 POST /signin 發出請求,參數是作業指定的使用者帳號密碼 - .post('/signin') - .type('urlencoded') - .send('email=root@example.com&password=12345678') + .post("/signin") + .type("urlencoded") + .send("email=root@example.com&password=12345678") // 期待登入驗證成功,重新導向 /restaurants - .expect('Location', '/restaurants') - .expect(302, done) - }) - }) + .expect("Location", "/restaurants") + .expect(302, done); + }); + }); - describe('# R01: 使用者權限管理', function () { + describe("# R01: 使用者權限管理", function () { // 前置準備 before(() => { // 製作假資料 // 本 context 會用這筆資料進行測試 - this.UserMock = createModelMock('User', { + this.UserMock = createModelMock("User", { id: 1, - email: 'root@example.com', - name: 'admin', - isAdmin: false - }) + email: "root@example.com", + name: "admin", + isAdmin: false, + }); // 修改 adminController 中的資料庫連線設定,由連向真實的資料庫 -> 改為連向模擬的 User table -<<<<<<< HEAD this.adminController = createControllerProxy( - '../controllers/adminController', + "../controllers/adminController", { User: this.UserMock } - ) -======= - this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) ->>>>>>> origin/R04-test - }) + ); + }); // 開始測試 - context('# [顯示使用者清單]', () => { - it(' GET /admin/users ', async () => { + context("# [顯示使用者清單]", () => { + it(" GET /admin/users ", async () => { // 模擬 request & response - const req = mockRequest() // 對 GET /admin/users 發出請求 - const res = mockResponse() + const req = mockRequest(); // 對 GET /admin/users 發出請求 + const res = mockResponse(); // 測試作業指定的 adminController.getUsers 函式 - await this.adminController.getUsers(req, res) + await this.adminController.getUsers(req, res); // getUser 執行完畢後,應呼叫 res.render // res.render 的第 2 個參數應是 users // 根據測試資料,users 中的第 1 筆資料,name 屬性值應該要是 'admin' - res.render.getCall(0).args[1].users[0].name.should.equal('admin') - }) - }) + res.render.getCall(0).args[1].users[0].name.should.equal("admin"); + }); + }); - context('# [修改使用者權限] for admin', () => { + context("# [修改使用者權限] for admin", () => { before(() => { // 製作假資料 // 本 context 會用這筆資料進行測試 - this.UserMock = createModelMock('User', { + this.UserMock = createModelMock("User", { id: 1, - email: 'root@example.com', - name: 'admin', - isAdmin: true // 是管理者 - }) + email: "root@example.com", + name: "admin", + isAdmin: true, // 是管理者 + }); // 將 adminController 中的 User db 取代成 User mock db -<<<<<<< HEAD this.adminController = createControllerProxy( - '../controllers/adminController', + "../controllers/adminController", { User: this.UserMock } - ) -======= - this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) ->>>>>>> origin/R04-test - }) + ); + }); - it(' PUT /admin/users/:id/toggleAdmin ', async () => { + it(" PUT /admin/users/:id/toggleAdmin ", async () => { // 模擬 request & response - const req = mockRequest({ params: { id: 1 } }) // 帶入 params.id = 1,對 PUT /admin/users/1/toggleAdmin 發出請求 - const res = mockResponse() + const req = mockRequest({ params: { id: 1 } }); // 帶入 params.id = 1,對 PUT /admin/users/1/toggleAdmin 發出請求 + const res = mockResponse(); // 測試作業指定的 adminController.toggleAdmin 函式 - await this.adminController.toggleAdmin(req, res) + await this.adminController.toggleAdmin(req, res); // toggleAdmin 正確執行的話,應呼叫 req.flash // req.flash 的參數應該要與下列字串一致 - req.flash.calledWith('error_messages', '禁止變更管理者權限').should.be - .true + req.flash.calledWith("error_messages", "禁止變更管理者權限").should.be + .true; // toggleAdmin 執行完畢後,應呼叫 res.redirect 並重新導向上一頁 - res.redirect.calledWith('back').should.be.true - }) - }) + res.redirect.calledWith("back").should.be.true; + }); + }); - context('# [修改使用者權限] for user', () => { + context("# [修改使用者權限] for user", () => { before(() => { // 製作假資料 // 本 context 會用這筆資料進行測試 - this.UserMock = createModelMock('User', { + this.UserMock = createModelMock("User", { id: 1, - email: 'user@example.com', - name: 'user', - isAdmin: false // 非管理者 - }) + email: "user@example.com", + name: "user", + isAdmin: false, // 非管理者 + }); // 將 adminController 中的 User db 取代成 User mock db -<<<<<<< HEAD this.adminController = createControllerProxy( - '../controllers/adminController', + "../controllers/adminController", { User: this.UserMock } - ) -======= - this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) ->>>>>>> origin/R04-test - }) + ); + }); - it(' PUT /admin/users/:id/toggleAdmin ', async () => { + it(" PUT /admin/users/:id/toggleAdmin ", async () => { // 模擬 request & response - const req = mockRequest({ params: { id: 1 } }) // 帶入 params.id = 1,對 PUT /admin/users/1/toggleAdmin 發出請求 - const res = mockResponse() + const req = mockRequest({ params: { id: 1 } }); // 帶入 params.id = 1,對 PUT /admin/users/1/toggleAdmin 發出請求 + const res = mockResponse(); // 測試作業指定的 adminController.toggleAdmin 函式 - await this.adminController.toggleAdmin(req, res) + await this.adminController.toggleAdmin(req, res); // toggleAdmin 正確執行的話,應呼叫 req.flash // req.flash 的參數應與下列字串一致 - req.flash.calledWith('success_messages', '使用者權限變更成功').should.be - .true + req.flash.calledWith("success_messages", "使用者權限變更成功").should.be + .true; // toggleAdmin 執行完畢後,應呼叫 res.redirect 並重新導向 /admin/users - res.redirect.calledWith('/admin/users').should.be.true + res.redirect.calledWith("/admin/users").should.be.true; // toggleAmin 執行完畢後,假資料中 id:1 使用者的應該要是 isAdmin:true // 將假資料撈出,比對確認有成功修改到 - const user = await this.UserMock.findOne({ where: { id: 1 } }) - user.isAdmin.should.equal(true) - }) - }) - }) -}) + const user = await this.UserMock.findOne({ where: { id: 1 } }); + user.isAdmin.should.equal(true); + }); + }); + }); +}); From f7d7a9ab02c0713b38bcc032b87cbc04250c575e Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Sat, 20 Nov 2021 20:55:44 +0000 Subject: [PATCH 185/198] fix duplicate mockRequest --- helpers/unitTestHelpers.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js index fa4baf8c..263c893d 100644 --- a/helpers/unitTestHelpers.js +++ b/helpers/unitTestHelpers.js @@ -103,19 +103,6 @@ const mockResponse = () => { }; }; -const mockRequest = (query) => { - return { - ...query, - flash: sinon.spy(), - }; -}; -const mockResponse = () => { - return { - redirect: sinon.spy(), - render: sinon.spy(), - }; -}; - module.exports = { createModelMock, createControllerProxy, From 389417e24138148aaf44bba6f475358e04d45671 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Sat, 20 Nov 2021 22:51:45 +0000 Subject: [PATCH 186/198] add like/unlike feature --- config/passport.js | 5 +- controllers/restController.js | 25 ++++-- controllers/userController.js | 19 +++++ helpers/unitTestHelpers.js | 104 +++++++++++------------ migrations/20211120211141-create-like.js | 32 +++++++ models/like.js | 24 ++++++ models/restaurant.js | 5 ++ models/user.js | 5 ++ routes/index.js | 2 + views/restaurant.handlebars | 31 +++++-- views/restaurants.handlebars | 13 ++- 11 files changed, 193 insertions(+), 72 deletions(-) create mode 100644 migrations/20211120211141-create-like.js create mode 100644 models/like.js diff --git a/config/passport.js b/config/passport.js index 29f674d4..9c142af2 100644 --- a/config/passport.js +++ b/config/passport.js @@ -44,7 +44,10 @@ passport.serializeUser((user, cb) => { }) passport.deserializeUser((id, cb) => { User.findByPk(id, { - include: [{ model: Restaurant, as: 'FavoritedRestaurants' }] + include: [ + { model: Restaurant, as: 'FavoritedRestaurants' }, + { model: Restaurant, as: 'LikedRestaurants' } + ] }).then((user) => { user = user.toJSON() return cb(null, user) diff --git a/controllers/restController.js b/controllers/restController.js index 296e85d3..2096294a 100644 --- a/controllers/restController.js +++ b/controllers/restController.js @@ -40,9 +40,13 @@ const restControllers = { ...r.dataValues, description: r.dataValues.description.substring(0, 50), categoryName: r.dataValues.Category.name, - isFavorited: req.user.FavoritedRestaurants.map( - (restaurant) => restaurant.id - ).includes(r.id) // narrow down from all favorited restaurants to current restaurant + // narrow down from all favorited restaurants to current restaurant + isFavorited: req.user.FavoritedRestaurants.filter( + (restaurant) => restaurant.id === r.id + ), + isLiked: req.user.LikedRestaurants.filter( + (restaurant) => restaurant.id === r.id + ) })) Category.findAll({ raw: true, nest: true }).then((categories) => { return res.render('restaurants', { @@ -63,16 +67,21 @@ const restControllers = { include: [ Category, { model: Comment, include: [User] }, - { model: User, as: 'FavoritedUsers' } + { model: User, as: 'FavoritedUsers' }, + { model: User, as: 'LikedUsers' } ] }).then((restaurant) => { - const isFavorited = restaurant.FavoritedUsers.map( - (user) => user.id - ).includes(req.user.id) + const isFavorited = restaurant.FavoritedUsers.filter( + (user) => user.id === req.user.id + ) + const isLiked = restaurant.LikedUsers.filter( + (user) => user.id === req.user.id + ) restaurant.increment('viewCounts') return res.render('restaurant', { restaurant: restaurant.toJSON(), - isFavorited: isFavorited + isFavorited: isFavorited, + isLiked: isLiked }) }) }, diff --git a/controllers/userController.js b/controllers/userController.js index 1929ee2c..efc4d69f 100644 --- a/controllers/userController.js +++ b/controllers/userController.js @@ -4,6 +4,7 @@ const User = db.User const Restaurant = db.Restaurant const Comment = db.Comment const Favorite = db.Favorite +const Like = db.Like const helpers = require('../_helpers') // imgur setup const imgur = require('imgur-node-api') @@ -156,6 +157,24 @@ const userController = { }) } }) + }, + addLike: (req, res) => { + return Like.create({ + UserId: helpers.getUser(req).id, + RestaurantId: req.params.restaurantId + }).then((resaurant) => { + return res.redirect('back') + }) + }, + removeLike: (req, res) => { + return Like.destroy({ + where: { + UserId: helpers.getUser(req).id, + RestaurantId: req.params.restaurantId + } + }).then((d) => { + return res.redirect('back') + }) } } diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js index 263c893d..b13887cf 100644 --- a/helpers/unitTestHelpers.js +++ b/helpers/unitTestHelpers.js @@ -1,111 +1,111 @@ -const SequelizeMock = require("sequelize-mock"); -const proxyquire = require("proxyquire"); -const sinon = require("sinon"); +const SequelizeMock = require('sequelize-mock') +const proxyquire = require('proxyquire') +const sinon = require('sinon') -const dbMock = new SequelizeMock(); +const dbMock = new SequelizeMock() const createModelMock = (name, defaultValue, data, joinedTableName) => { const mockModel = dbMock.define(name, defaultValue, { instanceMethods: { update: (changes) => { - mockModel._defaults = { ...changes }; - return Promise.resolve(); - }, - }, - }); + mockModel._defaults = { ...changes } + return Promise.resolve() + } + } + }) // 模擬 Sequelize 行為 // 將 mock user db 中的 findByPK 用 findOne 取代 (sequelize mock not support findByPK) - mockModel.findByPk = (id) => mockModel.findOne({ where: { id: id } }); + mockModel.findByPk = (id) => mockModel.findOne({ where: { id: id } }) // 將 count 的 function 預設回傳假資料數目 1 - mockModel.count = () => 1; + mockModel.count = () => 1 // 因為 mock 中的 create 有問題,因此指向 upsert function, 這樣可以在 useHandler 中取得 create 呼叫 - mockModel.create = mockModel.upsert; + mockModel.create = mockModel.upsert // modify middleware if (joinedTableName) { mockModel.$queryInterface.$useHandler((query, queryOptions) => { - if (query === "upsert") { + if (query === 'upsert') { // 新增 joinTable 資料到模擬資料 - const { UserId, RestaurantId } = queryOptions[0]; - const restaurant = data.find((d) => d.id === RestaurantId); - restaurant[joinedTableName].push({ UserId: UserId }); - return Promise.resolve(data.map((d) => mockModel.build(d))); - } else if (query === "findAll") { + const { UserId, RestaurantId } = queryOptions[0] + const restaurant = data.find((d) => d.id === RestaurantId) + restaurant[joinedTableName].push({ UserId: UserId }) + return Promise.resolve(data.map((d) => mockModel.build(d))) + } else if (query === 'findAll') { // 回傳模擬資料 if (!data) { - return mockModel.build([defaultValue]); + return mockModel.build([defaultValue]) } - return Promise.resolve(data ? data.map((d) => mockModel.build(d)) : []); - } else if (query === "destroy") { + return Promise.resolve(data ? data.map((d) => mockModel.build(d)) : []) + } else if (query === 'destroy') { // destroy 可以從 where 取得要刪除的資料 // 因此就可以模擬將模擬資料中的資料刪除 // 刪除模擬資料中的某一筆 joinTable 資料 - const { UserId, RestaurantId } = queryOptions[0].where; - const restaurant = data.find((d) => d.id === RestaurantId); + const { UserId, RestaurantId } = queryOptions[0].where + const restaurant = data.find((d) => d.id === RestaurantId) restaurant[joinedTableName] = restaurant[joinedTableName].filter( (d) => !(d.UserId === UserId) - ); - return Promise.resolve(data.map((d) => mockModel.build(d))); + ) + return Promise.resolve(data.map((d) => mockModel.build(d))) } - }); + }) } else { mockModel.$queryInterface.$useHandler((query, queryOptions, done) => { - if (query === "upsert") { + if (query === 'upsert') { // create 時會帶 userId 跟 restaurantId (ex: Like.create({ userId: 1, restaurantId: 2})) - const { UserId, RestaurantId } = queryOptions[0]; + const { UserId, RestaurantId } = queryOptions[0] // 新增這個 Like 的資訊到模擬資料裡 - data.push({ UserId, RestaurantId }); + data.push({ UserId, RestaurantId }) // 回傳模擬資料 - return Promise.resolve(mockModel.build(data)); - } else if (query === "findAll") { + return Promise.resolve(mockModel.build(data)) + } else if (query === 'findAll') { // 回傳模擬資料 if (!data) { - return mockModel.build([defaultValue]); + return mockModel.build([defaultValue]) } - return Promise.resolve(data ? data.map((d) => mockModel.build(d)) : []); - } else if (query === "destroy") { + return Promise.resolve(data ? data.map((d) => mockModel.build(d)) : []) + } else if (query === 'destroy') { // destroy 可以從 where 取得要刪除的資料 // 因此就可以模擬將模擬資料中的資料刪除 - const { UserId, RestaurantId } = queryOptions[0].where; + const { UserId, RestaurantId } = queryOptions[0].where data = data.filter( (d) => !(d.UserId === UserId && d.RestaurantId === RestaurantId) - ); + ) - return Promise.resolve(mockModel.build(data)); + return Promise.resolve(mockModel.build(data)) } - }); + }) } - return mockModel; -}; + return mockModel +} const createControllerProxy = (path, model) => { const controller = proxyquire(path, { - "../models": model, - }); + '../models': model + }) - return controller; -}; + return controller +} const mockRequest = (query) => { return { ...query, - flash: sinon.spy(), - }; -}; + flash: sinon.spy() + } +} const mockResponse = () => { return { redirect: sinon.spy(), - render: sinon.spy(), - }; -}; + render: sinon.spy() + } +} module.exports = { createModelMock, createControllerProxy, mockRequest, - mockResponse, -}; + mockResponse +} diff --git a/migrations/20211120211141-create-like.js b/migrations/20211120211141-create-like.js new file mode 100644 index 00000000..eba2f8b0 --- /dev/null +++ b/migrations/20211120211141-create-like.js @@ -0,0 +1,32 @@ +'use strict' +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.createTable('Likes', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + UserId: { + type: Sequelize.INTEGER, + references: { model: 'Users', key: 'id' } + }, + RestaurantId: { + type: Sequelize.INTEGER, + references: { model: 'Restaurants', key: 'id' } + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }) + }, + down: async (queryInterface, Sequelize) => { + await queryInterface.dropTable('Likes') + } +} diff --git a/models/like.js b/models/like.js new file mode 100644 index 00000000..2166de99 --- /dev/null +++ b/models/like.js @@ -0,0 +1,24 @@ +'use strict' +const { + Model +} = require('sequelize') +module.exports = (sequelize, DataTypes) => { + class Like extends Model { + /** + * Helper method for defining associations. + * This method is not a part of Sequelize lifecycle. + * The `models/index` file will call this method automatically. + */ + static associate (models) { + // define association here + } + }; + Like.init({ + UserId: DataTypes.INTEGER, + RestaurantId: DataTypes.INTEGER + }, { + sequelize, + modelName: 'Like' + }) + return Like +} diff --git a/models/restaurant.js b/models/restaurant.js index b8519550..c195b177 100644 --- a/models/restaurant.js +++ b/models/restaurant.js @@ -15,6 +15,11 @@ module.exports = (sequelize, DataTypes) => { foreignKey: 'RestaurantId', as: 'FavoritedUsers' }) + Restaurant.belongsToMany(models.User, { + through: models.Like, + foreignKey: 'RestaurantId', + as: 'LikedUsers' + }) } } Restaurant.init( diff --git a/models/user.js b/models/user.js index 0b59cd67..02b4e7e1 100644 --- a/models/user.js +++ b/models/user.js @@ -14,6 +14,11 @@ module.exports = (sequelize, DataTypes) => { foreignKey: 'UserId', as: 'FavoritedRestaurants' }) + User.belongsToMany(models.Restaurant, { + through: models.Like, + foreignKey: 'UserId', + as: 'LikedRestaurants' + }) } } User.init( diff --git a/routes/index.js b/routes/index.js index b21fd9c7..1f1194c7 100644 --- a/routes/index.js +++ b/routes/index.js @@ -147,4 +147,6 @@ module.exports = (app) => { authenticated, userController.removeFavorite ) + app.post('/like/:restaurantId', authenticated, userController.addLike) + app.delete('/like/:restaurantId', authenticated, userController.removeLike) } diff --git a/views/restaurant.handlebars b/views/restaurant.handlebars index a71fcf17..84129e8b 100644 --- a/views/restaurant.handlebars +++ b/views/restaurant.handlebars @@ -15,16 +15,29 @@

    {{restaurant.description}}

    - - {{#if isFavorited }} -
    - {{else}} -
    - {{/if}} +
    + + {{#if isFavorited }} +
    + {{else}} +
    +
    + {{/if}} + {{#if isLiked }} +
    + {{else}} +
    +
    + {{/if}} +
    +
    -
    + +


    所有評論:

    diff --git a/views/restaurants.handlebars b/views/restaurants.handlebars index 2e36fc83..9bfe2670 100644 --- a/views/restaurants.handlebars +++ b/views/restaurants.handlebars @@ -32,13 +32,22 @@

    {{this.categoryName}}

    {{this.description}}

    +
    {{#if this.isFavorited }} -
    {{else}} -
    +
    {{/if}} + {{#if this.isLiked }} +
    + {{else}} +
    +
    + {{/if}} +
    From 58e426a8ebb7c367f98af38b4a0031eb7deec82a Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Sun, 21 Nov 2021 00:07:59 +0000 Subject: [PATCH 187/198] fix testing failure --- controllers/userController.js | 161 +++++++++++++++++----------------- tests/R04.test.js | 127 +++++++++++++++------------ views/profile.handlebars | 2 +- 3 files changed, 153 insertions(+), 137 deletions(-) diff --git a/controllers/userController.js b/controllers/userController.js index efc4d69f..dcad1d1b 100644 --- a/controllers/userController.js +++ b/controllers/userController.js @@ -1,181 +1,182 @@ -const bcrypt = require('bcrypt-nodejs') -const db = require('../models') -const User = db.User -const Restaurant = db.Restaurant -const Comment = db.Comment -const Favorite = db.Favorite -const Like = db.Like -const helpers = require('../_helpers') +const bcrypt = require("bcrypt-nodejs"); +const db = require("../models"); +const User = db.User; +const Restaurant = db.Restaurant; +const Comment = db.Comment; +const Favorite = db.Favorite; +const Like = db.Like; +const helpers = require("../_helpers"); // imgur setup -const imgur = require('imgur-node-api') -const IMGUR_CLIENT_ID = process.env.IMGUR_CLIENT_ID +const imgur = require("imgur-node-api"); +const IMGUR_CLIENT_ID = process.env.IMGUR_CLIENT_ID; const userController = { signUpPage: (req, res) => { - return res.render('signup') + return res.render("signup"); }, signUp: (req, res) => { - const { name, email, password, passwordCheck } = req.body + const { name, email, password, passwordCheck } = req.body; if (password !== passwordCheck) { - req.flash('error_messages', '兩次密碼輸入不同') - return res.redirect('/signup') + req.flash("error_messages", "兩次密碼輸入不同"); + return res.redirect("/signup"); } else { User.findOne({ where: { email } }).then((user) => { if (user) { - req.flash('error_messages', '信箱已經註冊過') - return res.redirect('/signup') + req.flash("error_messages", "信箱已經註冊過"); + return res.redirect("/signup"); } else { User.create({ name, email, - password: bcrypt.hashSync(password, bcrypt.genSaltSync(10), null) + password: bcrypt.hashSync(password, bcrypt.genSaltSync(10), null), }).then((user) => { - req.flash('success_messages', '成功註冊帳號') - return res.redirect('/signin') - }) + req.flash("success_messages", "成功註冊帳號"); + return res.redirect("/signin"); + }); } - }) + }); } }, signInPage: (req, res) => { - return res.render('signin') + return res.render("signin"); }, signIn: (req, res) => { - req.flash('success_messages', '成功登入!') - res.redirect('/restaurants') + req.flash("success_messages", "成功登入!"); + res.redirect("/restaurants"); }, logout: (req, res) => { - req.flash('success_message', '登出成功!') - req.logout() - res.redirect('/signin') + req.flash("success_message", "登出成功!"); + req.logout(); + res.redirect("/signin"); }, getUser: async (req, res) => { try { const isEditable = - helpers.getUser(req).id === Number(req.params.id) || false + helpers.getUser(req).id === Number(req.params.id) || false; const user = await User.findByPk(req.params.id, { raw: true, - nest: true - }) + nest: true, + }); const comments = await Comment.findAll({ raw: true, nest: true, where: { UserId: req.params.id }, - include: [Restaurant] - }) + include: [Restaurant], + }); - return res.render('profile', { - user: { ...user, isEditable }, - comments: comments - }) + return res.render("profile", { + user, + comments, + isEditable, + }); } catch (error) { - console.log(error) + console.log(error); } }, editUser: (req, res) => { // can only edit your own profile if (helpers.getUser(req).id !== Number(req.params.id)) { req.flash( - 'error_messages', - '無法更改其他使用者資料,只可以瀏覽其他使用者資料' - ) - return res.redirect(`/users/${req.params.id}`) + "error_messages", + "無法更改其他使用者資料,只可以瀏覽其他使用者資料" + ); + return res.redirect(`/users/${req.params.id}`); } return User.findByPk(req.params.id).then((user) => { - return res.render('edit', { user: user.toJSON() }) - }) + return res.render("edit", { user: user.toJSON() }); + }); }, putUser: (req, res) => { - const { name, email } = req.body - const { file } = req + const { name, email } = req.body; + const { file } = req; if (helpers.getUser(req).id !== Number(req.params.id)) { - req.flash('error_messages', '無法更改其他使用者資料,請重新編輯你的資料') - return res.redirect('back') + req.flash("error_messages", "無法更改其他使用者資料,請重新編輯你的資料"); + return res.redirect("back"); } if (!name || !email) { - req.flash('error_messages', '使用者資料沒有變更') - return res.redirect('back') + req.flash("error_messages", "使用者資料沒有變更"); + return res.redirect("back"); } if (file) { - imgur.setClientID(IMGUR_CLIENT_ID) + imgur.setClientID(IMGUR_CLIENT_ID); imgur.upload(file.path, (err, img) => { - if (err) console.log('Error: ', err) + if (err) console.log("Error: ", err); return User.findByPk(req.params.id).then((user) => { user .update({ name, email, - image: file ? img.data.link : user.image + image: file ? img.data.link : user.image, }) .then((user) => { - req.flash('success_messages', '使用者資料編輯成功') - return res.redirect(`/users/${req.params.id}`) - }) - }) - }) + req.flash("success_messages", "使用者資料編輯成功"); + return res.redirect(`/users/${req.params.id}`); + }); + }); + }); } else { return User.findByPk(req.params.id).then((user) => { user .update({ name, - email + email, }) .then((user) => { - req.flash('success_messages', '使用者資料編輯成功') - return res.redirect(`/users/${req.params.id}`) - }) - }) + req.flash("success_messages", "使用者資料編輯成功"); + return res.redirect(`/users/${req.params.id}`); + }); + }); } }, addFavorite: (req, res) => { return Favorite.create({ UserId: helpers.getUser(req).id, - RestaurantId: req.params.restaurantId + RestaurantId: req.params.restaurantId, }).then((resaurant) => { - return res.redirect('back') - }) + return res.redirect("back"); + }); }, removeFavorite: (req, res) => { return Favorite.findOne({ where: { UserId: helpers.getUser(req).id, - RestaurantId: req.params.restaurantId - } + RestaurantId: req.params.restaurantId, + }, }).then((favorite) => { if (favorite) { favorite.destroy().then((restaurant) => { - return res.redirect('back') - }) + return res.redirect("back"); + }); } - }) + }); }, addLike: (req, res) => { return Like.create({ UserId: helpers.getUser(req).id, - RestaurantId: req.params.restaurantId + RestaurantId: req.params.restaurantId, }).then((resaurant) => { - return res.redirect('back') - }) + return res.redirect("back"); + }); }, removeLike: (req, res) => { return Like.destroy({ where: { UserId: helpers.getUser(req).id, - RestaurantId: req.params.restaurantId - } + RestaurantId: req.params.restaurantId, + }, }).then((d) => { - return res.redirect('back') - }) - } -} + return res.redirect("back"); + }); + }, +}; -module.exports = userController +module.exports = userController; diff --git a/tests/R04.test.js b/tests/R04.test.js index d736b2f3..5b000bbe 100644 --- a/tests/R04.test.js +++ b/tests/R04.test.js @@ -1,100 +1,115 @@ -const chai = require('chai') -const request = require('supertest') -const sinon = require('sinon') -const should = chai.should() +const chai = require("chai"); +const request = require("supertest"); +const sinon = require("sinon"); +const should = chai.should(); -const db = require('../models') -const helpers = require('../_helpers') -const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers') +const db = require("../models"); +const helpers = require("../_helpers"); +const { + createModelMock, + createControllerProxy, + mockRequest, + mockResponse, +} = require("../helpers/unitTestHelpers"); // 建立模擬的 Like 資料 let mockLikeData = [ { - userId: 1, - restaurantId: 2, + UserId: 1, + RestaurantId: 2, }, -] +]; -describe('# R04: Like / Unlike', function () { - context('# Q1: 使用者可以 Like 餐廳', () => { +describe("# R04: Like / Unlike", function () { + context("# Q1: 使用者可以 Like 餐廳", () => { before(() => { // 模擬登入驗證 this.ensureAuthenticated = sinon - .stub(helpers, 'ensureAuthenticated') - .returns(true) - this.getUser = sinon.stub(helpers, 'getUser').returns({ id: 1 }) + .stub(helpers, "ensureAuthenticated") + .returns(true); + this.getUser = sinon.stub(helpers, "getUser").returns({ id: 1 }); // 建立了一個模擬的 Like table,裡面目前是空的 - this.mockLikeData = [] - this.likeMock = createModelMock('Like', null, this.mockLikeData) - + this.mockLikeData = []; + this.likeMock = createModelMock("Like", null, this.mockLikeData); + // 連向模擬的 Like table - this.userController = createControllerProxy('../controllers/userController', {Like: this.likeMock}) - }) + this.userController = createControllerProxy( + "../controllers/userController", + { Like: this.likeMock } + ); + }); - it(' POST /like/:restaurantId ', async () => { + it(" POST /like/:restaurantId ", async () => { // 模擬 request & response - const req = mockRequest({ params: { restaurantId: 2 } }) // 帶入 params.restaurantId = 2,對 POST /like/2 發出請求 - const res = mockResponse() + const req = mockRequest({ params: { restaurantId: 2 } }); // 帶入 params.restaurantId = 2,對 POST /like/2 發出請求 + const res = mockResponse(); // 測試 userController.addLike 函式 - await this.userController.addLike(req, res) + await this.userController.addLike(req, res); // 將模擬的 Like table 內的資料全數撈出 - const likes = await this.likeMock.findAll() + const likes = await this.likeMock.findAll(); // addLike 執行完畢後,Like table 應會從空的 -> 變成有 1 筆資料 - likes.should.have.lengthOf(1) + likes.should.have.lengthOf(1); // 資料裡的 UserId 應該會跟我們傳入的 user id 一樣 - likes[0].UserId.should.equal(1) + likes[0].UserId.should.equal(1); // 資料裡的 RestaurantId 會跟我們傳入的 params.restaurantId 一樣 - likes[0].RestaurantId.should.equal(2) - }) + likes[0].RestaurantId.should.equal(2); + }); after(async () => { // 清除驗證資料 - this.ensureAuthenticated.restore() - this.getUser.restore() - }) - }) + this.ensureAuthenticated.restore(); + this.getUser.restore(); + }); + }); - context('# Q1: 使用者可以 unLike 餐廳', () => { + context("# Q1: 使用者可以 unLike 餐廳", () => { before(() => { // 模擬登入驗證 this.ensureAuthenticated = sinon - .stub(helpers, 'ensureAuthenticated') - .returns(true) - this.getUser = sinon.stub(helpers, 'getUser').returns({ id: 1 }) + .stub(helpers, "ensureAuthenticated") + .returns(true); + this.getUser = sinon.stub(helpers, "getUser").returns({ id: 1 }); // 製作假資料 // 下個 context 會用這筆資料進行測試 // 模擬 Like table 裡目前有 1 筆資料如下 - this.likeMock = createModelMock('Like', { - id: 1, - UserId: 1, - RestaurantId: 2, - }, mockLikeData); + this.likeMock = createModelMock( + "Like", + { + id: 1, + UserId: 1, + RestaurantId: 2, + }, + mockLikeData + ); // 連向模擬的 Like table - this.userController = createControllerProxy('../controllers/userController', { Like: this.likeMock }) - }) + this.userController = createControllerProxy( + "../controllers/userController", + { Like: this.likeMock } + ); + }); - it(' DELETE /like/:restaurantId ', async () => { + it(" DELETE /like/:restaurantId ", async () => { // 模擬 request & response // 模擬發出 request, 帶入 params.id = 1, restaurantId = 2 - const req = mockRequest({ params: { id: 1, restaurantId: 2 } }) // 帶入 params.id = 1,對 DELETE /like/2 發出請求 - const res = mockResponse() + const req = mockRequest({ params: { id: 1, restaurantId: 2 } }); // 帶入 params.id = 1,對 DELETE /like/2 發出請求 + const res = mockResponse(); // 測試作業指定的 userController.removeLike 函式 - await this.userController.removeLike(req, res) + await this.userController.removeLike(req, res); // 將模擬的 Like table 內的資料全數撈出 - const likes = await this.likeMock.findAll() + const likes = await this.likeMock.findAll(); // addLike 執行完畢後,Like table 應會從有 1 筆資料 -> 變成空的 - likes.should.have.lengthOf(0) - }) + likes.should.have.lengthOf(0); + }); after(async () => { // 清除模擬驗證資料 - this.ensureAuthenticated.restore() - this.getUser.restore() - }) - }) -}) + this.ensureAuthenticated.restore(); + this.getUser.restore(); + }); + }); +}); diff --git a/views/profile.handlebars b/views/profile.handlebars index 2fe498e3..5a666737 100644 --- a/views/profile.handlebars +++ b/views/profile.handlebars @@ -17,7 +17,7 @@
    {{user.name}}

    {{user.email}}

    {{#count comments}}{{/count}} 已評論餐廳

    - {{#if user.isEditable}} + {{#if isEditable}} edit {{/if}} From 73350d30a7d27c0d0aeba4a1392f6538b33c8b94 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Sun, 21 Nov 2021 00:15:22 +0000 Subject: [PATCH 188/198] fix css styling --- views/restaurant.handlebars | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/views/restaurant.handlebars b/views/restaurant.handlebars index 84129e8b..cea3e50e 100644 --- a/views/restaurant.handlebars +++ b/views/restaurant.handlebars @@ -40,6 +40,8 @@


    +
    +

    所有評論:

    {{#each restaurant.Comments}} {{#if ../user.isAdmin}} @@ -62,4 +64,6 @@ -回上一頁 \ No newline at end of file +回上一頁 +
    +
    > \ No newline at end of file From 915133d3463adff9ba28373ee6a5e816acfcfb6c Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Sun, 21 Nov 2021 00:15:36 +0000 Subject: [PATCH 189/198] fix css styling --- views/restaurant.handlebars | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/restaurant.handlebars b/views/restaurant.handlebars index cea3e50e..6b4491c1 100644 --- a/views/restaurant.handlebars +++ b/views/restaurant.handlebars @@ -66,4 +66,4 @@ 回上一頁
    -
    > \ No newline at end of file +
    \ No newline at end of file From 126baf82acf6695c0ce4cec2be1c4837b8448cab Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Sun, 21 Nov 2021 00:16:35 +0000 Subject: [PATCH 190/198] fix css styling --- views/restaurant.handlebars | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/views/restaurant.handlebars b/views/restaurant.handlebars index 84129e8b..6b4491c1 100644 --- a/views/restaurant.handlebars +++ b/views/restaurant.handlebars @@ -40,6 +40,8 @@


    +
    +

    所有評論:

    {{#each restaurant.Comments}} {{#if ../user.isAdmin}} @@ -62,4 +64,6 @@ -回上一頁 \ No newline at end of file +回上一頁 +
    +
    \ No newline at end of file From 37239f535999ac84a1197a5a0218277649406ed7 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Sun, 21 Nov 2021 01:21:38 +0000 Subject: [PATCH 191/198] add topUser page --- config/passport.js | 58 +++--- controllers/userController.js | 169 ++++++++++-------- .../20211121005048-create-followship.js | 8 +- models/followship.js | 14 +- models/user.js | 42 ++--- routes/index.js | 1 + views/feeds.handlebars | 3 + views/restaurants.handlebars | 33 ++-- views/topUser.handlebars | 43 +++++ 9 files changed, 218 insertions(+), 153 deletions(-) create mode 100644 views/topUser.handlebars diff --git a/config/passport.js b/config/passport.js index af8ccf96..c0da959b 100644 --- a/config/passport.js +++ b/config/passport.js @@ -1,18 +1,18 @@ -const passport = require("passport"); -const LocalStrategy = require("passport-local"); -const bcrypt = require("bcryptjs"); -const db = require("../models"); -const User = db.User; -const Restaurant = db.Restaurant; +const passport = require('passport') +const LocalStrategy = require('passport-local') +const bcrypt = require('bcryptjs') +const db = require('../models') +const User = db.User +const Restaurant = db.Restaurant // setup passport strategy passport.use( new LocalStrategy( // customize user field { - usernameField: "email", - passwordField: "password", - passReqToCallback: true, + usernameField: 'email', + passwordField: 'password', + passReqToCallback: true }, // authenticate user (req, username, password, cb) => { @@ -21,39 +21,39 @@ passport.use( return cb( null, false, - req.flash("error_messages", "帳號或密碼輸入錯誤") - ); + req.flash('error_messages', '帳號或密碼輸入錯誤') + ) } if (!bcrypt.compareSync(password, user.password)) { return cb( null, false, - req.flash("error_messages", "帳號或密碼輸入錯誤") - ); + req.flash('error_messages', '帳號或密碼輸入錯誤') + ) } - user = user.toJSON(); - return cb(null, user); - }); + user = user.toJSON() + return cb(null, user) + }) } ) -); +) // serialize and deserialize user. 只存id不存物件以便省空間 passport.serializeUser((user, cb) => { - cb(null, user.id); -}); + cb(null, user.id) +}) passport.deserializeUser((id, cb) => { User.findByPk(id, { include: [ - { model: Restaurant, as: "FavoritedRestaurants" }, - { model: Restaurant, as: "LikedRestaurants" }, - { model: User, as: "Followers" }, - { model: User, as: "Followings" }, - ], + { model: Restaurant, as: 'FavoritedRestaurants' }, + { model: Restaurant, as: 'LikedRestaurants' }, + { model: User, as: 'Followers' }, + { model: User, as: 'Followings' } + ] }).then((user) => { - user = user.toJSON(); - return cb(null, user); - }); -}); + user = user.toJSON() + return cb(null, user) + }) +}) -module.exports = passport; +module.exports = passport diff --git a/controllers/userController.js b/controllers/userController.js index dcad1d1b..f1498c84 100644 --- a/controllers/userController.js +++ b/controllers/userController.js @@ -1,182 +1,195 @@ -const bcrypt = require("bcrypt-nodejs"); -const db = require("../models"); -const User = db.User; -const Restaurant = db.Restaurant; -const Comment = db.Comment; -const Favorite = db.Favorite; -const Like = db.Like; -const helpers = require("../_helpers"); +const bcrypt = require('bcrypt-nodejs') +const db = require('../models') +const User = db.User +const Restaurant = db.Restaurant +const Comment = db.Comment +const Favorite = db.Favorite +const Like = db.Like +const helpers = require('../_helpers') // imgur setup -const imgur = require("imgur-node-api"); -const IMGUR_CLIENT_ID = process.env.IMGUR_CLIENT_ID; +const imgur = require('imgur-node-api') +const IMGUR_CLIENT_ID = process.env.IMGUR_CLIENT_ID const userController = { signUpPage: (req, res) => { - return res.render("signup"); + return res.render('signup') }, signUp: (req, res) => { - const { name, email, password, passwordCheck } = req.body; + const { name, email, password, passwordCheck } = req.body if (password !== passwordCheck) { - req.flash("error_messages", "兩次密碼輸入不同"); - return res.redirect("/signup"); + req.flash('error_messages', '兩次密碼輸入不同') + return res.redirect('/signup') } else { User.findOne({ where: { email } }).then((user) => { if (user) { - req.flash("error_messages", "信箱已經註冊過"); - return res.redirect("/signup"); + req.flash('error_messages', '信箱已經註冊過') + return res.redirect('/signup') } else { User.create({ name, email, - password: bcrypt.hashSync(password, bcrypt.genSaltSync(10), null), + password: bcrypt.hashSync(password, bcrypt.genSaltSync(10), null) }).then((user) => { - req.flash("success_messages", "成功註冊帳號"); - return res.redirect("/signin"); - }); + req.flash('success_messages', '成功註冊帳號') + return res.redirect('/signin') + }) } - }); + }) } }, signInPage: (req, res) => { - return res.render("signin"); + return res.render('signin') }, signIn: (req, res) => { - req.flash("success_messages", "成功登入!"); - res.redirect("/restaurants"); + req.flash('success_messages', '成功登入!') + res.redirect('/restaurants') }, logout: (req, res) => { - req.flash("success_message", "登出成功!"); - req.logout(); - res.redirect("/signin"); + req.flash('success_message', '登出成功!') + req.logout() + res.redirect('/signin') }, getUser: async (req, res) => { try { const isEditable = - helpers.getUser(req).id === Number(req.params.id) || false; + helpers.getUser(req).id === Number(req.params.id) || false const user = await User.findByPk(req.params.id, { raw: true, - nest: true, - }); + nest: true + }) const comments = await Comment.findAll({ raw: true, nest: true, where: { UserId: req.params.id }, - include: [Restaurant], - }); + include: [Restaurant] + }) - return res.render("profile", { + return res.render('profile', { user, comments, - isEditable, - }); + isEditable + }) } catch (error) { - console.log(error); + console.log(error) } }, editUser: (req, res) => { // can only edit your own profile if (helpers.getUser(req).id !== Number(req.params.id)) { req.flash( - "error_messages", - "無法更改其他使用者資料,只可以瀏覽其他使用者資料" - ); - return res.redirect(`/users/${req.params.id}`); + 'error_messages', + '無法更改其他使用者資料,只可以瀏覽其他使用者資料' + ) + return res.redirect(`/users/${req.params.id}`) } return User.findByPk(req.params.id).then((user) => { - return res.render("edit", { user: user.toJSON() }); - }); + return res.render('edit', { user: user.toJSON() }) + }) }, putUser: (req, res) => { - const { name, email } = req.body; - const { file } = req; + const { name, email } = req.body + const { file } = req if (helpers.getUser(req).id !== Number(req.params.id)) { - req.flash("error_messages", "無法更改其他使用者資料,請重新編輯你的資料"); - return res.redirect("back"); + req.flash('error_messages', '無法更改其他使用者資料,請重新編輯你的資料') + return res.redirect('back') } if (!name || !email) { - req.flash("error_messages", "使用者資料沒有變更"); - return res.redirect("back"); + req.flash('error_messages', '使用者資料沒有變更') + return res.redirect('back') } if (file) { - imgur.setClientID(IMGUR_CLIENT_ID); + imgur.setClientID(IMGUR_CLIENT_ID) imgur.upload(file.path, (err, img) => { - if (err) console.log("Error: ", err); + if (err) console.log('Error: ', err) return User.findByPk(req.params.id).then((user) => { user .update({ name, email, - image: file ? img.data.link : user.image, + image: file ? img.data.link : user.image }) .then((user) => { - req.flash("success_messages", "使用者資料編輯成功"); - return res.redirect(`/users/${req.params.id}`); - }); - }); - }); + req.flash('success_messages', '使用者資料編輯成功') + return res.redirect(`/users/${req.params.id}`) + }) + }) + }) } else { return User.findByPk(req.params.id).then((user) => { user .update({ name, - email, + email }) .then((user) => { - req.flash("success_messages", "使用者資料編輯成功"); - return res.redirect(`/users/${req.params.id}`); - }); - }); + req.flash('success_messages', '使用者資料編輯成功') + return res.redirect(`/users/${req.params.id}`) + }) + }) } }, addFavorite: (req, res) => { return Favorite.create({ UserId: helpers.getUser(req).id, - RestaurantId: req.params.restaurantId, + RestaurantId: req.params.restaurantId }).then((resaurant) => { - return res.redirect("back"); - }); + return res.redirect('back') + }) }, removeFavorite: (req, res) => { return Favorite.findOne({ where: { UserId: helpers.getUser(req).id, - RestaurantId: req.params.restaurantId, - }, + RestaurantId: req.params.restaurantId + } }).then((favorite) => { if (favorite) { favorite.destroy().then((restaurant) => { - return res.redirect("back"); - }); + return res.redirect('back') + }) } - }); + }) }, addLike: (req, res) => { return Like.create({ UserId: helpers.getUser(req).id, - RestaurantId: req.params.restaurantId, + RestaurantId: req.params.restaurantId }).then((resaurant) => { - return res.redirect("back"); - }); + return res.redirect('back') + }) }, removeLike: (req, res) => { return Like.destroy({ where: { UserId: helpers.getUser(req).id, - RestaurantId: req.params.restaurantId, - }, + RestaurantId: req.params.restaurantId + } }).then((d) => { - return res.redirect("back"); - }); + return res.redirect('back') + }) }, -}; + getTopUser: (req, res) => { + return User.findAll({ + include: [{ model: User, as: 'Followers' }] + }).then((users) => { + users = users.map((user) => ({ + ...user.dataValues, + FollowerCount: user.Followers.length, + isFollowed: req.user.Followings.map((d) => d.id).includes(user.id) + })) + users = users.sort((a, z) => z.FollowerCount - a.FollowerCount) + return res.render('topUser', { users }) + }) + } +} -module.exports = userController; +module.exports = userController diff --git a/migrations/20211121005048-create-followship.js b/migrations/20211121005048-create-followship.js index a2905e94..a10c1d81 100644 --- a/migrations/20211121005048-create-followship.js +++ b/migrations/20211121005048-create-followship.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' module.exports = { up: async (queryInterface, Sequelize) => { await queryInterface.createTable('Followships', { @@ -22,9 +22,9 @@ module.exports = { allowNull: false, type: Sequelize.DATE } - }); + }) }, down: async (queryInterface, Sequelize) => { - await queryInterface.dropTable('Followships'); + await queryInterface.dropTable('Followships') } -}; \ No newline at end of file +} diff --git a/models/followship.js b/models/followship.js index f1953616..a97b6d66 100644 --- a/models/followship.js +++ b/models/followship.js @@ -1,7 +1,7 @@ -'use strict'; +'use strict' const { Model -} = require('sequelize'); +} = require('sequelize') module.exports = (sequelize, DataTypes) => { class Followship extends Model { /** @@ -9,7 +9,7 @@ module.exports = (sequelize, DataTypes) => { * This method is not a part of Sequelize lifecycle. * The `models/index` file will call this method automatically. */ - static associate(models) { + static associate (models) { // define association here } }; @@ -18,7 +18,7 @@ module.exports = (sequelize, DataTypes) => { followingId: DataTypes.INTEGER }, { sequelize, - modelName: 'Followship', - }); - return Followship; -}; \ No newline at end of file + modelName: 'Followship' + }) + return Followship +} diff --git a/models/user.js b/models/user.js index ff2759e4..6f1b5053 100644 --- a/models/user.js +++ b/models/user.js @@ -1,5 +1,5 @@ -"use strict"; -const { Model } = require("sequelize"); +'use strict' +const { Model } = require('sequelize') module.exports = (sequelize, DataTypes) => { class User extends Model { /** @@ -7,28 +7,28 @@ module.exports = (sequelize, DataTypes) => { * This method is not a part of Sequelize lifecycle. * The `models/index` file will call this method automatically. */ - static associate(models) { - User.hasMany(models.Comment); + static associate (models) { + User.hasMany(models.Comment) User.belongsToMany(models.Restaurant, { through: models.Favorite, - foreignKey: "UserId", - as: "FavoritedRestaurants", - }); + foreignKey: 'UserId', + as: 'FavoritedRestaurants' + }) User.belongsToMany(models.Restaurant, { through: models.Like, - foreignKey: "UserId", - as: "LikedRestaurants", - }); + foreignKey: 'UserId', + as: 'LikedRestaurants' + }) User.belongsToMany(models.User, { through: models.Followship, - foreignKey: "followingId", - as: "Followers", - }); + foreignKey: 'followingId', + as: 'Followers' + }) User.belongsToMany(models.User, { through: models.Followship, - foreignKey: "followingId", - as: "Followings", - }); + foreignKey: 'followingId', + as: 'Followings' + }) } } User.init( @@ -37,12 +37,12 @@ module.exports = (sequelize, DataTypes) => { email: DataTypes.STRING, password: DataTypes.STRING, isAdmin: DataTypes.BOOLEAN, - image: DataTypes.STRING, + image: DataTypes.STRING }, { sequelize, - modelName: "User", + modelName: 'User' } - ); - return User; -}; + ) + return User +} diff --git a/routes/index.js b/routes/index.js index 1f1194c7..e325ef31 100644 --- a/routes/index.js +++ b/routes/index.js @@ -124,6 +124,7 @@ module.exports = (app) => { app.get('/restaurants/:id', authenticated, restController.getRestaurant) app.post('/comments', authenticated, commentController.postComment) app.delete('/comments/:id', authenticated, commentController.deleteComment) + app.get('/users/top', authenticated, userController.getTopUser) app.get('/users/:id', authenticated, userController.getUser) app.get('/users/:id/edit', authenticated, userController.editUser) app.put( diff --git a/views/feeds.handlebars b/views/feeds.handlebars index 2e7d2728..8a138ce8 100644 --- a/views/feeds.handlebars +++ b/views/feeds.handlebars @@ -5,6 +5,9 @@ +

    diff --git a/views/restaurants.handlebars b/views/restaurants.handlebars index 9bfe2670..7eacae4d 100644 --- a/views/restaurants.handlebars +++ b/views/restaurants.handlebars @@ -5,6 +5,9 @@ +

    diff --git a/views/topUser.handlebars b/views/topUser.handlebars new file mode 100644 index 00000000..5c4971c0 --- /dev/null +++ b/views/topUser.handlebars @@ -0,0 +1,43 @@ + +

    +

    美食達人

    +
    +
    + {{log users}} + {{#each users}} +
    + + + +

    {{this.name}}

    + 追蹤人數:{{this.FollowerCount}} +

    + {{#if this.isFollowed}} +

    + {{else}} +
    + {{/if}} +

    +
    + {{/each}} +
    \ No newline at end of file From a861070229689d1359ee6c9869a7a271e524fe51 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Sun, 21 Nov 2021 08:28:29 +0000 Subject: [PATCH 192/198] addfollowing and deletefollowing --- app.js | 2 -- controllers/userController.js | 46 +++++++++++++++++++++++++++-------- models/user.js | 2 +- routes/index.js | 6 +++++ views/topUser.handlebars | 14 +++++++++-- 5 files changed, 55 insertions(+), 15 deletions(-) diff --git a/app.js b/app.js index 860ee3b5..d951c209 100644 --- a/app.js +++ b/app.js @@ -37,8 +37,6 @@ app.use((req, res, next) => { }) app.listen(port, () => { - // what is this for? - db.sequelize.sync() console.log(`Example app listening at http://localhost:${port}`) }) diff --git a/controllers/userController.js b/controllers/userController.js index f1498c84..61e186fb 100644 --- a/controllers/userController.js +++ b/controllers/userController.js @@ -5,13 +5,14 @@ const Restaurant = db.Restaurant const Comment = db.Comment const Favorite = db.Favorite const Like = db.Like +const Followship = db.Followship const helpers = require('../_helpers') // imgur setup const imgur = require('imgur-node-api') const IMGUR_CLIENT_ID = process.env.IMGUR_CLIENT_ID const userController = { - signUpPage: (req, res) => { + signUpPage: (_req, res) => { return res.render('signup') }, @@ -30,7 +31,7 @@ const userController = { name, email, password: bcrypt.hashSync(password, bcrypt.genSaltSync(10), null) - }).then((user) => { + }).then((_user) => { req.flash('success_messages', '成功註冊帳號') return res.redirect('/signin') }) @@ -39,7 +40,7 @@ const userController = { } }, - signInPage: (req, res) => { + signInPage: (_req, res) => { return res.render('signin') }, @@ -117,7 +118,7 @@ const userController = { email, image: file ? img.data.link : user.image }) - .then((user) => { + .then((_user) => { req.flash('success_messages', '使用者資料編輯成功') return res.redirect(`/users/${req.params.id}`) }) @@ -130,7 +131,7 @@ const userController = { name, email }) - .then((user) => { + .then((_user) => { req.flash('success_messages', '使用者資料編輯成功') return res.redirect(`/users/${req.params.id}`) }) @@ -141,7 +142,7 @@ const userController = { return Favorite.create({ UserId: helpers.getUser(req).id, RestaurantId: req.params.restaurantId - }).then((resaurant) => { + }).then((_restaurant) => { return res.redirect('back') }) }, @@ -153,7 +154,7 @@ const userController = { } }).then((favorite) => { if (favorite) { - favorite.destroy().then((restaurant) => { + favorite.destroy().then((_restaurant) => { return res.redirect('back') }) } @@ -163,7 +164,7 @@ const userController = { return Like.create({ UserId: helpers.getUser(req).id, RestaurantId: req.params.restaurantId - }).then((resaurant) => { + }).then((_restaurant) => { return res.redirect('back') }) }, @@ -173,7 +174,7 @@ const userController = { UserId: helpers.getUser(req).id, RestaurantId: req.params.restaurantId } - }).then((d) => { + }).then((_d) => { return res.redirect('back') }) }, @@ -184,11 +185,36 @@ const userController = { users = users.map((user) => ({ ...user.dataValues, FollowerCount: user.Followers.length, - isFollowed: req.user.Followings.map((d) => d.id).includes(user.id) + isFollowed: helpers + .getUser(req) + .Followings.filter((following) => following.id === user.id).length })) users = users.sort((a, z) => z.FollowerCount - a.FollowerCount) return res.render('topUser', { users }) }) + }, + + addFollowing: (req, res) => { + console.log(req.user.Id, req.params.userId) + return Followship.create({ + followerId: req.user.id, + followingId: req.params.userId + }).then((followship) => { + return res.redirect('back') + }) + }, + + removeFollowing: (req, res) => { + return Followship.findOne({ + where: { + followerId: req.user.id, + followingId: req.params.userId + } + }).then((followship) => { + followship.destroy().then((followship) => { + return res.redirect('back') + }) + }) } } diff --git a/models/user.js b/models/user.js index 6f1b5053..6dc9b49b 100644 --- a/models/user.js +++ b/models/user.js @@ -26,7 +26,7 @@ module.exports = (sequelize, DataTypes) => { }) User.belongsToMany(models.User, { through: models.Followship, - foreignKey: 'followingId', + foreignKey: 'followerId', as: 'Followings' }) } diff --git a/routes/index.js b/routes/index.js index e325ef31..1afd77e1 100644 --- a/routes/index.js +++ b/routes/index.js @@ -150,4 +150,10 @@ module.exports = (app) => { ) app.post('/like/:restaurantId', authenticated, userController.addLike) app.delete('/like/:restaurantId', authenticated, userController.removeLike) + app.post('/following/:userId', authenticated, userController.addFollowing) + app.delete( + '/following/:userId', + authenticated, + userController.removeFollowing + ) } diff --git a/views/topUser.handlebars b/views/topUser.handlebars index 5c4971c0..eee35e14 100644 --- a/views/topUser.handlebars +++ b/views/topUser.handlebars @@ -13,11 +13,21 @@

    美食達人


    - {{log users}} {{#each users}} + {{log this.isFollowed}}
    - + {{#if this.image}} + + {{else}} +
    + +
    + {{/if}}

    {{this.name}}

    Date: Sun, 21 Nov 2021 08:31:26 +0000 Subject: [PATCH 193/198] pull R05-test --- config/config.json | 5 --- helpers/unitTestHelpers.js | 17 --------- package-lock.json | 78 +------------------------------------- package.json | 14 +------ tests/R01.test.js | 39 ------------------- tests/R02.test.js | 30 --------------- tests/R03.test.js | 8 ---- tests/R04.test.js | 6 --- 8 files changed, 2 insertions(+), 195 deletions(-) diff --git a/config/config.json b/config/config.json index 3f256b8a..c32bfca9 100644 --- a/config/config.json +++ b/config/config.json @@ -7,12 +7,7 @@ "dialect": "mysql" }, "production": { -<<<<<<< HEAD "use_env_variable": "CLEARDB_DATABASE_URL", -======= - "username": "root", - "password": null, ->>>>>>> origin/R05-test "database": "database_production", "host": "127.0.0.1", "dialect": "mysql" diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js index 75057395..d8f94887 100644 --- a/helpers/unitTestHelpers.js +++ b/helpers/unitTestHelpers.js @@ -34,17 +34,10 @@ const createModelMock = (name, defaultValue, data, joinedTableName) => { } else if (query === 'findAll') { // 回傳模擬資料 if (!data) { -<<<<<<< HEAD return mockModel.build([defaultValue]) } return Promise.resolve(data ? data.map((d) => mockModel.build(d)) : []) } else if (query === 'destroy') { -======= - return mockModel.build([defaultValue]); - } - return Promise.resolve( data ? data.map(d => mockModel.build(d)) : []) - }else if (query === 'destroy') { ->>>>>>> origin/R05-test // destroy 可以從 where 取得要刪除的資料 // 因此就可以模擬將模擬資料中的資料刪除 // 刪除模擬資料中的某一筆 joinTable 資料 @@ -70,15 +63,9 @@ const createModelMock = (name, defaultValue, data, joinedTableName) => { } else if (query === 'findAll') { // 回傳模擬資料 if (!data) { -<<<<<<< HEAD return mockModel.build([defaultValue]) } return Promise.resolve(data ? data.map((d) => mockModel.build(d)) : []) -======= - return mockModel.build([defaultValue]); - } - return Promise.resolve(data ? data.map(d => mockModel.build(d)) : []) ->>>>>>> origin/R05-test } else if (query === 'destroy') { // destroy 可以從 where 取得要刪除的資料 // 因此就可以模擬將模擬資料中的資料刪除 @@ -134,8 +121,4 @@ module.exports = { createControllerProxy, mockRequest, mockResponse -<<<<<<< HEAD -} -======= } ->>>>>>> origin/R05-test diff --git a/package-lock.json b/package-lock.json index 14ab1366..e570880f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,4 @@ { -<<<<<<< HEAD "name": "forum-express-grading", "version": "2.0.0", "lockfileVersion": 1, @@ -105,21 +104,11 @@ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" }, -======= - "name": "forum-express-workspace", - "version": "1.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { ->>>>>>> origin/R05-test "@sinonjs/commons": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", -<<<<<<< HEAD "dev": true, -======= ->>>>>>> origin/R05-test "requires": { "type-detect": "4.0.8" } @@ -128,10 +117,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", -<<<<<<< HEAD "dev": true, -======= ->>>>>>> origin/R05-test "requires": { "@sinonjs/commons": "^1.7.0" } @@ -140,10 +126,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-5.0.1.tgz", "integrity": "sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==", -<<<<<<< HEAD "dev": true, -======= ->>>>>>> origin/R05-test "requires": { "@sinonjs/commons": "^1", "@sinonjs/samsam": "^5.0.2" @@ -153,10 +136,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.2.0.tgz", "integrity": "sha512-CaIcyX5cDsjcW/ab7HposFWzV1kC++4HNsfnEdFJa7cP1QIuILAKV+BgfeqRXhcnSAc76r/Rh/O5C+300BwUIw==", -<<<<<<< HEAD "dev": true, -======= ->>>>>>> origin/R05-test "requires": { "@sinonjs/commons": "^1.6.0", "lodash.get": "^4.4.2", @@ -166,7 +146,6 @@ "@sinonjs/text-encoding": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", -<<<<<<< HEAD "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, @@ -183,9 +162,6 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true -======= - "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==" ->>>>>>> origin/R05-test }, "@types/node": { "version": "14.14.5", @@ -211,7 +187,6 @@ "negotiator": "0.6.2" } }, -<<<<<<< HEAD "acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", @@ -279,8 +254,6 @@ } } }, -======= ->>>>>>> origin/R05-test "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -313,14 +286,11 @@ "picomatch": "^2.0.4" } }, -<<<<<<< HEAD "append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", "integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY=" }, -======= ->>>>>>> origin/R05-test "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -334,7 +304,6 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, -<<<<<<< HEAD "array-includes": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", @@ -370,14 +339,11 @@ "es-abstract": "^1.19.0" } }, -======= ->>>>>>> origin/R05-test "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==" }, -<<<<<<< HEAD "astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -399,19 +365,12 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/aws-sign/-/aws-sign-0.2.0.tgz", "integrity": "sha1-xVAThWyBlOyFSgy+yQqrWgTOOsU=" -======= - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" ->>>>>>> origin/R05-test }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, -<<<<<<< HEAD "bcrypt-nodejs": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/bcrypt-nodejs/-/bcrypt-nodejs-0.0.3.tgz", @@ -422,8 +381,6 @@ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" }, -======= ->>>>>>> origin/R05-test "binary-extensions": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", @@ -451,7 +408,6 @@ "type-is": "~1.6.17" } }, -<<<<<<< HEAD "boom": { "version": "0.3.8", "resolved": "https://registry.npmjs.org/boom/-/boom-0.3.8.tgz", @@ -546,8 +502,6 @@ } } }, -======= ->>>>>>> origin/R05-test "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -570,7 +524,6 @@ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" }, -<<<<<<< HEAD "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -608,14 +561,11 @@ } } }, -======= ->>>>>>> origin/R05-test "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" }, -<<<<<<< HEAD "cacheable-request": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", @@ -661,13 +611,10 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, -======= ->>>>>>> origin/R05-test "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" -<<<<<<< HEAD }, "chai": { "version": "4.2.0", @@ -1340,8 +1287,6 @@ "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-16.0.3.tgz", "integrity": "sha512-x4fmJL5hGqNJKGHSjnLdgA6U6h1YW/G2dW9fA+cyVur4SK6lyue8+UgNKWlZtUDTXvgKDD/Oa3GQjmB5kjtVvg==", "dev": true -======= ->>>>>>> origin/R05-test }, "eslint-config-standard-jsx": { "version": "10.0.0", @@ -1932,7 +1877,6 @@ "flat-cache": "^3.0.4" } }, -<<<<<<< HEAD "fill-keys": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", @@ -1943,8 +1887,6 @@ "merge-descriptors": "~1.0.0" } }, -======= ->>>>>>> origin/R05-test "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -2483,7 +2425,6 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, -<<<<<<< HEAD "is-number-object": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", @@ -2509,8 +2450,6 @@ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==" }, -======= ->>>>>>> origin/R05-test "is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -2976,15 +2915,12 @@ } } }, -<<<<<<< HEAD "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 }, -======= ->>>>>>> origin/R05-test "moment": { "version": "2.29.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", @@ -3747,7 +3683,6 @@ "ipaddr.js": "1.9.1" } }, -<<<<<<< HEAD "proxyquire": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-2.1.3.tgz", @@ -3759,8 +3694,6 @@ "resolve": "^1.11.1" } }, -======= ->>>>>>> origin/R05-test "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -4193,7 +4126,6 @@ "yargs": "^13.1.0" } }, -<<<<<<< HEAD "sequelize-mock": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/sequelize-mock/-/sequelize-mock-0.10.2.tgz", @@ -4205,8 +4137,6 @@ "lodash": "^4.16.4" } }, -======= ->>>>>>> origin/R05-test "sequelize-pool": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-6.1.0.tgz", @@ -4431,14 +4361,11 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, -<<<<<<< HEAD "streamsearch": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" }, -======= ->>>>>>> origin/R05-test "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -4449,7 +4376,6 @@ "strip-ansi": "^5.1.0" } }, -<<<<<<< HEAD "string.prototype.matchall": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.6.tgz", @@ -4486,8 +4412,6 @@ "define-properties": "^1.1.3" } }, -======= ->>>>>>> origin/R05-test "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -5152,4 +5076,4 @@ } } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index c0b62635..9a606917 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,6 @@ "url": "git+https://github.com/ALPHACamp/forum-express-grading.git" }, "dependencies": { -<<<<<<< HEAD "bcrypt-nodejs": "0.0.3", "bcryptjs": "^2.4.3", "body-parser": "^1.19.0", @@ -34,33 +33,22 @@ "passport": "^0.5.0", "passport-local": "^1.0.0", "pg": "^8.7.1", -======= - "express": "^4.17.1", - "mysql2": "^2.2.5", - "nodemon": "^2.0.12", ->>>>>>> origin/R05-test "sequelize": "^6.3.5", "sequelize-cli": "^6.2.0" }, "devDependencies": { -<<<<<<< HEAD "standard": "^16.0.4", -======= ->>>>>>> origin/R05-test "chai": "^4.2.0", "mocha": "^8.2.0", "proxyquire": "^2.1.3", "sequelize-mock": "^0.10.2", "sinon": "^9.2.0", "supertest": "^5.0.0" -<<<<<<< HEAD }, "standard": { "ignore": [ "/node_modules", "/tests" ] -======= ->>>>>>> origin/R05-test } -} \ No newline at end of file +} diff --git a/tests/R01.test.js b/tests/R01.test.js index 2f708177..9d1d67a3 100644 --- a/tests/R01.test.js +++ b/tests/R01.test.js @@ -1,4 +1,3 @@ -<<<<<<< HEAD const chai = require("chai"); const request = require("supertest"); const should = chai.should(); @@ -13,17 +12,6 @@ const { describe("# R01", () => { describe("登入測試: POST /signin", function () { -======= -const chai = require('chai') -const request = require('supertest') -const should = chai.should() - -const app = require('../app') -const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers'); - -describe('# R01', () => { - describe('登入測試: POST /signin', function(){ ->>>>>>> origin/R05-test // 以下測試會發出請求,測試資料庫內是否有作業指定的使用者資料 // 測試資料的來源是真實的資料庫 it("#1 密碼錯誤", function (done) { @@ -73,18 +61,12 @@ describe('# R01', () => { }); // 修改 adminController 中的資料庫連線設定,由連向真實的資料庫 -> 改為連向模擬的 User table -<<<<<<< HEAD this.adminController = createControllerProxy( "../controllers/adminController", { User: this.UserMock } ); }); -======= - this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) - }) - ->>>>>>> origin/R05-test // 開始測試 context("# [顯示使用者清單]", () => { it(" GET /admin/users ", async () => { @@ -114,16 +96,11 @@ describe('# R01', () => { }); // 將 adminController 中的 User db 取代成 User mock db -<<<<<<< HEAD this.adminController = createControllerProxy( "../controllers/adminController", { User: this.UserMock } ); }); -======= - this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) - }) ->>>>>>> origin/R05-test it(" PUT /admin/users/:id/toggleAdmin ", async () => { // 模擬 request & response @@ -154,7 +131,6 @@ describe('# R01', () => { isAdmin: false, // 非管理者 }); // 將 adminController 中的 User db 取代成 User mock db -<<<<<<< HEAD this.adminController = createControllerProxy( "../controllers/adminController", { User: this.UserMock } @@ -162,12 +138,6 @@ describe('# R01', () => { }); it(" PUT /admin/users/:id/toggleAdmin ", async () => { -======= - this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) - }) - - it(' PUT /admin/users/:id/toggleAdmin ', async () => { ->>>>>>> origin/R05-test // 模擬 request & response const req = mockRequest({ params: { id: 1 } }); // 帶入 params.id = 1,對 PUT /admin/users/1/toggleAdmin 發出請求 const res = mockResponse(); @@ -184,18 +154,9 @@ describe('# R01', () => { // toggleAmin 執行完畢後,假資料中 id:1 使用者的應該要是 isAdmin:true // 將假資料撈出,比對確認有成功修改到 -<<<<<<< HEAD const user = await this.UserMock.findOne({ where: { id: 1 } }); user.isAdmin.should.equal(true); }); }); }); }); -======= - const user = await this.UserMock.findOne({ where: { id: 1 } }) - user.isAdmin.should.equal(true) - }) - }) - }) -}) ->>>>>>> origin/R05-test diff --git a/tests/R02.test.js b/tests/R02.test.js index 7d571000..a9473a27 100644 --- a/tests/R02.test.js +++ b/tests/R02.test.js @@ -1,4 +1,3 @@ -<<<<<<< HEAD const chai = require("chai"); const request = require("supertest"); const sinon = require("sinon"); @@ -16,20 +15,6 @@ const { describe("# R02", () => { describe("# R02: 建立 User Profile", function () { context("# [瀏覽 Profile]", () => { -======= -const chai = require('chai') -const request = require('supertest') -const sinon = require('sinon') -const should = chai.should() - -const helpers = require('../_helpers'); - -const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers'); - -describe('# R02', () => { - describe('# R02: 建立 User Profile', function () { - context('# [瀏覽 Profile]', () => { ->>>>>>> origin/R05-test // 前置準備 before(() => { // 模擬登入驗證 @@ -47,16 +32,11 @@ describe('# R02', () => { }); // 修改 userController 中的資料庫連線設定,由連向真實的資料庫 -> 改為連向模擬的 User table -<<<<<<< HEAD this.userController = createControllerProxy( "../controllers/userController", { User: this.UserMock } ); }); -======= - this.userController = createControllerProxy('../controllers/userController', { User: this.UserMock }) - }) ->>>>>>> origin/R05-test // 開始測試 it(" GET /users/:id ", async () => { @@ -100,16 +80,11 @@ describe('# R02', () => { }); // 連向模擬的 User table -<<<<<<< HEAD this.userController = createControllerProxy( "../controllers/userController", { User: this.UserMock } ); }); -======= - this.userController = createControllerProxy('../controllers/userController', { User: this.UserMock }) - }) ->>>>>>> origin/R05-test it(" GET /users/:id/edit ", async () => { // 模擬 request & response @@ -150,16 +125,11 @@ describe('# R02', () => { }); // 連向模擬的 User table -<<<<<<< HEAD this.userController = createControllerProxy( "../controllers/userController", { User: this.UserMock } ); }); -======= - this.userController = createControllerProxy('../controllers/userController', { User: this.UserMock }) - }) ->>>>>>> origin/R05-test it(" PUT /users/:id ", async () => { // 模擬 request & response diff --git a/tests/R03.test.js b/tests/R03.test.js index 68671c83..353f8175 100644 --- a/tests/R03.test.js +++ b/tests/R03.test.js @@ -3,7 +3,6 @@ const request = require("supertest"); const sinon = require("sinon"); const should = chai.should(); -<<<<<<< HEAD const helpers = require("../_helpers"); const { createModelMock, @@ -14,13 +13,6 @@ const { describe("# R03: 餐廳資訊整理:Dashboard", function () { context("# [Q1: Dashboard - 1 - controller / view / route]", () => { -======= -const helpers = require('../_helpers') -const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers'); - -describe('# R03: 餐廳資訊整理:Dashboard', function () { - context('# [Q1: Dashboard - 1 - controller / view / route]', () => { ->>>>>>> origin/R05-test before(async () => { // 製作假資料 // 本 context 會用這筆資料進行測試 diff --git a/tests/R04.test.js b/tests/R04.test.js index 0c017ef0..5b000bbe 100644 --- a/tests/R04.test.js +++ b/tests/R04.test.js @@ -3,7 +3,6 @@ const request = require("supertest"); const sinon = require("sinon"); const should = chai.should(); -<<<<<<< HEAD const db = require("../models"); const helpers = require("../_helpers"); const { @@ -12,11 +11,6 @@ const { mockRequest, mockResponse, } = require("../helpers/unitTestHelpers"); -======= -const db = require('../models') -const helpers = require('../_helpers') -const { createModelMock, createControllerProxy, mockRequest, mockResponse } = require('../helpers/unitTestHelpers') ->>>>>>> origin/R05-test // 建立模擬的 Like 資料 let mockLikeData = [ From 76f35071a89a4ec2aab8f0ea4a7123cd0f7d51ca Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Sun, 21 Nov 2021 08:35:55 +0000 Subject: [PATCH 194/198] fix merge issue with R05 --- helpers/unitTestHelpers.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js index d8f94887..b13887cf 100644 --- a/helpers/unitTestHelpers.js +++ b/helpers/unitTestHelpers.js @@ -103,19 +103,6 @@ const mockResponse = () => { } } -const mockRequest = (query) => { - return { - ...query, - flash: sinon.spy(), - } -} -const mockResponse = () => { - return { - redirect: sinon.spy(), - render: sinon.spy(), - } -} - module.exports = { createModelMock, createControllerProxy, From 12e28418e1e42709c7daa9f585ce8dd396b272fa Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Mon, 22 Nov 2021 00:16:55 +0000 Subject: [PATCH 195/198] view top restaurants --- controllers/restController.js | 28 +++++++++++++++++ controllers/userController.js | 16 ++++------ helpers/unitTestHelpers.js | 1 + models/favorite.js | 25 ++++++++-------- routes/index.js | 1 + views/feeds.handlebars | 3 ++ views/restaurants.handlebars | 2 ++ views/topRestaurants.handlebars | 53 +++++++++++++++++++++++++++++++++ views/topUser.handlebars | 5 +++- 9 files changed, 110 insertions(+), 24 deletions(-) create mode 100644 views/topRestaurants.handlebars diff --git a/controllers/restController.js b/controllers/restController.js index 2096294a..491ae021 100644 --- a/controllers/restController.js +++ b/controllers/restController.js @@ -1,8 +1,11 @@ +const { helpers } = require('faker') +const { sequelize } = require('../models') const db = require('../models') const Restaurant = db.Restaurant const Category = db.Category const User = db.User const Comment = db.Comment +const tools = require('../_helpers') const pageLimit = 10 @@ -119,6 +122,31 @@ const restControllers = { include: [Category, { model: Comment }] }) return res.render('dashboard', { restaurant: restaurant.toJSON() }) + }, + + getTopRestaurant: async (req, res) => { + let restaurants = await Restaurant.findAll({ + include: [{ model: User, as: 'FavoritedUsers' }] + }) + + restaurants = restaurants.map((restaurant) => ({ + ...restaurant.dataValues, + description: restaurant.dataValues.description.substring(0, 50), + favoritedCount: restaurant.dataValues.FavoritedUsers.length, + isFavorited: tools + .getUser(req) + .FavoritedRestaurants.filter( + (data) => data.id === restaurant.dataValues.id + ) + })) + + restaurants = restaurants + .sort((a, z) => z.favoritedCount - a.favoritedCount) + .slice(0, 10) + + return res.render('topRestaurants', { + restaurants + }) } } diff --git a/controllers/userController.js b/controllers/userController.js index 61e186fb..a58c5f20 100644 --- a/controllers/userController.js +++ b/controllers/userController.js @@ -147,17 +147,13 @@ const userController = { }) }, removeFavorite: (req, res) => { - return Favorite.findOne({ + return Favorite.destroy({ where: { - UserId: helpers.getUser(req).id, + UserId: req.user.id, RestaurantId: req.params.restaurantId } - }).then((favorite) => { - if (favorite) { - favorite.destroy().then((_restaurant) => { - return res.redirect('back') - }) - } + }).then((_d) => { + return res.redirect('back') }) }, addLike: (req, res) => { @@ -184,12 +180,12 @@ const userController = { }).then((users) => { users = users.map((user) => ({ ...user.dataValues, - FollowerCount: user.Followers.length, + followerCount: user.Followers.length, isFollowed: helpers .getUser(req) .Followings.filter((following) => following.id === user.id).length })) - users = users.sort((a, z) => z.FollowerCount - a.FollowerCount) + users = users.sort((a, z) => z.followerCount - a.followerCount) return res.render('topUser', { users }) }) }, diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js index b13887cf..13c47235 100644 --- a/helpers/unitTestHelpers.js +++ b/helpers/unitTestHelpers.js @@ -46,6 +46,7 @@ const createModelMock = (name, defaultValue, data, joinedTableName) => { restaurant[joinedTableName] = restaurant[joinedTableName].filter( (d) => !(d.UserId === UserId) ) + return Promise.resolve(data.map((d) => mockModel.build(d))) } }) diff --git a/models/favorite.js b/models/favorite.js index b86c0f68..eb062ab9 100644 --- a/models/favorite.js +++ b/models/favorite.js @@ -1,7 +1,5 @@ 'use strict' -const { - Model -} = require('sequelize') +const { Model } = require('sequelize') module.exports = (sequelize, DataTypes) => { class Favorite extends Model { /** @@ -9,16 +7,17 @@ module.exports = (sequelize, DataTypes) => { * This method is not a part of Sequelize lifecycle. * The `models/index` file will call this method automatically. */ - static associate (models) { - // define association here + static associate (models) {} + } + Favorite.init( + { + UserId: DataTypes.INTEGER, + RestaurantId: DataTypes.INTEGER + }, + { + sequelize, + modelName: 'Favorite' } - }; - Favorite.init({ - UserId: DataTypes.INTEGER, - RestaurantId: DataTypes.INTEGER - }, { - sequelize, - modelName: 'Favorite' - }) + ) return Favorite } diff --git a/routes/index.js b/routes/index.js index 1afd77e1..76333b69 100644 --- a/routes/index.js +++ b/routes/index.js @@ -121,6 +121,7 @@ module.exports = (app) => { categoryController.deletCategory ) app.get('/restaurants/feeds', authenticated, restController.getFeeds) + app.get('/restaurants/top', authenticated, restController.getTopRestaurant) app.get('/restaurants/:id', authenticated, restController.getRestaurant) app.post('/comments', authenticated, commentController.postComment) app.delete('/comments/:id', authenticated, commentController.deleteComment) diff --git a/views/feeds.handlebars b/views/feeds.handlebars index 8a138ce8..40b4d812 100644 --- a/views/feeds.handlebars +++ b/views/feeds.handlebars @@ -8,6 +8,9 @@ + + Top 10 人氣餐廳 +

    diff --git a/views/restaurants.handlebars b/views/restaurants.handlebars index 7eacae4d..b476ef81 100644 --- a/views/restaurants.handlebars +++ b/views/restaurants.handlebars @@ -7,6 +7,8 @@ + Top 10 人氣餐廳

    diff --git a/views/topRestaurants.handlebars b/views/topRestaurants.handlebars new file mode 100644 index 00000000..4776af98 --- /dev/null +++ b/views/topRestaurants.handlebars @@ -0,0 +1,53 @@ + +

    +

    Top 10 人氣餐廳

    +
    +
    + {{#each restaurants}} +
    +
    +
    +
    + Card image cap +
    +
    +

    + {{this.name}} +

    + 收藏數{{this.favoritedCount}} +

    {{this.description}}

    +
    +
    + + Show + + {{#if this.isFavorited }} +
    + {{else}} +
    +
    + {{/if}} +
    +
    +
    + +
    +
    +
    + {{/each}} +
    \ No newline at end of file diff --git a/views/topUser.handlebars b/views/topUser.handlebars index eee35e14..b4467aeb 100644 --- a/views/topUser.handlebars +++ b/views/topUser.handlebars @@ -8,6 +8,9 @@ + + Top 10 人氣餐廳 +

    美食達人

    @@ -32,7 +35,7 @@

    {{this.name}}

    追蹤人數:{{this.FollowerCount}} + >追蹤人數:{{this.followerCount}}

    {{#if this.isFollowed}}

    Date: Mon, 22 Nov 2021 00:17:05 +0000 Subject: [PATCH 196/198] fix R05 --- tests/R05.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/R05.test.js b/tests/R05.test.js index 891165d9..79d50c36 100644 --- a/tests/R05.test.js +++ b/tests/R05.test.js @@ -39,7 +39,7 @@ let mockRestaurantData = [ }, ]; -describe.only("# R05: TOP 10 人氣餐廳 ", function () { +describe("# R05: TOP 10 人氣餐廳 ", function () { context("# [網址正確、畫面正常執行]", () => { before(async () => { // 模擬登入驗證 From 76af7548687cf04b24ecfa438efd8cae5756b569 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Thu, 25 Nov 2021 22:24:40 +0000 Subject: [PATCH 197/198] refactor on admincontroller.getRestaurants --- controllers/adminController.js | 148 +++++++++++++------------- controllers/api/adminController.js | 16 +++ routes/apis.js | 8 ++ routes/index.js | 163 +---------------------------- routes/routes.js | 163 +++++++++++++++++++++++++++++ services/adminServices.js | 20 ++++ 6 files changed, 285 insertions(+), 233 deletions(-) create mode 100644 controllers/api/adminController.js create mode 100644 routes/apis.js create mode 100644 routes/routes.js create mode 100644 services/adminServices.js diff --git a/controllers/adminController.js b/controllers/adminController.js index 21e801f9..0c90c22a 100644 --- a/controllers/adminController.js +++ b/controllers/adminController.js @@ -1,40 +1,38 @@ -const db = require('../models') -const Category = db.Category -const Restaurant = db.Restaurant -const User = db.User +const db = require("../models"); +const Category = db.Category; +const Restaurant = db.Restaurant; +const User = db.User; // imgur setup -const imgur = require('imgur-node-api') -const IMGUR_CLIENT_ID = process.env.IMGUR_CLIENT_ID +const imgur = require("imgur-node-api"); +const IMGUR_CLIENT_ID = process.env.IMGUR_CLIENT_ID; +// Services +const adminServices = require("../services/adminServices"); const adminController = { getRestaurants: (req, res) => { - return Restaurant.findAll({ - raw: true, - nest: true, - include: [Category] - }).then((restaurants) => { - return res.render('admin/restaurants', { restaurants: restaurants }) - }) + return adminServices.getRestaurants(req, res, (data) => { + return res.render("admin/restaurants", data); + }); }, createRestaurants: (req, res) => { Category.findAll({ raw: true, nest: true }).then((categories) => { - return res.render('admin/create', { categories }) - }) + return res.render("admin/create", { categories }); + }); }, postRestaurants: (req, res) => { if (!req.body.name) { - req.flash('error_messages', "name didn't exist") - return res.redirect('back') + req.flash("error_messages", "name didn't exist"); + return res.redirect("back"); } const { name, tel, address, opening_hours, description, categoryId } = - req.body - const { file } = req + req.body; + const { file } = req; if (file) { - imgur.setClientID(IMGUR_CLIENT_ID) + imgur.setClientID(IMGUR_CLIENT_ID); imgur.upload(file.path, (err, img) => { - if (err) console.log('Error: ', err) + if (err) console.log("Error: ", err); return Restaurant.create({ name, tel, @@ -42,12 +40,12 @@ const adminController = { opening_hours, description, image: file ? img.data.link : null, - categoryId + categoryId, }).then((restaurant) => { - req.flash('success_messages', 'restaurant was successfully created') - return res.redirect('/admin/restaurants') - }) - }) + req.flash("success_messages", "restaurant was successfully created"); + return res.redirect("/admin/restaurants"); + }); + }); } else { return Restaurant.create({ name, @@ -55,47 +53,47 @@ const adminController = { address, opening_hours, description, - image: null + image: null, }).then((restaurant) => { - req.flash('success_messages', 'restaurant was successfully created') - return res.redirect('/admin/restaurants') - }) + req.flash("success_messages", "restaurant was successfully created"); + return res.redirect("/admin/restaurants"); + }); } }, getRestaurant: (req, res) => { return Restaurant.findByPk(req.params.id, { - include: [Category] + include: [Category], }).then((restaurant) => { - return res.render('admin/restaurant', { - restaurant: restaurant.toJSON() - }) - }) + return res.render("admin/restaurant", { + restaurant: restaurant.toJSON(), + }); + }); }, editRestaurant: (req, res) => { Category.findAll({ raw: true, nest: true }).then((categories) => { return Restaurant.findByPk(req.params.id, { raw: true, nest: true }).then( (restaurant) => { - return res.render('admin/create', { restaurant, categories }) + return res.render("admin/create", { restaurant, categories }); } - ) - }) + ); + }); }, putRestaurant: (req, res) => { if (!req.body.name) { - req.flash('error_message', 'name didnt exit') - return res.redirect('back') + req.flash("error_message", "name didnt exit"); + return res.redirect("back"); } - const { file } = req + const { file } = req; const { name, tel, address, opening_hours, description, categoryId } = - req.body + req.body; if (file) { - imgur.setClientID(IMGUR_CLIENT_ID) + imgur.setClientID(IMGUR_CLIENT_ID); imgur.upload(file.path, (err, img) => { - if (err) console.log('Error: ', err) + if (err) console.log("Error: ", err); return Restaurant.findByPk(req.params.id).then((restaurant) => { restaurant .update({ @@ -105,17 +103,17 @@ const adminController = { opening_hours, description, image: file ? img.data.link : restaurant.image, - categoryId + categoryId, }) .then((restaurant) => { req.flash( - 'success_messages', - 'restaurant was successfully to update' - ) - res.redirect('/admin/restaurants') - }) - }) - }) + "success_messages", + "restaurant was successfully to update" + ); + res.redirect("/admin/restaurants"); + }); + }); + }); } else { return Restaurant.findByPk(req.params.id).then((restaurant) => { restaurant @@ -125,47 +123,47 @@ const adminController = { address, opening_hours, description, - image: restaurant.image + image: restaurant.image, }) .then((restaurant) => { req.flash( - 'success_messages', - 'restaurant was successfully to update' - ) - res.redirect('/admin/restaurants') - }) - }) + "success_messages", + "restaurant was successfully to update" + ); + res.redirect("/admin/restaurants"); + }); + }); } }, deleteRestaurant: (req, res) => { return Restaurant.findByPk(req.params.id).then((restaurant) => { restaurant.destroy().then((restaurant) => { - res.redirect('/admin/restaurants') - }) - }) + res.redirect("/admin/restaurants"); + }); + }); }, getUsers: (req, res) => { return User.findAll({ raw: true, nest: true }).then((users) => { - return res.render('admin/users', { users }) - }) + return res.render("admin/users", { users }); + }); }, toggleAdmin: (req, res) => { return User.findByPk(req.params.id).then((user) => { - const { isAdmin, email } = user.toJSON() - if (email === 'root@example.com') { - req.flash('error_messages', '禁止變更管理者權限') - return res.redirect('back') + const { isAdmin, email } = user.toJSON(); + if (email === "root@example.com") { + req.flash("error_messages", "禁止變更管理者權限"); + return res.redirect("back"); } else { user.update({ isAdmin: !isAdmin }).then((user) => { - req.flash('success_messages', '使用者權限變更成功') - res.redirect('/admin/users') - }) + req.flash("success_messages", "使用者權限變更成功"); + res.redirect("/admin/users"); + }); } - }) - } -} + }); + }, +}; -module.exports = adminController +module.exports = adminController; diff --git a/controllers/api/adminController.js b/controllers/api/adminController.js new file mode 100644 index 00000000..a1991ed5 --- /dev/null +++ b/controllers/api/adminController.js @@ -0,0 +1,16 @@ +const db = require("../../models"); +const Restaurant = db.Restaurant; +const Category = db.Category; + +//services +const adminServices = require("../../services/adminServices"); + +const adminController = { + getRestaurants: (req, res) => { + return adminServices.getRestaurants(req, res, (data) => { + return res.json(data); + }); + }, +}; + +module.exports = adminController; diff --git a/routes/apis.js b/routes/apis.js new file mode 100644 index 00000000..186a32c3 --- /dev/null +++ b/routes/apis.js @@ -0,0 +1,8 @@ +const express = require("express"); +const router = express.Router(); + +const adminController = require("../controllers/api/adminController.js"); + +router.get("/admin/restaurants", adminController.getRestaurants); + +module.exports = router; diff --git a/routes/index.js b/routes/index.js index 76333b69..c42bf448 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,160 +1,7 @@ -const restController = require('../controllers/restController') -const adminController = require('../controllers/adminController') -const userController = require('../controllers/userController') -const commentController = require('../controllers/commentController') -const passport = require('passport') -const helpers = require('../_helpers') - -// multer setup -const multer = require('multer') -const categoryController = require('../controllers/categoryController') -const restControllers = require('../controllers/restController') -const upload = multer({ dest: 'temp/' }) +let routes = require("./routes"); +let apis = require("./apis"); module.exports = (app) => { - const authenticated = (req, res, next) => { - if (helpers.ensureAuthenticated(req)) { - return next() - } - res.redirect('/signin') - } - - const authenticatedAdmin = (req, res, next) => { - if (helpers.ensureAuthenticated(req)) { - if (helpers.getUser(req).isAdmin) { - return next() - } - return res.redirect('/') - } - res.redirect('/signin') - } - - // 如果使用者訪問首頁,就導向 /restaurants 的頁面 - app.get('/', authenticated, (req, res) => { - res.redirect('restaurants') - }) - app.get('/restaurants', authenticated, restController.getRestaurants) - - app.get('/admin', authenticatedAdmin, (req, res) => { - res.redirect('/admin/restaurants') - }) - app.get( - '/admin/restaurants', - authenticatedAdmin, - adminController.getRestaurants - ) - - app.get('/signup', userController.signUpPage) - app.post('/signup', userController.signUp) - - app.get('/signin', userController.signInPage) - app.post( - '/signin', - passport.authenticate('local', { - failureRedirect: '/signin', - failureFlash: true - }), - userController.signIn - ) - app.get('/logout', userController.logout) - app.get( - '/admin/restaurants/create', - authenticatedAdmin, - adminController.createRestaurants - ) - app.post( - '/admin/restaurants', - authenticatedAdmin, - upload.single('image'), - adminController.postRestaurants - ) - app.get( - '/admin/restaurants/:id', - authenticatedAdmin, - adminController.getRestaurant - ) - app.get( - '/admin/restaurants/:id/edit', - authenticatedAdmin, - adminController.editRestaurant - ) - app.put( - '/admin/restaurants/:id', - authenticatedAdmin, - upload.single('image'), - adminController.putRestaurant - ) - app.delete( - '/admin/restaurants/:id', - authenticatedAdmin, - adminController.deleteRestaurant - ) - app.get('/admin/users', authenticatedAdmin, adminController.getUsers) - app.put( - '/admin/users/:id/toggleAdmin', - authenticatedAdmin, - adminController.toggleAdmin - ) - app.get( - '/admin/categories', - authenticatedAdmin, - categoryController.getCategories - ) - app.post( - '/admin/categories', - authenticatedAdmin, - categoryController.postCategory - ) - app.get( - '/admin/categories/:id', - authenticatedAdmin, - categoryController.getCategories - ) - app.put( - '/admin/categories/:id', - authenticatedAdmin, - categoryController.putCategory - ) - app.delete( - '/admin/categories/:id', - authenticatedAdmin, - categoryController.deletCategory - ) - app.get('/restaurants/feeds', authenticated, restController.getFeeds) - app.get('/restaurants/top', authenticated, restController.getTopRestaurant) - app.get('/restaurants/:id', authenticated, restController.getRestaurant) - app.post('/comments', authenticated, commentController.postComment) - app.delete('/comments/:id', authenticated, commentController.deleteComment) - app.get('/users/top', authenticated, userController.getTopUser) - app.get('/users/:id', authenticated, userController.getUser) - app.get('/users/:id/edit', authenticated, userController.editUser) - app.put( - '/users/:id', - authenticated, - upload.single('image'), - userController.putUser - ) - app.get( - '/restaurants/:id/dashboard', - authenticated, - restControllers.getDashBoard - ) - app.post( - '/favorite/:restaurantId', - authenticated, - userController.addFavorite - ) - app.delete( - '/favorite/:restaurantId', - authenticated, - userController.removeFavorite - ) - app.post('/like/:restaurantId', authenticated, userController.addLike) - app.delete('/like/:restaurantId', authenticated, userController.removeLike) - app.post('/following/:userId', authenticated, userController.addFollowing) - app.delete( - '/following/:userId', - authenticated, - userController.removeFollowing - ) -} + app.use("/", routes); + app.use("/api", apis); +}; diff --git a/routes/routes.js b/routes/routes.js new file mode 100644 index 00000000..787947bc --- /dev/null +++ b/routes/routes.js @@ -0,0 +1,163 @@ +const express = require("express"); +const router = express.Router(); + +const restController = require("../controllers/restController"); +const adminController = require("../controllers/adminController"); +const userController = require("../controllers/userController"); +const commentController = require("../controllers/commentController"); +const categoryController = require("../controllers/categoryController"); +const restControllers = require("../controllers/restController"); +const passport = require("passport"); +const helpers = require("../_helpers"); + +// multer setup +const multer = require("multer"); +const upload = multer({ dest: "temp/" }); + +const authenticated = (req, res, next) => { + if (helpers.ensureAuthenticated(req)) { + return next(); + } + res.redirect("/signin"); +}; + +const authenticatedAdmin = (req, res, next) => { + if (helpers.ensureAuthenticated(req)) { + if (helpers.getUser(req).isAdmin) { + return next(); + } + return res.redirect("/"); + } + res.redirect("/signin"); +}; + +// 如果使用者訪問首頁,就導向 /restaurants 的頁面 +router.get("/", authenticated, (req, res) => { + res.redirect("restaurants"); +}); +router.get("/restaurants", authenticated, restController.getRestaurants); + +router.get("/admin", authenticatedAdmin, (req, res) => { + res.redirect("/admin/restaurants"); +}); +router.get( + "/admin/restaurants", + authenticatedAdmin, + adminController.getRestaurants +); + +router.get("/signup", userController.signUpPage); +router.post("/signup", userController.signUp); + +router.get("/signin", userController.signInPage); +router.post( + "/signin", + passport.authenticate("local", { + failureRedirect: "/signin", + failureFlash: true, + }), + userController.signIn +); +router.get("/logout", userController.logout); +router.get( + "/admin/restaurants/create", + authenticatedAdmin, + adminController.createRestaurants +); +router.post( + "/admin/restaurants", + authenticatedAdmin, + upload.single("image"), + adminController.postRestaurants +); +router.get( + "/admin/restaurants/:id", + authenticatedAdmin, + adminController.getRestaurant +); +router.get( + "/admin/restaurants/:id/edit", + authenticatedAdmin, + adminController.editRestaurant +); +router.put( + "/admin/restaurants/:id", + authenticatedAdmin, + upload.single("image"), + adminController.putRestaurant +); +router.delete( + "/admin/restaurants/:id", + authenticatedAdmin, + adminController.deleteRestaurant +); +router.get("/admin/users", authenticatedAdmin, adminController.getUsers); +router.put( + "/admin/users/:id/toggleAdmin", + authenticatedAdmin, + adminController.toggleAdmin +); +router.get( + "/admin/categories", + authenticatedAdmin, + categoryController.getCategories +); +router.post( + "/admin/categories", + authenticatedAdmin, + categoryController.postCategory +); +router.get( + "/admin/categories/:id", + authenticatedAdmin, + categoryController.getCategories +); +router.put( + "/admin/categories/:id", + authenticatedAdmin, + categoryController.putCategory +); +router.delete( + "/admin/categories/:id", + authenticatedAdmin, + categoryController.deletCategory +); +router.get("/restaurants/feeds", authenticated, restController.getFeeds); +router.get("/restaurants/top", authenticated, restController.getTopRestaurant); +router.get("/restaurants/:id", authenticated, restController.getRestaurant); +router.post("/comments", authenticated, commentController.postComment); +router.delete("/comments/:id", authenticated, commentController.deleteComment); +router.get("/users/top", authenticated, userController.getTopUser); +router.get("/users/:id", authenticated, userController.getUser); +router.get("/users/:id/edit", authenticated, userController.editUser); +router.put( + "/users/:id", + authenticated, + upload.single("image"), + userController.putUser +); +router.get( + "/restaurants/:id/dashboard", + authenticated, + restControllers.getDashBoard +); +router.post( + "/favorite/:restaurantId", + authenticated, + userController.addFavorite +); +router.delete( + "/favorite/:restaurantId", + authenticated, + userController.removeFavorite +); +router.post("/like/:restaurantId", authenticated, userController.addLike); +router.delete("/like/:restaurantId", authenticated, userController.removeLike); +router.post("/following/:userId", authenticated, userController.addFollowing); +router.delete( + "/following/:userId", + authenticated, + userController.removeFollowing +); + +module.exports = router; diff --git a/services/adminServices.js b/services/adminServices.js new file mode 100644 index 00000000..f5b2da91 --- /dev/null +++ b/services/adminServices.js @@ -0,0 +1,20 @@ +const db = require("../models"); +const Category = db.Category; +const Restaurant = db.Restaurant; + +const adminServices = { + getRestaurants: async (req, res, callback) => { + try { + const restaurants = await Restaurant.findAll({ + raw: true, + nest: true, + include: [Category], + }); + return callback({ restaurants }); + } catch (error) { + console.log(error); + } + }, +}; + +module.exports = adminServices; From 42a27ed366eebdc2e1ad8fb7bf5c4e8749710318 Mon Sep 17 00:00:00 2001 From: heidichen90 Date: Thu, 25 Nov 2021 22:38:32 +0000 Subject: [PATCH 198/198] refactor adminController.getRestaurant --- controllers/adminController.js | 8 ++---- controllers/api/adminController.js | 5 ++++ controllers/categoryController.js | 46 +++++++++++++++--------------- routes/apis.js | 1 + services/adminServices.js | 11 +++++++ 5 files changed, 42 insertions(+), 29 deletions(-) diff --git a/controllers/adminController.js b/controllers/adminController.js index 0c90c22a..cd8fa69f 100644 --- a/controllers/adminController.js +++ b/controllers/adminController.js @@ -62,12 +62,8 @@ const adminController = { }, getRestaurant: (req, res) => { - return Restaurant.findByPk(req.params.id, { - include: [Category], - }).then((restaurant) => { - return res.render("admin/restaurant", { - restaurant: restaurant.toJSON(), - }); + return adminServices.getRestaurant(req, res, (data) => { + return res.render("admin/restaurant", data); }); }, diff --git a/controllers/api/adminController.js b/controllers/api/adminController.js index a1991ed5..bbba321d 100644 --- a/controllers/api/adminController.js +++ b/controllers/api/adminController.js @@ -11,6 +11,11 @@ const adminController = { return res.json(data); }); }, + getRestaurant: (req, res) => { + return adminServices.getRestaurant(req, res, (data) => { + return res.json(data); + }); + }, }; module.exports = adminController; diff --git a/controllers/categoryController.js b/controllers/categoryController.js index 2909b698..709330c3 100644 --- a/controllers/categoryController.js +++ b/controllers/categoryController.js @@ -1,54 +1,54 @@ -const db = require('../models') -const Category = db.Category +const db = require("../models"); +const Category = db.Category; const categoryController = { getCategories: (req, res) => { return Category.findAll({ raw: true, - nest: true + nest: true, }).then((categories) => { if (req.params.id) { Category.findByPk(req.params.id).then((category) => { if (category) { - return res.render('admin/categories', { + return res.render("admin/categories", { categories, - category: category.toJSON() - }) + category: category.toJSON(), + }); } - }) + }); } else { - return res.render('admin/categories', { categories }) + return res.render("admin/categories", { categories }); } - }) + }); }, postCategory: (req, res) => { if (!req.body.name) { - req.flash('error_messages', "name didn't exist") - return res.redirect('back') + req.flash("error_messages", "name didn't exist"); + return res.redirect("back"); } else { return Category.create({ name: req.body.name }).then((category) => { - res.redirect('/admin/categories') - }) + res.redirect("/admin/categories"); + }); } }, putCategory: (req, res) => { if (!req.body.name) { - req.flash('error_messages', "name didn't exist") - return res.redirect('back') + req.flash("error_messages", "name didn't exist"); + return res.redirect("back"); } else { return Category.findByPk(req.params.id).then((category) => { category .update(req.body) - .then((category) => res.redirect('/admin/categories')) - }) + .then((category) => res.redirect("/admin/categories")); + }); } }, deletCategory: (req, res) => { return Category.findByPk(req.params.id).then((category) => { category.destroy().then((category) => { - res.redirect('/admin/categories') - }) - }) - } -} + res.redirect("/admin/categories"); + }); + }); + }, +}; -module.exports = categoryController +module.exports = categoryController; diff --git a/routes/apis.js b/routes/apis.js index 186a32c3..d3c4601b 100644 --- a/routes/apis.js +++ b/routes/apis.js @@ -4,5 +4,6 @@ const router = express.Router(); const adminController = require("../controllers/api/adminController.js"); router.get("/admin/restaurants", adminController.getRestaurants); +router.get("/admin/restaurants/:id", adminController.getRestaurant); module.exports = router; diff --git a/services/adminServices.js b/services/adminServices.js index f5b2da91..27948e7c 100644 --- a/services/adminServices.js +++ b/services/adminServices.js @@ -15,6 +15,17 @@ const adminServices = { console.log(error); } }, + getRestaurant: async (req, res, callback) => { + try { + const restaurant = await Restaurant.findByPk(req.params.id, { + include: [Category], + }); + + return callback({ restaurant: restaurant.toJSON() }); + } catch (error) { + console.log(errror); + } + }, }; module.exports = adminServices;