├── .gitignore ├── README.md ├── TODO.md ├── package-lock.json ├── package.json ├── public ├── css │ └── main.css └── images │ ├── products │ ├── default-product-image.png │ ├── product-1578923355448.jpg │ └── product-1578925078790.jpg │ └── users │ ├── default-user-image.png │ ├── user-1578944461157.png │ └── user-1578949183440.png └── src ├── app.js ├── controllers ├── productsController.js ├── staticsController.js └── usersController.js ├── data ├── products.json ├── userTokens.json └── users.json ├── middlewares ├── auth.js ├── guestRoute.js └── userRoute.js ├── models └── jsonModel.js ├── routes ├── products.js ├── static.js └── users.js └── views ├── about.ejs ├── faq.ejs ├── index.ejs ├── partials ├── footer.ejs ├── head.ejs ├── main-nav.ejs └── user-nav.ejs ├── products ├── 404.ejs ├── create.ejs ├── detail.ejs ├── edit.ejs ├── index.ejs └── partials │ ├── formFields.ejs │ └── product.ejs └── users ├── 404.ejs ├── create.ejs ├── detail.ejs ├── edit.ejs ├── index.ejs ├── login.ejs └── partials ├── formFields.ejs └── user.ejs /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CRUD de productos basado en JSON hecho con Node 2 | 3 | A continuación se describe el proceso de desarrollo de manera resumida. 4 | 5 | Los cambios referentes a HTML o CSS no se mencionan ya que el foco está en el funcionamiento de la aplicación y el entendimiento de Node. 6 | 7 | También se incluyen para mayor claridad las fuentes de datos que normalmente no estarían presentes en el repositorio. 8 | 9 | ## Pasos 10 | 11 | La mayoría de los pasos o grupos de pasos se coinciden con los commits del repositorio, de manera que es posible traerlos uno a uno para ver la progresión de manera más evidente. 12 | 13 | ### Iniciando el proyecto 14 | 1. Creamos el directorio e iniciamos el proyecto de node `npm init`. 15 | 2. Instalamos las librerías que vamos a estar utilizando, de momento Express y EJS `npm i express ejs`. 16 | 3. Creamos la carpeta **src** donde irá nuestro código. 17 | 4. Creamos el archivo **app.js** dentro de **src**, dentro requerimos Express e inicializamos un servidor con el método **listen()**. 18 | 19 | Corremos la aplicación y verificamos que el servidor corra correctamente. 20 | 21 | ### [Opcional] Creamos los scripts para correr la aplicación 22 | 1. Instalamos **nodemon** como dependencia de desarrollo `npm i nodemon --save-dev` 23 | 2. Agregamos el script de inicio normal `"run": "node src/app.js"` 24 | 3. Agregamos el script de inicio para desarrollo `"rundev": "npx nodemon src/app.js -e js,ejs"` 25 | Como vamos a trabajar con JSON y no queremos que nodemon reinicie la aplicación cada vez que los modifiquemos, le decimos que sólo mire las extensiones js y ejs. 26 | 27 | ### [Opcional] Preparamos el proyecto para utilizar GIT 28 | 1. Inicializamos el repositorio 29 | 2. Creamos el archivo **.gitignore** e incluimos el directorio de **node_modules/** 30 | 31 | ### Configuración de Express y vistas iniciales 32 | 1. Creamos nuestra carpeta de vistas **src/views** con un archivo **index.ejs** que servirá de página principal. 33 | 2. Configuramos Express para que utilice **EJS** como motor de plantillas y para que tome nuestra carpeta **src/views** como fuente de plantillas. 34 | 3. Creamos una ruta que carge nuestra página principal con el método **render()**. 35 | 36 | Verificamos que el servidor levante las vistas de EJS correctamente. 37 | 38 | **--- Fin del commit 1 ---** 39 | 40 | ### Estructura MVC, rutas y controladores 41 | 1. Creamos nuestra carpeta de rutas **src/routes** y un archivo **static.js**, que servirá para todas las páginas estáticas 42 | 2. Creamos nuestra carpeta de controladores **src/controllers** y un archivo **staticController.js** que implementará la funcionalidad de las rutas anteriores. 43 | 3. Creamos el método **index** en nuestro controllador que renderizará la plantilla de la página principal. 44 | 5. Creamos la ruta que responda a la raiz de nuestro sitio **/** e implementamos el método anterior 45 | 6. Reemplazamos la ruta de nuestro archivo **app.js** por nuestro nuevo archivo de rutas. 46 | 47 | Verificamos que el servidor siga levantando las vistas de EJS correctamente. 48 | 49 | ### Parciales y vistas adicionales 50 | 1. Creamos la carpeta de vistas parciales **views/partials** que utilizaremos para los componentes de las vistas. 51 | 2. Separamos el header y el footer en archivos parciales. 52 | 3. Los implementamos en nuestra vista con la directiva **include** de EJS 53 | 4. Creamos vistas adicionales e implementamos la misma estructura. 54 | - Rutas 55 | - Métodos del controlador 56 | - Parciales de EJS 57 | 58 | Verificamos que el servidor responda a las nuevas rutas. 59 | 60 | ### Recursos estáticos 61 | 1. Creamos la carpeta **public** donde pondremos nuestros recursos estáticos. 62 | 2. Creamos la carpeta **public/css** y un archivo **main.css** donde escribiremos algunos estilos básicos para nuestro sitio. 63 | 3. Configuramos Express para que tome la carpeta **public** como fuente de contenido estático. 64 | 4. Implementamos la hoja de estilo en nuestro parcial **head.ejs** 65 | 66 | Verificamos que el servidor haya tomado los estilos correctamente. 67 | 68 | **--- Fin del commit 2 ---** 69 | 70 | ### Agregamos la entidad de productos 71 | 1. Repetimos los pasos anteriores para los **productos** 72 | - Archivo de rutas **routes/products.js**, de momento solo la ruta del listado **/**. 73 | - Archivo de controllador **controllers/productsController.js**, de momento sólo el método **index**. 74 | - Carpeta de vistas **views/products** con la vista del listado **index.ejs**. 75 | - Estilos básicos para la vista 76 | - **app.use()** en **src/app.js** con las rutas de productos. 77 | 78 | **--- Fin del commit 3 ---** 79 | 80 | ### Colecciones de productos en formato JSON 81 | 1. Creamos la carpeta **src/data** donde pondremos nuestras colecciones en formato JSON. 82 | 2. Creamos el archivo **products.json** en la carpeta anterior, de momento con algunos productos agregados manualmente. 83 | 3. Creamos la carpeta **models** que guardará nuestros modelos, ellos interactuarán con las colecciones. 84 | 4. Creamos el modelo genérico para JSON **models/jsonModel.js**, en él tendremos dos métodos iniciales: 85 | - Lectura del archivo JSON 86 | - Traer todos los elementos 87 | 5. Actualizamos el controlador de productos para que utilize nuestro nuevo modelo y envíe los productos a la vista. 88 | 6. Actualizamos la vista para que muestre los productos de manera dinámica. 89 | 90 | **--- Fin del commit 4 ---** 91 | 92 | ### Detalle de productos 93 | 1. Detalle de producto: 94 | - Modelo: método find() 95 | - Ruta: **/productos/:id** (GET) 96 | - Controlador: método **show()** 97 | - Vista: **products/detail.ejs** 98 | - Error: **products/404.ejs** 99 | 100 | **--- Fin del commit 5 ---** 101 | 102 | ### Creación de productos 103 | 1. Como vamos a trabajar con formularios, necesitamos implementar dos middlewares de Express 104 | - express.urlencoded() 105 | - express.json() 106 | 2. Formulario de nuevo producto: 107 | - Ruta: **/productos/nuevo** (GET) 108 | - Controlador: método **create()** 109 | - Vista: **products/create.ejs** 110 | 3. Almacenamiento en la colección de productos 111 | - Modelo: método **generatePk()** y **save()** 112 | - Ruta: **/productos** (POST) 113 | - Controlador: método **store()** 114 | 115 | **--- Fin del commit 6 ---** 116 | 117 | ### Edición de productos 118 | 1. Formulario de edición producto: 119 | - Ruta: **/productos/:id/editar** (GET) 120 | - Controlador: método **edit()** 121 | - Vista: **products/edit.ejs** 122 | 2. Almacenamiento en la colección de productos: 123 | - Modelo: método **update()** 124 | - Ruta: **/productos** (PUT) 125 | - Controlador: método **update()** 126 | 3. Como vamos a trabajar con métodos HTTP no soportados por el navegador (primero PUT y luego DELETE) necesitamos implementar un nuevo módulo: 127 | - `npm i method-override` 128 | - Lo requerimos en nuestro **src/app.js** 129 | - Lo implementamos como middleware de aplicación. `app.use(methodOverride('_method'));` 130 | - Lo implementamos en el formulario a través del query string del request `?_method=PUT` 131 | 4. Como gran parte del contenido del formulario se repite para creación y edicion, generamos un parcial para esa parte. 132 | 133 | **--- Fin del commit 7 ---** 134 | 135 | ### Eliminación de productos 136 | 1. Eliminando en la colección de productos: 137 | - Modelo: método **destroy()** 138 | - Ruta: **/productos/:id** (DELETE) 139 | - Controlador: método **destroy()** 140 | - Vista: **products/detail.ejs**, formulario apuntando a la ruta correspondiente. 141 | 142 | **--- Fin del commit 8 ---** 143 | 144 | ### Subida de archivos 145 | 1. Preparamos el formulario de productos para que pueda subir archivos: 146 | - Agregamos el campo de archivo ``, en este caso cargaremos una imagen 147 | - Agregamos el atributo en codificación para el formulario **enctype="multipart/form-data"** 148 | - Creamos la carpeta donde guardaremos las imágenes **public/images/products/** 149 | - Agregamos una imagen por defecto para productos nuevos o sin imagen 150 | 2. Implementamos **multer** que nos servirá para procesar los archivos enviados: 151 | - `npm i multer` 152 | - Lo requerimos en nuestro archivo de rutas **src/routes/products.js** 153 | - Configuramos el almacenamiento en el disco rígido `const storage = multer.diskStorage({ ... });` 154 | - Ejecutamos multer `const upload = multer({ storage });` 155 | - Implementamos el middleware en las rutas que correspondan: **edit** y **update** 156 | - **Importante:** si trabajan en Mac o Linux, es posible que tengan que agregar permisos de escritura al directorio para poder subir archivos. 157 | 3. Sumamos el guardado de la imagen a los métodos **store** y **update** 158 | - En este caso el nombre de la imagen vendra en req.file.filename 159 | - Para considerar el caso en el que no nos llegue imagen, en el formulario de update sumamos un campo que guarde la imagen actual. 160 | 4. Sumamos la imagen al listado y detalle de productos. 161 | 162 | **--- Fin del commit 9 ---** 163 | 164 | ### Entidad de usuarios 165 | 1. Implementamos la entidad de usuarios replicando la de productos 166 | - Rutas: **src/routes/user.js** 167 | - Controlador: **src/controllers/userController.js** 168 | - Vistas: **src/views/users/** 169 | - Directorio para imágenes: **public/images/users/** 170 | - Coleccion: **src/data/users.json** 171 | 172 | **--- Fin del commit 10 ---** 173 | 174 | ### Login y perfil 175 | 1. Implementamos las vistas de registro (usamos el create que ya teníamos) y login 176 | 2. Implementamos las rutas correspondientes 177 | - **/usuarios/login** (GET y POST) 178 | - **/usuarios/logout** (GET) 179 | 3. Implementamos en el modelo, el método que nos permitirá buscar usuarios por email (o cualquier otro campo) 180 | 4. Implementamos la encriptación de contraseñas 181 | - `npm i bcrypt` 182 | - Lo requerimos en el controlador de usuarios 183 | - Lo implementamos en el método **create**, `bcrypt.hashSync(...)` 184 | - Lo implementamos en el método **login**, `bcrypt.compareSync(...)` 185 | 5. Implementamos el uso de sesiones 186 | - `npm i express-session` 187 | - Lo requerimos en **src/app.js** 188 | - Lo inicializamos con la configuración mínima sugerida `{ secret..., resave..., saveUninitialized...}` 189 | - Creamos la sesión al hacer el login y guardamos los datos del usuario en ella. 190 | - Destruimos la sesión al hacer logout 191 | 192 | **--- Fin del commit 11 ---** 193 | 194 | ### Middlewares de autenticación, rutas de huésped y rutas de usuario 195 | 196 | 1. Implementamos un middleware de autenticación 197 | El middleware se encargará de verificar si existe un usuario en sesión y en ese caso hará disponible su información para las vistas. 198 | - Creamos la carpeta **src/middlewares** 199 | - Creamos el middleware **src/middlewares/auth** 200 | - Lo implementamos en **src/app.js** 201 | - Modificamos la barra de navegación para que muestre los enlaces que correspondan según el usuario esté logeado o no. 202 | 2. Implementamos dos middlewares para tener rutas de 203 | - Creamos el middleware **src/middlewares/guestRoute** 204 | - Si un usuario accede a esta ruta, lo redirigimos 205 | - Creamos el middleware **src/middlewares/userRoute** 206 | - Si un huésped (alquien no logeado) accede a esta ruta, lo redirigimos 207 | - Los implementamos en **src/routes/users.js** 208 | 209 | **--- Fin del commit 12 ---** 210 | 211 | ## Implementamos la funcionalidad de recordar al usuario 212 | 213 | 1. Implementamos el módulo de manejo de cookies 214 | - `npm i cookie-parser` 215 | - Lo requerimos en **src/app.js** 216 | - Lo inicializamos con **app.use(...)** 217 | 2. Implementamos la funcionalidad para recordar al usuario 218 | - Modelo 219 | - Implementamos el método para traer todos los registros por campo 220 | - Controlador 221 | - Utilizamos el modulo **crypto** para generar un token seguro 222 | - Creamos la cookie con el token si llega el campo **remember** durante el **login** 223 | - Destruimos la cookie y el documento durante el **logout** 224 | - Coleccion: **src/data/userTokens.json** 225 | 3. Modificamos nuestro middleware de autenticación para que detecte la cookie y loguee al usuario 226 | 227 | **TODO:** Tal vez sería más prolijo a esta altura tener un controlador aparte para la autenticación. 228 | 229 | **--- Fin del commit 13 ---** 230 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # Pendientes y posibles agregados 2 | 3 | - Carpeta **storage** dentro de src con un link simbólico a public 4 | - Archivos de configuración **.env** 5 | - Implementar JSDOC --> https://devdocs.io/jsdoc/ 6 | 7 | - Generación de imágenes en diferentes tamaños 8 | 9 | # Hallazgos interesantes 10 | - Los middlewares se pasan como callbacks, no se ejecuta la función que define el middleware 11 | https://stackoverflow.com/questions/34696796/request-is-undefined-in-express-js -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json-another-crud", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "abbrev": { 8 | "version": "1.1.1", 9 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 10 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" 11 | }, 12 | "accepts": { 13 | "version": "1.3.7", 14 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 15 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 16 | "requires": { 17 | "mime-types": "~2.1.24", 18 | "negotiator": "0.6.2" 19 | } 20 | }, 21 | "ansi-align": { 22 | "version": "2.0.0", 23 | "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", 24 | "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", 25 | "dev": true, 26 | "requires": { 27 | "string-width": "^2.0.0" 28 | } 29 | }, 30 | "ansi-regex": { 31 | "version": "3.0.0", 32 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", 33 | "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" 34 | }, 35 | "ansi-styles": { 36 | "version": "3.2.1", 37 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 38 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 39 | "dev": true, 40 | "requires": { 41 | "color-convert": "^1.9.0" 42 | } 43 | }, 44 | "anymatch": { 45 | "version": "3.1.1", 46 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", 47 | "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", 48 | "dev": true, 49 | "requires": { 50 | "normalize-path": "^3.0.0", 51 | "picomatch": "^2.0.4" 52 | } 53 | }, 54 | "append-field": { 55 | "version": "1.0.0", 56 | "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", 57 | "integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY=" 58 | }, 59 | "aproba": { 60 | "version": "1.2.0", 61 | "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", 62 | "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" 63 | }, 64 | "are-we-there-yet": { 65 | "version": "1.1.5", 66 | "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", 67 | "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", 68 | "requires": { 69 | "delegates": "^1.0.0", 70 | "readable-stream": "^2.0.6" 71 | }, 72 | "dependencies": { 73 | "isarray": { 74 | "version": "1.0.0", 75 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 76 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 77 | }, 78 | "readable-stream": { 79 | "version": "2.3.7", 80 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 81 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 82 | "requires": { 83 | "core-util-is": "~1.0.0", 84 | "inherits": "~2.0.3", 85 | "isarray": "~1.0.0", 86 | "process-nextick-args": "~2.0.0", 87 | "safe-buffer": "~5.1.1", 88 | "string_decoder": "~1.1.1", 89 | "util-deprecate": "~1.0.1" 90 | } 91 | }, 92 | "string_decoder": { 93 | "version": "1.1.1", 94 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 95 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 96 | "requires": { 97 | "safe-buffer": "~5.1.0" 98 | } 99 | } 100 | } 101 | }, 102 | "array-flatten": { 103 | "version": "1.1.1", 104 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 105 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 106 | }, 107 | "balanced-match": { 108 | "version": "1.0.0", 109 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 110 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 111 | }, 112 | "bcrypt": { 113 | "version": "3.0.7", 114 | "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-3.0.7.tgz", 115 | "integrity": "sha512-K5UglF9VQvBMHl/1elNyyFvAfOY9Bj+rpKrCSR9sFwcW8FywAYJSRwTURNej5TaAK2TEJkcJ6r6lh1YPmspx5Q==", 116 | "requires": { 117 | "nan": "2.14.0", 118 | "node-pre-gyp": "0.13.0" 119 | } 120 | }, 121 | "binary-extensions": { 122 | "version": "2.0.0", 123 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", 124 | "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", 125 | "dev": true 126 | }, 127 | "body-parser": { 128 | "version": "1.19.0", 129 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 130 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 131 | "requires": { 132 | "bytes": "3.1.0", 133 | "content-type": "~1.0.4", 134 | "debug": "2.6.9", 135 | "depd": "~1.1.2", 136 | "http-errors": "1.7.2", 137 | "iconv-lite": "0.4.24", 138 | "on-finished": "~2.3.0", 139 | "qs": "6.7.0", 140 | "raw-body": "2.4.0", 141 | "type-is": "~1.6.17" 142 | } 143 | }, 144 | "boxen": { 145 | "version": "1.3.0", 146 | "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", 147 | "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", 148 | "dev": true, 149 | "requires": { 150 | "ansi-align": "^2.0.0", 151 | "camelcase": "^4.0.0", 152 | "chalk": "^2.0.1", 153 | "cli-boxes": "^1.0.0", 154 | "string-width": "^2.0.0", 155 | "term-size": "^1.2.0", 156 | "widest-line": "^2.0.0" 157 | } 158 | }, 159 | "brace-expansion": { 160 | "version": "1.1.11", 161 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 162 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 163 | "requires": { 164 | "balanced-match": "^1.0.0", 165 | "concat-map": "0.0.1" 166 | } 167 | }, 168 | "braces": { 169 | "version": "3.0.2", 170 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 171 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 172 | "dev": true, 173 | "requires": { 174 | "fill-range": "^7.0.1" 175 | } 176 | }, 177 | "buffer-from": { 178 | "version": "1.1.1", 179 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 180 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" 181 | }, 182 | "busboy": { 183 | "version": "0.2.14", 184 | "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", 185 | "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", 186 | "requires": { 187 | "dicer": "0.2.5", 188 | "readable-stream": "1.1.x" 189 | } 190 | }, 191 | "bytes": { 192 | "version": "3.1.0", 193 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 194 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 195 | }, 196 | "camelcase": { 197 | "version": "4.1.0", 198 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", 199 | "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", 200 | "dev": true 201 | }, 202 | "capture-stack-trace": { 203 | "version": "1.0.1", 204 | "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", 205 | "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==", 206 | "dev": true 207 | }, 208 | "chalk": { 209 | "version": "2.4.2", 210 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 211 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 212 | "dev": true, 213 | "requires": { 214 | "ansi-styles": "^3.2.1", 215 | "escape-string-regexp": "^1.0.5", 216 | "supports-color": "^5.3.0" 217 | } 218 | }, 219 | "chokidar": { 220 | "version": "3.3.1", 221 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz", 222 | "integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==", 223 | "dev": true, 224 | "requires": { 225 | "anymatch": "~3.1.1", 226 | "braces": "~3.0.2", 227 | "fsevents": "~2.1.2", 228 | "glob-parent": "~5.1.0", 229 | "is-binary-path": "~2.1.0", 230 | "is-glob": "~4.0.1", 231 | "normalize-path": "~3.0.0", 232 | "readdirp": "~3.3.0" 233 | } 234 | }, 235 | "chownr": { 236 | "version": "1.1.3", 237 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", 238 | "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==" 239 | }, 240 | "ci-info": { 241 | "version": "1.6.0", 242 | "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", 243 | "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", 244 | "dev": true 245 | }, 246 | "cli-boxes": { 247 | "version": "1.0.0", 248 | "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", 249 | "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", 250 | "dev": true 251 | }, 252 | "code-point-at": { 253 | "version": "1.1.0", 254 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 255 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" 256 | }, 257 | "color-convert": { 258 | "version": "1.9.3", 259 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 260 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 261 | "dev": true, 262 | "requires": { 263 | "color-name": "1.1.3" 264 | } 265 | }, 266 | "color-name": { 267 | "version": "1.1.3", 268 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 269 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 270 | "dev": true 271 | }, 272 | "concat-map": { 273 | "version": "0.0.1", 274 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 275 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 276 | }, 277 | "concat-stream": { 278 | "version": "1.6.2", 279 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", 280 | "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", 281 | "requires": { 282 | "buffer-from": "^1.0.0", 283 | "inherits": "^2.0.3", 284 | "readable-stream": "^2.2.2", 285 | "typedarray": "^0.0.6" 286 | }, 287 | "dependencies": { 288 | "isarray": { 289 | "version": "1.0.0", 290 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 291 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 292 | }, 293 | "readable-stream": { 294 | "version": "2.3.7", 295 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 296 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 297 | "requires": { 298 | "core-util-is": "~1.0.0", 299 | "inherits": "~2.0.3", 300 | "isarray": "~1.0.0", 301 | "process-nextick-args": "~2.0.0", 302 | "safe-buffer": "~5.1.1", 303 | "string_decoder": "~1.1.1", 304 | "util-deprecate": "~1.0.1" 305 | } 306 | }, 307 | "string_decoder": { 308 | "version": "1.1.1", 309 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 310 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 311 | "requires": { 312 | "safe-buffer": "~5.1.0" 313 | } 314 | } 315 | } 316 | }, 317 | "configstore": { 318 | "version": "3.1.2", 319 | "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", 320 | "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", 321 | "dev": true, 322 | "requires": { 323 | "dot-prop": "^4.1.0", 324 | "graceful-fs": "^4.1.2", 325 | "make-dir": "^1.0.0", 326 | "unique-string": "^1.0.0", 327 | "write-file-atomic": "^2.0.0", 328 | "xdg-basedir": "^3.0.0" 329 | } 330 | }, 331 | "console-control-strings": { 332 | "version": "1.1.0", 333 | "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", 334 | "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" 335 | }, 336 | "content-disposition": { 337 | "version": "0.5.3", 338 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 339 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 340 | "requires": { 341 | "safe-buffer": "5.1.2" 342 | } 343 | }, 344 | "content-type": { 345 | "version": "1.0.4", 346 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 347 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 348 | }, 349 | "cookie": { 350 | "version": "0.4.0", 351 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 352 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 353 | }, 354 | "cookie-parser": { 355 | "version": "1.4.4", 356 | "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.4.tgz", 357 | "integrity": "sha512-lo13tqF3JEtFO7FyA49CqbhaFkskRJ0u/UAiINgrIXeRCY41c88/zxtrECl8AKH3B0hj9q10+h3Kt8I7KlW4tw==", 358 | "requires": { 359 | "cookie": "0.3.1", 360 | "cookie-signature": "1.0.6" 361 | }, 362 | "dependencies": { 363 | "cookie": { 364 | "version": "0.3.1", 365 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 366 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 367 | } 368 | } 369 | }, 370 | "cookie-signature": { 371 | "version": "1.0.6", 372 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 373 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 374 | }, 375 | "core-util-is": { 376 | "version": "1.0.2", 377 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 378 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 379 | }, 380 | "create-error-class": { 381 | "version": "3.0.2", 382 | "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", 383 | "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", 384 | "dev": true, 385 | "requires": { 386 | "capture-stack-trace": "^1.0.0" 387 | } 388 | }, 389 | "cross-spawn": { 390 | "version": "5.1.0", 391 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", 392 | "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", 393 | "dev": true, 394 | "requires": { 395 | "lru-cache": "^4.0.1", 396 | "shebang-command": "^1.2.0", 397 | "which": "^1.2.9" 398 | } 399 | }, 400 | "crypto-random-string": { 401 | "version": "1.0.0", 402 | "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", 403 | "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", 404 | "dev": true 405 | }, 406 | "debug": { 407 | "version": "2.6.9", 408 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 409 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 410 | "requires": { 411 | "ms": "2.0.0" 412 | } 413 | }, 414 | "deep-extend": { 415 | "version": "0.6.0", 416 | "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", 417 | "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" 418 | }, 419 | "delegates": { 420 | "version": "1.0.0", 421 | "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", 422 | "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" 423 | }, 424 | "depd": { 425 | "version": "1.1.2", 426 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 427 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 428 | }, 429 | "destroy": { 430 | "version": "1.0.4", 431 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 432 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 433 | }, 434 | "detect-libc": { 435 | "version": "1.0.3", 436 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", 437 | "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" 438 | }, 439 | "dicer": { 440 | "version": "0.2.5", 441 | "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", 442 | "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", 443 | "requires": { 444 | "readable-stream": "1.1.x", 445 | "streamsearch": "0.1.2" 446 | } 447 | }, 448 | "dot-prop": { 449 | "version": "4.2.0", 450 | "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", 451 | "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", 452 | "dev": true, 453 | "requires": { 454 | "is-obj": "^1.0.0" 455 | } 456 | }, 457 | "duplexer3": { 458 | "version": "0.1.4", 459 | "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", 460 | "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", 461 | "dev": true 462 | }, 463 | "ee-first": { 464 | "version": "1.1.1", 465 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 466 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 467 | }, 468 | "ejs": { 469 | "version": "3.0.1", 470 | "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.0.1.tgz", 471 | "integrity": "sha512-cuIMtJwxvzumSAkqaaoGY/L6Fc/t6YvoP9/VIaK0V/CyqKLEQ8sqODmYfy/cjXEdZ9+OOL8TecbJu+1RsofGDw==" 472 | }, 473 | "encodeurl": { 474 | "version": "1.0.2", 475 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 476 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 477 | }, 478 | "escape-html": { 479 | "version": "1.0.3", 480 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 481 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 482 | }, 483 | "escape-string-regexp": { 484 | "version": "1.0.5", 485 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 486 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 487 | "dev": true 488 | }, 489 | "etag": { 490 | "version": "1.8.1", 491 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 492 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 493 | }, 494 | "execa": { 495 | "version": "0.7.0", 496 | "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", 497 | "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", 498 | "dev": true, 499 | "requires": { 500 | "cross-spawn": "^5.0.1", 501 | "get-stream": "^3.0.0", 502 | "is-stream": "^1.1.0", 503 | "npm-run-path": "^2.0.0", 504 | "p-finally": "^1.0.0", 505 | "signal-exit": "^3.0.0", 506 | "strip-eof": "^1.0.0" 507 | } 508 | }, 509 | "express": { 510 | "version": "4.17.1", 511 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 512 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 513 | "requires": { 514 | "accepts": "~1.3.7", 515 | "array-flatten": "1.1.1", 516 | "body-parser": "1.19.0", 517 | "content-disposition": "0.5.3", 518 | "content-type": "~1.0.4", 519 | "cookie": "0.4.0", 520 | "cookie-signature": "1.0.6", 521 | "debug": "2.6.9", 522 | "depd": "~1.1.2", 523 | "encodeurl": "~1.0.2", 524 | "escape-html": "~1.0.3", 525 | "etag": "~1.8.1", 526 | "finalhandler": "~1.1.2", 527 | "fresh": "0.5.2", 528 | "merge-descriptors": "1.0.1", 529 | "methods": "~1.1.2", 530 | "on-finished": "~2.3.0", 531 | "parseurl": "~1.3.3", 532 | "path-to-regexp": "0.1.7", 533 | "proxy-addr": "~2.0.5", 534 | "qs": "6.7.0", 535 | "range-parser": "~1.2.1", 536 | "safe-buffer": "5.1.2", 537 | "send": "0.17.1", 538 | "serve-static": "1.14.1", 539 | "setprototypeof": "1.1.1", 540 | "statuses": "~1.5.0", 541 | "type-is": "~1.6.18", 542 | "utils-merge": "1.0.1", 543 | "vary": "~1.1.2" 544 | } 545 | }, 546 | "express-session": { 547 | "version": "1.17.0", 548 | "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.0.tgz", 549 | "integrity": "sha512-t4oX2z7uoSqATbMfsxWMbNjAL0T5zpvcJCk3Z9wnPPN7ibddhnmDZXHfEcoBMG2ojKXZoCyPMc5FbtK+G7SoDg==", 550 | "requires": { 551 | "cookie": "0.4.0", 552 | "cookie-signature": "1.0.6", 553 | "debug": "2.6.9", 554 | "depd": "~2.0.0", 555 | "on-headers": "~1.0.2", 556 | "parseurl": "~1.3.3", 557 | "safe-buffer": "5.2.0", 558 | "uid-safe": "~2.1.5" 559 | }, 560 | "dependencies": { 561 | "depd": { 562 | "version": "2.0.0", 563 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 564 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" 565 | }, 566 | "safe-buffer": { 567 | "version": "5.2.0", 568 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", 569 | "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" 570 | } 571 | } 572 | }, 573 | "fill-range": { 574 | "version": "7.0.1", 575 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 576 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 577 | "dev": true, 578 | "requires": { 579 | "to-regex-range": "^5.0.1" 580 | } 581 | }, 582 | "finalhandler": { 583 | "version": "1.1.2", 584 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 585 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 586 | "requires": { 587 | "debug": "2.6.9", 588 | "encodeurl": "~1.0.2", 589 | "escape-html": "~1.0.3", 590 | "on-finished": "~2.3.0", 591 | "parseurl": "~1.3.3", 592 | "statuses": "~1.5.0", 593 | "unpipe": "~1.0.0" 594 | } 595 | }, 596 | "forwarded": { 597 | "version": "0.1.2", 598 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 599 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 600 | }, 601 | "fresh": { 602 | "version": "0.5.2", 603 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 604 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 605 | }, 606 | "fs-minipass": { 607 | "version": "1.2.7", 608 | "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", 609 | "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", 610 | "requires": { 611 | "minipass": "^2.6.0" 612 | } 613 | }, 614 | "fs.realpath": { 615 | "version": "1.0.0", 616 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 617 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 618 | }, 619 | "fsevents": { 620 | "version": "2.1.2", 621 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", 622 | "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", 623 | "dev": true, 624 | "optional": true 625 | }, 626 | "gauge": { 627 | "version": "2.7.4", 628 | "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", 629 | "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", 630 | "requires": { 631 | "aproba": "^1.0.3", 632 | "console-control-strings": "^1.0.0", 633 | "has-unicode": "^2.0.0", 634 | "object-assign": "^4.1.0", 635 | "signal-exit": "^3.0.0", 636 | "string-width": "^1.0.1", 637 | "strip-ansi": "^3.0.1", 638 | "wide-align": "^1.1.0" 639 | }, 640 | "dependencies": { 641 | "ansi-regex": { 642 | "version": "2.1.1", 643 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 644 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" 645 | }, 646 | "is-fullwidth-code-point": { 647 | "version": "1.0.0", 648 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 649 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 650 | "requires": { 651 | "number-is-nan": "^1.0.0" 652 | } 653 | }, 654 | "string-width": { 655 | "version": "1.0.2", 656 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 657 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 658 | "requires": { 659 | "code-point-at": "^1.0.0", 660 | "is-fullwidth-code-point": "^1.0.0", 661 | "strip-ansi": "^3.0.0" 662 | } 663 | }, 664 | "strip-ansi": { 665 | "version": "3.0.1", 666 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 667 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 668 | "requires": { 669 | "ansi-regex": "^2.0.0" 670 | } 671 | } 672 | } 673 | }, 674 | "get-stream": { 675 | "version": "3.0.0", 676 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", 677 | "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", 678 | "dev": true 679 | }, 680 | "glob": { 681 | "version": "7.1.6", 682 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 683 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 684 | "requires": { 685 | "fs.realpath": "^1.0.0", 686 | "inflight": "^1.0.4", 687 | "inherits": "2", 688 | "minimatch": "^3.0.4", 689 | "once": "^1.3.0", 690 | "path-is-absolute": "^1.0.0" 691 | } 692 | }, 693 | "glob-parent": { 694 | "version": "5.1.0", 695 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", 696 | "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", 697 | "dev": true, 698 | "requires": { 699 | "is-glob": "^4.0.1" 700 | } 701 | }, 702 | "global-dirs": { 703 | "version": "0.1.1", 704 | "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", 705 | "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", 706 | "dev": true, 707 | "requires": { 708 | "ini": "^1.3.4" 709 | } 710 | }, 711 | "got": { 712 | "version": "6.7.1", 713 | "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", 714 | "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", 715 | "dev": true, 716 | "requires": { 717 | "create-error-class": "^3.0.0", 718 | "duplexer3": "^0.1.4", 719 | "get-stream": "^3.0.0", 720 | "is-redirect": "^1.0.0", 721 | "is-retry-allowed": "^1.0.0", 722 | "is-stream": "^1.0.0", 723 | "lowercase-keys": "^1.0.0", 724 | "safe-buffer": "^5.0.1", 725 | "timed-out": "^4.0.0", 726 | "unzip-response": "^2.0.1", 727 | "url-parse-lax": "^1.0.0" 728 | } 729 | }, 730 | "graceful-fs": { 731 | "version": "4.2.3", 732 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", 733 | "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", 734 | "dev": true 735 | }, 736 | "has-flag": { 737 | "version": "3.0.0", 738 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 739 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 740 | "dev": true 741 | }, 742 | "has-unicode": { 743 | "version": "2.0.1", 744 | "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", 745 | "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" 746 | }, 747 | "http-errors": { 748 | "version": "1.7.2", 749 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 750 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 751 | "requires": { 752 | "depd": "~1.1.2", 753 | "inherits": "2.0.3", 754 | "setprototypeof": "1.1.1", 755 | "statuses": ">= 1.5.0 < 2", 756 | "toidentifier": "1.0.0" 757 | } 758 | }, 759 | "iconv-lite": { 760 | "version": "0.4.24", 761 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 762 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 763 | "requires": { 764 | "safer-buffer": ">= 2.1.2 < 3" 765 | } 766 | }, 767 | "ignore-by-default": { 768 | "version": "1.0.1", 769 | "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", 770 | "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", 771 | "dev": true 772 | }, 773 | "ignore-walk": { 774 | "version": "3.0.3", 775 | "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", 776 | "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", 777 | "requires": { 778 | "minimatch": "^3.0.4" 779 | } 780 | }, 781 | "import-lazy": { 782 | "version": "2.1.0", 783 | "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", 784 | "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", 785 | "dev": true 786 | }, 787 | "imurmurhash": { 788 | "version": "0.1.4", 789 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 790 | "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", 791 | "dev": true 792 | }, 793 | "inflight": { 794 | "version": "1.0.6", 795 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 796 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 797 | "requires": { 798 | "once": "^1.3.0", 799 | "wrappy": "1" 800 | } 801 | }, 802 | "inherits": { 803 | "version": "2.0.3", 804 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 805 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 806 | }, 807 | "ini": { 808 | "version": "1.3.5", 809 | "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", 810 | "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" 811 | }, 812 | "ipaddr.js": { 813 | "version": "1.9.0", 814 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", 815 | "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" 816 | }, 817 | "is-binary-path": { 818 | "version": "2.1.0", 819 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 820 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 821 | "dev": true, 822 | "requires": { 823 | "binary-extensions": "^2.0.0" 824 | } 825 | }, 826 | "is-ci": { 827 | "version": "1.2.1", 828 | "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", 829 | "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", 830 | "dev": true, 831 | "requires": { 832 | "ci-info": "^1.5.0" 833 | } 834 | }, 835 | "is-extglob": { 836 | "version": "2.1.1", 837 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 838 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", 839 | "dev": true 840 | }, 841 | "is-fullwidth-code-point": { 842 | "version": "2.0.0", 843 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 844 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" 845 | }, 846 | "is-glob": { 847 | "version": "4.0.1", 848 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", 849 | "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", 850 | "dev": true, 851 | "requires": { 852 | "is-extglob": "^2.1.1" 853 | } 854 | }, 855 | "is-installed-globally": { 856 | "version": "0.1.0", 857 | "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", 858 | "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", 859 | "dev": true, 860 | "requires": { 861 | "global-dirs": "^0.1.0", 862 | "is-path-inside": "^1.0.0" 863 | } 864 | }, 865 | "is-npm": { 866 | "version": "1.0.0", 867 | "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", 868 | "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=", 869 | "dev": true 870 | }, 871 | "is-number": { 872 | "version": "7.0.0", 873 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 874 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 875 | "dev": true 876 | }, 877 | "is-obj": { 878 | "version": "1.0.1", 879 | "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", 880 | "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", 881 | "dev": true 882 | }, 883 | "is-path-inside": { 884 | "version": "1.0.1", 885 | "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", 886 | "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", 887 | "dev": true, 888 | "requires": { 889 | "path-is-inside": "^1.0.1" 890 | } 891 | }, 892 | "is-redirect": { 893 | "version": "1.0.0", 894 | "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", 895 | "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", 896 | "dev": true 897 | }, 898 | "is-retry-allowed": { 899 | "version": "1.2.0", 900 | "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", 901 | "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", 902 | "dev": true 903 | }, 904 | "is-stream": { 905 | "version": "1.1.0", 906 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", 907 | "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", 908 | "dev": true 909 | }, 910 | "isarray": { 911 | "version": "0.0.1", 912 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 913 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" 914 | }, 915 | "isexe": { 916 | "version": "2.0.0", 917 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 918 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 919 | "dev": true 920 | }, 921 | "latest-version": { 922 | "version": "3.1.0", 923 | "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", 924 | "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", 925 | "dev": true, 926 | "requires": { 927 | "package-json": "^4.0.0" 928 | } 929 | }, 930 | "lowercase-keys": { 931 | "version": "1.0.1", 932 | "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", 933 | "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", 934 | "dev": true 935 | }, 936 | "lru-cache": { 937 | "version": "4.1.5", 938 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", 939 | "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", 940 | "dev": true, 941 | "requires": { 942 | "pseudomap": "^1.0.2", 943 | "yallist": "^2.1.2" 944 | } 945 | }, 946 | "make-dir": { 947 | "version": "1.3.0", 948 | "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", 949 | "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", 950 | "dev": true, 951 | "requires": { 952 | "pify": "^3.0.0" 953 | } 954 | }, 955 | "media-typer": { 956 | "version": "0.3.0", 957 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 958 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 959 | }, 960 | "merge-descriptors": { 961 | "version": "1.0.1", 962 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 963 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 964 | }, 965 | "method-override": { 966 | "version": "3.0.0", 967 | "resolved": "https://registry.npmjs.org/method-override/-/method-override-3.0.0.tgz", 968 | "integrity": "sha512-IJ2NNN/mSl9w3kzWB92rcdHpz+HjkxhDJWNDBqSlas+zQdP8wBiJzITPg08M/k2uVvMow7Sk41atndNtt/PHSA==", 969 | "requires": { 970 | "debug": "3.1.0", 971 | "methods": "~1.1.2", 972 | "parseurl": "~1.3.2", 973 | "vary": "~1.1.2" 974 | }, 975 | "dependencies": { 976 | "debug": { 977 | "version": "3.1.0", 978 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 979 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 980 | "requires": { 981 | "ms": "2.0.0" 982 | } 983 | } 984 | } 985 | }, 986 | "methods": { 987 | "version": "1.1.2", 988 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 989 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 990 | }, 991 | "mime": { 992 | "version": "1.6.0", 993 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 994 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 995 | }, 996 | "mime-db": { 997 | "version": "1.43.0", 998 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", 999 | "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" 1000 | }, 1001 | "mime-types": { 1002 | "version": "2.1.26", 1003 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", 1004 | "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", 1005 | "requires": { 1006 | "mime-db": "1.43.0" 1007 | } 1008 | }, 1009 | "minimatch": { 1010 | "version": "3.0.4", 1011 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 1012 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 1013 | "requires": { 1014 | "brace-expansion": "^1.1.7" 1015 | } 1016 | }, 1017 | "minimist": { 1018 | "version": "1.2.0", 1019 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 1020 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" 1021 | }, 1022 | "minipass": { 1023 | "version": "2.9.0", 1024 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", 1025 | "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", 1026 | "requires": { 1027 | "safe-buffer": "^5.1.2", 1028 | "yallist": "^3.0.0" 1029 | }, 1030 | "dependencies": { 1031 | "yallist": { 1032 | "version": "3.1.1", 1033 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", 1034 | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" 1035 | } 1036 | } 1037 | }, 1038 | "minizlib": { 1039 | "version": "1.3.3", 1040 | "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", 1041 | "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", 1042 | "requires": { 1043 | "minipass": "^2.9.0" 1044 | } 1045 | }, 1046 | "mkdirp": { 1047 | "version": "0.5.1", 1048 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 1049 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 1050 | "requires": { 1051 | "minimist": "0.0.8" 1052 | }, 1053 | "dependencies": { 1054 | "minimist": { 1055 | "version": "0.0.8", 1056 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 1057 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" 1058 | } 1059 | } 1060 | }, 1061 | "ms": { 1062 | "version": "2.0.0", 1063 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1064 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 1065 | }, 1066 | "multer": { 1067 | "version": "1.4.2", 1068 | "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.2.tgz", 1069 | "integrity": "sha512-xY8pX7V+ybyUpbYMxtjM9KAiD9ixtg5/JkeKUTD6xilfDv0vzzOFcCp4Ljb1UU3tSOM3VTZtKo63OmzOrGi3Cg==", 1070 | "requires": { 1071 | "append-field": "^1.0.0", 1072 | "busboy": "^0.2.11", 1073 | "concat-stream": "^1.5.2", 1074 | "mkdirp": "^0.5.1", 1075 | "object-assign": "^4.1.1", 1076 | "on-finished": "^2.3.0", 1077 | "type-is": "^1.6.4", 1078 | "xtend": "^4.0.0" 1079 | } 1080 | }, 1081 | "nan": { 1082 | "version": "2.14.0", 1083 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", 1084 | "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" 1085 | }, 1086 | "needle": { 1087 | "version": "2.4.0", 1088 | "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", 1089 | "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", 1090 | "requires": { 1091 | "debug": "^3.2.6", 1092 | "iconv-lite": "^0.4.4", 1093 | "sax": "^1.2.4" 1094 | }, 1095 | "dependencies": { 1096 | "debug": { 1097 | "version": "3.2.6", 1098 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", 1099 | "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", 1100 | "requires": { 1101 | "ms": "^2.1.1" 1102 | } 1103 | }, 1104 | "ms": { 1105 | "version": "2.1.2", 1106 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1107 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 1108 | } 1109 | } 1110 | }, 1111 | "negotiator": { 1112 | "version": "0.6.2", 1113 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 1114 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 1115 | }, 1116 | "node-pre-gyp": { 1117 | "version": "0.13.0", 1118 | "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.13.0.tgz", 1119 | "integrity": "sha512-Md1D3xnEne8b/HGVQkZZwV27WUi1ZRuZBij24TNaZwUPU3ZAFtvT6xxJGaUVillfmMKnn5oD1HoGsp2Ftik7SQ==", 1120 | "requires": { 1121 | "detect-libc": "^1.0.2", 1122 | "mkdirp": "^0.5.1", 1123 | "needle": "^2.2.1", 1124 | "nopt": "^4.0.1", 1125 | "npm-packlist": "^1.1.6", 1126 | "npmlog": "^4.0.2", 1127 | "rc": "^1.2.7", 1128 | "rimraf": "^2.6.1", 1129 | "semver": "^5.3.0", 1130 | "tar": "^4" 1131 | }, 1132 | "dependencies": { 1133 | "nopt": { 1134 | "version": "4.0.1", 1135 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", 1136 | "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", 1137 | "requires": { 1138 | "abbrev": "1", 1139 | "osenv": "^0.1.4" 1140 | } 1141 | } 1142 | } 1143 | }, 1144 | "nodemon": { 1145 | "version": "2.0.2", 1146 | "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.2.tgz", 1147 | "integrity": "sha512-GWhYPMfde2+M0FsHnggIHXTqPDHXia32HRhh6H0d75Mt9FKUoCBvumNHr7LdrpPBTKxsWmIEOjoN+P4IU6Hcaw==", 1148 | "dev": true, 1149 | "requires": { 1150 | "chokidar": "^3.2.2", 1151 | "debug": "^3.2.6", 1152 | "ignore-by-default": "^1.0.1", 1153 | "minimatch": "^3.0.4", 1154 | "pstree.remy": "^1.1.7", 1155 | "semver": "^5.7.1", 1156 | "supports-color": "^5.5.0", 1157 | "touch": "^3.1.0", 1158 | "undefsafe": "^2.0.2", 1159 | "update-notifier": "^2.5.0" 1160 | }, 1161 | "dependencies": { 1162 | "debug": { 1163 | "version": "3.2.6", 1164 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", 1165 | "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", 1166 | "dev": true, 1167 | "requires": { 1168 | "ms": "^2.1.1" 1169 | } 1170 | }, 1171 | "ms": { 1172 | "version": "2.1.2", 1173 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1174 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 1175 | "dev": true 1176 | } 1177 | } 1178 | }, 1179 | "nopt": { 1180 | "version": "1.0.10", 1181 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", 1182 | "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", 1183 | "dev": true, 1184 | "requires": { 1185 | "abbrev": "1" 1186 | } 1187 | }, 1188 | "normalize-path": { 1189 | "version": "3.0.0", 1190 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 1191 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 1192 | "dev": true 1193 | }, 1194 | "npm-bundled": { 1195 | "version": "1.1.1", 1196 | "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", 1197 | "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", 1198 | "requires": { 1199 | "npm-normalize-package-bin": "^1.0.1" 1200 | } 1201 | }, 1202 | "npm-normalize-package-bin": { 1203 | "version": "1.0.1", 1204 | "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", 1205 | "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" 1206 | }, 1207 | "npm-packlist": { 1208 | "version": "1.4.7", 1209 | "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.7.tgz", 1210 | "integrity": "sha512-vAj7dIkp5NhieaGZxBJB8fF4R0078rqsmhJcAfXZ6O7JJhjhPK96n5Ry1oZcfLXgfun0GWTZPOxaEyqv8GBykQ==", 1211 | "requires": { 1212 | "ignore-walk": "^3.0.1", 1213 | "npm-bundled": "^1.0.1" 1214 | } 1215 | }, 1216 | "npm-run-path": { 1217 | "version": "2.0.2", 1218 | "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", 1219 | "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", 1220 | "dev": true, 1221 | "requires": { 1222 | "path-key": "^2.0.0" 1223 | } 1224 | }, 1225 | "npmlog": { 1226 | "version": "4.1.2", 1227 | "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", 1228 | "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", 1229 | "requires": { 1230 | "are-we-there-yet": "~1.1.2", 1231 | "console-control-strings": "~1.1.0", 1232 | "gauge": "~2.7.3", 1233 | "set-blocking": "~2.0.0" 1234 | } 1235 | }, 1236 | "number-is-nan": { 1237 | "version": "1.0.1", 1238 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 1239 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" 1240 | }, 1241 | "object-assign": { 1242 | "version": "4.1.1", 1243 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1244 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 1245 | }, 1246 | "on-finished": { 1247 | "version": "2.3.0", 1248 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 1249 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 1250 | "requires": { 1251 | "ee-first": "1.1.1" 1252 | } 1253 | }, 1254 | "on-headers": { 1255 | "version": "1.0.2", 1256 | "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", 1257 | "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" 1258 | }, 1259 | "once": { 1260 | "version": "1.4.0", 1261 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1262 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1263 | "requires": { 1264 | "wrappy": "1" 1265 | } 1266 | }, 1267 | "os-homedir": { 1268 | "version": "1.0.2", 1269 | "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", 1270 | "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" 1271 | }, 1272 | "os-tmpdir": { 1273 | "version": "1.0.2", 1274 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 1275 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" 1276 | }, 1277 | "osenv": { 1278 | "version": "0.1.5", 1279 | "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", 1280 | "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", 1281 | "requires": { 1282 | "os-homedir": "^1.0.0", 1283 | "os-tmpdir": "^1.0.0" 1284 | } 1285 | }, 1286 | "p-finally": { 1287 | "version": "1.0.0", 1288 | "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", 1289 | "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", 1290 | "dev": true 1291 | }, 1292 | "package-json": { 1293 | "version": "4.0.1", 1294 | "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", 1295 | "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", 1296 | "dev": true, 1297 | "requires": { 1298 | "got": "^6.7.1", 1299 | "registry-auth-token": "^3.0.1", 1300 | "registry-url": "^3.0.3", 1301 | "semver": "^5.1.0" 1302 | } 1303 | }, 1304 | "parseurl": { 1305 | "version": "1.3.3", 1306 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1307 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 1308 | }, 1309 | "path-is-absolute": { 1310 | "version": "1.0.1", 1311 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1312 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 1313 | }, 1314 | "path-is-inside": { 1315 | "version": "1.0.2", 1316 | "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", 1317 | "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", 1318 | "dev": true 1319 | }, 1320 | "path-key": { 1321 | "version": "2.0.1", 1322 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", 1323 | "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", 1324 | "dev": true 1325 | }, 1326 | "path-to-regexp": { 1327 | "version": "0.1.7", 1328 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 1329 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 1330 | }, 1331 | "picomatch": { 1332 | "version": "2.2.1", 1333 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.1.tgz", 1334 | "integrity": "sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==", 1335 | "dev": true 1336 | }, 1337 | "pify": { 1338 | "version": "3.0.0", 1339 | "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", 1340 | "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", 1341 | "dev": true 1342 | }, 1343 | "prepend-http": { 1344 | "version": "1.0.4", 1345 | "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", 1346 | "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", 1347 | "dev": true 1348 | }, 1349 | "process-nextick-args": { 1350 | "version": "2.0.1", 1351 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 1352 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 1353 | }, 1354 | "proxy-addr": { 1355 | "version": "2.0.5", 1356 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", 1357 | "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", 1358 | "requires": { 1359 | "forwarded": "~0.1.2", 1360 | "ipaddr.js": "1.9.0" 1361 | } 1362 | }, 1363 | "pseudomap": { 1364 | "version": "1.0.2", 1365 | "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", 1366 | "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", 1367 | "dev": true 1368 | }, 1369 | "pstree.remy": { 1370 | "version": "1.1.7", 1371 | "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.7.tgz", 1372 | "integrity": "sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A==", 1373 | "dev": true 1374 | }, 1375 | "qs": { 1376 | "version": "6.7.0", 1377 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 1378 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 1379 | }, 1380 | "random-bytes": { 1381 | "version": "1.0.0", 1382 | "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", 1383 | "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" 1384 | }, 1385 | "range-parser": { 1386 | "version": "1.2.1", 1387 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1388 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 1389 | }, 1390 | "raw-body": { 1391 | "version": "2.4.0", 1392 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 1393 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 1394 | "requires": { 1395 | "bytes": "3.1.0", 1396 | "http-errors": "1.7.2", 1397 | "iconv-lite": "0.4.24", 1398 | "unpipe": "1.0.0" 1399 | } 1400 | }, 1401 | "rc": { 1402 | "version": "1.2.8", 1403 | "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", 1404 | "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", 1405 | "requires": { 1406 | "deep-extend": "^0.6.0", 1407 | "ini": "~1.3.0", 1408 | "minimist": "^1.2.0", 1409 | "strip-json-comments": "~2.0.1" 1410 | } 1411 | }, 1412 | "readable-stream": { 1413 | "version": "1.1.14", 1414 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", 1415 | "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", 1416 | "requires": { 1417 | "core-util-is": "~1.0.0", 1418 | "inherits": "~2.0.1", 1419 | "isarray": "0.0.1", 1420 | "string_decoder": "~0.10.x" 1421 | } 1422 | }, 1423 | "readdirp": { 1424 | "version": "3.3.0", 1425 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz", 1426 | "integrity": "sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==", 1427 | "dev": true, 1428 | "requires": { 1429 | "picomatch": "^2.0.7" 1430 | } 1431 | }, 1432 | "registry-auth-token": { 1433 | "version": "3.4.0", 1434 | "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", 1435 | "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", 1436 | "dev": true, 1437 | "requires": { 1438 | "rc": "^1.1.6", 1439 | "safe-buffer": "^5.0.1" 1440 | } 1441 | }, 1442 | "registry-url": { 1443 | "version": "3.1.0", 1444 | "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", 1445 | "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", 1446 | "dev": true, 1447 | "requires": { 1448 | "rc": "^1.0.1" 1449 | } 1450 | }, 1451 | "rimraf": { 1452 | "version": "2.7.1", 1453 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", 1454 | "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", 1455 | "requires": { 1456 | "glob": "^7.1.3" 1457 | } 1458 | }, 1459 | "safe-buffer": { 1460 | "version": "5.1.2", 1461 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1462 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 1463 | }, 1464 | "safer-buffer": { 1465 | "version": "2.1.2", 1466 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1467 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 1468 | }, 1469 | "sax": { 1470 | "version": "1.2.4", 1471 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", 1472 | "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" 1473 | }, 1474 | "semver": { 1475 | "version": "5.7.1", 1476 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 1477 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 1478 | }, 1479 | "semver-diff": { 1480 | "version": "2.1.0", 1481 | "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", 1482 | "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", 1483 | "dev": true, 1484 | "requires": { 1485 | "semver": "^5.0.3" 1486 | } 1487 | }, 1488 | "send": { 1489 | "version": "0.17.1", 1490 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 1491 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 1492 | "requires": { 1493 | "debug": "2.6.9", 1494 | "depd": "~1.1.2", 1495 | "destroy": "~1.0.4", 1496 | "encodeurl": "~1.0.2", 1497 | "escape-html": "~1.0.3", 1498 | "etag": "~1.8.1", 1499 | "fresh": "0.5.2", 1500 | "http-errors": "~1.7.2", 1501 | "mime": "1.6.0", 1502 | "ms": "2.1.1", 1503 | "on-finished": "~2.3.0", 1504 | "range-parser": "~1.2.1", 1505 | "statuses": "~1.5.0" 1506 | }, 1507 | "dependencies": { 1508 | "ms": { 1509 | "version": "2.1.1", 1510 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 1511 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 1512 | } 1513 | } 1514 | }, 1515 | "serve-static": { 1516 | "version": "1.14.1", 1517 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 1518 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 1519 | "requires": { 1520 | "encodeurl": "~1.0.2", 1521 | "escape-html": "~1.0.3", 1522 | "parseurl": "~1.3.3", 1523 | "send": "0.17.1" 1524 | } 1525 | }, 1526 | "set-blocking": { 1527 | "version": "2.0.0", 1528 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 1529 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" 1530 | }, 1531 | "setprototypeof": { 1532 | "version": "1.1.1", 1533 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 1534 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 1535 | }, 1536 | "shebang-command": { 1537 | "version": "1.2.0", 1538 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", 1539 | "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", 1540 | "dev": true, 1541 | "requires": { 1542 | "shebang-regex": "^1.0.0" 1543 | } 1544 | }, 1545 | "shebang-regex": { 1546 | "version": "1.0.0", 1547 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", 1548 | "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", 1549 | "dev": true 1550 | }, 1551 | "signal-exit": { 1552 | "version": "3.0.2", 1553 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", 1554 | "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" 1555 | }, 1556 | "statuses": { 1557 | "version": "1.5.0", 1558 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 1559 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 1560 | }, 1561 | "streamsearch": { 1562 | "version": "0.1.2", 1563 | "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", 1564 | "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" 1565 | }, 1566 | "string-width": { 1567 | "version": "2.1.1", 1568 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", 1569 | "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", 1570 | "requires": { 1571 | "is-fullwidth-code-point": "^2.0.0", 1572 | "strip-ansi": "^4.0.0" 1573 | } 1574 | }, 1575 | "string_decoder": { 1576 | "version": "0.10.31", 1577 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", 1578 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" 1579 | }, 1580 | "strip-ansi": { 1581 | "version": "4.0.0", 1582 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", 1583 | "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", 1584 | "requires": { 1585 | "ansi-regex": "^3.0.0" 1586 | } 1587 | }, 1588 | "strip-eof": { 1589 | "version": "1.0.0", 1590 | "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", 1591 | "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", 1592 | "dev": true 1593 | }, 1594 | "strip-json-comments": { 1595 | "version": "2.0.1", 1596 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 1597 | "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" 1598 | }, 1599 | "supports-color": { 1600 | "version": "5.5.0", 1601 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 1602 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 1603 | "dev": true, 1604 | "requires": { 1605 | "has-flag": "^3.0.0" 1606 | } 1607 | }, 1608 | "tar": { 1609 | "version": "4.4.13", 1610 | "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", 1611 | "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", 1612 | "requires": { 1613 | "chownr": "^1.1.1", 1614 | "fs-minipass": "^1.2.5", 1615 | "minipass": "^2.8.6", 1616 | "minizlib": "^1.2.1", 1617 | "mkdirp": "^0.5.0", 1618 | "safe-buffer": "^5.1.2", 1619 | "yallist": "^3.0.3" 1620 | }, 1621 | "dependencies": { 1622 | "yallist": { 1623 | "version": "3.1.1", 1624 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", 1625 | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" 1626 | } 1627 | } 1628 | }, 1629 | "term-size": { 1630 | "version": "1.2.0", 1631 | "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", 1632 | "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", 1633 | "dev": true, 1634 | "requires": { 1635 | "execa": "^0.7.0" 1636 | } 1637 | }, 1638 | "timed-out": { 1639 | "version": "4.0.1", 1640 | "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", 1641 | "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", 1642 | "dev": true 1643 | }, 1644 | "to-regex-range": { 1645 | "version": "5.0.1", 1646 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1647 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1648 | "dev": true, 1649 | "requires": { 1650 | "is-number": "^7.0.0" 1651 | } 1652 | }, 1653 | "toidentifier": { 1654 | "version": "1.0.0", 1655 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 1656 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 1657 | }, 1658 | "touch": { 1659 | "version": "3.1.0", 1660 | "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", 1661 | "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", 1662 | "dev": true, 1663 | "requires": { 1664 | "nopt": "~1.0.10" 1665 | } 1666 | }, 1667 | "type-is": { 1668 | "version": "1.6.18", 1669 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 1670 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1671 | "requires": { 1672 | "media-typer": "0.3.0", 1673 | "mime-types": "~2.1.24" 1674 | } 1675 | }, 1676 | "typedarray": { 1677 | "version": "0.0.6", 1678 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 1679 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" 1680 | }, 1681 | "uid-safe": { 1682 | "version": "2.1.5", 1683 | "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", 1684 | "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", 1685 | "requires": { 1686 | "random-bytes": "~1.0.0" 1687 | } 1688 | }, 1689 | "undefsafe": { 1690 | "version": "2.0.2", 1691 | "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz", 1692 | "integrity": "sha1-Il9rngM3Zj4Njnz9aG/Cg2zKznY=", 1693 | "dev": true, 1694 | "requires": { 1695 | "debug": "^2.2.0" 1696 | } 1697 | }, 1698 | "unique-string": { 1699 | "version": "1.0.0", 1700 | "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", 1701 | "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", 1702 | "dev": true, 1703 | "requires": { 1704 | "crypto-random-string": "^1.0.0" 1705 | } 1706 | }, 1707 | "unpipe": { 1708 | "version": "1.0.0", 1709 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1710 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 1711 | }, 1712 | "unzip-response": { 1713 | "version": "2.0.1", 1714 | "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", 1715 | "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", 1716 | "dev": true 1717 | }, 1718 | "update-notifier": { 1719 | "version": "2.5.0", 1720 | "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", 1721 | "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", 1722 | "dev": true, 1723 | "requires": { 1724 | "boxen": "^1.2.1", 1725 | "chalk": "^2.0.1", 1726 | "configstore": "^3.0.0", 1727 | "import-lazy": "^2.1.0", 1728 | "is-ci": "^1.0.10", 1729 | "is-installed-globally": "^0.1.0", 1730 | "is-npm": "^1.0.0", 1731 | "latest-version": "^3.0.0", 1732 | "semver-diff": "^2.0.0", 1733 | "xdg-basedir": "^3.0.0" 1734 | } 1735 | }, 1736 | "url-parse-lax": { 1737 | "version": "1.0.0", 1738 | "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", 1739 | "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", 1740 | "dev": true, 1741 | "requires": { 1742 | "prepend-http": "^1.0.1" 1743 | } 1744 | }, 1745 | "util-deprecate": { 1746 | "version": "1.0.2", 1747 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1748 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 1749 | }, 1750 | "utils-merge": { 1751 | "version": "1.0.1", 1752 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1753 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 1754 | }, 1755 | "vary": { 1756 | "version": "1.1.2", 1757 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1758 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 1759 | }, 1760 | "which": { 1761 | "version": "1.3.1", 1762 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", 1763 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", 1764 | "dev": true, 1765 | "requires": { 1766 | "isexe": "^2.0.0" 1767 | } 1768 | }, 1769 | "wide-align": { 1770 | "version": "1.1.3", 1771 | "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", 1772 | "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", 1773 | "requires": { 1774 | "string-width": "^1.0.2 || 2" 1775 | } 1776 | }, 1777 | "widest-line": { 1778 | "version": "2.0.1", 1779 | "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", 1780 | "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", 1781 | "dev": true, 1782 | "requires": { 1783 | "string-width": "^2.1.1" 1784 | } 1785 | }, 1786 | "wrappy": { 1787 | "version": "1.0.2", 1788 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1789 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 1790 | }, 1791 | "write-file-atomic": { 1792 | "version": "2.4.3", 1793 | "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", 1794 | "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", 1795 | "dev": true, 1796 | "requires": { 1797 | "graceful-fs": "^4.1.11", 1798 | "imurmurhash": "^0.1.4", 1799 | "signal-exit": "^3.0.2" 1800 | } 1801 | }, 1802 | "xdg-basedir": { 1803 | "version": "3.0.0", 1804 | "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", 1805 | "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", 1806 | "dev": true 1807 | }, 1808 | "xtend": { 1809 | "version": "4.0.2", 1810 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", 1811 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" 1812 | }, 1813 | "yallist": { 1814 | "version": "2.1.2", 1815 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", 1816 | "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", 1817 | "dev": true 1818 | } 1819 | } 1820 | } 1821 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json-another-crud", 3 | "version": "0.0.1", 4 | "description": "JSON based crud made with Node.js", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "node src/app.js", 8 | "startdev": "npx nodemon src/app.js -e js,ejs", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "Ezequiel Cortés", 12 | "license": "ISC", 13 | "dependencies": { 14 | "bcrypt": "^3.0.7", 15 | "cookie-parser": "^1.4.4", 16 | "ejs": "^3.0.1", 17 | "express": "^4.17.1", 18 | "express-session": "^1.17.0", 19 | "method-override": "^3.0.0", 20 | "multer": "^1.4.2" 21 | }, 22 | "devDependencies": { 23 | "nodemon": "^2.0.2" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /public/css/main.css: -------------------------------------------------------------------------------- 1 | /* Estilos generales*/ 2 | 3 | /* https://coolors.co/d8a47f-ef8354-ee4b6a-df3b57-0f7173 */ 4 | 5 | :root { 6 | --primary-color: #0f7173; 7 | --dark-grey-color: #333; 8 | --light-grey-color: #e9f2f2; 9 | 10 | --danger-color: #df3b57; 11 | --border-radius: .5rem; 12 | --padding: 1rem; 13 | } 14 | 15 | * { box-sizing: border-box; } 16 | 17 | body { 18 | display: flex; 19 | flex-direction: column; 20 | min-height: 100vh; 21 | margin: 0; 22 | padding: 0; 23 | background-color: var(--light-grey-color); 24 | font-family: sans-serif; 25 | color: var(--dark-grey-color); 26 | } 27 | 28 | a { 29 | color: inherit; 30 | text-decoration: inherit; 31 | } 32 | 33 | /* Clases de ayuda */ 34 | 35 | .container { 36 | width: 100%; 37 | max-width: 1200px; 38 | margin: 0 auto; 39 | padding: 1rem; 40 | } 41 | 42 | .main-container { 43 | flex-grow: 1; 44 | padding-top: 3rem; 45 | padding-bottom: 3rem; 46 | } 47 | 48 | .image img { width: 100%; } 49 | 50 | /* Formularios */ 51 | 52 | .form-container { 53 | max-width: 600px; 54 | background-color: white; 55 | border-radius: var(--border-radius); 56 | padding: var(--padding); 57 | } 58 | 59 | .form-container .title { 60 | font-size: 2rem; 61 | padding: 0 0 1rem; 62 | } 63 | 64 | .form-container .image img { 65 | border-radius: var(--border-radius); 66 | border: 10px solid var(--light-grey-color); 67 | } 68 | 69 | input:not([type='checkbox']), 70 | textarea, 71 | select { 72 | width: 100%; 73 | padding: .5rem; 74 | margin: .5rem 0 1rem; 75 | background-color: var(--light-grey-color); 76 | border: 0; 77 | border-radius: var(--border-radius); 78 | } 79 | 80 | .buttons { 81 | display: flex; 82 | justify-content: flex-end; 83 | } 84 | 85 | .button { 86 | display: inline-block; 87 | padding: var(--padding); 88 | background-color: white; 89 | border-radius: var(--border-radius); 90 | border: 1px solid var(--dark-grey-color); 91 | cursor: pointer; 92 | font-size: inherit; 93 | } 94 | 95 | .button.is-primary, 96 | .button.is-danger { 97 | color: white; 98 | border: 0; 99 | } 100 | 101 | .button.is-primary { background-color: var(--primary-color); } 102 | .button.is-danger { background-color: var(--danger-color); } 103 | 104 | .button + .button, 105 | .button + form.has-buttons, 106 | form.has-buttons + .button { margin-left: 1rem; } 107 | 108 | /* Mensajes de la plataforma */ 109 | 110 | .message { 111 | background-color: white; 112 | border-radius: var(--border-radius); 113 | padding: var(--padding); 114 | } 115 | 116 | .message.error-message { 117 | background-color: #ee4b6a; 118 | color: white; 119 | } 120 | 121 | /* Componentes principales */ 122 | 123 | header, 124 | footer { 125 | background-color: #ee4b6a; 126 | color: white; 127 | } 128 | 129 | header a, 130 | footer a{ 131 | display: inline-block; 132 | padding: .5rem; 133 | color: white; 134 | } 135 | 136 | .user-badge { 137 | display: inline-flex; 138 | align-items: center; 139 | padding: .5rem; 140 | border-radius: 2rem; 141 | background-color: #0f7173; 142 | } 143 | 144 | .user-badge .user-image { 145 | border-radius: 50%; 146 | height: 2rem; 147 | width: 2rem; 148 | } 149 | 150 | .user-badge .user-name { 151 | margin: 0 .5rem; 152 | } 153 | 154 | /* Estilos de cada página */ 155 | 156 | /* Productos y usuarios */ 157 | 158 | .products, 159 | .users { 160 | display: flex; 161 | flex-wrap: wrap; 162 | justify-content: space-between; 163 | } 164 | 165 | .products::after, 166 | .users::after { 167 | content: ""; 168 | flex-grow: 1; 169 | } 170 | 171 | .product, 172 | .user { 173 | display: flex; 174 | flex-direction: column; 175 | background-color: white; 176 | border-radius: var(--border-radius); 177 | padding: var(--padding); 178 | margin: 1rem .5rem 179 | } 180 | 181 | .product .desc { 182 | flex-grow: 1; 183 | } 184 | 185 | .product .price { 186 | color: #0f7173; 187 | font-size: 2rem; 188 | margin: 0.5rem 0; 189 | border-top: 1px solid #0f7173; 190 | padding: .5rem 0 0; 191 | } 192 | 193 | @media (min-width: 768px) { 194 | .products .product, 195 | .users .user { 196 | width: calc(25% - 1rem); 197 | } 198 | } -------------------------------------------------------------------------------- /public/images/products/default-product-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codelando/dh-node-express-json-crud/f07b4d878c7a85712f12cbe2c0c982dcc7669188/public/images/products/default-product-image.png -------------------------------------------------------------------------------- /public/images/products/product-1578923355448.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codelando/dh-node-express-json-crud/f07b4d878c7a85712f12cbe2c0c982dcc7669188/public/images/products/product-1578923355448.jpg -------------------------------------------------------------------------------- /public/images/products/product-1578925078790.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codelando/dh-node-express-json-crud/f07b4d878c7a85712f12cbe2c0c982dcc7669188/public/images/products/product-1578925078790.jpg -------------------------------------------------------------------------------- /public/images/users/default-user-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codelando/dh-node-express-json-crud/f07b4d878c7a85712f12cbe2c0c982dcc7669188/public/images/users/default-user-image.png -------------------------------------------------------------------------------- /public/images/users/user-1578944461157.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codelando/dh-node-express-json-crud/f07b4d878c7a85712f12cbe2c0c982dcc7669188/public/images/users/user-1578944461157.png -------------------------------------------------------------------------------- /public/images/users/user-1578949183440.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codelando/dh-node-express-json-crud/f07b4d878c7a85712f12cbe2c0c982dcc7669188/public/images/users/user-1578949183440.png -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | const methodOverride = require('method-override'); 4 | const session = require('express-session'); 5 | const cookieParser = require('cookie-parser'); 6 | const auth = require('./middlewares/auth'); 7 | 8 | // View Engine 9 | app.set('view engine', 'ejs'); 10 | app.set('views', 'src/views'); 11 | app.use(express.static('public')); 12 | 13 | // Middlewares 14 | app.use(express.urlencoded({ extended: true })); 15 | app.use(express.json()); 16 | app.use(methodOverride('_method')); 17 | app.use(session({ 18 | secret: 'Node JSON CRUD', 19 | resave: false, 20 | saveUninitialized: true 21 | })); 22 | app.use(cookieParser()); 23 | app.use(auth); 24 | 25 | // Routes 26 | app.use('/', require('./routes/static')); 27 | app.use('/productos', require('./routes/products')); 28 | app.use('/usuarios', require('./routes/users')); 29 | 30 | app.listen(3000, () => console.log('Servidor corriendo en el puerto 3000')); -------------------------------------------------------------------------------- /src/controllers/productsController.js: -------------------------------------------------------------------------------- 1 | const JsonModel = require('../models/jsonModel'); 2 | 3 | const productsModel = new JsonModel('products'); 4 | 5 | const controller = { 6 | index: (req, res) => { 7 | let products = productsModel.all(); 8 | res.render('products/index', { products }); 9 | }, 10 | show: (req, res) => { 11 | let product = productsModel.find(req.params.id); 12 | 13 | if (product) { 14 | res.render('products/detail', { product }); 15 | } else { 16 | res.render('products/404', { 17 | message: { 18 | class: 'error-message', 19 | title: 'Inexistente', 20 | desc: 'El producto que buscas ya no existe, nunca existió y tal vez nunca exista.' 21 | } 22 | }); 23 | } 24 | }, 25 | create: (req, res) => { 26 | res.render('products/create'); 27 | }, 28 | store: (req, res) => { 29 | req.body.image = req.file ? req.file.filename : ''; 30 | let productId = productsModel.save(req.body); 31 | 32 | res.redirect('/productos/' + productId); 33 | }, 34 | edit: (req, res) => { 35 | let product = productsModel.find(req.params.id); 36 | 37 | if (product) { 38 | res.render('products/edit', { product }); 39 | } else { 40 | res.render('products/404', { 41 | message: { 42 | class: 'error-message', 43 | title: 'Inexistente', 44 | desc: 'El producto que buscas ya no existe, nunca existió y tal vez nunca exista.' 45 | } 46 | }); 47 | } 48 | }, 49 | update: (req, res) => { 50 | req.body.id = req.params.id; 51 | /* Si nos lleva imagen guardamos esa, de lo contrario mantenemos la anterior */ 52 | req.body.image = req.file ? req.file.filename : req.body.oldImage; 53 | productsModel.update(req.body); 54 | 55 | res.redirect('/productos/' + req.params.id); 56 | }, 57 | destroy: (req, res) => { 58 | productsModel.destroy(req.params.id); 59 | res.redirect('/productos'); 60 | }, 61 | } 62 | 63 | module.exports = controller; -------------------------------------------------------------------------------- /src/controllers/staticsController.js: -------------------------------------------------------------------------------- 1 | const controller = { 2 | index: (req, res) => { 3 | res.render('index'); 4 | }, 5 | about: (req, res) => { 6 | res.render('about'); 7 | }, 8 | faq: (req, res) => { 9 | res.render('faq'); 10 | } 11 | } 12 | 13 | module.exports = controller; -------------------------------------------------------------------------------- /src/controllers/usersController.js: -------------------------------------------------------------------------------- 1 | const bcrypt = require('bcrypt'); 2 | const crypto = require('crypto'); 3 | const JsonModel = require('../models/jsonModel'); 4 | 5 | const usersModel = new JsonModel('users'); 6 | const userTokensModel = new JsonModel('userTokens'); 7 | 8 | const controller = { 9 | index: (req, res) => { 10 | let users = usersModel.all(); 11 | res.render('users/index', { users }); 12 | }, 13 | show: (req, res) => { 14 | let user = usersModel.find(req.params.id); 15 | 16 | if (user) { 17 | res.render('users/detail', { user }); 18 | } else { 19 | res.render('users/404', { 20 | message: { 21 | class: 'error-message', 22 | title: 'Inexistente', 23 | desc: 'El usuario que buscas ya no existe, nunca existió y tal vez nunca exista.' 24 | } 25 | }); 26 | } 27 | }, 28 | create: (req, res) => { 29 | res.render('users/create'); 30 | }, 31 | store: (req, res) => { 32 | req.body.image = req.file ? req.file.filename : ''; 33 | req.body.password = bcrypt.hashSync(req.body.password, 10); 34 | usersModel.save(req.body); 35 | 36 | res.redirect('/'); 37 | }, 38 | loginForm: (req, res) => { 39 | res.render('users/login'); 40 | }, 41 | login: (req, res) => { 42 | let user = usersModel.findByField('email', req.body.email); 43 | if (user) { 44 | if (bcrypt.compareSync(req.body.password, user.password)) { 45 | delete user.password; 46 | req.session.user = user; 47 | res.locals.user = req.session.user; 48 | 49 | if (req.body.remember) { 50 | // https://stackoverflow.com/questions/8855687/secure-random-token-in-node-js 51 | const token = crypto.randomBytes(64).toString('base64'); 52 | res.cookie('rememberToken', token, { maxAge: 1000 * 60 * 60 * 24 * 90 }); 53 | userTokensModel.save({ userId: user.id, token}); 54 | } 55 | 56 | res.redirect('/usuarios/perfil'); 57 | 58 | } else { 59 | res.render('users/404', { 60 | message: { 61 | class: 'error-message', 62 | title: 'Inválido', 63 | desc: 'Los datos de acceso son inválidos.' 64 | } 65 | }); 66 | } 67 | } else { 68 | res.render('users/404', { 69 | message: { 70 | class: 'error-message', 71 | title: 'Inexistente', 72 | desc: 'El usuario que buscas ya no existe, nunca existió y tal vez nunca exista.' 73 | } 74 | }); 75 | } 76 | }, 77 | logout: (req, res) => { 78 | // Al hacer logout borramos todos las cookies activas 79 | let tokens = userTokensModel.findAllByField('userId', req.session.user.id); 80 | if (tokens) { 81 | tokens.forEach(token => { 82 | userTokensModel.destroy(token.id); 83 | }); 84 | } 85 | 86 | // La otra opción sería solo borrar la que corresponda a esta sesión. 87 | // let token = userTokensModel.findByField('token', req.cookies.rememberToken); 88 | // if (token) { userTokensModel.destroy(token.id) } 89 | 90 | req.session.destroy(); 91 | res.cookie('rememberToken', null, { maxAge: -1 }); 92 | res.redirect('/'); 93 | }, 94 | profile: (req, res) => { 95 | let user = usersModel.find(req.session.user.id); 96 | res.render('users/detail', { user }); 97 | }, 98 | edit: (req, res) => { 99 | let user = usersModel.find(req.params.id); 100 | if (user) { 101 | res.render('users/edit', { user }); 102 | } else { 103 | res.render('users/404', { 104 | message: { 105 | class: 'error-message', 106 | title: 'Inexistente', 107 | desc: 'El usuario que buscas ya no existe, nunca existió y tal vez nunca exista.' 108 | } 109 | }); 110 | } 111 | }, 112 | update: (req, res) => { 113 | req.body.id = req.params.id; 114 | /* Si nos llega imagen guardamos esa, de lo contrario mantenemos la anterior */ 115 | req.body.image = req.file ? req.file.filename : req.body.oldImage; 116 | usersModel.update(req.body); 117 | 118 | res.redirect('/usuarios/' + req.params.id); 119 | }, 120 | destroy: (req, res) => { 121 | usersModel.destroy(req.params.id); 122 | res.redirect('/usuarios'); 123 | }, 124 | } 125 | 126 | module.exports = controller; -------------------------------------------------------------------------------- /src/data/products.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "Zapatilla Negra", 4 | "desc": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet accusamus obcaecati animi doloribus temporibus qui aliquam placeat pariatur totam ea error, sequi, eos fuga fugiat. Nisi unde nihil recusandae quo?", 5 | "price": "2341", 6 | "id": "1", 7 | "image": "product-1578923355448.jpg" 8 | }, 9 | { 10 | "title": "Zapatilla Griss", 11 | "desc": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet accusamus obcaecati animi doloribus temporibus qui aliquam placeat pariatur totam ea error, sequi, eos fuga fugiat. Nisi unde nihil recusandae quo?", 12 | "oldImage": "product-1578925078790.jpg", 13 | "price": "1234", 14 | "id": "2", 15 | "image": "product-1578925078790.jpg" 16 | } 17 | ] -------------------------------------------------------------------------------- /src/data/userTokens.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /src/data/users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Test", 4 | "email": "test@test.com", 5 | "password": "$2b$10$EpaPZyIWU4PZDyFcAFQj2OsSvrNh/MhBa27Zzx8kGE5SPMb4YCwlq", 6 | "image": "user-1578944461157.png", 7 | "id": 1 8 | }, 9 | { 10 | "name": "test", 11 | "email": "testtesttest@test.com", 12 | "password": "$2b$10$EpaPZyIWU4PZDyFcAFQj2OsSvrNh/MhBa27Zzx8kGE5SPMb4YCwlq", 13 | "image": "user-1578949183440.png", 14 | "id": 2 15 | } 16 | ] -------------------------------------------------------------------------------- /src/middlewares/auth.js: -------------------------------------------------------------------------------- 1 | const JsonModel = require('../models/jsonModel'); 2 | 3 | const usersModel = new JsonModel('users'); 4 | const userTokensModel = new JsonModel('userTokens'); 5 | 6 | const locals = (req, res, next) => { 7 | // https://expressjs.com/es/4x/api.html 8 | // res.locals 9 | 10 | res.locals.isAuthenticated = false; 11 | 12 | if (req.session.user) { 13 | res.locals.isAuthenticated = true; 14 | res.locals.user = req.session.user; 15 | } else if (req.cookies.rememberToken) { 16 | // Si existe el token en la colección entonces es válido 17 | let userToken = userTokensModel.findByField('token', req.cookies.rememberToken); 18 | if(userToken) { 19 | // Si encontramos un usuario que coincida, lo logeamos 20 | let user = usersModel.find(userToken.userId); 21 | if(user) { 22 | delete user.password; 23 | req.session.user = user; 24 | res.locals.isAuthenticated = true; 25 | res.locals.user = req.session.user; 26 | } 27 | } 28 | 29 | // Si no existe en la colección es inválido, borramos la cookie 30 | res.cookie('rememberToken', null, { maxAge: -1 }); 31 | } 32 | 33 | next(); 34 | } 35 | 36 | module.exports = locals; -------------------------------------------------------------------------------- /src/middlewares/guestRoute.js: -------------------------------------------------------------------------------- 1 | const guest = (req, res, next) => { 2 | 3 | if (res.locals.isAuthenticated) { 4 | res.redirect('/usuarios/perfil'); 5 | } 6 | 7 | next(); 8 | } 9 | 10 | module.exports = guest; -------------------------------------------------------------------------------- /src/middlewares/userRoute.js: -------------------------------------------------------------------------------- 1 | const guest = (req, res, next) => { 2 | 3 | if (!res.locals.isAuthenticated) { 4 | res.redirect('/usuarios/ingresar'); 5 | } 6 | 7 | next(); 8 | } 9 | 10 | module.exports = guest; -------------------------------------------------------------------------------- /src/models/jsonModel.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | class JsonModel { 5 | constructor(name) { 6 | this.name = name; 7 | this.dataDir = '../data/'; 8 | this.dataPath = path.resolve(__dirname, this.dataDir , this.name + '.json'); 9 | } 10 | 11 | /** Lee el archivo JSON */ 12 | readJsonFile() { 13 | let fileContents = fs.readFileSync(this.dataPath, 'utf-8'); 14 | if (fileContents) { 15 | return JSON.parse(fileContents); 16 | } 17 | return []; 18 | } 19 | 20 | /** Escribe el archivo JSON */ 21 | writeJsonFile(data) { 22 | let jsonData = JSON.stringify(data, null, ' '); 23 | fs.writeFileSync(this.dataPath, jsonData); 24 | } 25 | 26 | /** Genera el próximo valor para la clave primaria */ 27 | generatePk() { 28 | let items = this.readJsonFile(); 29 | let lastItem = items.pop(); 30 | 31 | if(lastItem) { 32 | return ++lastItem.id; 33 | } 34 | 35 | return 1; 36 | } 37 | 38 | /** Trae todos los documentos */ 39 | all() { return this.readJsonFile() } 40 | 41 | /** Trae un documento por su valor de clave primaria */ 42 | find(id) { 43 | let items = this.readJsonFile(); 44 | return items.find(item => item.id == id) 45 | } 46 | 47 | /** Trae un documento por su valor de clave primaria */ 48 | findByField(field, value) { 49 | let items = this.readJsonFile(); 50 | return items.find(item => item[field] == value); 51 | } 52 | 53 | /** Trae un documento por su valor de clave primaria */ 54 | findAllByField(field, value) { 55 | let items = this.readJsonFile(); 56 | return items.filter(item => item[field] == value); 57 | } 58 | 59 | /** Guarda el documento en la colección */ 60 | save(item) { 61 | let items = this.readJsonFile(); 62 | item.id = this.generatePk(); 63 | items.push(item); 64 | 65 | this.writeJsonFile(items); 66 | 67 | return item.id; 68 | } 69 | 70 | /** Actualiza el documento en la colección */ 71 | update(item) { 72 | let items = this.readJsonFile(); 73 | 74 | let updatedItems = items.map(currentItem => { 75 | if (currentItem.id == item.id) { 76 | return currentItem = item; 77 | } 78 | return currentItem; 79 | }); 80 | 81 | this.writeJsonFile(updatedItems); 82 | 83 | return item.id; 84 | } 85 | 86 | /** Elimina el documento en la colección */ 87 | destroy(id) { 88 | let items = this.readJsonFile(); 89 | 90 | let filteredItems = items.filter(currentItem => currentItem.id != id ); 91 | 92 | this.writeJsonFile(filteredItems); 93 | } 94 | } 95 | 96 | module.exports = JsonModel; -------------------------------------------------------------------------------- /src/routes/products.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const multer = require('multer'); 4 | const path = require('path'); 5 | 6 | const storage = multer.diskStorage({ 7 | destination: (req, file, cb) => { 8 | cb(null, path.resolve(__dirname, '../../public/images/products')); 9 | }, 10 | filename: (req, file, cb) => { 11 | cb(null, 'product-' + Date.now() + path.extname(file.originalname)); 12 | } 13 | }); 14 | 15 | const upload = multer({ storage }); 16 | 17 | const productsController = require('../controllers/productsController'); 18 | 19 | router.get('/', productsController.index); 20 | router.get('/nuevo', productsController.create); 21 | router.post('/', upload.single('image'), productsController.store); 22 | router.get('/:id', productsController.show); 23 | router.get('/:id/editar', productsController.edit); 24 | router.put('/:id', upload.single('image'), productsController.update); 25 | router.delete('/:id', productsController.destroy); 26 | 27 | module.exports = router; -------------------------------------------------------------------------------- /src/routes/static.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const staticsController = require('../controllers/staticsController') 4 | 5 | router.get('/', staticsController.index); 6 | router.get('/acerca-de', staticsController.about); 7 | router.get('/preguntas-frecuentes', staticsController.faq); 8 | 9 | module.exports = router; -------------------------------------------------------------------------------- /src/routes/users.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const multer = require('multer'); 4 | const path = require('path'); 5 | 6 | const storage = multer.diskStorage({ 7 | destination: (req, file, cb) => { 8 | cb(null, path.resolve(__dirname, '../../public/images/users')); 9 | }, 10 | filename: (req, file, cb) => { 11 | cb(null, 'user-' + Date.now() + path.extname(file.originalname)); 12 | } 13 | }); 14 | 15 | const upload = multer({ storage }); 16 | 17 | const usersController = require('../controllers/usersController'); 18 | const userRoute = require('../middlewares/userRoute'); 19 | const guestRoute = require('../middlewares/guestRoute'); 20 | 21 | router.get('/', usersController.index); 22 | router.get('/nuevo', guestRoute, usersController.create); 23 | router.post('/', upload.single('image'), usersController.store); 24 | router.get('/ingresar', guestRoute, usersController.loginForm); 25 | router.post('/ingresar', usersController.login); 26 | router.get('/perfil', userRoute, usersController.profile); 27 | router.get('/salir', usersController.logout); 28 | router.get('/:id', usersController.show); 29 | 30 | router.get('/:id/editar', usersController.edit); 31 | router.put('/:id', upload.single('image'), usersController.update); 32 | router.delete('/:id', usersController.destroy); 33 | 34 | module.exports = router; -------------------------------------------------------------------------------- /src/views/about.ejs: -------------------------------------------------------------------------------- 1 | <%- include('./partials/head') %> 2 |

Acerca de nosotros

3 |
4 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Delectus eaque atque consequatur debitis culpa dolorum ipsum labore, iure odit beatae nisi ut ab facere voluptatibus eveniet magnam sit illo molestias!

5 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Quam voluptatem, eos voluptas tempora repellendus quia sit nemo possimus dolorem enim officiis modi excepturi a eum. Libero ratione culpa perspiciatis labore!

6 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Porro esse consequatur, quisquam repellat rem aspernatur voluptates beatae odio. Asperiores facere autem eligendi dolore tempora amet molestias facilis? Nesciunt, assumenda! Quibusdam.

7 |

Lorem ipsum dolor, sit amet consectetur adipisicing elit. Ad iure corporis officiis velit itaque ipsa earum? Beatae eum commodi doloribus asperiores neque molestiae debitis recusandae iste! Enim odit sed deleniti.

8 |
9 | <%- include('./partials/footer') %> -------------------------------------------------------------------------------- /src/views/faq.ejs: -------------------------------------------------------------------------------- 1 | <%- include('./partials/head') %> 2 |

Preguntas frecuentes

3 |
4 |
5 |

Primera pregunta

6 |

Lorem ipsum, dolor sit amet consectetur adipisicing elit. Numquam, dolorum facere, sunt, velit ipsum porro architecto eius fuga enim mollitia ea? Doloribus nostrum tempore sint laudantium officiis reprehenderit debitis velit!

7 |
8 |
9 |

Segunda pregunta

10 |

Lorem ipsum dolor sit, amet consectetur adipisicing elit. Iusto, delectus? Numquam iure aperiam ducimus voluptas sapiente fuga, debitis omnis commodi, praesentium cumque culpa laudantium, enim inventore esse dolor adipisci amet?

11 |
12 |
13 |

Tercera pregunta

14 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Tenetur minima, sunt ipsum, natus quo sapiente aut, quasi hic ducimus vero eveniet reprehenderit. Explicabo ut reprehenderit magnam. Quas quod quae hic.

15 |
16 |
17 | 18 | <%- include('./partials/footer') %> -------------------------------------------------------------------------------- /src/views/index.ejs: -------------------------------------------------------------------------------- 1 | <%- include('./partials/head') %> 2 |

Página principal

3 |
4 |

Bienvenidos a nuestro sitio hecho con Node y Express.

5 |

Lorem ipsum dolor sit, amet consectetur adipisicing elit. Ducimus consectetur unde dicta dolor minus voluptas corrupti at nam modi. Quibusdam nulla fugiat itaque repellendus? Nulla doloribus placeat odio deserunt corporis.

6 |
7 | <%- include('./partials/footer') %> -------------------------------------------------------------------------------- /src/views/partials/footer.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | -------------------------------------------------------------------------------- /src/views/partials/head.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | JSON CRUD con Node y Express 9 | 10 | 11 |
12 |
13 | <%- include('main-nav') %> 14 | <%- include('user-nav') %> 15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /src/views/partials/main-nav.ejs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/partials/user-nav.ejs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/products/404.ejs: -------------------------------------------------------------------------------- 1 | <%- include('../partials/head') %> 2 |

Upps... eso es un error

3 |
4 |
5 |

<%= message.title %>

6 |

<%= message.desc %>

7 |
8 |
9 | <%- include('../partials/footer') %> -------------------------------------------------------------------------------- /src/views/products/create.ejs: -------------------------------------------------------------------------------- 1 | <%- include('../partials/head') %> 2 |
3 |
4 |

Nuevo producto

5 |
6 | <%- include('./partials/formFields'); %> 7 |

8 | 9 |

10 |
11 |
12 |
13 | <%- include('../partials/footer') %> -------------------------------------------------------------------------------- /src/views/products/detail.ejs: -------------------------------------------------------------------------------- 1 | <%- include('../partials/head') %> 2 |

Detalle de producto

3 |
4 | <%- include('./partials/product', { product }) %> 5 |
6 |
7 | ← Volver al listado 8 |
9 | 10 |
11 | Editar producto 12 |
13 | <%- include('../partials/footer') %> -------------------------------------------------------------------------------- /src/views/products/edit.ejs: -------------------------------------------------------------------------------- 1 | <%- include('../partials/head') %> 2 |
3 |
4 |

Editar producto

5 |
6 | <%- include('./partials/formFields'); %> 7 |

8 | ← Volver al detalle 9 | 10 |

11 |
12 |
13 |
14 | 15 | <%- include('../partials/footer') %> -------------------------------------------------------------------------------- /src/views/products/index.ejs: -------------------------------------------------------------------------------- 1 | <%- include('../partials/head') %> 2 |

