diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..541a886c --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +IMGUR_CLIENT_ID=SKIP diff --git a/Procfile b/Procfile new file mode 100644 index 00000000..2cbe96df --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: NODE_ENV=production node app.js 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/app.js b/app.js index f70e64d6..0d83131e 100644 --- a/app.js +++ b/app.js @@ -1,11 +1,51 @@ const express = require('express') +const handlebars = require('express-handlebars') // 引入 handlebars +const bodyParser = require('body-parser')// Express 升級到了 4.17.1 之後,不用額外載入 body-parser,這行程式碼便不用寫 +const flash = require('connect-flash') +const helpers = require('./_helpers') +if (process.env.NODE_ENV !== 'production') { + require('dotenv').config() +} + +const session = require('express-session') +const passport = require('./config/passport') +const methodOverride = require('method-override') + +const db = require('./models') // 引入資料庫 const app = express() -const port = 3000 +const port = process.env.PORT || 3000 + + +app.engine('handlebars', handlebars({ + defaultLayout: 'main', + helpers: require('./config/handlebars-helpers') +})) // Handlebars 註冊樣板引擎 +app.set('view engine', 'handlebars') // 設定使用 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()) + +// 把 req.flash 放到 res.locals 裡面 +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 //讓views能存取user + res.locals.user = helpers.getUser(req) // 取代 req.user + next() +}) + +app.use(methodOverride('_method')) +app.use('/upload', express.static(__dirname + '/upload')) + app.listen(port, () => { console.log(`Example app listening at http://localhost:${port}`) }) +// 引入 routes 並將 app 傳進去,讓 routes 可以用 app 這個物件來指定路由 -require('./routes')(app) +require('./routes')(app, passport) module.exports = app diff --git a/config/config.json b/config/config.json index 93cd6cd8..3b298c28 100644 --- a/config/config.json +++ b/config/config.json @@ -7,11 +7,7 @@ "dialect": "mysql" }, "production": { - "username": "root", - "password": null, - "database": "database_production", - "host": "127.0.0.1", - "dialect": "mysql" + "use_env_variable": "CLEARDB_DATABASE_URL" }, "travis": { "username": "travis", @@ -20,4 +16,4 @@ "dialect": "mysql", "logging": false } -} +} \ No newline at end of file diff --git a/config/handlebars-helpers.js b/config/handlebars-helpers.js new file mode 100644 index 00000000..4e956036 --- /dev/null +++ b/config/handlebars-helpers.js @@ -0,0 +1,14 @@ +const moment = require('moment') + + +module.exports = { + ifCond: function (a, b, options) { + if (a === b) { + return options.fn(this) + } + return options.inverse(this) + }, + moment: function (a) { + return moment(a).fromNow() + } +} \ No newline at end of file diff --git a/config/passport.js b/config/passport.js new file mode 100644 index 00000000..eef0be9e --- /dev/null +++ b/config/passport.js @@ -0,0 +1,45 @@ +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 + }, + // authenticate user + (req, username, password, cb) => { + User.findOne({ where: { email: username } }).then(user => { + if (!user) return cb(null, false, req.flash('error_messages', '帳號或密碼輸入錯誤')) + if (!bcrypt.compareSync(password, user.password)) return cb(null, false, req.flash('error_messages', '帳號或密碼輸入錯誤!')) + return cb(null, user) + }) + } +)) + +// serialize and deserialize user +passport.serializeUser((user, cb) => { + cb(null, user.id) +}) +passport.deserializeUser((id, cb) => { + User.findByPk(id, { + include: [ + { model: Restaurant, as: 'FavoritedRestaurants' }, + { model: Restaurant, as: 'LikeRestaurants' } + + ] + }).then(user => { + user = user.toJSON() + return cb(null, user) + }) +}) + +module.exports = passport \ No newline at end of file diff --git a/controllers/adminController.js b/controllers/adminController.js new file mode 100644 index 00000000..35416db3 --- /dev/null +++ b/controllers/adminController.js @@ -0,0 +1,183 @@ +const db = require('../models') +const Restaurant = db.Restaurant +const fs = require('fs') +const imgur = require('imgur-node-api') +const category = require('../models/category') +const IMGUR_CLIENT_ID = process.env.IMGUR_CLIENT_ID +const User = db.User +const Category = db.Category + + +const adminController = { + getRestaurants: (req, res) => { + return Restaurant.findAll({ + raw: true, + nest: true, + include: [Category] + }).then(restaurants => { + // console.log(restaurants) + return res.render('admin/restaurants', { restaurants: restaurants }) + }) + }, + + createRestaurant: (req, res) => { + Category.findAll({ + raw: true, + nest: true + }).then(categories => { + return res.render('admin/create', { + categories: categories + }) + }) + }, + + postRestaurant: (req, res) => { + if (!req.body.name) { + req.flash('error_messages', "name didn't exist") + return res.redirect('back') + } + + const { file } = req + if (file) { + imgur.setClientID(IMGUR_CLIENT_ID); + imgur.upload(file.path, (err, img) => { + return Restaurant.create({ + name: req.body.name, + tel: req.body.tel, + address: req.body.address, + opening_hours: req.body.opening_hours, + description: req.body.description, + image: file ? img.data.link : null, + CategoryId: req.body.categoryId + + }).then((restaurant) => { + req.flash('success_messages', 'restaurant was successfully created') + return res.redirect('/admin/restaurants') + }) + }) + } + else { + return Restaurant.create({ + name: req.body.name, + tel: req.body.tel, + address: req.body.address, + opening_hours: req.body.opening_hours, + description: req.body.description, + image: null, + CategoryId: req.body.categoryId + + }).then((restaurant) => { + req.flash('success_messages', 'restaurant was successfully created') + return res.redirect('/admin/restaurants') + }) + } + }, + getRestaurant: (req, res) => { + return Restaurant.findByPk(req.params.id, { include: [Category] }).then( + restaurant => { + console.log(restaurant) + 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).then(restaurant => { + return res.render('admin/create', { + categories: categories, + restaurant: restaurant.toJSON() + }) + }) + + }) + }, + + putRestaurant: (req, res) => { + if (!req.body.name) { + req.flash('error_messages', "name didn't exist") + return res.redirect('back') + } + + const { file } = req + if (file) { + imgur.setClientID(IMGUR_CLIENT_ID); + imgur.upload(file.path, (err, img) => { + 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, + image: file ? img.data.link : restaurant.image, + CategoryId: req.body.categoryId + + }) + .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: req.body.name, + tel: req.body.tel, + address: req.body.address, + opening_hours: req.body.opening_hours, + description: req.body.description, + image: restaurant.image, + CategoryId: req.body.categoryId + + }) + .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') + }) + }) + }, + + getUsers: (req, res) => { + return User.findAll({ raw: true }).then(users => { + return res.render('admin/users', { 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"); + } else { + user.update({ isAdmin: !isAdmin }).then((user) => { + req.flash("success_messages", "使用者權限變更成功"); + res.redirect("/admin/users"); + }); + } + }); + }, +} + +module.exports = adminController \ No newline at end of file diff --git a/controllers/categoryController.js b/controllers/categoryController.js new file mode 100644 index 00000000..de629466 --- /dev/null +++ b/controllers/categoryController.js @@ -0,0 +1,62 @@ +const db = require('../models') +const category = require('../models/category') +const Category = db.Category + +let categoryController = { + getCategories: (req, res) => { + return Category.findAll({ + raw: true, + nest: true + }).then(categories => { + if (req.params.id) { + Category.findByPk(req.params.id) + .then((category) => { + return res.render('admin/categories', { + categories: categories, + category: category.toJSON() + }) + }) + } else { + return res.render('admin/categories', { categories: categories }) + } + }) + }, + postCategories: (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) => { + res.redirect('/admin/categories') + + }) + } + }, + 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') + }) + }) + } + }, + deleteCategory: (req, res) => { + return Category.findByPk(req.params.id).then((category) => { + category.destroy() + .then((category) => { + res.redirect('/admin/categories') + }) + }) + } +} + +module.exports = categoryController \ No newline at end of file diff --git a/controllers/commentController.js b/controllers/commentController.js new file mode 100644 index 00000000..e74b1a34 --- /dev/null +++ b/controllers/commentController.js @@ -0,0 +1,25 @@ +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 + }).then((comment) => { + 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}`) + }) + }) + } +} + +module.exports = commentController \ No newline at end of file diff --git a/controllers/restController.js b/controllers/restController.js new file mode 100644 index 00000000..84a35602 --- /dev/null +++ b/controllers/restController.js @@ -0,0 +1,138 @@ +const db = require('../models') +const Restaurant = db.Restaurant +const Category = db.Category +const pageLimit = 10 +const Comment = db.Comment +const User = db.User +const Like = db.Like + + +const restController = { + getRestaurants: (req, res) => { + let offset = 0 + const whereQuery = {} + let categoryId = '' + if (req.query.page) { + offset = (req.query.page - 1) * pageLimit + } + if (req.query.categoryId) { + categoryId = Number(req.query.categoryId) + whereQuery.CategoryId = categoryId + } + Restaurant.findAndCountAll({ + include: Category, + where: whereQuery, + offset: offset, + limit: pageLimit + }).then((result) => { + // data for pagination + const page = Number(req.query.page) || 1 + const pages = Math.ceil(result.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 + + // clean up restaurant data + const data = result.rows.map(r => ({ + ...r.dataValues, + description: r.dataValues.description.substring(0, 50), + categoryName: r.dataValues.Category.name, + isFavorited: req.user.FavoritedRestaurants.map(d => d.id).includes(r.id), + isLike: req.user.LikeRestaurants.map(d => d.id).includes(r.id), + })) + Category.findAll({ + raw: true, + nest: true + }).then(categories => { + return res.render('restaurants', { + restaurants: data, + categories: categories, + categoryId: categoryId, + page: page, + totalPage: totalPage, + prev: prev, + next: next + }) + }) + }) + }, + getRestaurant: (req, res) => { + return Restaurant.findByPk(req.params.id, { + include: [ + Category, + { model: User, as: 'FavoritedUsers' }, // 加入關聯資料 + { model: User, as: 'LikeUsers' }, + { model: Comment, include: [User] } + ] + }).then(restaurant => { + // console.log(restaurant.Comments[0].dataValues) + const isFavorited = restaurant.FavoritedUsers.map(d => d.id).includes(req.user.id) // 找出收藏此餐廳的 user + const isLike = restaurant.LikeUsers.map(d => d.id).includes(req.user.id) + restaurant.increment("viewCounts"); //sequelize遞增方法,用increment + return res.render('restaurant', { + restaurant: restaurant.toJSON(), + isFavorited: isFavorited, // 將資料傳到前端 + isLike: isLike + }) + }) + }, + + // getFeeds: (req, res) => { + // return Restaurant.findAll({ + // limit: 10, + // raw: true, + // nest: true, + // order: [['createdAt', 'DESC']], + // include: [Category] + // }).then((restaurants) => { + // return Comment.findAll({ + // limit: 10, + // raw: true, + // nest: true, + // order: [['createdAt', 'DESC']], + // include: [User, Restaurant] + // }).then((comments) => { + // return res.render('feeds', { restaurants, comments }) + // }) + // }) + // } + + // Promise.all 寫法 + getFeeds: (req, res) => { + return Promise.all([ + Restaurant.findAll({ + limit: 10, + raw: true, + nest: true, + order: [['createdAt', 'DESC']], + include: [Category] + }), + Comment.findAll({ + limit: 10, + raw: true, + nest: true, + order: [['createdAt', 'DESC']], + include: [User, Restaurant] + }) + ]).then(([restaurants, comments]) => { + return res.render('feeds', { restaurants, comments }) + }) + }, + getDashBoard: (req, res) => { + return Restaurant.findByPk( + req.params.id, + { + include: [ + Category, + { model: Comment, include: [Restaurant] } + ] + }, + + ).then((restaurant) => { + console.log(restaurant.toJSON()) + return res.render('dashboard', { restaurant: restaurant.toJSON() }) + }) + } + +} +module.exports = restController \ No newline at end of file diff --git a/controllers/userController.js b/controllers/userController.js new file mode 100644 index 00000000..59ee07c7 --- /dev/null +++ b/controllers/userController.js @@ -0,0 +1,190 @@ +const bcrypt = require('bcryptjs') +const db = require('../models') +const User = db.User //存取到之前建立的User model +const Restaurant = db.Restaurant +const Comment = db.Comment +const Favorite = db.Favorite +const Like = db.Like +const helpers = require('../_helpers') +const imgur = require('imgur-node-api') +const IMGUR_CLIENT_ID = process.env.IMGUR_CLIENT_ID + + +const userController = { + + signUpPage: (req, res) => { + return res.render('signup') + }, + + signUp: (req, res) => { + // confirm password + if (req.body.passwordCheck !== req.body.password) { + req.flash('error_messages', '兩次密碼輸入不同!') + return res.redirect('/signup') + } else { + // confirm unique user + User.findOne({ where: { email: req.body.email } }).then(user => { + if (user) { + req.flash('error_messages', '信箱重複!') + return res.redirect('/signup') + } else { + User.create({ + name: req.body.name, + email: req.body.email, + password: bcrypt.hashSync(req.body.password, bcrypt.genSaltSync(10), null) + }).then(user => { + 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_messages', '登出成功!') + req.logout() + res.redirect('/signin') + }, + + getUser: (req, res) => { + + return User.findByPk(req.params.id, { + include: [ + { model: Comment, include: [Restaurant] } + ] + }).then((user) => { + // console.log(user.toJSON().Comments) + const comment = user.toJSON().Comments + res.render('profile', { user: user.toJSON(), comment }) + + }) + }, + editUser: (req, res) => { + + if (Number(req.params.id) !== helpers.getUser(req).id) { + req.flash('error_messages', "只能修改自己資料") + return res.redirect('back') + } + + return User.findByPk(req.params.id).then( + user => { + return res.render('edit', { user: user.toJSON() }) + } + ) + }, + + putUser: (req, res) => { + + + + if (!req.body.name || !req.body.email) { + req.flash('error_messages', "name or email didn't exist") + return res.redirect('back') + + } + + const { file } = req + if (file) { + imgur.setClientID(IMGUR_CLIENT_ID); + imgur.upload(file.path, (err, img) => { + return User.findByPk(req.params.id).then((user) => { + user.update({ + name: req.body.name, + email: req.body.email, + image: file ? img.data.link : user.image, + }) + .then((user) => { + req.flash('success_messages', '使用者資料編輯成功') + res.redirect(`/users/${req.params.id}`) + }) + }) + }) + + } else { + return User.findByPk(req.params.id).then((user) => { + user.update({ + name: req.body.name, + email: req.body.email, + image: user.image + + }) + .then((user) => { + req.flash('success_messages', '使用者資料編輯成功') + res.redirect(`/users/${req.params.id}`) + }) + }) + + } + + }, + + addFavorite: (req, res) => { + return Favorite.create({ + UserId: helpers.getUser(req).id, + RestaurantId: req.params.restaurantId + }) + .then((restaurant) => { + return res.redirect('back') + }) + }, + removeFavorite: (req, res) => { + return Favorite.findOne({ + where: { + UserId: helpers.getUser(req).id, + RestaurantId: req.params.restaurantId + } + }) + .then((favorite) => { + favorite.destroy() + .then((restaurant) => { + return res.redirect('back') + }) + }) + }, + addLike: (req, res) => { + return Like.create({ + UserId: helpers.getUser(req).id, + RestaurantId: req.params.restaurantId + }) + .then((restaurant) => { + return res.redirect('back') + }) + }, + removeLike: (req, res) => { + + return Like.destroy({ + where: { + UserId: helpers.getUser(req).id, + RestaurantId: req.params.restaurantId + } + }).then((restaurant) => { + return res.redirect('back') + }) + + // return Like.findOne({ + // where: { + // UserId: helpers.getUser(req).id, + // RestaurantId: req.params.restaurantId + // } + // }) + // .then((like) => { + // like.destroy() + // .then((restaurant) => { + // return res.redirect('back') + // }) + // }) + } +} + + +module.exports = userController \ No newline at end of file diff --git a/helpers/unitTestHelpers.js b/helpers/unitTestHelpers.js new file mode 100644 index 00000000..29631c99 --- /dev/null +++ b/helpers/unitTestHelpers.js @@ -0,0 +1,107 @@ +const SequelizeMock = require('sequelize-mock') +const proxyquire = require('proxyquire') +const sinon = require('sinon') + +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') { + // 回傳模擬資料 + if (!data) { + return mockModel.build([defaultValue]); + } + 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))) + } + }); + } 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') { + // 回傳模擬資料 + if (!data) { + 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 + 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; +} + +const mockRequest = (query) => { + return { + ...query, + flash: sinon.spy(), + } +} +const mockResponse = () => { + return { + redirect: sinon.spy(), + render: sinon.spy(), + } +} + +module.exports = { + createModelMock, + createControllerProxy, + mockRequest, + mockResponse +} \ No newline at end of file diff --git a/migrations/20211109174638-create-user.js b/migrations/20211109174638-create-user.js new file mode 100644 index 00000000..58a162b2 --- /dev/null +++ b/migrations/20211109174638-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/migrations/20211110065754-add-isAdmin-to-users.js b/migrations/20211110065754-add-isAdmin-to-users.js new file mode 100644 index 00000000..60e6fe08 --- /dev/null +++ b/migrations/20211110065754-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/migrations/20211110180411-create-restaurant.js b/migrations/20211110180411-create-restaurant.js new file mode 100644 index 00000000..9ae279d5 --- /dev/null +++ b/migrations/20211110180411-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/migrations/20211111070245-add-image-to-restaurants.js b/migrations/20211111070245-add-image-to-restaurants.js new file mode 100644 index 00000000..91125e87 --- /dev/null +++ b/migrations/20211111070245-add-image-to-restaurants.js @@ -0,0 +1,11 @@ +'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/migrations/20211115180800-create-category.js b/migrations/20211115180800-create-category.js new file mode 100644 index 00000000..ab0ee89a --- /dev/null +++ b/migrations/20211115180800-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/20211115181845-add-categoryId-to-restaurant.js b/migrations/20211115181845-add-categoryId-to-restaurant.js new file mode 100644 index 00000000..3cb7a30a --- /dev/null +++ b/migrations/20211115181845-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/migrations/20211117190223-create-comment.js b/migrations/20211117190223-create-comment.js new file mode 100644 index 00000000..8e42b57c --- /dev/null +++ b/migrations/20211117190223-create-comment.js @@ -0,0 +1,33 @@ +'use strict'; +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.createTable('Comments', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + text: { + type: Sequelize.STRING + }, + UserId: { + type: Sequelize.INTEGER + }, + RestaurantId: { + type: Sequelize.INTEGER + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }); + }, + down: async (queryInterface, Sequelize) => { + await queryInterface.dropTable('Comments'); + } +}; \ No newline at end of file diff --git a/migrations/20211118071339-add-image-to-user.js b/migrations/20211118071339-add-image-to-user.js new file mode 100644 index 00000000..1acc1f54 --- /dev/null +++ b/migrations/20211118071339-add-image-to-user.js @@ -0,0 +1,14 @@ +'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') + } +} \ No newline at end of file diff --git a/migrations/20211120202308-add-viewCounts-to-restaurant.js b/migrations/20211120202308-add-viewCounts-to-restaurant.js new file mode 100644 index 00000000..c9110761 --- /dev/null +++ b/migrations/20211120202308-add-viewCounts-to-restaurant.js @@ -0,0 +1,15 @@ +'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') + } +} \ No newline at end of file diff --git a/migrations/20211121094350-create-favorite.js b/migrations/20211121094350-create-favorite.js new file mode 100644 index 00000000..6b9a5748 --- /dev/null +++ b/migrations/20211121094350-create-favorite.js @@ -0,0 +1,30 @@ +'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 + }, + RestaurantId: { + type: Sequelize.INTEGER + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }); + }, + down: async (queryInterface, Sequelize) => { + await queryInterface.dropTable('Favorites'); + } +}; \ No newline at end of file diff --git a/migrations/20211121125450-create-like.js b/migrations/20211121125450-create-like.js new file mode 100644 index 00000000..ad2df697 --- /dev/null +++ b/migrations/20211121125450-create-like.js @@ -0,0 +1,30 @@ +'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 + }, + RestaurantId: { + type: Sequelize.INTEGER + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }); + }, + down: async (queryInterface, Sequelize) => { + await queryInterface.dropTable('Likes'); + } +}; \ No newline at end of file diff --git a/models/category.js b/models/category.js new file mode 100644 index 00000000..dfd3c793 --- /dev/null +++ b/models/category.js @@ -0,0 +1,18 @@ +'use strict' +const { + Model +} = require('sequelize') +module.exports = (sequelize, DataTypes) => { + class Category extends Model { + static associate(models) { + Category.hasMany(models.Restaurant) + } + }; + Category.init({ + name: DataTypes.STRING + }, { + sequelize, + modelName: 'Category' + }) + return Category +} \ No newline at end of file diff --git a/models/comment.js b/models/comment.js new file mode 100644 index 00000000..fea7e034 --- /dev/null +++ b/models/comment.js @@ -0,0 +1,27 @@ +'use strict'; +const { + Model +} = require('sequelize'); +module.exports = (sequelize, DataTypes) => { + class Comment 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 + Comment.belongsTo(models.Restaurant) + Comment.belongsTo(models.User) + } + }; + Comment.init({ + text: DataTypes.STRING, + UserId: DataTypes.INTEGER, + RestaurantId: DataTypes.INTEGER + }, { + sequelize, + modelName: 'Comment', + }); + return Comment; +}; \ No newline at end of file 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/index.js b/models/index.js index 66860356..02297298 100644 --- a/models/index.js +++ b/models/index.js @@ -23,7 +23,7 @@ fs return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js') }) .forEach(file => { - const model = sequelize['import'](path.join(__dirname, file)) + const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes) db[model.name] = model }) @@ -38,4 +38,4 @@ Object.keys(db).forEach(modelName => { db.sequelize = sequelize db.Sequelize = Sequelize -module.exports = db +module.exports = db \ No newline at end of file diff --git a/models/like.js b/models/like.js new file mode 100644 index 00000000..b45c4dde --- /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; +}; \ No newline at end of file diff --git a/models/restaurant.js b/models/restaurant.js new file mode 100644 index 00000000..7e6548dc --- /dev/null +++ b/models/restaurant.js @@ -0,0 +1,43 @@ +'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.belongsTo(models.Category) // 加入關聯設定 + Restaurant.hasMany(models.Comment) + Restaurant.belongsToMany(models.User, { + through: models.Favorite, + foreignKey: 'RestaurantId', + as: 'FavoritedUsers' + }), + Restaurant.belongsToMany(models.User, { + through: models.Like, + foreignKey: 'RestaurantId', + as: 'LikeUsers' + }) + } + }; + Restaurant.init({ + name: DataTypes.STRING, + tel: DataTypes.STRING, + address: DataTypes.STRING, + opening_hours: DataTypes.STRING, + description: DataTypes.TEXT, + image: DataTypes.STRING, + CategoryId: DataTypes.INTEGER, // 更新欄位清單 + viewCounts: DataTypes.INTEGER, + + }, { + sequelize, + modelName: 'Restaurant', + }); + return Restaurant; +}; \ No newline at end of file diff --git a/models/user.js b/models/user.js new file mode 100644 index 00000000..1c497e73 --- /dev/null +++ b/models/user.js @@ -0,0 +1,39 @@ +'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.hasMany(models.Comment) + User.belongsToMany(models.Restaurant, { + through: models.Favorite, + foreignKey: 'UserId', + as: 'FavoritedRestaurants' + }), + User.belongsToMany(models.Restaurant, { + through: models.Like, + foreignKey: 'UserId', + as: 'LikeRestaurants' + }) + } + }; + User.init({ + name: DataTypes.STRING, + email: DataTypes.STRING, + password: DataTypes.STRING, + isAdmin: DataTypes.BOOLEAN, + image: DataTypes.STRING + + }, { + sequelize, + modelName: 'User', + }); + return User; +}; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 26dc7139..830094fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,16 +4,10 @@ "lockfileVersion": 1, "requires": true, "dependencies": { - "@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" - }, "@sinonjs/commons": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", - "dev": true, "requires": { "type-detect": "4.0.8" } @@ -22,7 +16,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", - "dev": true, "requires": { "@sinonjs/commons": "^1.7.0" } @@ -31,7 +24,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-5.0.1.tgz", "integrity": "sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==", - "dev": true, "requires": { "@sinonjs/commons": "^1", "@sinonjs/samsam": "^5.0.2" @@ -41,7 +33,6 @@ "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==", - "dev": true, "requires": { "@sinonjs/commons": "^1.6.0", "lodash.get": "^4.4.2", @@ -51,16 +42,7 @@ "@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==", - "dev": true - }, - "@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "requires": { - "defer-to-connect": "^1.0.1" - } + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==" }, "@types/node": { "version": "14.14.5", @@ -70,8 +52,7 @@ "@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==", - "dev": true + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==" }, "abbrev": { "version": "1.1.1", @@ -87,54 +68,10 @@ "negotiator": "0.6.2" } }, - "ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "requires": { - "string-width": "^4.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - } - } - }, "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==" }, "ansi-regex": { "version": "2.1.1", @@ -163,11 +100,15 @@ "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", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, "requires": { "sprintf-js": "~1.0.2" } @@ -180,20 +121,33 @@ "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true + "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 + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "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", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, + "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", @@ -221,90 +175,12 @@ "type-is": "~1.6.17" } }, - "boxen": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", - "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", - "requires": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.2", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==" - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - } + "boom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/boom/-/boom-0.3.8.tgz", + "integrity": "sha1-yM2wQUNZEnQWKMBE7Mcy0dF8Ceo=", + "requires": { + "hoek": "0.7.x" } }, "brace-expansion": { @@ -327,43 +203,50 @@ "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==", - "dev": true + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" }, - "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + "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==" }, - "cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" + "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", + "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", + "requires": { + "dicer": "0.2.5", + "readable-stream": "1.1.x" }, "dependencies": { - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "requires": { - "pump": "^3.0.0" + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" } }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + "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", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -373,7 +256,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", - "dev": true, "requires": { "assertion-error": "^1.1.0", "check-error": "^1.0.2", @@ -418,8 +300,7 @@ "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=" }, "chokidar": { "version": "3.4.3", @@ -436,16 +317,6 @@ "readdirp": "~3.5.0" } }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" - }, - "cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==" - }, "cli-color": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz", @@ -469,14 +340,6 @@ "wrap-ansi": "^5.1.0" } }, - "clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "requires": { - "mimic-response": "^1.0.0" - } - }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -494,7 +357,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -507,14 +369,53 @@ "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" }, "concat-map": { "version": "0.0.1", "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", @@ -524,18 +425,10 @@ "proto-list": "~1.2.1" } }, - "configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "requires": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - } + "connect-flash": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/connect-flash/-/connect-flash-0.1.1.tgz", + "integrity": "sha1-2GMPJtlaf4UfmVax6MxnMvO2qjA=" }, "content-disposition": { "version": "0.5.3", @@ -555,6 +448,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", @@ -563,13 +461,20 @@ "cookiejar": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", - "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", - "dev": true + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==" }, - "crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==" + "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==" + }, + "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" + } }, "d": { "version": "1.0.1", @@ -593,38 +498,18 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "requires": { - "mimic-response": "^1.0.0" - } - }, "deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, "requires": { "type-detect": "^4.0.0" } }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" - }, - "defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" - }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "denque": { "version": "1.4.1", @@ -641,30 +526,48 @@ "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", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" }, - "dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "requires": { - "is-obj": "^2.0.0" - } + "dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==" }, "dottie": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.2.tgz", "integrity": "sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg==" }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" - }, "editorconfig": { "version": "0.15.3", "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", @@ -712,14 +615,6 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "requires": { - "once": "^1.4.0" - } - }, "es5-ext": { "version": "0.10.53", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", @@ -760,11 +655,6 @@ "es6-symbol": "^3.1.1" } }, - "escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==" - }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -773,14 +663,12 @@ "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "etag": { "version": "1.8.1", @@ -833,6 +721,68 @@ "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==" + } + } + }, + "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", @@ -848,17 +798,20 @@ } } }, + "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", - "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==", - "dev": true + "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" }, "fill-keys": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", "integrity": "sha1-mo+jb06K1jTjv2tPPIiCVRRS6yA=", - "dev": true, "requires": { "is-object": "~1.0.1", "merge-descriptors": "~1.0.0" @@ -897,14 +850,17 @@ "flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true + "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", "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", - "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -914,8 +870,7 @@ "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==", - "dev": true + "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==" }, "forwarded": { "version": "0.1.2", @@ -969,16 +924,7 @@ "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=", - "dev": true - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "requires": { - "pump": "^3.0.0" - } + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=" }, "glob": { "version": "7.1.6", @@ -1001,39 +947,6 @@ "is-glob": "^4.0.1" } }, - "global-dirs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", - "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", - "requires": { - "ini": "2.0.0" - }, - "dependencies": { - "ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==" - } - } - }, - "got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "requires": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - } - }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", @@ -1042,8 +955,19 @@ "growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true + "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", @@ -1058,21 +982,26 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, - "has-yarn": { - "version": "2.1.0", - "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==", - "dev": true + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, - "http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + "hoek": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-0.7.6.tgz", + "integrity": "sha1-YPvZBFV1Qc0rh5Wr8wihs3cOFVo=" }, "http-errors": { "version": "1.7.2", @@ -1094,20 +1023,13 @@ "safer-buffer": ">= 2.1.2 < 3" } }, - "ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=" - }, - "import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=" - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + "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" + } }, "inflection": { "version": "1.12.0", @@ -1146,14 +1068,6 @@ "binary-extensions": "^2.0.0" } }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "requires": { - "ci-info": "^2.0.0" - } - }, "is-core-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.0.0.tgz", @@ -1180,46 +1094,20 @@ "is-extglob": "^2.1.1" } }, - "is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "requires": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - } - }, - "is-npm": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", - "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==" - }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" - }, "is-object": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", - "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", - "dev": true - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==" + "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==" }, "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==", - "dev": true + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==" }, "is-promise": { "version": "2.2.2", @@ -1231,27 +1119,15 @@ "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==" - }, "isarray": { "version": "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", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "js-beautify": { "version": "1.13.0", @@ -1269,16 +1145,15 @@ "version": "3.14.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", - "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" } }, - "json-buffer": { + "json-stringify-safe": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-3.0.0.tgz", + "integrity": "sha1-nbew5TDH8onF6MhDKvGRwv91pbM=" }, "jsonfile": { "version": "4.0.0", @@ -1291,24 +1166,7 @@ "just-extend": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz", - "integrity": "sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==", - "dev": true - }, - "keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "requires": { - "json-buffer": "3.0.0" - } - }, - "latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", - "requires": { - "package-json": "^6.3.0" - } + "integrity": "sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==" }, "locate-path": { "version": "3.0.0", @@ -1327,14 +1185,12 @@ "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", - "dev": true + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" }, "log-symbols": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", - "dev": true, "requires": { "chalk": "^4.0.0" } @@ -1344,11 +1200,6 @@ "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" - }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -1365,21 +1216,6 @@ "es5-ext": "~0.10.2" } }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } - }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -1405,6 +1241,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", @@ -1428,11 +1285,6 @@ "mime-db": "1.44.0" } }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" - }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -1455,7 +1307,6 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.0.tgz", "integrity": "sha512-lEWEMq2LMfNJMKeuEwb5UELi+OgFDollXaytR5ggQcHpzG3NP/R7rvixAvF+9/lLsTWhWG+4yD2M70GsM06nxw==", - "dev": true, "requires": { "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", @@ -1488,7 +1339,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "dev": true, "requires": { "ms": "2.1.2" } @@ -1497,7 +1347,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, "requires": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -1507,7 +1356,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, "requires": { "p-locate": "^5.0.0" } @@ -1515,14 +1363,12 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "p-limit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", - "dev": true, "requires": { "p-try": "^2.0.0" } @@ -1531,7 +1377,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, "requires": { "p-limit": "^3.0.2" } @@ -1539,16 +1384,14 @@ "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" } } }, "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 + "integrity": "sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=" }, "moment": { "version": "2.29.1", @@ -1568,6 +1411,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", @@ -1620,14 +1488,18 @@ "nanoid": { "version": "3.1.12", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz", - "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==", - "dev": true + "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==" }, "negotiator": { "version": "0.6.2", "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", @@ -1637,7 +1509,6 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/nise/-/nise-4.0.4.tgz", "integrity": "sha512-bTTRUNlemx6deJa+ZyoCUTRvH3liK5+N6VQZ4NIw90AgDXY6iPnsqplNFf6STcj+ePk0H/xqxnP75Lr0J0Fq3A==", - "dev": true, "requires": { "@sinonjs/commons": "^1.7.0", "@sinonjs/fake-timers": "^6.0.0", @@ -1650,63 +1521,12 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dev": true, "requires": { "isarray": "0.0.1" } } } }, - "nodemon": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.14.tgz", - "integrity": "sha512-frcpDx+PviKEQRSYzwhckuO2zoHcBYLHI754RE9z5h1RGtrngerc04mLpQQCPWBkH/2ObrX7We9YiwVSYZpFJQ==", - "requires": { - "chokidar": "^3.2.2", - "debug": "^3.2.6", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.0.4", - "pstree.remy": "^1.1.7", - "semver": "^5.7.1", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.3", - "update-notifier": "^5.1.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "requires": { - "ms": "^2.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, "nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -1720,10 +1540,15 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, - "normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==" + "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", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "on-finished": { "version": "2.3.0", @@ -1733,6 +1558,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", @@ -1741,11 +1571,6 @@ "wrappy": "1" } }, - "p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==" - }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -1767,29 +1592,38 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, - "package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "requires": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } + "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", "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", @@ -1813,18 +1647,99 @@ "pathval": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", - "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", - "dev": true + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=" + }, + "pause": { + "version": "0.0.1", + "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==" }, - "prepend-http": { + "postgres-array": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" + "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" + } + }, + "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", @@ -1844,7 +1759,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/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", @@ -1856,38 +1770,20 @@ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, - "pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "pupa": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", - "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", - "requires": { - "escape-goat": "^2.0.0" - } - }, "qs": { "version": "6.7.0", "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", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, "requires": { "safe-buffer": "^5.1.0" } @@ -1908,29 +1804,10 @@ "unpipe": "1.0.0" } }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" - } - } - }, "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==", - "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -1945,20 +1822,62 @@ "picomatch": "^2.2.1" } }, - "registry-auth-token": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", - "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", - "requires": { - "rc": "^1.2.8" - } - }, - "registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "requires": { - "rc": "^1.2.8" + "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": { @@ -1980,14 +1899,6 @@ "path-parse": "^1.0.6" } }, - "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "requires": { - "lowercase-keys": "^1.0.0" - } - }, "retry-as-promised": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-3.2.0.tgz", @@ -2011,21 +1922,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" }, - "semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", - "requires": { - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } - }, "send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", @@ -2111,7 +2007,6 @@ "version": "0.10.2", "resolved": "https://registry.npmjs.org/sequelize-mock/-/sequelize-mock-0.10.2.tgz", "integrity": "sha1-GdOXHM2utbhkFwwkznkqinHxRL0=", - "dev": true, "requires": { "bluebird": "^3.4.6", "inflection": "^1.10.0", @@ -2127,7 +2022,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", - "dev": true, "requires": { "randombytes": "^2.1.0" } @@ -2158,16 +2052,10 @@ "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" }, - "signal-exit": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz", - "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==" - }, "sinon": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.0.tgz", "integrity": "sha512-eSNXz1XMcGEMHw08NJXSyTHIu6qTCOiN8x9ODACmZpNQpr0aXTBXBnI4xTzQzR+TEpOmLiKowGf9flCuKIzsbw==", - "dev": true, "requires": { "@sinonjs/commons": "^1.8.1", "@sinonjs/fake-timers": "^6.0.1", @@ -2178,11 +2066,31 @@ "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", + "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", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "sqlstring": { "version": "2.3.2", @@ -2194,6 +2102,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", @@ -2208,7 +2121,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" }, @@ -2216,8 +2128,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==" } } }, @@ -2239,14 +2150,12 @@ "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" }, "superagent": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/superagent/-/superagent-6.1.0.tgz", "integrity": "sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg==", - "dev": true, "requires": { "component-emitter": "^1.3.0", "cookiejar": "^2.1.2", @@ -2265,7 +2174,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "dev": true, "requires": { "ms": "2.1.2" } @@ -2273,20 +2181,17 @@ "mime": { "version": "2.4.6", "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", - "dev": true + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "qs": { "version": "6.9.4", "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", - "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==", - "dev": true + "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==" } } }, @@ -2294,7 +2199,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/supertest/-/supertest-5.0.0.tgz", "integrity": "sha512-2JAWpPrUOZF4hHH5ZTCN2xjKXvJS3AEwPNXl0HUseHsfcXFvMy9kcsufIHCNAmQ5hlGCvgeAqaR5PBEouN3hlQ==", - "dev": true, "requires": { "methods": "1.1.2", "superagent": "6.1.0" @@ -2317,11 +2221,6 @@ "next-tick": "1" } }, - "to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==" - }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -2340,23 +2239,10 @@ "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=" }, - "touch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", - "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", - "requires": { - "nopt": "~1.0.10" - }, - "dependencies": { - "nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", - "requires": { - "abbrev": "1" - } - } - } + "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", @@ -2366,13 +2252,7 @@ "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==", - "dev": true - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" }, "type-is": { "version": "1.6.18", @@ -2383,12 +2263,23 @@ "mime-types": "~2.1.24" } }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "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 + }, + "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": { - "is-typedarray": "^1.0.0" + "random-bytes": "~1.0.0" } }, "umzug": { @@ -2399,19 +2290,6 @@ "bluebird": "^3.7.2" } }, - "undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" - }, - "unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "requires": { - "crypto-random-string": "^2.0.0" - } - }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -2422,50 +2300,10 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, - "update-notifier": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", - "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", - "requires": { - "boxen": "^5.0.0", - "chalk": "^4.1.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.4.0", - "is-npm": "^5.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.1.0", - "pupa": "^2.1.1", - "semver": "^7.3.4", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, - "dependencies": { - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "requires": { - "prepend-http": "^2.0.0" - } - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "utils-merge": { "version": "1.0.1", @@ -2491,7 +2329,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "requires": { "isexe": "^2.0.0" } @@ -2505,7 +2342,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, "requires": { "string-width": "^1.0.2 || 2" }, @@ -2513,14 +2349,12 @@ "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -2530,56 +2364,12 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, "requires": { "ansi-regex": "^3.0.0" } } } }, - "widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "requires": { - "string-width": "^4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - } - } - }, "wkx": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", @@ -2588,11 +2378,15 @@ "@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", - "integrity": "sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==", - "dev": true + "integrity": "sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==" }, "wrap-ansi": { "version": "5.1.0", @@ -2609,21 +2403,10 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==" + "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", @@ -2665,7 +2448,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, "requires": { "camelcase": "^6.0.0", "decamelize": "^4.0.0", @@ -2676,16 +2458,14 @@ "camelcase": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==" }, "decamelize": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==" } } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index ec0db71d..5fd95e91 100644 --- a/package.json +++ b/package.json @@ -12,18 +12,29 @@ "url": "git+https://github.com/ALPHACamp/forum-express-grading.git" }, "dependencies": { - "express": "^4.17.1", - "mysql2": "^2.2.5", - "nodemon": "^2.0.12", - "sequelize": "^6.3.5", - "sequelize-cli": "^6.2.0" - }, - "devDependencies": { + "bcryptjs": "^2.4.3", + "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", + "moment": "^2.29.1", + "multer": "^1.4.3", + "mysql2": "^2.2.5", + "passport": "^0.5.0", + "passport-local": "^1.0.0", + "pg": "^8.7.1", "proxyquire": "^2.1.3", + "sequelize": "^6.3.5", + "sequelize-cli": "^6.2.0", "sequelize-mock": "^0.10.2", "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 fc55e842..cdfd043e 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,6 +1,79 @@ -module.exports = (app) => { +const restController = require('../controllers/restController.js') +const adminController = require('../controllers/adminController.js') +const userController = require('../controllers/userController.js') +const categoryController = require('../controllers/categoryController.js') +const commentController = require('../controllers/commentController.js') +const { get } = require('superagent') +const multer = require('multer') +const upload = multer({ dest: 'temp/' }) +const helpers = require('../_helpers') - app.get('/', (req, res) => { - res.send('Hello World!') - }) -} +module.exports = (app, passport) => { + + 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')) + //在 /restaurants 底下則交給 restController.getRestaurants 來處理 + app.get('/restaurants', authenticated, restController.getRestaurants) + app.get('/restaurants/feeds', authenticated, restController.getFeeds) + app.get('/restaurants/:id/dashboard', authenticated, restController.getDashBoard) + app.get('/restaurants/:id', authenticated, restController.getRestaurant) + 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) + + + + // 連到 /admin 頁面就轉到 /admin/restaurants + app.get('/admin', authenticatedAdmin, (req, res) => res.redirect('/admin/restaurants')) + // 在 /admin/restaurants 底下則交給 adminController.getRestaurants 處理 + app.get('/admin/restaurants', authenticatedAdmin, adminController.getRestaurants) + app.get('/admin/restaurants/create', authenticatedAdmin, adminController.createRestaurant) + app.post('/admin/restaurants', authenticatedAdmin, upload.single('image'), adminController.postRestaurant) + 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('/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) + + // categories admin + app.get('/admin/categories', authenticatedAdmin, categoryController.getCategories) + app.post('/admin/categories', authenticatedAdmin, categoryController.postCategories) + app.get('/admin/categories/:id', authenticatedAdmin, categoryController.getCategories) + app.put('/admin/categories/:id', authenticatedAdmin, categoryController.putCategory) + app.delete('/admin/categories/:id', authenticatedAdmin, categoryController.deleteCategory) + + // comment + + app.post('/comments', authenticated, commentController.postComment) + app.delete('/comments/:id', authenticatedAdmin, commentController.deleteComment) + + // profile + + 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) +} \ No newline at end of file diff --git a/seeders/20211111180715-users-seed-file.js b/seeders/20211111180715-users-seed-file.js new file mode 100644 index 00000000..cb31a38a --- /dev/null +++ b/seeders/20211111180715-users-seed-file.js @@ -0,0 +1,34 @@ +'use strict' +const bcrypt = require('bcryptjs') +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.bulkInsert('Users', [{ + id: 1, + email: 'root@example.com', + password: bcrypt.hashSync('12345678', bcrypt.genSaltSync(10), null), + isAdmin: true, + name: 'root', + createdAt: new Date(), + updatedAt: new Date() + }, { + id: 2, + email: 'user1@example.com', + password: bcrypt.hashSync('12345678', bcrypt.genSaltSync(10), null), + isAdmin: false, + name: 'user1', + createdAt: new Date(), + updatedAt: new Date() + }, { + id: 3, + 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, {}) + } +} \ No newline at end of file diff --git a/seeders/20211115192526-categories-seed-file.js b/seeders/20211115192526-categories-seed-file.js new file mode 100644 index 00000000..846e6a50 --- /dev/null +++ b/seeders/20211115192526-categories-seed-file.js @@ -0,0 +1,20 @@ +'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/20211115192606-restaurants-seed-file.js b/seeders/20211115192606-restaurants-seed-file.js new file mode 100644 index 00000000..ef9d9ff5 --- /dev/null +++ b/seeders/20211115192606-restaurants-seed-file.js @@ -0,0 +1,24 @@ +'use strict' +const faker = require('faker') +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.bulkInsert('Restaurants', + Array.from({ length: 50 }).map((d, i) => + ({ + id: i * 10 + 1, + 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(), + CategoryId: (Math.floor(Math.random() * 6)) * 10 + 1 + }) + ), {}) + }, + down: async (queryInterface, Sequelize) => { + await queryInterface.bulkDelete('Restaurants', null, {}) + } +}; diff --git a/seeders/20211120120220-comments-seed-file.js b/seeders/20211120120220-comments-seed-file.js new file mode 100644 index 00000000..5fd354c6 --- /dev/null +++ b/seeders/20211120120220-comments-seed-file.js @@ -0,0 +1,21 @@ +'use strict' +const faker = require('faker') + +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.bulkInsert('Comments', + Array.from({ length: 10 }).map((d, i) => + ({ + text: faker.name.findName(), + createdAt: new Date(), + updatedAt: new Date(), + UserId: (Math.floor(Math.random() * 3)) + 1, + RestaurantId: (Math.floor(Math.random() * 10)) * 10 + 1, + }) + ), {}) + }, + + down: async (queryInterface, Sequelize) => { + await queryInterface.bulkDelete('Comments', null, {}) + } +} \ No newline at end of file diff --git a/tests/R01.test.js b/tests/R01.test.js new file mode 100644 index 00000000..1fb32793 --- /dev/null +++ b/tests/R01.test.js @@ -0,0 +1,151 @@ +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 () { + // 以下測試會發出請求,測試資料庫內是否有作業指定的使用者資料 + // 測試資料的來源是真實的資料庫 + 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) { + request(app) + // 對 POST /signin 發出請求,參數是錯誤的帳號 + .post('/signin') + .type('urlencoded') + .send('email=tu&password=12345678') + // 期待登入驗證回應失敗,重新導向 /signin + .expect('Location', '/signin') + .expect(302, 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('# R01: 使用者權限管理', function () { + // 前置準備 + before(() => { + // 製作假資料 + // 本 context 會用這筆資料進行測試 + this.UserMock = createModelMock('User', { + id: 1, + email: 'root@example.com', + name: 'admin', + isAdmin: false, + }) + + // 修改 adminController 中的資料庫連線設定,由連向真實的資料庫 -> 改為連向模擬的 User table + this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) + }) + + // 開始測試 + context('# [顯示使用者清單]', () => { + it(' GET /admin/users ', async () => { + // 模擬 request & response + const req = mockRequest() // 對 GET /admin/users 發出請求 + const res = mockResponse() + + // 測試作業指定的 adminController.getUsers 函式 + 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') + }) + }) + + context('# [修改使用者權限] for admin', () => { + before(() => { + // 製作假資料 + // 本 context 會用這筆資料進行測試 + this.UserMock = createModelMock( + 'User', + { + id: 1, + email: 'root@example.com', + name: 'admin', + isAdmin: true, // 是管理者 + } + ) + + // 將 adminController 中的 User db 取代成 User mock db + this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) + }) + + 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() + + // 測試作業指定的 adminController.toggleAdmin 函式 + await this.adminController.toggleAdmin(req, res) + + // 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 = createModelMock( + 'User', + { + id: 1, + email: 'user@example.com', + name: 'user', + isAdmin: false, // 非管理者 + } + ) + // 將 adminController 中的 User db 取代成 User mock db + this.adminController = createControllerProxy('../controllers/adminController', { User: this.UserMock }) + }) + + 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() + + // 測試作業指定的 adminController.toggleAdmin 函式 + await this.adminController.toggleAdmin(req, res) + + // 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 執行完畢後,假資料中 id:1 使用者的應該要是 isAdmin:true + // 將假資料撈出,比對確認有成功修改到 + const user = await this.UserMock.findOne({ where: { id: 1 } }) + user.isAdmin.should.equal(true) + }) + }) + }) +}) diff --git a/tests/R02.test.js b/tests/R02.test.js new file mode 100644 index 00000000..26c927b3 --- /dev/null +++ b/tests/R02.test.js @@ -0,0 +1,154 @@ +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]', () => { + // 前置準備 + 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/R03.test.js b/tests/R03.test.js new file mode 100644 index 00000000..f415be50 --- /dev/null +++ b/tests/R03.test.js @@ -0,0 +1,59 @@ +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('# 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 new file mode 100644 index 00000000..5b000bbe --- /dev/null +++ b/tests/R04.test.js @@ -0,0 +1,115 @@ +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"); + +// 建立模擬的 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/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/categories.handlebars b/views/admin/categories.handlebars new file mode 100644 index 00000000..da36dd91 --- /dev/null +++ b/views/admin/categories.handlebars @@ -0,0 +1,52 @@ +
| # | +Category Name | +# | +
|---|---|---|
| {{this.id}} | +{{this.name}} | ++ + + | +
[{{ restaurant.Category.name}}] +
+{{restaurant.description}}
+| # | +Name | +Category | +# | +
|---|---|---|---|
| {{this.id}} | +{{this.name}} | +{{this.Category.name}} | ++ + + + | +
| # | +Name | +Role | + + +# | +|
|---|---|---|---|---|
| {{this.id}} | +{{this.name}} | +{{this.email}} | +{{#if this.isAdmin}}admin{{else}}user{{/if}} + | ++ + | > + + +
[{{restaurant.Category.name}}]
+ +{{this.text}}
+ by {{this.User.name}} + at {{moment this.createdAt}} +[{{restaurant.Category.name}}]
+{{restaurant.description}}
+ Dashboard + {{#if isFavorited }} + + {{else}} + + {{/if}} + {{#if isLike }} + + {{else}} + + {{/if}} +++{{this.User.name}}
+{{this.text}}
+ +
{{this.description}}
+ {{#if this.isFavorited }} + + {{else}} + + {{/if}} +