Meeting Minute === ###### tags: `Templates` `Meeting` :::info - **Location:** Room A - **Date:** Nov 1, 2030 2:30 PM (CET) - **Agenda** 1. Walk through signup flow `45min` > [name=Yukai] 2. Sprint planning `45min` 3. Revisit onboarding v1 `20min` - **Participants:** - Max (MX) - Yukai (YK) - Yuhsuan (YH) - Arwen (YC) - **Contact:** Max <max@example.com> - **Host:** YK - **Reference:** - [Last week meeting minute](/s/template-meeting-note) ::: ## Walk through signup flow - [Slide to explain the flow](/p/slide-example) :books: Sección 11 - Agregando Model View Controller - Modelos y Bases de Datos --- ### Que es un ORM (Object Relational Mapping)? :::info Permite almacenar o leer objectos de tu base de datos. Un ORM es un modelo de programación que permite mapear las estructuras de una base de datos relacional (SQL Server, Oracle, MySQL, etc.), en adelante RDBMS (Relational Database Management System), sobre una estructura lógica de entidades con el objeto de simplificar y acelerar el desarrollo de nuestras aplicaciones. Basado en primise (.then y .catch) - **Object:** Los objetos se definen con un lenguaje de programacion - **Relational:** Es la base de datos - **Mapping:** es la union entre ambos ::: :::info ORM'S de Node - **Sequelize:** Soporta MySql,PostgreSql, SqlServer y SqlLite - **Mongoose:** Soporta Mongodb ::: Instalar sequelize ```npm npm install --save mysql2 sequelize ``` Conectar una base de datos en Node utilizando Sequelize,en el archivo de configuracion db.js ```javascript=1 const { Sequelize } = require('sequelize'); const db = new Sequelize('upTaskNode', 'root', 'root', { host: 'localhost', dialect: 'mysql', port:3306, define:{ timestamps:false }, operatorsAliases: false, pool:{ max:5, min:0, acquire:30000, idle:10000 } }); /**Exportamos para ser utilizado */ module.exports = db; ``` Crear el Modelo.js ```javascript=1 const Sequelize = require('sequelize'); /* Importar la configuracion de la base de datos*/ const db = require('../config/db'); /*Definir la estructura */ const Proyectos = db.define('proyectos',{ id:{ type: Sequelize.INTEGER, primaryKey:true, autoIncrement:true }, nombre: Sequelize.STRING, url: Sequelize.STRING }); /* Exportar para utilizar en otros archivos*/ module.exports = Proyectos; ``` Agregar la configuracion y el modelo al index.js ```javascript=10 //importar el modelo require('./models/Proyectos'); db.sync() .then(()=>console.log('Conectado al servidor')) .catch(error => console.log(error)) ``` :::info **bd.authenticate :** Solo se conecta al servidor **db.sync :** Crea las tablas con el modelo previamente importado. ::: :books: Sección 12 - Insertando Proyectos en la base de datos --- En el controlador importar el modelo y usar el metodo create() de sequelize ```javascript=1 const Proyectos = require('../models/Proyectos'); exports.nuevoProyecto = async (req,res) =>{ const { nombre } = req.body; //insertar en la base de datos const propyecto = await Proyectos.create({nombre}); //redireccionar al home res.redirect('/'); } ``` ### Sanitizar entradas de datos :::info **Sanitizar:** Se utiliza para evitar que el usuario intente ingresar caracteres no validos, espacios en blanco entre otras cosas. por ejemplo que intente escribir <> ?? // <Script></Script> ::: instalar express validator ```npm npm install --save express-validator ``` Por lo general las vlidacion van en el router ```javascript=1 //importar express valitador const { body } = require('express-validator/check'); module.exports = function (){ //agregar nuevo proyecto router.post('/nuevo-proyecto', body('nombre').not().isEmpty().trim().escape(), proyectoController.nuevoProyecto); return router; } ``` ### Generar Urls Slug pasa de "Tienda Virtual" a "tienda-virtual" para ser agregada en las urls. ```npm npm install --save slug ``` ```javascript=1 const slug = require('slug'); const url = slug(nombre).toLowerCase(); const propyecto = await Proyectos.create({nombre,url}); ``` Pueden haber dos datos con la misma url para solucionar esto se usan Hooks de Sequelize :::info **Hooks:** Los Hooks (también conocidos como eventos del ciclo de vida), son funciones que se llaman antes y después de que se ejecuten las llamadas en sequelize. Por ejemplo, si desea establecer siempre un valor en un modelo antes de guardarlo, puede agregar un enlace beforeUpdate. [Hooks de Sequelize](https://sequelize.org/master/manual/hooks.html) ::: Agregamos un Hook de sequelize en el Modelo, para agregar un id unico al final de la url usamos ShorId ```node npm install --save shortid ``` ```javascript=1 const Sequelize = require('sequelize'); const slug = require('slug'); const shortid = require('shortid'); /* Importar la configuracion de la base de datos*/ const db = require('../config/db'); /*Definir la estructura */ const Proyectos = db.define('proyectos',{ id:{ type: Sequelize.INTEGER, primaryKey:true, autoIncrement:true }, nombre: Sequelize.STRING, url: Sequelize.STRING },{ hooks:{ //se ejecuta antes de crean el dato beforeCreate(proyecto){ const url = slug(proyecto.nombre).toLowerCase(); proyecto.url = `${url}-${shortid.generate()}`; } } }); /* Exportar para utilizar en otros archivos*/ module.exports = Proyectos; ``` :books: Sección 13 - Mostrando Proyectos de la base de datos --- En el controlador usar el metodo finall() para traer todos los datos de la base de datos. y pasar la informacion a la vista ```javascript=1 const Proyectos = require('../models/Proyectos'); exports.proyectosHome = async (req,res)=>{ const proyectos = await Proyectos.findAll(); res.render('index',{ nombrePagina:'Proyectos', proyectos }); } ``` En la vista iteramos el array **proyectos** que pasamos como parametro en el controlador ```pug=1 .panel.lista-proyectos h2 Proyectos ul#proyectos.proyectos if(proyectos) each proyecto in proyectos li a(href="#")= proyecto.nombre block contenido ``` Ahora crearemos un **Helper** para ver los datos como un json ya que Node no lo trae. Creamos un archivo **Helpers.js** con un metodo.(puede tener cuantos metodos queramos) ```javascript==1 exports.vardump = (objeto) => JSON.stringify(objeto,null,2); ``` ahora lo importamos en el index.js ```javascript==5 //helpers con algunas fuciones const helpers = require('./helpers'); ``` ```javascript=33 //pasar vardump a la aplicacion app.use((req,res,next) => { //locals crear varables en este archivo y consumirlo en otro archivo res.locals.vardump = helpers.vardump; //siguiente next(); }); ``` :::info **Midelware:** Un middleware es un bloque de código que se ejecuta entre la petición que hace el usuario (request) hasta que la petición llega al servidor. ::: Ahora vamos a gregar los ruting para la navegacion y que tome la url unica que almacenamos y de acuerdo a eso muestre un proyecto y sus detalles. en el archivo de routes agregar la direccion mas la url como comodin ```javascript=1 module.exports = function (){ //listar proyecto :url es el comodin router.get('/proyectos/:url',proyectoController.proyectoPorUrl); return router; } ``` y en el controlador agregar el metodo **proyectoPorUrl** ```javascript=20 exports.proyectoPorUrl = async (req,res,next) => { //buscar solo uno const proyecto = await Proyectos.findOne({ //condicion where:{ url:req.params.url } }) const proyectos = await Proyectos.findAll(); if(!proyecto) return next(); //render a la vista res.render('tareas',{ nombrePagina:'tareas del proyecto', proyecto, proyectos }); } ``` :books: Sección 14 - Editando el Proyecto Actual --- Al dar click al boton editar se tiene que redireccionar a un formluario con los datos **views/tareas.pug** ```pug=1 extends layout block contenido .contenido-principal h1 #{nombrePagina} - #{proyecto.nombre} //todo formulario //listado de pendientes //acciones .contenedor-acciones // pega el enponit para editar y le pasa el id a(href=`/proyectos/editar/${proyecto.id}` class="boton") Editar Proyecto button#eliminar-proyecto(type="button" class="boton eliminar") Eliminar Proyecto ``` y se agrega en las rutas **routes/index.js** ```javascript=1 module.exports = function (){ //actualizar el proyecto router.get('/proyectos/editar/:id',proyectoController.formularioEditar); return router; } ``` y en el controlaro de agrega el metodo formularioE itar **controllers/proyectoController.js** ```javascript=1 exports.formularioEditar = async (req,res) =>{ const { id } = req.params; const proyectoPromise = Proyectos.findByPk(id); const proyectosPromise = Proyectos.findAll(); // se utiliza promesas cuando un metodo no depende del otro const [proyectos,proyecto] = await Promise.all([proyectosPromise,proyectoPromise]); //utiliza la misma plantilla para agregar uno nuevo res.render('nuevoProyecto',{ nombrePagina:'Editar Proyecto', proyecto, proyectos }) } ``` Utilizamos Mixins para que en el mismo formulario de nuevo podamos editar y guardar. Se crear una carpeta llamada mixins donde tiene un archivo nuevoProyecto :::info Los **Mixins** te permiten crear bloques reutilizables de Pug. ::: **views/mixins/crearProyecto.pug** ```pug= //recibe un objeto si no lo recibe lo deja en vacio mixin crearProyecto(proyecto = {}) //condicional para el action si estamos editando o agregando uno nuevo form.agregar-proyecto(action=`/nuevo-proyecto/${proyecto.id || ''}` method='POST') .campo label(for="nombre") Nombre Proyecto input(value=proyecto.nombre type="text" id="nombre" name="nombre" placeholder="Nombre Proyecto") .campo.eviar //validar que nombre se mostrara en el boton input(type="submit" value=`${proyecto.nombre ? 'Guardar':'Agregar'}` class="boton") ``` Se invoca en la plantilla **views/nuevoProyecto.pug** ```pug=1 extends layout //incluimos el archivo que creamos anteriormente include mixins/crearProyecto block contenido .contenido-principal h1 #{nombrePagina} if errores each error in errores .alerta= error.texto //utilizamos el mixins y pasamos el objeto +crearProyecto(proyecto) ``` Agregamos la ruta del nuevo enpoint **routes/index.js** ```javascript=1 module.exports = function (){ //editar y guardar router.post('/nuevo-proyecto/:id', body('nombre').not().isEmpty().trim().escape(), proyectoController.actualizarProyecto); return router; } ``` luego agregamos en el controlador el nuevo metodo **controller/proyectosController.js** ```javascript=1 exports.actualizarProyecto = async (req,res) => { const { nombre} = req.body; const { id } = req.params; let errores = []; const proyectos = await Proyectos.findAll(); if(!nombre){ errores.push({'texto':'agrega un nombre'}); } if(errores.length > 0){ res.render('nuevoProyecto',{ nombrePagina:'Nuevo Proyecto', errores, proyectos }) }else{ //buscamos y actualizamos en la base de datos await Proyectos.update( {nombre:nombre}, {where: {id:id} } ); res.redirect('/'); } } ``` :books: Sección 15 - Eliminando el Proyecto Actual --- ```npm npm install --save-dev @babel/core babel-loader @babel/preset-env webpack ``` **concurrently** permite ejecutar diferentes scripts al mismo tiempo ```npm npm install --save concurrently ``` Instalar **Axios** y **Sweetalert2** ```npm npm install --save axios sweetalert2 ``` :books: Sección 16 - Agregando Tareas en los Proyectos --- 1) Crear el Modelo de tareas. **models/tareas.js** ```javascript=1 const Sequelize = require('sequelize'); const db = require('../config/db'); const Tareas = db.define('tareas',{ id:{ type:Sequelize.INTEGER(11), primaryKey:true, autoIncrement:true }, tarea: Sequelize.STRING(100), estado: Sequelize.INTEGER(1) }); /*cada tarea pertenece a un proyecto una o muchas tarea pertenece a un proyecto */ Tareas.belongsTo(Proyectos); module.exports = Tareas; ``` 2) Agregar el modelo en el index para que se genere las tablas **index.js** ```javascript=20 //importar el modelo require('./models/Proyectos'); require('./models/Tareas'); ``` :books: Sprint Backlog --- - Email invite feature - Interview users :mag: Sprint Retro --- ### What we can start Doing - New initiatives and experiments we want to start improving :closed_book: Tasks -- ==Importance== (1 - 5) / Name / **Estimate** (1, 2, 3, 5, 8, 13) ### Development Team: - [ ] ==5== Email invite - [x] ==4== Email registration page **5** - [ ] ==5== Email invitees **3** - [ ] ==4== Setup e2e test in production **2** ### Design Team: - [ ] ==4== Interview users **8** - [ ] ==5== Build roll-up display content **5** - [ ] ==5== Help user discover new features **5** ## Notes <!-- Other important details discussed during the meeting can be entered here. -->