Listado de productos

3 |
4 | <% products.forEach(product => { %> 5 | <%- include('./partials/product', { product }) %> 6 | <% }); %> 7 |
8 |
9 | Nuevo producto 10 |
11 | <%- include('../partials/footer') %> -------------------------------------------------------------------------------- /src/views/products/partials/formFields.ejs: -------------------------------------------------------------------------------- 1 |

2 | 6 |

7 |

8 | 12 |

13 |

14 | <% if (locals.product) { %> 15 | 16 | 17 | <% } %> 18 |

19 |

20 | 24 |

25 |

26 | 30 |

-------------------------------------------------------------------------------- /src/views/products/partials/product.ejs: -------------------------------------------------------------------------------- 1 |
2 | 3 |

<%= product.title %>

4 |

5 | 6 |

7 |

<%= product.desc %>

8 |

$ <%= product.price %>

9 |
10 |
-------------------------------------------------------------------------------- /src/views/users/404.ejs: -------------------------------------------------------------------------------- 1 | <%- include('../partials/head') %> 2 |

Upps... eso es un error

3 |
4 |
5 |

<%= message.title %>

6 |

<%= message.desc %>

7 |
8 |
9 | <%- include('../partials/footer') %> -------------------------------------------------------------------------------- /src/views/users/create.ejs: -------------------------------------------------------------------------------- 1 | <%- include('../partials/head') %> 2 |
3 |
4 |

