├── .example.env ├── .gitignore ├── README.md ├── app.js ├── assets └── no-image.jpg ├── controllers ├── auth.js ├── buscar.js ├── categorias.js ├── productos.js ├── uploads.js └── usuarios.js ├── database └── config.js ├── helpers ├── db-validators.js ├── generar-jwt.js ├── google-verify.js ├── index.js └── subir-archivo.js ├── middlewares ├── index.js ├── validar-archivo.js ├── validar-campos.js ├── validar-jwt.js └── validar-roles.js ├── models ├── categoria.js ├── index.js ├── producto.js ├── role.js ├── server.js └── usuario.js ├── package-lock.json ├── package.json ├── public └── index.html ├── routes ├── auth.js ├── buscar.js ├── categorias.js ├── productos.js ├── uploads.js └── usuarios.js └── uploads └── readme.md /.example.env: -------------------------------------------------------------------------------- 1 | PORT=8080 2 | MONGODB_CNN= 3 | SECRETORPRIVATEKEY= 4 | 5 | 6 | GOOGLE_CLIENT_ID= 7 | GOOGLE_SECRET_ID= 8 | 9 | 10 | CLOUDINARY_URL= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | 3 | node_modules/ 4 | 5 | .env 6 | 7 | uploads/productos/**.* 8 | uploads/usuarios/**.* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebServer + RestServer 2 | 3 | Recuerden que deben de ejecutar ```npm install``` para reconstruir los módulos de Node. 4 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const Server = require('./models/server'); 3 | 4 | 5 | const server = new Server(); 6 | 7 | 8 | 9 | server.listen(); -------------------------------------------------------------------------------- /assets/no-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Klerith/curso-node-restserver/61454a24dfdb6f97ec891d373fd1f30ae940dda8/assets/no-image.jpg -------------------------------------------------------------------------------- /controllers/auth.js: -------------------------------------------------------------------------------- 1 | const { response } = require('express'); 2 | const bcryptjs = require('bcryptjs') 3 | 4 | const Usuario = require('../models/usuario'); 5 | 6 | const { generarJWT } = require('../helpers/generar-jwt'); 7 | const { googleVerify } = require('../helpers/google-verify'); 8 | 9 | 10 | const login = async(req, res = response) => { 11 | 12 | const { correo, password } = req.body; 13 | 14 | try { 15 | 16 | // Verificar si el email existe 17 | const usuario = await Usuario.findOne({ correo }); 18 | if ( !usuario ) { 19 | return res.status(400).json({ 20 | msg: 'Usuario / Password no son correctos - correo' 21 | }); 22 | } 23 | 24 | // SI el usuario está activo 25 | if ( !usuario.estado ) { 26 | return res.status(400).json({ 27 | msg: 'Usuario / Password no son correctos - estado: false' 28 | }); 29 | } 30 | 31 | // Verificar la contraseña 32 | const validPassword = bcryptjs.compareSync( password, usuario.password ); 33 | if ( !validPassword ) { 34 | return res.status(400).json({ 35 | msg: 'Usuario / Password no son correctos - password' 36 | }); 37 | } 38 | 39 | // Generar el JWT 40 | const token = await generarJWT( usuario.id ); 41 | 42 | res.json({ 43 | usuario, 44 | token 45 | }) 46 | 47 | } catch (error) { 48 | console.log(error) 49 | res.status(500).json({ 50 | msg: 'Hable con el administrador' 51 | }); 52 | } 53 | 54 | } 55 | 56 | 57 | const googleSignin = async(req, res = response) => { 58 | 59 | const { id_token } = req.body; 60 | 61 | try { 62 | const { correo, nombre, img } = await googleVerify( id_token ); 63 | 64 | let usuario = await Usuario.findOne({ correo }); 65 | 66 | if ( !usuario ) { 67 | // Tengo que crearlo 68 | const data = { 69 | nombre, 70 | correo, 71 | password: ':P', 72 | img, 73 | google: true 74 | }; 75 | 76 | usuario = new Usuario( data ); 77 | await usuario.save(); 78 | } 79 | 80 | // Si el usuario en DB 81 | if ( !usuario.estado ) { 82 | return res.status(401).json({ 83 | msg: 'Hable con el administrador, usuario bloqueado' 84 | }); 85 | } 86 | 87 | // Generar el JWT 88 | const token = await generarJWT( usuario.id ); 89 | 90 | res.json({ 91 | usuario, 92 | token 93 | }); 94 | 95 | } catch (error) { 96 | 97 | res.status(400).json({ 98 | msg: 'Token de Google no es válido' 99 | }) 100 | 101 | } 102 | 103 | 104 | 105 | } 106 | 107 | 108 | 109 | module.exports = { 110 | login, 111 | googleSignin 112 | } 113 | -------------------------------------------------------------------------------- /controllers/buscar.js: -------------------------------------------------------------------------------- 1 | const { response } = require('express'); 2 | const { ObjectId } = require('mongoose').Types; 3 | 4 | const { Usuario, Categoria, Producto } = require('../models'); 5 | 6 | const coleccionesPermitidas = [ 7 | 'usuarios', 8 | 'categorias', 9 | 'productos', 10 | 'roles' 11 | ]; 12 | 13 | const buscarUsuarios = async( termino = '', res = response ) => { 14 | 15 | const esMongoID = ObjectId.isValid( termino ); // TRUE 16 | 17 | if ( esMongoID ) { 18 | const usuario = await Usuario.findById(termino); 19 | return res.json({ 20 | results: ( usuario ) ? [ usuario ] : [] 21 | }); 22 | } 23 | 24 | const regex = new RegExp( termino, 'i' ); 25 | const usuarios = await Usuario.find({ 26 | $or: [{ nombre: regex }, { correo: regex }], 27 | $and: [{ estado: true }] 28 | }); 29 | 30 | res.json({ 31 | results: usuarios 32 | }); 33 | 34 | } 35 | 36 | const buscarCategorias = async( termino = '', res = response ) => { 37 | 38 | const esMongoID = ObjectId.isValid( termino ); // TRUE 39 | 40 | if ( esMongoID ) { 41 | const categoria = await Categoria.findById(termino); 42 | return res.json({ 43 | results: ( categoria ) ? [ categoria ] : [] 44 | }); 45 | } 46 | 47 | const regex = new RegExp( termino, 'i' ); 48 | const categorias = await Categoria.find({ nombre: regex, estado: true }); 49 | 50 | res.json({ 51 | results: categorias 52 | }); 53 | 54 | } 55 | 56 | const buscarProductos = async( termino = '', res = response ) => { 57 | 58 | const esMongoID = ObjectId.isValid( termino ); // TRUE 59 | 60 | if ( esMongoID ) { 61 | const producto = await Producto.findById(termino) 62 | .populate('categoria','nombre'); 63 | return res.json({ 64 | results: ( producto ) ? [ producto ] : [] 65 | }); 66 | } 67 | 68 | const regex = new RegExp( termino, 'i' ); 69 | const productos = await Producto.find({ nombre: regex, estado: true }) 70 | .populate('categoria','nombre') 71 | 72 | res.json({ 73 | results: productos 74 | }); 75 | 76 | } 77 | 78 | 79 | const buscar = ( req, res = response ) => { 80 | 81 | const { coleccion, termino } = req.params; 82 | 83 | if ( !coleccionesPermitidas.includes( coleccion ) ) { 84 | return res.status(400).json({ 85 | msg: `Las colecciones permitidas son: ${ coleccionesPermitidas }` 86 | }) 87 | } 88 | 89 | switch (coleccion) { 90 | case 'usuarios': 91 | buscarUsuarios(termino, res); 92 | break; 93 | case 'categorias': 94 | buscarCategorias(termino, res); 95 | break; 96 | case 'productos': 97 | buscarProductos(termino, res); 98 | break; 99 | 100 | default: 101 | res.status(500).json({ 102 | msg: 'Se le olvido hacer esta búsquda' 103 | }) 104 | } 105 | 106 | } 107 | 108 | 109 | 110 | module.exports = { 111 | buscar 112 | } -------------------------------------------------------------------------------- /controllers/categorias.js: -------------------------------------------------------------------------------- 1 | const { response } = require('express'); 2 | const { Categoria } = require('../models'); 3 | 4 | 5 | const obtenerCategorias = async(req, res = response ) => { 6 | 7 | const { limite = 5, desde = 0 } = req.query; 8 | const query = { estado: true }; 9 | 10 | const [ total, categorias ] = await Promise.all([ 11 | Categoria.countDocuments(query), 12 | Categoria.find(query) 13 | .populate('usuario', 'nombre') 14 | .skip( Number( desde ) ) 15 | .limit(Number( limite )) 16 | ]); 17 | 18 | res.json({ 19 | total, 20 | categorias 21 | }); 22 | } 23 | 24 | const obtenerCategoria = async(req, res = response ) => { 25 | 26 | const { id } = req.params; 27 | const categoria = await Categoria.findById( id ) 28 | .populate('usuario', 'nombre'); 29 | 30 | res.json( categoria ); 31 | 32 | } 33 | 34 | const crearCategoria = async(req, res = response ) => { 35 | 36 | const nombre = req.body.nombre.toUpperCase(); 37 | 38 | const categoriaDB = await Categoria.findOne({ nombre }); 39 | 40 | if ( categoriaDB ) { 41 | return res.status(400).json({ 42 | msg: `La categoria ${ categoriaDB.nombre }, ya existe` 43 | }); 44 | } 45 | 46 | // Generar la data a guardar 47 | const data = { 48 | nombre, 49 | usuario: req.usuario._id 50 | } 51 | 52 | const categoria = new Categoria( data ); 53 | 54 | // Guardar DB 55 | await categoria.save(); 56 | 57 | res.status(201).json(categoria); 58 | 59 | } 60 | 61 | const actualizarCategoria = async( req, res = response ) => { 62 | 63 | const { id } = req.params; 64 | const { estado, usuario, ...data } = req.body; 65 | 66 | data.nombre = data.nombre.toUpperCase(); 67 | data.usuario = req.usuario._id; 68 | 69 | const categoria = await Categoria.findByIdAndUpdate(id, data, { new: true }); 70 | 71 | res.json( categoria ); 72 | 73 | } 74 | 75 | const borrarCategoria = async(req, res =response ) => { 76 | 77 | const { id } = req.params; 78 | const categoriaBorrada = await Categoria.findByIdAndUpdate( id, { estado: false }, {new: true }); 79 | 80 | res.json( categoriaBorrada ); 81 | } 82 | 83 | 84 | 85 | 86 | module.exports = { 87 | crearCategoria, 88 | obtenerCategorias, 89 | obtenerCategoria, 90 | actualizarCategoria, 91 | borrarCategoria 92 | } -------------------------------------------------------------------------------- /controllers/productos.js: -------------------------------------------------------------------------------- 1 | const { response } = require('express'); 2 | const { Producto } = require('../models'); 3 | 4 | 5 | const obtenerProductos = async(req, res = response ) => { 6 | 7 | const { limite = 5, desde = 0 } = req.query; 8 | const query = { estado: true }; 9 | 10 | const [ total, productos ] = await Promise.all([ 11 | Producto.countDocuments(query), 12 | Producto.find(query) 13 | .populate('usuario', 'nombre') 14 | .populate('categoria', 'nombre') 15 | .skip( Number( desde ) ) 16 | .limit(Number( limite )) 17 | ]); 18 | 19 | res.json({ 20 | total, 21 | productos 22 | }); 23 | } 24 | 25 | const obtenerProducto = async(req, res = response ) => { 26 | 27 | const { id } = req.params; 28 | const producto = await Producto.findById( id ) 29 | .populate('usuario', 'nombre') 30 | .populate('categoria', 'nombre'); 31 | 32 | res.json( producto ); 33 | 34 | } 35 | 36 | const crearProducto = async(req, res = response ) => { 37 | 38 | const { estado, usuario, ...body } = req.body; 39 | 40 | const productoDB = await Producto.findOne({ nombre: body.nombre }); 41 | 42 | if ( productoDB ) { 43 | return res.status(400).json({ 44 | msg: `El producto ${ productoDB.nombre }, ya existe` 45 | }); 46 | } 47 | 48 | // Generar la data a guardar 49 | const data = { 50 | ...body, 51 | nombre: body.nombre.toUpperCase(), 52 | usuario: req.usuario._id 53 | } 54 | 55 | const producto = new Producto( data ); 56 | 57 | // Guardar DB 58 | await producto.save(); 59 | 60 | res.status(201).json(producto); 61 | 62 | } 63 | 64 | const actualizarProducto = async( req, res = response ) => { 65 | 66 | const { id } = req.params; 67 | const { estado, usuario, ...data } = req.body; 68 | 69 | if( data.nombre ) { 70 | data.nombre = data.nombre.toUpperCase(); 71 | } 72 | 73 | data.usuario = req.usuario._id; 74 | 75 | const producto = await Producto.findByIdAndUpdate(id, data, { new: true }); 76 | 77 | res.json( producto ); 78 | 79 | } 80 | 81 | const borrarProducto = async(req, res = response ) => { 82 | 83 | const { id } = req.params; 84 | const productoBorrado = await Producto.findByIdAndUpdate( id, { estado: false }, {new: true }); 85 | 86 | res.json( productoBorrado ); 87 | } 88 | 89 | 90 | 91 | 92 | module.exports = { 93 | crearProducto, 94 | obtenerProductos, 95 | obtenerProducto, 96 | actualizarProducto, 97 | borrarProducto 98 | } -------------------------------------------------------------------------------- /controllers/uploads.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | 4 | const cloudinary = require('cloudinary').v2 5 | cloudinary.config( process.env.CLOUDINARY_URL ); 6 | 7 | const { response } = require('express'); 8 | const { subirArchivo } = require('../helpers'); 9 | 10 | const { Usuario, Producto } = require('../models'); 11 | 12 | 13 | const cargarArchivo = async(req, res = response) => { 14 | 15 | 16 | try { 17 | 18 | // txt, md 19 | // const nombre = await subirArchivo( req.files, ['txt','md'], 'textos' ); 20 | const nombre = await subirArchivo( req.files, undefined, 'imgs' ); 21 | res.json({ nombre }); 22 | 23 | } catch (msg) { 24 | res.status(400).json({ msg }); 25 | } 26 | 27 | } 28 | 29 | 30 | const actualizarImagen = async(req, res = response ) => { 31 | 32 | const { id, coleccion } = req.params; 33 | 34 | let modelo; 35 | 36 | switch ( coleccion ) { 37 | case 'usuarios': 38 | modelo = await Usuario.findById(id); 39 | if ( !modelo ) { 40 | return res.status(400).json({ 41 | msg: `No existe un usuario con el id ${ id }` 42 | }); 43 | } 44 | 45 | break; 46 | 47 | case 'productos': 48 | modelo = await Producto.findById(id); 49 | if ( !modelo ) { 50 | return res.status(400).json({ 51 | msg: `No existe un producto con el id ${ id }` 52 | }); 53 | } 54 | 55 | break; 56 | 57 | default: 58 | return res.status(500).json({ msg: 'Se me olvidó validar esto'}); 59 | } 60 | 61 | 62 | // Limpiar imágenes previas 63 | if ( modelo.img ) { 64 | // Hay que borrar la imagen del servidor 65 | const pathImagen = path.join( __dirname, '../uploads', coleccion, modelo.img ); 66 | if ( fs.existsSync( pathImagen ) ) { 67 | fs.unlinkSync( pathImagen ); 68 | } 69 | } 70 | 71 | 72 | const nombre = await subirArchivo( req.files, undefined, coleccion ); 73 | modelo.img = nombre; 74 | 75 | await modelo.save(); 76 | 77 | 78 | res.json( modelo ); 79 | 80 | } 81 | 82 | 83 | const actualizarImagenCloudinary = async(req, res = response ) => { 84 | 85 | const { id, coleccion } = req.params; 86 | 87 | let modelo; 88 | 89 | switch ( coleccion ) { 90 | case 'usuarios': 91 | modelo = await Usuario.findById(id); 92 | if ( !modelo ) { 93 | return res.status(400).json({ 94 | msg: `No existe un usuario con el id ${ id }` 95 | }); 96 | } 97 | 98 | break; 99 | 100 | case 'productos': 101 | modelo = await Producto.findById(id); 102 | if ( !modelo ) { 103 | return res.status(400).json({ 104 | msg: `No existe un producto con el id ${ id }` 105 | }); 106 | } 107 | 108 | break; 109 | 110 | default: 111 | return res.status(500).json({ msg: 'Se me olvidó validar esto'}); 112 | } 113 | 114 | 115 | // Limpiar imágenes previas 116 | if ( modelo.img ) { 117 | const nombreArr = modelo.img.split('/'); 118 | const nombre = nombreArr[ nombreArr.length - 1 ]; 119 | const [ public_id ] = nombre.split('.'); 120 | cloudinary.uploader.destroy( public_id ); 121 | } 122 | 123 | 124 | const { tempFilePath } = req.files.archivo 125 | const { secure_url } = await cloudinary.uploader.upload( tempFilePath ); 126 | modelo.img = secure_url; 127 | 128 | await modelo.save(); 129 | 130 | 131 | res.json( modelo ); 132 | 133 | } 134 | 135 | const mostrarImagen = async(req, res = response ) => { 136 | 137 | const { id, coleccion } = req.params; 138 | 139 | let modelo; 140 | 141 | switch ( coleccion ) { 142 | case 'usuarios': 143 | modelo = await Usuario.findById(id); 144 | if ( !modelo ) { 145 | return res.status(400).json({ 146 | msg: `No existe un usuario con el id ${ id }` 147 | }); 148 | } 149 | 150 | break; 151 | 152 | case 'productos': 153 | modelo = await Producto.findById(id); 154 | if ( !modelo ) { 155 | return res.status(400).json({ 156 | msg: `No existe un producto con el id ${ id }` 157 | }); 158 | } 159 | 160 | break; 161 | 162 | default: 163 | return res.status(500).json({ msg: 'Se me olvidó validar esto'}); 164 | } 165 | 166 | 167 | // Limpiar imágenes previas 168 | if ( modelo.img ) { 169 | // Hay que borrar la imagen del servidor 170 | const pathImagen = path.join( __dirname, '../uploads', coleccion, modelo.img ); 171 | if ( fs.existsSync( pathImagen ) ) { 172 | return res.sendFile( pathImagen ) 173 | } 174 | } 175 | 176 | const pathImagen = path.join( __dirname, '../assets/no-image.jpg'); 177 | res.sendFile( pathImagen ); 178 | } 179 | 180 | 181 | 182 | 183 | module.exports = { 184 | cargarArchivo, 185 | actualizarImagen, 186 | mostrarImagen, 187 | actualizarImagenCloudinary 188 | } -------------------------------------------------------------------------------- /controllers/usuarios.js: -------------------------------------------------------------------------------- 1 | const { response, request } = require('express'); 2 | const bcryptjs = require('bcryptjs'); 3 | 4 | 5 | const Usuario = require('../models/usuario'); 6 | 7 | 8 | 9 | const usuariosGet = async(req = request, res = response) => { 10 | 11 | const { limite = 5, desde = 0 } = req.query; 12 | const query = { estado: true }; 13 | 14 | const [ total, usuarios ] = await Promise.all([ 15 | Usuario.countDocuments(query), 16 | Usuario.find(query) 17 | .skip( Number( desde ) ) 18 | .limit(Number( limite )) 19 | ]); 20 | 21 | res.json({ 22 | total, 23 | usuarios 24 | }); 25 | } 26 | 27 | const usuariosPost = async(req, res = response) => { 28 | 29 | const { nombre, correo, password, rol } = req.body; 30 | const usuario = new Usuario({ nombre, correo, password, rol }); 31 | 32 | // Encriptar la contraseña 33 | const salt = bcryptjs.genSaltSync(); 34 | usuario.password = bcryptjs.hashSync( password, salt ); 35 | 36 | // Guardar en BD 37 | await usuario.save(); 38 | 39 | res.json({ 40 | usuario 41 | }); 42 | } 43 | 44 | const usuariosPut = async(req, res = response) => { 45 | 46 | const { id } = req.params; 47 | const { _id, password, google, correo, ...resto } = req.body; 48 | 49 | if ( password ) { 50 | // Encriptar la contraseña 51 | const salt = bcryptjs.genSaltSync(); 52 | resto.password = bcryptjs.hashSync( password, salt ); 53 | } 54 | 55 | const usuario = await Usuario.findByIdAndUpdate( id, resto ); 56 | 57 | res.json(usuario); 58 | } 59 | 60 | const usuariosPatch = (req, res = response) => { 61 | res.json({ 62 | msg: 'patch API - usuariosPatch' 63 | }); 64 | } 65 | 66 | const usuariosDelete = async(req, res = response) => { 67 | 68 | const { id } = req.params; 69 | const usuario = await Usuario.findByIdAndUpdate( id, { estado: false } ); 70 | 71 | 72 | res.json(usuario); 73 | } 74 | 75 | 76 | 77 | 78 | module.exports = { 79 | usuariosGet, 80 | usuariosPost, 81 | usuariosPut, 82 | usuariosPatch, 83 | usuariosDelete, 84 | } -------------------------------------------------------------------------------- /database/config.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | 4 | 5 | const dbConnection = async() => { 6 | 7 | try { 8 | 9 | await mongoose.connect( process.env.MONGODB_CNN, { 10 | useNewUrlParser: true, 11 | useUnifiedTopology: true, 12 | useCreateIndex: true, 13 | useFindAndModify: false 14 | }); 15 | 16 | console.log('Base de datos online'); 17 | 18 | } catch (error) { 19 | console.log(error); 20 | throw new Error('Error a la hora de iniciar la base de datos'); 21 | } 22 | 23 | 24 | } 25 | 26 | 27 | 28 | module.exports = { 29 | dbConnection 30 | } 31 | -------------------------------------------------------------------------------- /helpers/db-validators.js: -------------------------------------------------------------------------------- 1 | const Role = require('../models/role'); 2 | const { Usuario, Categoria, Producto } = require('../models'); 3 | 4 | const esRoleValido = async(rol = '') => { 5 | 6 | const existeRol = await Role.findOne({ rol }); 7 | if ( !existeRol ) { 8 | throw new Error(`El rol ${ rol } no está registrado en la BD`); 9 | } 10 | } 11 | 12 | const emailExiste = async( correo = '' ) => { 13 | 14 | // Verificar si el correo existe 15 | const existeEmail = await Usuario.findOne({ correo }); 16 | if ( existeEmail ) { 17 | throw new Error(`El correo: ${ correo }, ya está registrado`); 18 | } 19 | } 20 | 21 | const existeUsuarioPorId = async( id ) => { 22 | 23 | // Verificar si el correo existe 24 | const existeUsuario = await Usuario.findById(id); 25 | if ( !existeUsuario ) { 26 | throw new Error(`El id no existe ${ id }`); 27 | } 28 | } 29 | 30 | /** 31 | * Categorias 32 | */ 33 | const existeCategoriaPorId = async( id ) => { 34 | 35 | // Verificar si el correo existe 36 | const existeCategoria = await Categoria.findById(id); 37 | if ( !existeCategoria ) { 38 | throw new Error(`El id no existe ${ id }`); 39 | } 40 | } 41 | 42 | /** 43 | * Productos 44 | */ 45 | const existeProductoPorId = async( id ) => { 46 | 47 | // Verificar si el correo existe 48 | const existeProducto = await Producto.findById(id); 49 | if ( !existeProducto ) { 50 | throw new Error(`El id no existe ${ id }`); 51 | } 52 | } 53 | 54 | /** 55 | * Validar colecciones permitidas 56 | */ 57 | const coleccionesPermitidas = ( coleccion = '', colecciones = []) => { 58 | 59 | const incluida = colecciones.includes( coleccion ); 60 | if ( !incluida ) { 61 | throw new Error(`La colección ${ coleccion } no es permitida, ${ colecciones }`); 62 | } 63 | return true; 64 | } 65 | 66 | 67 | module.exports = { 68 | esRoleValido, 69 | emailExiste, 70 | existeUsuarioPorId, 71 | existeCategoriaPorId, 72 | existeProductoPorId, 73 | coleccionesPermitidas 74 | } 75 | 76 | -------------------------------------------------------------------------------- /helpers/generar-jwt.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken'); 2 | 3 | 4 | 5 | const generarJWT = ( uid = '' ) => { 6 | 7 | return new Promise( (resolve, reject) => { 8 | 9 | const payload = { uid }; 10 | 11 | jwt.sign( payload, process.env.SECRETORPRIVATEKEY, { 12 | expiresIn: '4h' 13 | }, ( err, token ) => { 14 | 15 | if ( err ) { 16 | console.log(err); 17 | reject( 'No se pudo generar el token' ) 18 | } else { 19 | resolve( token ); 20 | } 21 | }) 22 | 23 | }) 24 | } 25 | 26 | 27 | 28 | 29 | module.exports = { 30 | generarJWT 31 | } 32 | 33 | -------------------------------------------------------------------------------- /helpers/google-verify.js: -------------------------------------------------------------------------------- 1 | const { OAuth2Client } = require('google-auth-library'); 2 | 3 | const client = new OAuth2Client( process.env.GOOGLE_CLIENT_ID ); 4 | 5 | const googleVerify = async( idToken = '' ) => { 6 | 7 | const ticket = await client.verifyIdToken({ 8 | idToken, 9 | audience: process.env.GOOGLE_CLIENT_ID, // Specify the CLIENT_ID of the app that accesses the backend 10 | // Or, if multiple clients access the backend: 11 | //[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3] 12 | }); 13 | 14 | const { name: nombre, 15 | picture: img, 16 | email: correo 17 | } = ticket.getPayload(); 18 | 19 | return { nombre, img, correo }; 20 | 21 | } 22 | 23 | 24 | module.exports = { 25 | googleVerify 26 | } -------------------------------------------------------------------------------- /helpers/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const dbValidators = require('./db-validators'); 4 | const generarJWT = require('./generar-jwt'); 5 | const googleVerify = require('./google-verify'); 6 | const subirArchivo = require('./subir-archivo'); 7 | 8 | 9 | module.exports = { 10 | ...dbValidators, 11 | ...generarJWT, 12 | ...googleVerify, 13 | ...subirArchivo, 14 | } -------------------------------------------------------------------------------- /helpers/subir-archivo.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { v4: uuidv4 } = require('uuid'); 3 | 4 | const subirArchivo = ( files, extensionesValidas = ['png','jpg','jpeg','gif'], carpeta = '' ) => { 5 | 6 | return new Promise( (resolve, reject) => { 7 | 8 | const { archivo } = files; 9 | const nombreCortado = archivo.name.split('.'); 10 | const extension = nombreCortado[ nombreCortado.length - 1 ]; 11 | 12 | // Validar la extension 13 | if ( !extensionesValidas.includes( extension ) ) { 14 | return reject(`La extensión ${ extension } no es permitida - ${ extensionesValidas }`); 15 | } 16 | 17 | const nombreTemp = uuidv4() + '.' + extension; 18 | const uploadPath = path.join( __dirname, '../uploads/', carpeta, nombreTemp ); 19 | 20 | archivo.mv(uploadPath, (err) => { 21 | if (err) { 22 | reject(err); 23 | } 24 | 25 | resolve( nombreTemp ); 26 | }); 27 | 28 | }); 29 | 30 | } 31 | 32 | 33 | 34 | module.exports = { 35 | subirArchivo 36 | } -------------------------------------------------------------------------------- /middlewares/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const validaCampos = require('../middlewares/validar-campos'); 4 | const validarJWT = require('../middlewares/validar-jwt'); 5 | const validaRoles = require('../middlewares/validar-roles'); 6 | const validarArchivo = require('../middlewares/validar-archivo'); 7 | 8 | module.exports = { 9 | ...validaCampos, 10 | ...validarJWT, 11 | ...validaRoles, 12 | ...validarArchivo 13 | } -------------------------------------------------------------------------------- /middlewares/validar-archivo.js: -------------------------------------------------------------------------------- 1 | const { response } = require("express") 2 | 3 | 4 | const validarArchivoSubir = (req, res = response, next ) => { 5 | 6 | if (!req.files || Object.keys(req.files).length === 0 || !req.files.archivo ) { 7 | return res.status(400).json({ 8 | msg: 'No hay archivos que subir - validarArchivoSubir' 9 | }); 10 | } 11 | 12 | next(); 13 | 14 | } 15 | 16 | 17 | module.exports = { 18 | validarArchivoSubir 19 | } 20 | -------------------------------------------------------------------------------- /middlewares/validar-campos.js: -------------------------------------------------------------------------------- 1 | const { validationResult } = require('express-validator'); 2 | 3 | 4 | const validarCampos = ( req, res, next ) => { 5 | 6 | const errors = validationResult(req); 7 | if( !errors.isEmpty() ){ 8 | return res.status(400).json(errors); 9 | } 10 | 11 | next(); 12 | } 13 | 14 | 15 | 16 | module.exports = { 17 | validarCampos 18 | } 19 | -------------------------------------------------------------------------------- /middlewares/validar-jwt.js: -------------------------------------------------------------------------------- 1 | const { response, request } = require('express'); 2 | const jwt = require('jsonwebtoken'); 3 | 4 | const Usuario = require('../models/usuario'); 5 | 6 | 7 | const validarJWT = async( req = request, res = response, next ) => { 8 | 9 | const token = req.header('x-token'); 10 | 11 | if ( !token ) { 12 | return res.status(401).json({ 13 | msg: 'No hay token en la petición' 14 | }); 15 | } 16 | 17 | try { 18 | 19 | const { uid } = jwt.verify( token, process.env.SECRETORPRIVATEKEY ); 20 | 21 | // leer el usuario que corresponde al uid 22 | const usuario = await Usuario.findById( uid ); 23 | 24 | if( !usuario ) { 25 | return res.status(401).json({ 26 | msg: 'Token no válido - usuario no existe DB' 27 | }) 28 | } 29 | 30 | // Verificar si el uid tiene estado true 31 | if ( !usuario.estado ) { 32 | return res.status(401).json({ 33 | msg: 'Token no válido - usuario con estado: false' 34 | }) 35 | } 36 | 37 | 38 | req.usuario = usuario; 39 | next(); 40 | 41 | } catch (error) { 42 | 43 | console.log(error); 44 | res.status(401).json({ 45 | msg: 'Token no válido' 46 | }) 47 | } 48 | 49 | } 50 | 51 | 52 | 53 | 54 | module.exports = { 55 | validarJWT 56 | } -------------------------------------------------------------------------------- /middlewares/validar-roles.js: -------------------------------------------------------------------------------- 1 | const { response } = require('express') 2 | 3 | 4 | const esAdminRole = ( req, res = response, next ) => { 5 | 6 | if ( !req.usuario ) { 7 | return res.status(500).json({ 8 | msg: 'Se quiere verificar el role sin validar el token primero' 9 | }); 10 | } 11 | 12 | const { rol, nombre } = req.usuario; 13 | 14 | if ( rol !== 'ADMIN_ROLE' ) { 15 | return res.status(401).json({ 16 | msg: `${ nombre } no es administrador - No puede hacer esto` 17 | }); 18 | } 19 | 20 | next(); 21 | } 22 | 23 | 24 | const tieneRole = ( ...roles ) => { 25 | return (req, res = response, next) => { 26 | 27 | if ( !req.usuario ) { 28 | return res.status(500).json({ 29 | msg: 'Se quiere verificar el role sin validar el token primero' 30 | }); 31 | } 32 | 33 | if ( !roles.includes( req.usuario.rol ) ) { 34 | return res.status(401).json({ 35 | msg: `El servicio requiere uno de estos roles ${ roles }` 36 | }); 37 | } 38 | 39 | 40 | next(); 41 | } 42 | } 43 | 44 | 45 | 46 | module.exports = { 47 | esAdminRole, 48 | tieneRole 49 | } -------------------------------------------------------------------------------- /models/categoria.js: -------------------------------------------------------------------------------- 1 | const { Schema, model } = require('mongoose'); 2 | 3 | const CategoriaSchema = Schema({ 4 | nombre: { 5 | type: String, 6 | required: [true, 'El nombre es obligatorio'], 7 | unique: true 8 | }, 9 | estado: { 10 | type: Boolean, 11 | default: true, 12 | required: true 13 | }, 14 | usuario: { 15 | type: Schema.Types.ObjectId, 16 | ref: 'Usuario', 17 | required: true 18 | } 19 | }); 20 | 21 | 22 | CategoriaSchema.methods.toJSON = function() { 23 | const { __v, estado, ...data } = this.toObject(); 24 | return data; 25 | } 26 | 27 | 28 | module.exports = model( 'Categoria', CategoriaSchema ); 29 | -------------------------------------------------------------------------------- /models/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const Categoria = require('./categoria'); 4 | const Producto = require('./producto'); 5 | const Role = require('./role'); 6 | const Server = require('./server'); 7 | const Usuario = require('./usuario'); 8 | 9 | 10 | 11 | module.exports = { 12 | Categoria, 13 | Producto, 14 | Role, 15 | Server, 16 | Usuario, 17 | } 18 | 19 | -------------------------------------------------------------------------------- /models/producto.js: -------------------------------------------------------------------------------- 1 | const { Schema, model } = require('mongoose'); 2 | 3 | const ProductoSchema = Schema({ 4 | nombre: { 5 | type: String, 6 | required: [true, 'El nombre es obligatorio'], 7 | unique: true 8 | }, 9 | estado: { 10 | type: Boolean, 11 | default: true, 12 | required: true 13 | }, 14 | usuario: { 15 | type: Schema.Types.ObjectId, 16 | ref: 'Usuario', 17 | required: true 18 | }, 19 | precio: { 20 | type: Number, 21 | default: 0 22 | }, 23 | categoria: { 24 | type: Schema.Types.ObjectId, 25 | ref: 'Categoria', 26 | required: true 27 | }, 28 | descripcion: { type: String }, 29 | disponible: { type: Boolean, defult: true }, 30 | img: { type: String }, 31 | }); 32 | 33 | 34 | ProductoSchema.methods.toJSON = function() { 35 | const { __v, estado, ...data } = this.toObject(); 36 | return data; 37 | } 38 | 39 | 40 | module.exports = model( 'Producto', ProductoSchema ); 41 | -------------------------------------------------------------------------------- /models/role.js: -------------------------------------------------------------------------------- 1 | const { Schema, model } = require('mongoose'); 2 | 3 | const RoleSchema = Schema({ 4 | rol: { 5 | type: String, 6 | required: [true, 'El rol es obligatorio'] 7 | } 8 | }); 9 | 10 | 11 | module.exports = model( 'Role', RoleSchema ); 12 | -------------------------------------------------------------------------------- /models/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const cors = require('cors'); 3 | const fileUpload = require('express-fileupload'); 4 | 5 | const { dbConnection } = require('../database/config'); 6 | 7 | class Server { 8 | 9 | constructor() { 10 | this.app = express(); 11 | this.port = process.env.PORT; 12 | 13 | this.paths = { 14 | auth: '/api/auth', 15 | buscar: '/api/buscar', 16 | categorias: '/api/categorias', 17 | productos: '/api/productos', 18 | usuarios: '/api/usuarios', 19 | uploads: '/api/uploads', 20 | } 21 | 22 | 23 | // Conectar a base de datos 24 | this.conectarDB(); 25 | 26 | // Middlewares 27 | this.middlewares(); 28 | 29 | // Rutas de mi aplicación 30 | this.routes(); 31 | } 32 | 33 | async conectarDB() { 34 | await dbConnection(); 35 | } 36 | 37 | 38 | middlewares() { 39 | 40 | // CORS 41 | this.app.use( cors() ); 42 | 43 | // Lectura y parseo del body 44 | this.app.use( express.json() ); 45 | 46 | // Directorio Público 47 | this.app.use( express.static('public') ); 48 | 49 | // Fileupload - Carga de archivos 50 | this.app.use( fileUpload({ 51 | useTempFiles : true, 52 | tempFileDir : '/tmp/', 53 | createParentPath: true 54 | })); 55 | 56 | } 57 | 58 | routes() { 59 | 60 | this.app.use( this.paths.auth, require('../routes/auth')); 61 | this.app.use( this.paths.buscar, require('../routes/buscar')); 62 | this.app.use( this.paths.categorias, require('../routes/categorias')); 63 | this.app.use( this.paths.productos, require('../routes/productos')); 64 | this.app.use( this.paths.usuarios, require('../routes/usuarios')); 65 | this.app.use( this.paths.uploads, require('../routes/uploads')); 66 | 67 | } 68 | 69 | listen() { 70 | this.app.listen( this.port, () => { 71 | console.log('Servidor corriendo en puerto', this.port ); 72 | }); 73 | } 74 | 75 | } 76 | 77 | 78 | 79 | 80 | module.exports = Server; 81 | -------------------------------------------------------------------------------- /models/usuario.js: -------------------------------------------------------------------------------- 1 | 2 | const { Schema, model } = require('mongoose'); 3 | 4 | const UsuarioSchema = Schema({ 5 | nombre: { 6 | type: String, 7 | required: [true, 'El nombre es obligatorio'] 8 | }, 9 | correo: { 10 | type: String, 11 | required: [true, 'El correo es obligatorio'], 12 | unique: true 13 | }, 14 | password: { 15 | type: String, 16 | required: [true, 'La contraseña es obligatoria'], 17 | }, 18 | img: { 19 | type: String, 20 | }, 21 | rol: { 22 | type: String, 23 | required: true, 24 | default: 'USER_ROLE', 25 | emun: ['ADMIN_ROLE', 'USER_ROLE'] 26 | }, 27 | estado: { 28 | type: Boolean, 29 | default: true 30 | }, 31 | google: { 32 | type: Boolean, 33 | default: false 34 | }, 35 | }); 36 | 37 | 38 | 39 | UsuarioSchema.methods.toJSON = function() { 40 | const { __v, password, _id, ...usuario } = this.toObject(); 41 | usuario.uid = _id; 42 | return usuario; 43 | } 44 | 45 | module.exports = model( 'Usuario', UsuarioSchema ); 46 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "07-restserver", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/bson": { 8 | "version": "4.0.3", 9 | "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.3.tgz", 10 | "integrity": "sha512-mVRvYnTOZJz3ccpxhr3wgxVmSeiYinW+zlzQz3SXWaJmD1DuL05Jeq7nKw3SnbKmbleW5qrLG5vdyWe/A9sXhw==", 11 | "requires": { 12 | "@types/node": "*" 13 | } 14 | }, 15 | "@types/mongodb": { 16 | "version": "3.6.6", 17 | "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.6.tgz", 18 | "integrity": "sha512-ghYevKiSh/TGk2MAwSRZP7T1ilR9Pw8Fa7pT9GGVGZPUsWKdZjZ4G6LG3MqK2iXKdNba994F8W9ikA+qx2Eo3A==", 19 | "requires": { 20 | "@types/bson": "*", 21 | "@types/node": "*" 22 | } 23 | }, 24 | "@types/node": { 25 | "version": "14.14.25", 26 | "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.25.tgz", 27 | "integrity": "sha512-EPpXLOVqDvisVxtlbvzfyqSsFeQxltFbluZNRndIb8tr9KiBnYNLzrc1N3pyKUCww2RNrfHDViqDWWE1LCJQtQ==" 28 | }, 29 | "abort-controller": { 30 | "version": "3.0.0", 31 | "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", 32 | "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", 33 | "requires": { 34 | "event-target-shim": "^5.0.0" 35 | } 36 | }, 37 | "accepts": { 38 | "version": "1.3.7", 39 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 40 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 41 | "requires": { 42 | "mime-types": "~2.1.24", 43 | "negotiator": "0.6.2" 44 | } 45 | }, 46 | "agent-base": { 47 | "version": "6.0.2", 48 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", 49 | "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", 50 | "requires": { 51 | "debug": "4" 52 | }, 53 | "dependencies": { 54 | "debug": { 55 | "version": "4.3.1", 56 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", 57 | "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", 58 | "requires": { 59 | "ms": "2.1.2" 60 | } 61 | }, 62 | "ms": { 63 | "version": "2.1.2", 64 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 65 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 66 | } 67 | } 68 | }, 69 | "array-flatten": { 70 | "version": "1.1.1", 71 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 72 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 73 | }, 74 | "arrify": { 75 | "version": "2.0.1", 76 | "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", 77 | "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" 78 | }, 79 | "base64-js": { 80 | "version": "1.5.1", 81 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 82 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" 83 | }, 84 | "bcryptjs": { 85 | "version": "2.4.3", 86 | "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", 87 | "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" 88 | }, 89 | "bignumber.js": { 90 | "version": "9.0.1", 91 | "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", 92 | "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==" 93 | }, 94 | "bl": { 95 | "version": "2.2.1", 96 | "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", 97 | "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", 98 | "requires": { 99 | "readable-stream": "^2.3.5", 100 | "safe-buffer": "^5.1.1" 101 | } 102 | }, 103 | "bluebird": { 104 | "version": "3.5.1", 105 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", 106 | "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" 107 | }, 108 | "body-parser": { 109 | "version": "1.19.0", 110 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 111 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 112 | "requires": { 113 | "bytes": "3.1.0", 114 | "content-type": "~1.0.4", 115 | "debug": "2.6.9", 116 | "depd": "~1.1.2", 117 | "http-errors": "1.7.2", 118 | "iconv-lite": "0.4.24", 119 | "on-finished": "~2.3.0", 120 | "qs": "6.7.0", 121 | "raw-body": "2.4.0", 122 | "type-is": "~1.6.17" 123 | } 124 | }, 125 | "bson": { 126 | "version": "1.1.5", 127 | "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz", 128 | "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg==" 129 | }, 130 | "buffer-equal-constant-time": { 131 | "version": "1.0.1", 132 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", 133 | "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" 134 | }, 135 | "busboy": { 136 | "version": "0.3.1", 137 | "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.3.1.tgz", 138 | "integrity": "sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==", 139 | "requires": { 140 | "dicer": "0.3.0" 141 | } 142 | }, 143 | "bytes": { 144 | "version": "3.1.0", 145 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 146 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 147 | }, 148 | "cloudinary": { 149 | "version": "1.24.0", 150 | "resolved": "https://registry.npmjs.org/cloudinary/-/cloudinary-1.24.0.tgz", 151 | "integrity": "sha512-bILjdVB/FCv5Zyypuhp5IdKeoDDMbA/8ybq8kILV9At5l2AV3ZwdjvYSNF04hTg9+Zan1d5id6LmV5liGsx1bw==", 152 | "requires": { 153 | "cloudinary-core": "^2.10.2", 154 | "core-js": "3.6.5", 155 | "lodash": "^4.17.11", 156 | "q": "^1.5.1" 157 | } 158 | }, 159 | "cloudinary-core": { 160 | "version": "2.11.3", 161 | "resolved": "https://registry.npmjs.org/cloudinary-core/-/cloudinary-core-2.11.3.tgz", 162 | "integrity": "sha512-ZRnpjSgvx+LbSf+aEz5NKzxDB4Z0436aY/0BSDa90kAHiwAyd84VyEi95I74SE80e15Ri9t5S2xtksTXpzk9Xw==" 163 | }, 164 | "content-disposition": { 165 | "version": "0.5.3", 166 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 167 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 168 | "requires": { 169 | "safe-buffer": "5.1.2" 170 | } 171 | }, 172 | "content-type": { 173 | "version": "1.0.4", 174 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 175 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 176 | }, 177 | "cookie": { 178 | "version": "0.4.0", 179 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 180 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 181 | }, 182 | "cookie-signature": { 183 | "version": "1.0.6", 184 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 185 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 186 | }, 187 | "core-js": { 188 | "version": "3.6.5", 189 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", 190 | "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" 191 | }, 192 | "core-util-is": { 193 | "version": "1.0.2", 194 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 195 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 196 | }, 197 | "cors": { 198 | "version": "2.8.5", 199 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 200 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 201 | "requires": { 202 | "object-assign": "^4", 203 | "vary": "^1" 204 | } 205 | }, 206 | "debug": { 207 | "version": "2.6.9", 208 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 209 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 210 | "requires": { 211 | "ms": "2.0.0" 212 | } 213 | }, 214 | "denque": { 215 | "version": "1.5.0", 216 | "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz", 217 | "integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==" 218 | }, 219 | "depd": { 220 | "version": "1.1.2", 221 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 222 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 223 | }, 224 | "destroy": { 225 | "version": "1.0.4", 226 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 227 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 228 | }, 229 | "dicer": { 230 | "version": "0.3.0", 231 | "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", 232 | "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", 233 | "requires": { 234 | "streamsearch": "0.1.2" 235 | } 236 | }, 237 | "dotenv": { 238 | "version": "8.2.0", 239 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", 240 | "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" 241 | }, 242 | "ecdsa-sig-formatter": { 243 | "version": "1.0.11", 244 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", 245 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", 246 | "requires": { 247 | "safe-buffer": "^5.0.1" 248 | } 249 | }, 250 | "ee-first": { 251 | "version": "1.1.1", 252 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 253 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 254 | }, 255 | "encodeurl": { 256 | "version": "1.0.2", 257 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 258 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 259 | }, 260 | "escape-html": { 261 | "version": "1.0.3", 262 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 263 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 264 | }, 265 | "etag": { 266 | "version": "1.8.1", 267 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 268 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 269 | }, 270 | "event-target-shim": { 271 | "version": "5.0.1", 272 | "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", 273 | "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" 274 | }, 275 | "express": { 276 | "version": "4.17.1", 277 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 278 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 279 | "requires": { 280 | "accepts": "~1.3.7", 281 | "array-flatten": "1.1.1", 282 | "body-parser": "1.19.0", 283 | "content-disposition": "0.5.3", 284 | "content-type": "~1.0.4", 285 | "cookie": "0.4.0", 286 | "cookie-signature": "1.0.6", 287 | "debug": "2.6.9", 288 | "depd": "~1.1.2", 289 | "encodeurl": "~1.0.2", 290 | "escape-html": "~1.0.3", 291 | "etag": "~1.8.1", 292 | "finalhandler": "~1.1.2", 293 | "fresh": "0.5.2", 294 | "merge-descriptors": "1.0.1", 295 | "methods": "~1.1.2", 296 | "on-finished": "~2.3.0", 297 | "parseurl": "~1.3.3", 298 | "path-to-regexp": "0.1.7", 299 | "proxy-addr": "~2.0.5", 300 | "qs": "6.7.0", 301 | "range-parser": "~1.2.1", 302 | "safe-buffer": "5.1.2", 303 | "send": "0.17.1", 304 | "serve-static": "1.14.1", 305 | "setprototypeof": "1.1.1", 306 | "statuses": "~1.5.0", 307 | "type-is": "~1.6.18", 308 | "utils-merge": "1.0.1", 309 | "vary": "~1.1.2" 310 | } 311 | }, 312 | "express-fileupload": { 313 | "version": "1.2.1", 314 | "resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-1.2.1.tgz", 315 | "integrity": "sha512-fWPNAkBj+Azt9Itmcz/Reqdg3LeBfaXptDEev2JM8bCC0yDptglCnlizhf0YZauyU5X/g6v7v4Xxqhg8tmEfEA==", 316 | "requires": { 317 | "busboy": "^0.3.1" 318 | } 319 | }, 320 | "express-validator": { 321 | "version": "6.9.2", 322 | "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-6.9.2.tgz", 323 | "integrity": "sha512-Yqlsw2/uBobtBVkP+gnds8OMmVAEb3uTI4uXC93l0Ym5JGHgr8Vd4ws7oSo7GGYpWn5YCq4UePMEppKchURXrw==", 324 | "requires": { 325 | "lodash": "^4.17.20", 326 | "validator": "^13.5.2" 327 | } 328 | }, 329 | "extend": { 330 | "version": "3.0.2", 331 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 332 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" 333 | }, 334 | "fast-text-encoding": { 335 | "version": "1.0.3", 336 | "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz", 337 | "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==" 338 | }, 339 | "finalhandler": { 340 | "version": "1.1.2", 341 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 342 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 343 | "requires": { 344 | "debug": "2.6.9", 345 | "encodeurl": "~1.0.2", 346 | "escape-html": "~1.0.3", 347 | "on-finished": "~2.3.0", 348 | "parseurl": "~1.3.3", 349 | "statuses": "~1.5.0", 350 | "unpipe": "~1.0.0" 351 | } 352 | }, 353 | "forwarded": { 354 | "version": "0.1.2", 355 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 356 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 357 | }, 358 | "fresh": { 359 | "version": "0.5.2", 360 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 361 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 362 | }, 363 | "gaxios": { 364 | "version": "4.1.0", 365 | "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.1.0.tgz", 366 | "integrity": "sha512-vb0to8xzGnA2qcgywAjtshOKKVDf2eQhJoiL6fHhgW5tVN7wNk7egnYIO9zotfn3lQ3De1VPdf7V5/BWfCtCmg==", 367 | "requires": { 368 | "abort-controller": "^3.0.0", 369 | "extend": "^3.0.2", 370 | "https-proxy-agent": "^5.0.0", 371 | "is-stream": "^2.0.0", 372 | "node-fetch": "^2.3.0" 373 | } 374 | }, 375 | "gcp-metadata": { 376 | "version": "4.2.1", 377 | "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.2.1.tgz", 378 | "integrity": "sha512-tSk+REe5iq/N+K+SK1XjZJUrFPuDqGZVzCy2vocIHIGmPlTGsa8owXMJwGkrXr73NO0AzhPW4MF2DEHz7P2AVw==", 379 | "requires": { 380 | "gaxios": "^4.0.0", 381 | "json-bigint": "^1.0.0" 382 | } 383 | }, 384 | "google-auth-library": { 385 | "version": "6.1.6", 386 | "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz", 387 | "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==", 388 | "requires": { 389 | "arrify": "^2.0.0", 390 | "base64-js": "^1.3.0", 391 | "ecdsa-sig-formatter": "^1.0.11", 392 | "fast-text-encoding": "^1.0.0", 393 | "gaxios": "^4.0.0", 394 | "gcp-metadata": "^4.2.0", 395 | "gtoken": "^5.0.4", 396 | "jws": "^4.0.0", 397 | "lru-cache": "^6.0.0" 398 | }, 399 | "dependencies": { 400 | "jwa": { 401 | "version": "2.0.0", 402 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", 403 | "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", 404 | "requires": { 405 | "buffer-equal-constant-time": "1.0.1", 406 | "ecdsa-sig-formatter": "1.0.11", 407 | "safe-buffer": "^5.0.1" 408 | } 409 | }, 410 | "jws": { 411 | "version": "4.0.0", 412 | "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", 413 | "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", 414 | "requires": { 415 | "jwa": "^2.0.0", 416 | "safe-buffer": "^5.0.1" 417 | } 418 | } 419 | } 420 | }, 421 | "google-p12-pem": { 422 | "version": "3.0.3", 423 | "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.3.tgz", 424 | "integrity": "sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==", 425 | "requires": { 426 | "node-forge": "^0.10.0" 427 | } 428 | }, 429 | "gtoken": { 430 | "version": "5.2.1", 431 | "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.2.1.tgz", 432 | "integrity": "sha512-OY0BfPKe3QnMsY9MzTHTSKn+Vl2l1CcLe6BwDEQj00mbbkl5nyQ/7EUREstg4fQNZ8iYE7br4JJ7TdKeDOPWmw==", 433 | "requires": { 434 | "gaxios": "^4.0.0", 435 | "google-p12-pem": "^3.0.3", 436 | "jws": "^4.0.0" 437 | }, 438 | "dependencies": { 439 | "jwa": { 440 | "version": "2.0.0", 441 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", 442 | "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", 443 | "requires": { 444 | "buffer-equal-constant-time": "1.0.1", 445 | "ecdsa-sig-formatter": "1.0.11", 446 | "safe-buffer": "^5.0.1" 447 | } 448 | }, 449 | "jws": { 450 | "version": "4.0.0", 451 | "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", 452 | "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", 453 | "requires": { 454 | "jwa": "^2.0.0", 455 | "safe-buffer": "^5.0.1" 456 | } 457 | } 458 | } 459 | }, 460 | "http-errors": { 461 | "version": "1.7.2", 462 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 463 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 464 | "requires": { 465 | "depd": "~1.1.2", 466 | "inherits": "2.0.3", 467 | "setprototypeof": "1.1.1", 468 | "statuses": ">= 1.5.0 < 2", 469 | "toidentifier": "1.0.0" 470 | } 471 | }, 472 | "https-proxy-agent": { 473 | "version": "5.0.0", 474 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", 475 | "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", 476 | "requires": { 477 | "agent-base": "6", 478 | "debug": "4" 479 | }, 480 | "dependencies": { 481 | "debug": { 482 | "version": "4.3.1", 483 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", 484 | "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", 485 | "requires": { 486 | "ms": "2.1.2" 487 | } 488 | }, 489 | "ms": { 490 | "version": "2.1.2", 491 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 492 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 493 | } 494 | } 495 | }, 496 | "iconv-lite": { 497 | "version": "0.4.24", 498 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 499 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 500 | "requires": { 501 | "safer-buffer": ">= 2.1.2 < 3" 502 | } 503 | }, 504 | "inherits": { 505 | "version": "2.0.3", 506 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 507 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 508 | }, 509 | "ipaddr.js": { 510 | "version": "1.9.1", 511 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 512 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 513 | }, 514 | "is-stream": { 515 | "version": "2.0.0", 516 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", 517 | "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" 518 | }, 519 | "isarray": { 520 | "version": "1.0.0", 521 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 522 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 523 | }, 524 | "json-bigint": { 525 | "version": "1.0.0", 526 | "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", 527 | "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", 528 | "requires": { 529 | "bignumber.js": "^9.0.0" 530 | } 531 | }, 532 | "jsonwebtoken": { 533 | "version": "8.5.1", 534 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", 535 | "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", 536 | "requires": { 537 | "jws": "^3.2.2", 538 | "lodash.includes": "^4.3.0", 539 | "lodash.isboolean": "^3.0.3", 540 | "lodash.isinteger": "^4.0.4", 541 | "lodash.isnumber": "^3.0.3", 542 | "lodash.isplainobject": "^4.0.6", 543 | "lodash.isstring": "^4.0.1", 544 | "lodash.once": "^4.0.0", 545 | "ms": "^2.1.1", 546 | "semver": "^5.6.0" 547 | }, 548 | "dependencies": { 549 | "ms": { 550 | "version": "2.1.3", 551 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 552 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 553 | } 554 | } 555 | }, 556 | "jwa": { 557 | "version": "1.4.1", 558 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", 559 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", 560 | "requires": { 561 | "buffer-equal-constant-time": "1.0.1", 562 | "ecdsa-sig-formatter": "1.0.11", 563 | "safe-buffer": "^5.0.1" 564 | } 565 | }, 566 | "jws": { 567 | "version": "3.2.2", 568 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", 569 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", 570 | "requires": { 571 | "jwa": "^1.4.1", 572 | "safe-buffer": "^5.0.1" 573 | } 574 | }, 575 | "kareem": { 576 | "version": "2.3.2", 577 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", 578 | "integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==" 579 | }, 580 | "lodash": { 581 | "version": "4.17.20", 582 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", 583 | "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" 584 | }, 585 | "lodash.includes": { 586 | "version": "4.3.0", 587 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", 588 | "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" 589 | }, 590 | "lodash.isboolean": { 591 | "version": "3.0.3", 592 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", 593 | "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" 594 | }, 595 | "lodash.isinteger": { 596 | "version": "4.0.4", 597 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", 598 | "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" 599 | }, 600 | "lodash.isnumber": { 601 | "version": "3.0.3", 602 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", 603 | "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" 604 | }, 605 | "lodash.isplainobject": { 606 | "version": "4.0.6", 607 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", 608 | "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" 609 | }, 610 | "lodash.isstring": { 611 | "version": "4.0.1", 612 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", 613 | "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" 614 | }, 615 | "lodash.once": { 616 | "version": "4.1.1", 617 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", 618 | "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" 619 | }, 620 | "lru-cache": { 621 | "version": "6.0.0", 622 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 623 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 624 | "requires": { 625 | "yallist": "^4.0.0" 626 | } 627 | }, 628 | "media-typer": { 629 | "version": "0.3.0", 630 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 631 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 632 | }, 633 | "memory-pager": { 634 | "version": "1.5.0", 635 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", 636 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", 637 | "optional": true 638 | }, 639 | "merge-descriptors": { 640 | "version": "1.0.1", 641 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 642 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 643 | }, 644 | "methods": { 645 | "version": "1.1.2", 646 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 647 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 648 | }, 649 | "mime": { 650 | "version": "1.6.0", 651 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 652 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 653 | }, 654 | "mime-db": { 655 | "version": "1.45.0", 656 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", 657 | "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==" 658 | }, 659 | "mime-types": { 660 | "version": "2.1.28", 661 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz", 662 | "integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==", 663 | "requires": { 664 | "mime-db": "1.45.0" 665 | } 666 | }, 667 | "mongodb": { 668 | "version": "3.6.3", 669 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.3.tgz", 670 | "integrity": "sha512-rOZuR0QkodZiM+UbQE5kDsJykBqWi0CL4Ec2i1nrGrUI3KO11r6Fbxskqmq3JK2NH7aW4dcccBuUujAP0ERl5w==", 671 | "requires": { 672 | "bl": "^2.2.1", 673 | "bson": "^1.1.4", 674 | "denque": "^1.4.1", 675 | "require_optional": "^1.0.1", 676 | "safe-buffer": "^5.1.2", 677 | "saslprep": "^1.0.0" 678 | } 679 | }, 680 | "mongoose": { 681 | "version": "5.11.15", 682 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.11.15.tgz", 683 | "integrity": "sha512-8T4bT6eCGB7MqCm40oVhnhT/1AyAdwe+y1rYUhdl3ljsks3BpYz8whZgcMkIoh6VoCCjipOXRqZqdk1UByvlYA==", 684 | "requires": { 685 | "@types/mongodb": "^3.5.27", 686 | "bson": "^1.1.4", 687 | "kareem": "2.3.2", 688 | "mongodb": "3.6.3", 689 | "mongoose-legacy-pluralize": "1.0.2", 690 | "mpath": "0.8.3", 691 | "mquery": "3.2.3", 692 | "ms": "2.1.2", 693 | "regexp-clone": "1.0.0", 694 | "safe-buffer": "5.2.1", 695 | "sift": "7.0.1", 696 | "sliced": "1.0.1" 697 | }, 698 | "dependencies": { 699 | "ms": { 700 | "version": "2.1.2", 701 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 702 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 703 | }, 704 | "safe-buffer": { 705 | "version": "5.2.1", 706 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 707 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 708 | } 709 | } 710 | }, 711 | "mongoose-legacy-pluralize": { 712 | "version": "1.0.2", 713 | "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", 714 | "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==" 715 | }, 716 | "mpath": { 717 | "version": "0.8.3", 718 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.3.tgz", 719 | "integrity": "sha512-eb9rRvhDltXVNL6Fxd2zM9D4vKBxjVVQNLNijlj7uoXUy19zNDsIif5zR+pWmPCWNKwAtqyo4JveQm4nfD5+eA==" 720 | }, 721 | "mquery": { 722 | "version": "3.2.3", 723 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.3.tgz", 724 | "integrity": "sha512-cIfbP4TyMYX+SkaQ2MntD+F2XbqaBHUYWk3j+kqdDztPWok3tgyssOZxMHMtzbV1w9DaSlvEea0Iocuro41A4g==", 725 | "requires": { 726 | "bluebird": "3.5.1", 727 | "debug": "3.1.0", 728 | "regexp-clone": "^1.0.0", 729 | "safe-buffer": "5.1.2", 730 | "sliced": "1.0.1" 731 | }, 732 | "dependencies": { 733 | "debug": { 734 | "version": "3.1.0", 735 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 736 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 737 | "requires": { 738 | "ms": "2.0.0" 739 | } 740 | } 741 | } 742 | }, 743 | "ms": { 744 | "version": "2.0.0", 745 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 746 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 747 | }, 748 | "negotiator": { 749 | "version": "0.6.2", 750 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 751 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 752 | }, 753 | "node-fetch": { 754 | "version": "2.6.1", 755 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", 756 | "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" 757 | }, 758 | "node-forge": { 759 | "version": "0.10.0", 760 | "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", 761 | "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" 762 | }, 763 | "object-assign": { 764 | "version": "4.1.1", 765 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 766 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 767 | }, 768 | "on-finished": { 769 | "version": "2.3.0", 770 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 771 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 772 | "requires": { 773 | "ee-first": "1.1.1" 774 | } 775 | }, 776 | "parseurl": { 777 | "version": "1.3.3", 778 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 779 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 780 | }, 781 | "path-to-regexp": { 782 | "version": "0.1.7", 783 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 784 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 785 | }, 786 | "process-nextick-args": { 787 | "version": "2.0.1", 788 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 789 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 790 | }, 791 | "proxy-addr": { 792 | "version": "2.0.6", 793 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", 794 | "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", 795 | "requires": { 796 | "forwarded": "~0.1.2", 797 | "ipaddr.js": "1.9.1" 798 | } 799 | }, 800 | "q": { 801 | "version": "1.5.1", 802 | "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", 803 | "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" 804 | }, 805 | "qs": { 806 | "version": "6.7.0", 807 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 808 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 809 | }, 810 | "range-parser": { 811 | "version": "1.2.1", 812 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 813 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 814 | }, 815 | "raw-body": { 816 | "version": "2.4.0", 817 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 818 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 819 | "requires": { 820 | "bytes": "3.1.0", 821 | "http-errors": "1.7.2", 822 | "iconv-lite": "0.4.24", 823 | "unpipe": "1.0.0" 824 | } 825 | }, 826 | "readable-stream": { 827 | "version": "2.3.7", 828 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 829 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 830 | "requires": { 831 | "core-util-is": "~1.0.0", 832 | "inherits": "~2.0.3", 833 | "isarray": "~1.0.0", 834 | "process-nextick-args": "~2.0.0", 835 | "safe-buffer": "~5.1.1", 836 | "string_decoder": "~1.1.1", 837 | "util-deprecate": "~1.0.1" 838 | } 839 | }, 840 | "regexp-clone": { 841 | "version": "1.0.0", 842 | "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", 843 | "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==" 844 | }, 845 | "require_optional": { 846 | "version": "1.0.1", 847 | "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", 848 | "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", 849 | "requires": { 850 | "resolve-from": "^2.0.0", 851 | "semver": "^5.1.0" 852 | } 853 | }, 854 | "resolve-from": { 855 | "version": "2.0.0", 856 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", 857 | "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" 858 | }, 859 | "safe-buffer": { 860 | "version": "5.1.2", 861 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 862 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 863 | }, 864 | "safer-buffer": { 865 | "version": "2.1.2", 866 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 867 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 868 | }, 869 | "saslprep": { 870 | "version": "1.0.3", 871 | "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", 872 | "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", 873 | "optional": true, 874 | "requires": { 875 | "sparse-bitfield": "^3.0.3" 876 | } 877 | }, 878 | "semver": { 879 | "version": "5.7.1", 880 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 881 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 882 | }, 883 | "send": { 884 | "version": "0.17.1", 885 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 886 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 887 | "requires": { 888 | "debug": "2.6.9", 889 | "depd": "~1.1.2", 890 | "destroy": "~1.0.4", 891 | "encodeurl": "~1.0.2", 892 | "escape-html": "~1.0.3", 893 | "etag": "~1.8.1", 894 | "fresh": "0.5.2", 895 | "http-errors": "~1.7.2", 896 | "mime": "1.6.0", 897 | "ms": "2.1.1", 898 | "on-finished": "~2.3.0", 899 | "range-parser": "~1.2.1", 900 | "statuses": "~1.5.0" 901 | }, 902 | "dependencies": { 903 | "ms": { 904 | "version": "2.1.1", 905 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 906 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 907 | } 908 | } 909 | }, 910 | "serve-static": { 911 | "version": "1.14.1", 912 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 913 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 914 | "requires": { 915 | "encodeurl": "~1.0.2", 916 | "escape-html": "~1.0.3", 917 | "parseurl": "~1.3.3", 918 | "send": "0.17.1" 919 | } 920 | }, 921 | "setprototypeof": { 922 | "version": "1.1.1", 923 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 924 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 925 | }, 926 | "sift": { 927 | "version": "7.0.1", 928 | "resolved": "https://registry.npmjs.org/sift/-/sift-7.0.1.tgz", 929 | "integrity": "sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g==" 930 | }, 931 | "sliced": { 932 | "version": "1.0.1", 933 | "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", 934 | "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" 935 | }, 936 | "sparse-bitfield": { 937 | "version": "3.0.3", 938 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", 939 | "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", 940 | "optional": true, 941 | "requires": { 942 | "memory-pager": "^1.0.2" 943 | } 944 | }, 945 | "statuses": { 946 | "version": "1.5.0", 947 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 948 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 949 | }, 950 | "streamsearch": { 951 | "version": "0.1.2", 952 | "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", 953 | "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" 954 | }, 955 | "string_decoder": { 956 | "version": "1.1.1", 957 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 958 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 959 | "requires": { 960 | "safe-buffer": "~5.1.0" 961 | } 962 | }, 963 | "toidentifier": { 964 | "version": "1.0.0", 965 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 966 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 967 | }, 968 | "type-is": { 969 | "version": "1.6.18", 970 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 971 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 972 | "requires": { 973 | "media-typer": "0.3.0", 974 | "mime-types": "~2.1.24" 975 | } 976 | }, 977 | "unpipe": { 978 | "version": "1.0.0", 979 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 980 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 981 | }, 982 | "util-deprecate": { 983 | "version": "1.0.2", 984 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 985 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 986 | }, 987 | "utils-merge": { 988 | "version": "1.0.1", 989 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 990 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 991 | }, 992 | "uuid": { 993 | "version": "8.3.2", 994 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", 995 | "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" 996 | }, 997 | "validator": { 998 | "version": "13.5.2", 999 | "resolved": "https://registry.npmjs.org/validator/-/validator-13.5.2.tgz", 1000 | "integrity": "sha512-mD45p0rvHVBlY2Zuy3F3ESIe1h5X58GPfAtslBjY7EtTqGquZTj+VX/J4RnHWN8FKq0C9WRVt1oWAcytWRuYLQ==" 1001 | }, 1002 | "vary": { 1003 | "version": "1.1.2", 1004 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1005 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 1006 | }, 1007 | "yallist": { 1008 | "version": "4.0.0", 1009 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 1010 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 1011 | } 1012 | } 1013 | } 1014 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "07-restserver", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node app" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "bcryptjs": "^2.4.3", 14 | "cloudinary": "^1.24.0", 15 | "cors": "^2.8.5", 16 | "dotenv": "^8.2.0", 17 | "express": "^4.17.1", 18 | "express-fileupload": "^1.2.1", 19 | "express-validator": "^6.9.2", 20 | "google-auth-library": "^6.1.6", 21 | "jsonwebtoken": "^8.5.1", 22 | "mongoose": "^5.11.15", 23 | "uuid": "^8.3.2" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |