05 Notas + Auth + Vue.js

Utilizando la autenticación anterior:

Models nota

import mongoose from "mongoose";
const Schema = mongoose.Schema;

const notaSchema = new Schema({
  nombre: { type: String, required: [true, "El nombre es necesario"] },
  descripcion: String,
  usuarioId: String,
  date: { type: Date, default: Date.now },
  activo: Boolean
});

// Convertir a modelo
const Nota = mongoose.model("Nota", notaSchema);

export default Nota;

Ruta nota

var express = require("express");
var router = express.Router();

// Importamos modelos
import User from "../models/user";
import Nota from "../models/nota";

// Middlewares
const { verificaToken, verificaRol } = require("../middlewares/autenticacion");

//RUTAS
router.post("/nueva", verificaToken, async (req, res) => {
  let body = req.body;
  console.log(req.usuario);
  body.usuarioId = req.usuario._id;

  try {
    const tareaDB = await Nota.create(body);
    return res.json(tareaDB);
  } catch (error) {
    return res.status(400).json({
      mensaje: "Error al crear nota",
      error
    });
  }
});

router.get("/", verificaToken, async (req, res) => {
  let usuarioId = req.usuario._id;

  try {
    const tareaDB = await Nota.find({ usuarioId });
    return res.json(tareaDB);
  } catch (error) {
    return res.status(400).json({
      mensaje: "Error al crear nota",
      error
    });
  }
});

module.exports = router;

App.js

app.use("/nota", require("./routes/nota"));

Vue.js

Basados en un proyecto con Vue + VueRouter + Vuex

Agregar Vista Login

<template>
  <div class="container">
    <h1>Login</h1>
    <form @submit.prevent="login">
      <input
        type="text"
        class="form-control my-2"
        placeholder="email"
        v-model="usuario.email"
      />
      <input
        type="text"
        class="form-control my-2"
        placeholder="pass"
        v-model="usuario.pass"
      />
      <b-button type="submit">Acceder</b-button>
    </form>
    <div v-if="mensaje !== ''">
      <p>{{mensaje}}</p>
    </div>
  </div>
</template>
import { mapState, mapMutations, mapActions } from "vuex";
import router from '../router';

export default {
  data() {
    return {
      usuario: {email: 'prueba1@bluuweb.cl', pass: '123123'},
      mensaje: ''
    }
  },
  methods:{
    ...mapMutations(['obtenerUsuario']),
    ...mapActions(['guardarUsuario', 'leerToken', 'cerrarSesion']),
    login(){
      this.axios.post('/login', this.usuario)
        .then(res => {
          // console.log(res.data.token);
          const token = res.data.token;
          // this.usuarioDB = res.data.usuarioDB
          this.guardarUsuario(token);
        })
        .catch(err => {
          console.log(err.response.data.mensaje);
          this.mensaje = err.response.data.mensaje;
        })
    }
  }
}

store.js

Vamos a instalar jwt-decode, Documentación

npm i jwt-decode --save
import Vue from 'vue'
import Vuex from 'vuex'

import router from './router'

// para decodificar el jwt
import decode from 'jwt-decode'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    token: '',
    usuarioDB: ''
  },
  mutations: {
    obtenerUsuario(state, payload){
      state.token = payload;
      if(payload === ''){
        state.usuarioDB = ''
      }else{
        state.usuarioDB = decode(payload);
        router.push({name: 'notas'})
      }
    }
  },
  actions: {

    guardarUsuario({commit}, payload){
      localStorage.setItem('token', payload);
      commit('obtenerUsuario', payload)
    },
    cerrarSesion({commit}){
      commit('obtenerUsuario', '');
      localStorage.removeItem('token');
      router.push({name: 'login'});
    },
    leerToken({commit}){

      const token = localStorage.getItem('token');
      if(token){
        commit('obtenerUsuario', token);
      }else{
        commit('obtenerUsuario', '');
      }

    }

  },
  getters:{
    estaActivo: state => !!state.token
  }
})

router.js

import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'

// Importamos la tienda
import store from './store.js';

Vue.use(Router)

const router = new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/login',
      name: 'login',
      component: () => import(/* webpackChunkName: "about" */ './views/Login.vue')
    },
    {
      path: '/nota',
      name: 'nota',
      component: () => import(/* webpackChunkName: "nota" */ './views/Nota.vue'),
      meta: {requireAuth: true}
    }
  ]
});

router.beforeEach((to, from, next) => {
  const rutaProtegida = to.matched.some(record => record.meta.requireAuth);

  if(rutaProtegida && store.state.token === ''){
    // console.log(store.state.token);
    next({name: 'login'})

  }else{
    next()
  }

})

export default router;

nota.vue

<template>
  <div class="container">
    <h1>Ruta privada!</h1>
    <table class="table">
      <thead>
        <tr>
          <th scope="col">#</th>
          <th scope="col">Nombre</th>
          <th scope="col">Descripción</th>
          <th scope="col">Fecha</th>
          <th scope="col">Acciones</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="(item, index) in notas" :key="index">
          <th scope="row">{{item._id}}</th>
          <td>{{item.nombre}}</td>
          <td>{{item.descripcion}}</td>
          <td>{{item.date}}</td>
          <td>
            <!-- <b-button class="btn-warning btn-sm mx-2" @click="activarEdicion(item._id)">Actualizar</b-button> -->
            <!-- <b-button class="btn-danger btn-sm mx-2" @click="eliminarTarea(item._id)">Eliminar</b-button> -->
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>
import { mapState, mapActions } from "vuex";
export default {
  data() {
    return {
      notas: []
    };
  },
  computed: {
    ...mapState(["token"])
  },
  methods: {
    listarNotas(){
      let config = {
        headers: {
          token: this.token
        }
      }

      this.axios.get('/nota', config)
        .then(res => {
          console.log(res.data);
          this.notas = res.data;
        })
        .catch(e => {
          console.log(e.response);
        })
    },
    agregarNota(){
      let config = {
        headers: {
          token: this.token
        }
      }
      // console.log(this.nota);
      this.axios.post('/nueva-nota', this.nota, config)
        .then(res => {
          this.notas.push(res.data)
          this.nota.nombre = '';
          this.nota.descripcion = '';
          this.mensaje.color = 'success';
          this.mensaje.texto = 'Nota Agregada!';
          this.showAlert()
        })
        .catch(e => {
          console.log(e.response);
          if(e.response.data.error.errors.nombre.message){
            this.mensaje.texto = e.response.data.error.errors.nombre.message
          }else{
            this.mensaje.texto = 'Error de sistema';
          }
          this.mensaje.color = 'danger';
          this.showAlert()
        })
    },
  },
  created() {
    this.getTareas();
  }
}

app.vue

<template>
  <div id="app">
    <div id="nav">
      <router-link to="/">Home</router-link> |
      <router-link to="/login" v-if="!estaActivo">login</router-link> |
      <router-link to="/nota" v-if="estaActivo">nota</router-link> |
      <button @click="cerrarSesion()" v-if="estaActivo">Cerrar Sesión</button>
    </div>
    <router-view/>
  </div>
</template>
import { mapActions, mapGetters } from "vuex";
export default {
  methods:{
    ...mapActions(['cerrarSesion', 'leerToken'])
  },
  computed:{
    ...mapGetters(['estaActivo'])
  },
  created(){
    this.leerToken();
  }
}

https://bootstrap-vue.js.org/docs/components/navbar

<div>
  <b-navbar toggleable="md" type="dark" variant="info">
    <b-navbar-brand href="#">NavBar</b-navbar-brand>

    <b-navbar-toggle target="nav-collapse"></b-navbar-toggle>

    <b-collapse id="nav-collapse" is-nav>
      <b-navbar-nav>
        <b-nav-item to="/">Home</b-nav-item>
        <b-nav-item to="/about">About</b-nav-item>
        <b-nav-item to="/login" v-if="!estaActivo">Login</b-nav-item>
        <b-nav-item to="/notas" v-if="estaActivo">Notas</b-nav-item>
      </b-navbar-nav>

      <!-- Right aligned nav items -->
      <b-navbar-nav class="ml-auto">
        <b-nav-form>
          <b-form-input size="sm" class="mr-sm-2" placeholder="Search"></b-form-input>
          <b-button size="sm" class="my-2 my-sm-0" type="submit">Search</b-button>
        </b-nav-form>

        <b-nav-item-dropdown right v-if="estaActivo">
          <!-- Using 'button-content' slot -->
          <template v-slot:button-content>
            <em>User</em>
          </template>
          <b-dropdown-item href="#">Profile</b-dropdown-item>
          <b-dropdown-item @click="cerrarSesion()">Sign Out</b-dropdown-item>
        </b-nav-item-dropdown>
      </b-navbar-nav>
    </b-collapse>
  </b-navbar>
</div>

Producción

  1. Cambiar url base en vue.js
// axios.defaults.baseURL = 'http://localhost:3000/api';
axios.defaults.baseURL = 'https://app-mevn-bluuweb.herokuapp.com/api';
  1. Cambiar conexión a mongoDB en la nube en node.js
// Conexion en la nube
const uri = 'mongodb+srv://usuario:contraseña@udemy-ncdk5.mongodb.net/udemy?retryWrites=true&w=majority';
  1. Compilar proyecto de vue.js
npm run build
  1. Copiar archivos de carpeta dist a carpeta public de node.js
  2. Ejecutar los siguientes comandos en node.js
git add .
git commit -m "mensaje personalizado"
git push heroku master

Listo! XD