Nuevo usuario

5 |
6 | <%- include('./partials/formFields'); %> 7 |

8 | 9 |

10 |
11 |
12 |
13 | <%- include('../partials/footer') %> -------------------------------------------------------------------------------- /src/views/users/detail.ejs: -------------------------------------------------------------------------------- 1 | <%- include('../partials/head') %> 2 |

Detalle de usuario

3 |
4 | <%- include('./partials/user', { user }) %> 5 |
6 |
7 | ← Volver al listado 8 |
9 | 10 |
11 | Editar usuario 12 |
13 | <%- include('../partials/footer') %> -------------------------------------------------------------------------------- /src/views/users/edit.ejs: -------------------------------------------------------------------------------- 1 | <%- include('../partials/head') %> 2 |
3 |
4 |

Editar usuario

5 |
6 | <%- include('./partials/formFields'); %> 7 |

8 | ← Volver al detalle 9 | 10 |

11 |
12 |
13 |
14 | 15 | <%- include('../partials/footer') %> -------------------------------------------------------------------------------- /src/views/users/index.ejs: -------------------------------------------------------------------------------- 1 | <%- include('../partials/head') %> 2 |

Listado de usuarios

3 |
4 | <% users.forEach(user => { %> 5 | <%- include('./partials/user', { user }) %> 6 | <% }); %> 7 |
8 |
9 | Nuevo usuario 10 |
11 | <%- include('../partials/footer') %> -------------------------------------------------------------------------------- /src/views/users/login.ejs: -------------------------------------------------------------------------------- 1 | <%- include('../partials/head') %> 2 |
3 |
4 |

Login

5 |
6 |

7 | 11 |

12 |

13 | 17 |

18 |

19 | 23 |

24 |

25 | 26 |

27 |
28 |
29 |
30 | <%- include('../partials/footer') %> -------------------------------------------------------------------------------- /src/views/users/partials/formFields.ejs: -------------------------------------------------------------------------------- 1 |

2 | 6 |

7 |

8 | 12 |

13 |

14 | 18 |

19 |

20 | <% if (locals.user) { %> 21 | 22 | 23 | <% } %> 24 |

25 |

26 | 30 |

-------------------------------------------------------------------------------- /src/views/users/partials/user.ejs: -------------------------------------------------------------------------------- 1 |
2 | 3 |

<%= user.name %>

4 |

5 | 6 |

7 | 8 |
9 |
--------------------------------------------------------------------------------