Angular 10 | MEAN, Google auth, JWT, Lazyload, upload de archivos, Guards, Pipes, Zona admin, dashboard y mucho más.
Tags: angular, javascript, mean, spanish
Published: 2022-10-19 20:49:13

2020-2022

Este es un **challenge** de un curso de [**Fernando herrera**](https://www.udemy.com/course/angular-avanzado-fernando-herrera/)**.**

# Sistema de hospitales — para controlar médicos, hospitales y usuarios

Les comento que **me he disfrutado este curso** ☕ si señor **fueron casi 2 años** mientras iba echando códigos, aprendiendo **dentro de mi día a día y mis responsabilidades laborales**, vean todos los **Repositorios.**

## **Me divertí, realicé este curso para refrescar conocimientos y obtener nuevos.**

Mi perfil en LinkedIn: [**💡Dennys Jose Marquez Reyes 🧠 | LinkedIn**](https://www.linkedin.com/in/dennysjmarquez/) **👍**

![](https://miro.medium.com/max/700/1*4EVYZDTFZ6fsbhHXlZDCMA.png)

**Demo:** [https://adminpro-system-hospitals.onrender.com/](https://adminpro-system-hospitals.onrender.com/)

**Código fuentes**

**Cliente:** [https://github.com/dennysjmarquez/angular-adv-adminpro](https://github.com/dennysjmarquez/angular-adv-adminpro)

**Server:** [https://github.com/dennysjmarquez/angular-adv-adminpro-backend](https://github.com/dennysjmarquez/angular-adv-adminpro-backend)

## Bien, comencemos a describir todo lo que hice utilice y aprendí de este maravilloso curso:

**MEAN Stack**

_Mongo, Express, Angular, Node.js._

---
# Sesión 1 — Front-End
---
**Google SignIn protegido por token desde el Front-End hasta el Backend**

Uso de librerías de terceros en proyectos de Angular, **gapi** Google Sign-In, **JQuery**, etc.

Rutas con configuraciones.

Control de versiones y releases.

Manejo de módulos, Servicios, Lazyload.

Rutas hijas — **ForChild( )**, **@inputs**, **@Outputs** y **@ViewChild** — Referencia a elementos en el HTML.

Implementación de **Charts** (Gráficas) de **ng2-charts.**

**Reactive Forms,** Validaciones del formulario, uso de **SweetAlert,** Guardar información en el **LocalStorage**

Rxjs Observables, **pipes:** **Retry**, **Take**, **filter, map**

## El uso de **interval**, Observable, Observer.

```js
returnObservable(): Observable<number> {
   let i = 0;

   const ob$ = new Observable((observer: Observer<number>) => {

      const interval = setInterval(() => {
         observer.next(i);

         if (i === 4) {
            clearInterval(interval);
            observer.complete();
         }

         if (i === 2) {
            i = 0;

            observer.error('i llego al valor 2');
         }

         ++i;
      }, 1000);
   });

   return ob$;
}
```

---

```js
this._intervalSubs = this.returnInterval()
      .pipe(
         // Especifica cuantas veces se va a ejecutar el Observable
         take(10),

         // Sirve para filtrar los valores y en este caso solo se muestran
         // los números pares
         filter((value) => value % 2 === 0),

         // Este operador recibe la información y la muta
         map((value) => {
            return 'Hola mundo ' + (value + 1);
         })
      )
      .subscribe(
         (valor) => console.log('[returnInterval] valor', valor),
         (error) => console.warn('[returnInterval] Error', error),
         () => console.log('[returnInterval] Terminado')
      );
}
returnInterval() {
   return interval(100);
}
```

---

## **Pipe** de Angular para mostrar una imagen de una URL o desde el server

```js
import { Pipe, PipeTransform } from '@angular/core';
import { environment } from '@env';

const baseUrl = environment.baseUrl;

@Pipe({
  name: 'getImage',
})
export class GetImagePipe implements PipeTransform {
  transform(value: any, type: 'users' | 'medicos' | 'hospitals'): any {
    return value && value.includes('://') ? value : `${baseUrl}/upload/${type}/${value || 'no-imagen'}`;
  }
}
```

## Implantación de Lazyload con protección de rutas y carga de componentes

```js
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule, Routes } from '@angular/router';
import { PagesComponent } from './pages.component';

// Guards
import { AuthGuard } from '../guards/auth.guard';

const APP_ROUTES: Routes = [
  // Template principal
  {
    path: 'dashboard',
    component: PagesComponent,
      canLoad: [AuthGuard],
      canActivate: [AuthGuard],
    loadChildren: () => import('./pages-child-router.module').then(module => module.PagesChildRouterModule),

    // Las rutas hijas se cargan con lazyload
    // children: [],
  },
];

const APP_ROUTING = RouterModule.forChild(APP_ROUTES);

@NgModule({
  declarations: [],
  imports: [CommonModule, APP_ROUTING],
  exports: [RouterModule],
})
export class PagesRouter {}
```

## Todo organizado en módulos, buenas prácticas 🤜🏻🤛🏻


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uuin5xp73mm8zbwl6ixw.png)

Use **ngZone.run()** para notificar a Angular que refresque la vista, ya que algo sucedió fuera de los **ciclos de vida de Angular** y no lo detecta como se espera que fuese un proceso de Angular, porque es una **librería externa** que hizo un cambio fuera del control de cambio de Angular.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pdi0txqcd0hnqe185vt1.png)

**Más información sobre ngZone aquí**

👇

[https://dennysjmarquez.dev/magazine/ngzone-como-runoutsideangular-podria-reducir-las-llamadas-de-deteccion-de-cambios-H41jASdhwzMReJe2JUZk/](https://dennysjmarquez.dev/magazine/ngzone-como-runoutsideangular-podria-reducir-las-llamadas-de-deteccion-de-cambios-H41jASdhwzMReJe2JUZk/)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yrw68rtx3tovqmc8oy9h.png)

## Sistema completo para identificar al usuario tanto en Google auth como con una cuenta normal en el back

google-auth.service.ts

```js
import {Injectable} from '@angular/core';
import Swal, {SweetAlertIcon} from 'sweetalert2';

import {AuthService} from './auth.service';

declare var gapi: any;

@Injectable({
    providedIn: 'root'
})
export class GoogleAuthService {

    constructor(private _authService: AuthService) {
    }

    makertGoogleLoginBtn(options: {
        // Id del botón de Google en el HTML
        btnSignin: string,
        // Parámetros para el mensaje de Error si algo falla al iniciar la App para el login
        errors?: {
            title?: string,
            text?: string,
            icon?: SweetAlertIcon,
            confirmButtonText?: string
        },
        // Función de se llama luego de un inicio exitoso
        callbackStartApp: Function
    }) {


        // Renderiza el botón de Google
        gapi.signin2.render(options.btnSignin, {
            'scope': 'profile email',
            'width': 240,
            'height': 50,
            'longtitle': false,
            'onsuccess': (googleUser) => {},
            'onfailure': console.log
        });


        // Inicia el login con Google
        this._authService.google.startApp('goole-signin').then((profile: any) => {

            options.callbackStartApp(profile);


        }).catch(error => {
            Swal.fire({
                title: options?.errors?.title || 'Error!',
                text: options?.errors?.text || error?.error?.msg || 'Error desconocido',
                icon: options?.errors?.icon || 'error',
                confirmButtonText: options?.errors?.confirmButtonText || 'Ok'
            });
        });

    }

}
```
auth.service.ts

```js
import { Injectable, NgZone } from '@angular/core';
import { LoginGoogleData } from '../interfaces/login-google-data.interface';
import { tap } from 'rxjs/operators';
import { Observable, throwError } from 'rxjs';
import { UserModel } from '../models/user.model';
import { HttpClient } from '@angular/common/http';
import { Router } from '@angular/router';
import { environment } from '@env';
import { LoginForm } from '../interfaces/login-form.interface';

declare var gapi: any;
declare var $: any;

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  baseURL = environment.baseUrl;
   google_id = environment.GOOGLE_ID

  public currentUser: UserModel;

  constructor(private http: HttpClient, private _router: Router, private _ngZone: NgZone) {}

  google = {
    /**
     *
     * Obtiene una sesión de Google
     *
     */
    initGoogleAuth: () => {
      return new Promise((resolve) => {
        gapi.load('auth2', () => {
          this.google.startApp['gapiAuth2'] = gapi.auth2;

          // Retrieve the singleton for the GoogleAuth library and set up the client.
          const auth2Init = gapi.auth2.init({
            client_id: this.google_id,
            cookiepolicy: 'single_host_origin',
            // Request scopes in addition to 'profile' and 'email'
            //scope: 'additional_scope'
          });

          resolve(auth2Init);
        });
      });
    },

    /**
     *
     * Obtiene una sesión de Google y se coloca él escucha del evento clic sobre el botón de Google
     *
     * @param btnSignin {string} Id del botón de Google en el HTML
     */
    startApp: (btnSignin: string) =>
      new Promise(async (resolve, reject) => {
        // Se obtiene una sesión de Google
        const auth2Init: any = await this.google.initGoogleAuth();
        const element = document.getElementById(btnSignin);

        // Se captura el evento clic en el botón de Google
        auth2Init.attachClickHandler(
          element,
          {},
          (googleUser) => {
            const profile = googleUser.getBasicProfile();
            const token = googleUser.getAuthResponse().id_token;
                  $(".preloader").fadeIn();
            this.google.login({ token }).subscribe(
              (resp) => {
                resolve(profile);
              },
              (error) => {
                        $(".preloader").fadeOut();
                reject(error);
              }
            );
          },
          function (error) {
            alert(JSON.stringify(error, undefined, 2));
          }
        );
      }),

    /**
     *
     * Se intensifica en el servidor de la App
     *
     * @param gToken {string} Token devuelto por Google
     */
    login: (gToken: LoginGoogleData) => {

         this.resetCurrentUser();

      return this.http.post(`${this.baseURL}/login/google`, gToken).pipe(
        tap(({ token = '' }: any) => {
          localStorage.setItem('token', token);
        }),
        tap((data: any) => this.setCurrentUser(data))
      );
    },

    /**
     *
     * Lleva a cabo el logOut de la App
     *
     * @param callback {Function} Función anónima que es llamada luego que se haya hecho el logOut
     */
    logOut: (callback?: Function) => {
      const logOut = () => {
        this.resetCurrentUser();
        const auth2 = this.google.startApp['gapiAuth2'].getAuthInstance();

        auth2.signOut().then(() => {
          typeof callback === 'function' && this._ngZone.run(() => callback());
        });
      };

      // Por si se pierde la sesión porque se refresca la pagina
      if (!this.google.startApp['gapiAuth2']) {
        this.google.initGoogleAuth().then(() => logOut());
      } else {
        logOut();
      }
    },
  };

  /**
   *
   * Obtiene el Token y lo almacena localmente
   *
   */
  get token(): string {
    return localStorage.getItem('token') || '';
  }

  /**
   *
   * Valida el token este método se usa en auth.guard para conceder el acceso o deniegarlo
   * en ciertas zonas o paginas también almacena información sensible del usuario
   * en este servicio, tales como: name, email, img, google, role, uid
   *
   * En la prop public user: UserModel de la class
   *
   */
  validateToken(): Observable<any> {
    // Obtiene el Token almacenado localmente
    const token = this.token;

    // Se chequea primero si el token existe antes de ser enviado al servidor para su validación
    if (!token) {
      return throwError('Usuario no logeado');
    }

    return this.http
      .get(`${this.baseURL}/login/tokenrenew`, { headers: { Authorization: token } })

      .pipe(
        tap(({ token = '' }: any) => {
          // Almacena el nuevo token
          localStorage.setItem('token', token);
        }),
        tap((data: any) => this.setCurrentUser(data))
      );
  }

  loginUser(formData: LoginForm): Observable<any> {

      this.resetCurrentUser();

    return this.http.post(`${this.baseURL}/login`, formData).pipe(
      tap(({ token = '' }: any) => {
        localStorage.setItem('token', token);
      }),
      tap((data: any) => this.setCurrentUser(data))
    );
  }

  resetCurrentUser() {
    this.currentUser = new UserModel(null, null, null, null, null, null, null);
      localStorage.removeItem('token');
  }

  private setCurrentUser({ usuario: { name, email, img, google, role, uid } }) {
    this.currentUser = new UserModel(name, email, '', img, google, role, uid);
  }
}
```
**Models**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bxe2w97ksu2p3z74m0f9.png)

**Interfaces**


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1ktdo7m37c9cxaot814p.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bxdoobu2zibkb7meh012.png)

## Al respecto, de sí usar Class o Interfaces, les dejo este artículo para más información

**Usar Modelos, clases e Interfaces en Angular**

👇

[https://dennysjmarquez.dev/magazine/usar-modelos-clases-e-interfaces-en-angular-lH8cmIS9YrgW0nONyoQe/](https://dennysjmarquez.dev/magazine/usar-modelos-clases-e-interfaces-en-angular-lH8cmIS9YrgW0nONyoQe/)

**Uso de import { FormBuilder, FormGroup, Validators } from ‘@angular/forms’;**

---

Custom validator o validaciones a medida


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/86juvm9e2as4vgnevn9w.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mwkn2vgcju2f674a96d3.png)

Mantenimientos de Hospitales, usuarios y médicos

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lhs0032m8on3dlhf651c.png)

---


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hb29bgmwhxzciu4d0hwa.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/44ci8njqg3b11c4tz449.png)

# **Usuarios**


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/un99prjntfsiazgzcyqa.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/en5cd8h377kp1bn15u0b.png)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i2ol20gczjtj0nks24pj.png)

# Hospitales


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pddrb1ux8cv5ibo8miiy.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/af3o6i0bbxfh85qubz1m.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c8n82s48vjcc7u4ix1o8.png)

# Médicos


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3mrkrgmt2waispgthled.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tydl0nqe8ov09iy89q0v.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q90ses4oncivaskwmuoo.png)

# Profile


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z4qxz7487qgn8ir3sf6n.png)

---

# Sesión 2 — Back-End

---

Node — Express — MongoDB

**Demo:** [https://adminpro-system-hospitals.onrender.com/](https://adminpro-system-hospitals.onrender.com/)

**Código fuente Server:** [https://github.com/dennysjmarquez/angular-adv-adminpro-backend](https://github.com/dennysjmarquez/angular-adv-adminpro-backend)

---

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7739i5bzy39nigu3ye5p.png)

Uso de **MongoDb compass**, **Mongo Atlas** para alojamiento de la dB y **configuraciones**.

Configuaciones como ejemplo: añadir la IP `0.0.0.0/0`, en **Network Access** de **MongoDB Atlas** con lo que abriríamos nuestra dB para que cualquier dirección IP pueda conectarse. 🤘🏻

## Conectar el Back con Mongo Atlas usando [Mongoosejs](https://mongoosejs.com/)

database/config.js


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qf8z0v2c1obzv296dml3.png)

index.js


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fie0zqu2wvmkf72jn5xt.png)

## Creación de modelos para interactuar con la dB de MongoDB Atlas CRUD

Modelos para los Hospitales, Usuarios y Médicos


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z6hq7ws060hcdscg38qq.png)

**Schema con referencias y el uso de populate, para agregar información extra o necesaria al esquema en cuestión.**

models/hospital.model.js

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qiuhrx5tp1881q9hjjoh.png)

models/medico.model.js


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ytq9gz4e2thyzd1ecwwp.png)

controllers/hospitals.controller.js


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dn7tnsi3h5odqj5kwrp1.png)

controllers/medicos.controller.js


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2jv9wu4ln4kf5038dddu.png)

**Manejo de los nombres de los esquemas a medida, con** { collection: ‘hospitales’ } lo podemos personalizar 🤟🏻


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u53t6jlvf18bieabf42w.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/okhgok6gyiwr0zwmykul.png)

models/hospital.model.js

```js
const { Schema, model } = require('mongoose');

const hospitalSchema = Schema(
  {
    name: {
      type: String,
      required: true,
    },
    img: {
      type: String,
    },
    user: {
      required: true,
      type: Schema.Types.ObjectID,
      ref: 'User',
    },
  },

  // Por defecto mongoose le agrega al los modelos una s al final del nombre del modelo,
  // y en este caso sería por defecto “Hospitals” y con esta opción le damos un
  // nombre personalizado “hospitales” y así va a aparecer en la Db de mongoose
  { collection: 'hospitales' }
);

// Esto para modificar los nombres de los campos retornados de la Db
hospitalSchema.method('toJSON', function () {
  // Al extraer los campos dejan de ser regresados, como por ejemplo
  // el Password no conviene que se muestre ese valor por seguridad y
  // por lo tanto no se regresa , igual se extrae el __v por pura estetica

  const { __v, _id, ...object } = this.toObject();

  object.uid = _id;

  return object;
});

module.exports = model('Hospital', hospitalSchema);
```

models/medico.model.js

```js
const { Schema, model } = require('mongoose');

const medicoSchema = Schema({
  user: {
    type: Schema.Types.ObjectID,
    ref: 'User',
    required: true,
  },
  name: {
    type: String,
    required: true,
  },
  img: {
    type: String,
  },
  hospital: {
    type: Schema.Types.ObjectID,
    ref: 'Hospital',
    required: true,
  },
});

// Esto para modificar los nombres de los campos que retornados de la Db
medicoSchema.method('toJSON', function () {
  // Al extraer los campos dejan de ser regresados, como por ejemplo
  // el Password, no conviene que se muestre ese valor por seguridad,
  // por lo tanto, no se regresa.

  // Igual se puede extraer el __v por pura estética, también se puede
  // cambiar si se necesita el _id por uid, se retornaría el object
  // con los campos modificados.

  const { __v, _id, ...object } = this.toObject();
  object.uid = _id;

  return object;
});

module.exports = model('Medico', medicoSchema);
```

models/usuario.model.js

```js
const {ROLES} = require('../constant');
const {Schema, model} = require('mongoose');

const userSchema = Schema({
   name: {
      type: String,
      required: true,
   },
   email: {
      type: String,
      required: true,
      unique: true,
   },
   password: {
      type: String,
      required: true,
   },
   img: {
      type: String,
   },
   role: {
      type: String,
      required: true,
      default: ROLES.USER_ROLE,
   },
   google: {
      type: Boolean,
      default: false,
   },
});

// Esto para modificar los nombres de los campos retornados de la Db
userSchema.method('toJSON', function () {

   const {__v, _id, password, ...object} = this.toObject();

   object.uid = _id;

   return object;
});

module.exports = model('User', userSchema);
```

**Validación del JWT**

Haciendo uso del Middleware — middlewares/validate-jwt.middleware.js


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/stvo50787fzv1plf2s47.png)

**El uso de _express-validator para_** _v_alidar los datos enviados al servidor en el body

routes/auth.route.js

Es usado en las rutas como un Middleware


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nrq2k6u7fu3g3k66v7uy.png)

**Validar un MongoID**

.isMongoId()

```js
check('hospital', 'El id del hospital no es válido').isMongoId(),

```

  CRUD de médicos, usuarios y hospitales

## Uso de los modelos de mongoose para obtener los datos, buscar, actualizar y borrar información en la dB

Con el uso de los Schema se crea el modelo

**El Modelo de Hospitales como ejemplo:**

models/hospital.model.js

Obtener todos los hospitales data guardada en la collection.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8fqdaqtvw924s76yz99d.png)

Guardar información.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hxc53u2qq7sdjrmk5dsb.png)

Actualizar la información.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zqtube2artdny1np78uu.png)

Borrar información un hospital.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7gdjl46r8i3ok6k8us06.png)

Búsqueda de un Hospital haciendo uso de las expresiones regular.

controllers/search.controller.js


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h8bv9w3xpfueit7so144.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w4oetgrqjpsa8s9l1k9w.png)

Si se usa **find({})** sin parámetros devuelve toda la collection.

**Búsquedas en varias colecciones a la vez.**

controllers/search.controller.js

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g5zkbj5jg7a2gsgwmfgi.png)

**Paginación de los datos haciendo uso de .skip y .limit**


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ma8m3xk4virn8kywknu6.png)

## Protección de rutas basadas en JWT y sistema de Roles

constant.js

```js
/**
 *
 * Global constants file
 *
 */
module.exports = {
   ROLES: {
      ADMIN_ROLE: 'ADMIN_ROLE',
      USER_ROLE: 'USER_ROLE'
   }
}
```

middlewares/validate-role.middleware.js

```js
const { request, response } = require('express');
const UsersModel = require('../models/usuario.model');

const validateRole =
  (roles = [], paramsUID = false) =>
  async (req = request, res = response, next) => {
    try {
      let getParamsUID;
      const { uid } = req.usuario;

      // Obtener el usuario del uid
      const usuario = await UsersModel.findById(uid);

      if (paramsUID) {
        getParamsUID = req.params.id;
      }

      if (!usuario || !roles.includes(usuario.role) && !(paramsUID && getParamsUID === uid)) {
        return res.status(403).json({
          msg: 'Acceso denegado',
        });
      }

      next();
    } catch (e) {
      return res.status(403).json({
        msg: 'Acceso denegado',
      });
    }
  };

module.exports = {
  validateRole,
};
```

middlewares/validate-jwt.middleware.js

```js
const { response, request } = require('express');
const jwt = require('jsonwebtoken');

const validateJWT = async (req = request, res = response, next) => {
  const { authorization: token } = req.headers;

  try {
    if (!token) {
      return res.status(401).json({
        msg: 'Token no definido',
      });
    }

    // Leer Token
    const data = await jwt.verify(token, process.env.JWT_SECRET);
    const { uid, role } = data.payLoad;

    // se pasa al controlador el uid
    req.usuario = { uid, role };

    next();
  } catch (e) {
    res.status(500).json({
      msg: 'Token no valido',
    });
  }
};

module.exports = {
  validateJWT,
};
```

**Login con Google y verificación de su Token**

helpers/googleVerifyIdToken.helper.js

```js
const { OAuth2Client } = require('google-auth-library');
const client = new OAuth2Client(process.env.GOOGLE_ID);

const googleVerifyIdToken = async (token) => {

  const ticket = await client.verifyIdToken({
    idToken: token,
    audience: process.env.GOOGLE_ID,  // Specify the CLIENT_ID of the app that accesses the backend
  });

  return { email, name, picture } = ticket.getPayload();

}


module.exports = {
  googleVerifyIdToken
}
```

controllers/auth.controller.js

```js
const loginGoogle = async (req = request, res = response) => {
  const { token: G_token } = req.body;

  try {
    const { email, name, picture } = await googleVerifyIdToken(G_token);

    // Se chequea si el usuario existe o se va a crear uno nuevo
    const userDB = await UsersModel.findOne({ email });

    let userNew;

    if (!userDB) {
      userNew = new UsersModel({
        password: '123456',
        name,
        email,
        google: true,
        img: picture,
      });

      // Guarda en la Db el user
      await userNew.save();
    } else {
      userNew = userDB;
      userNew.google = true;

      // Guarda en la Db el user
      await userNew.save();
    }

    const payLoad = {
      uid: userNew.id,
      role: userNew.role,
    };

    // Genera un Token de JWT
    const token = await generateJWT(payLoad);

    res.json({
      token,
      usuario: userNew
    });
  } catch (e) {
    console.log(e);

    res.status(500).json({
      msg: 'El Token no es correcto ',
    });
  }
};
```

**Login normal.**

controllers/auth.controller.js

Uso de **findOne** para devolver la primera conciencien en la collection.

```js
const login = async (req = request, res = response) => {
  const { email, password } = req.body;

  try {
    // Verifica el Email
    const userDb = await UsersModel.findOne({ email });
    if (!userDb) {
      return res.status(404).json({
        msg: 'No se ha podido encontrar tu cuenta',
      });
    }

    // Verifica el Password
    const validPass = bcrypt.compareSync(password, userDb.password);

    if (!validPass) {
      return res.status(400).json({
        msg: 'Contraseña incorrecta',
      });
    }

    const payLoad = {
      uid: userDb.id,
      role: userDb.role,
    };

    // Genera un Token de JWT
    const token = await generateJWT(payLoad);

    res.json({
      token,
      usuario: userDb
    });
  } catch (e) {

    res.status(500).json({
      msg: 'Error inesperado… revisar logs',
    });
  }
};
```

## Uso de **express-fileupload** para subir archivos

routes/upload.route.js

```js
const { Router } = require('express');
const router = Router();
const { ROLES } = require('../constant');

// Middlewares
const { validateJWT } = require('../middlewares/validate-jwt.middleware');
const { validateUploads } = require('../middlewares/validate-uploads.middleware');

const fileUpload = require('express-fileupload');
router.use(fileUpload());

// Controllers
const { upLoad, returnImg } = require('../controllers/upload.controller');
const { validateRole } = require('../middlewares/validate-role.middleware');


router.put('/:type/:id', [validateJWT, validateRole([ROLES.ADMIN_ROLE], true), validateUploads], upLoad);

router.get('/:type/:photo', [validateUploads], returnImg);

module.exports = router;
```

controllers/upload.controller.js

```js
const { request, response } = require('express');
const { v4: uuidv4 } = require('uuid');
const { upDateImage } = require('../helpers/upDate-image.helper');
const path = require('path');
const fs = require('fs');


const upLoad = async (req = request, res = response) => {

  try {

    const { id, type } = req.params;

    // Valida que se haya mandado un archivo
    if (!req.files || Object.keys(req.files).length === 0) {
      return res.status(400).json({
        msg: 'Error: No se ha mandado ningún archivo'
      });
    }

    // Se procesa la imagen

    const file = req.files.image;
    const nameSplit = file.name.split('.');
    const extFile = nameSplit[nameSplit.length -1].toLowerCase();

    // Extensiones Validas permitidas
    const mimeTypeValid = [
      'image/jpeg',
      'image/png',
      'image/gif'
    ];

    // Verifica que lo que se envié sea del tipo permitido
    if(!mimeTypeValid.includes(file.mimetype)){

      return res.status(400).json({
        msg: 'Error: No es un archivo permitido'
      });

    }

    // Genera el nuevo nombre del archivo
    const nameFile = `${ uuidv4() }.${ extFile }`;

    // Path para guardar el archivo

    const path = `./uploads/${type}/${nameFile}`;

    // Mueve la imagen
    await file.mv(path, (err) =>{

      if (err){

        console.log(err);

        res.status(500).json({
          msg: 'Error inesperado no se pudo subir la imagen… revisar logs'
        });

      }

      // Actualizar base de datos
      upDateImage(type, id, nameFile);


      res.json({
        upLoad: true,
        nameFile
      });

    });

  }catch (e) {

    console.log(e)

    res.status(500).json({
      msg: 'Error inesperado… revisar logs'
    });

  }

}

const returnImg = async (req = request, res = response) => {

  try {

    const {photo, type} = req.params;
    let pathImg = path.join(__dirname, `../uploads/${type}/${photo}`);

    // Si no existe la imagen se manda una por defecto
    if(!fs.existsSync(pathImg)){

      pathImg = path.join(__dirname, `../uploads/no-img.jpg`);

    }

    return res.sendFile(pathImg);

  } catch (e) {

    console.log(e)

    res.status(500).json({
      msg: 'Error inesperado… revisar logs'
    });

  }

}

module.exports = {

  upLoad,
  returnImg

};
```

helpers/upDate-image.helper.js

```js
const fs = require('fs');

const UsersModel = require('../models/usuario.model');
const HospitalsModel = require('../models/hospital.model');
const MedicosModel = require('../models/medico.model');

const deleteImg = (path) => {
  if (fs.existsSync(path)) {
    try {
      fs.unlinkSync(path);
    } catch (e) {
      return false;
    }
  }
};

const upDateImage = async (type, id, nameFile) => {
  switch (type) {
    case 'hospitals': {
      const hospital = await HospitalsModel.findById(id);

      if (!hospital) {
        console.log('El id del hospital no existe');

        return false;
      }

      if (hospital.img) {
        const oldPath = `./uploads/${type}/${hospital.img}`;

        // Borrar la imagen anterior
        deleteImg(oldPath);
      }

      hospital.img = nameFile;

      try {
        await hospital.save();

        return true;
      } catch (e) {
        return false;
      }
    }

    case 'medicos': {
      const medico = await MedicosModel.findById(id);

      if (!medico) {
        console.log('El id del medico no existe');

        return false;
      }

      if (medico.img) {
        const oldPath = `./uploads/${type}/${medico.img}`;

        // Borrar la imagen anterior
        deleteImg(oldPath);
      }

      medico.img = nameFile;

      try {
        await medico.save();

        return true;
      } catch (e) {
        return false;
      }
    }

    case 'users': {
      const user = await UsersModel.findById(id);

      if (!user) {
        console.log('El id del hospital no existe');

        return false;
      }

      if (user.img) {
        const oldPath = `./uploads/${type}/${user.img}`;

        // Borrar la imagen anterior
        deleteImg(oldPath);
      }

      user.img = nameFile;

      try {
        await user.save();

        return true;
      } catch (e) {
        return false;
      }
    }
  }
};

module.exports = {
   upDateImage
}

```

**Habilitación de una carpeta pública para servir el proyecto de Angular compilado**

index.js


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/te5s9kikxjddl2kmf402.png)

---
# Sesión 3 — Pruebas unitarias y de integración
---

**Demo:** [https://replit.com/@dennysjmarquez/angular-13-unit-test-and-integration-demo](https://replit.com/@dennysjmarquez/angular-13-unit-test-and-integration-demo)

{% embed https://replit.com/@dennysjmarquez/angular-13-unit-test-and-integration-demo %}

**Código fuente:** [https://github.com/dennysjmarquez/angular-13-unit-test-and-integration](https://github.com/dennysjmarquez/angular-13-unit-test-and-integration)

# Las pruebas están separadas en 4 categorías:

## **Básicas**

En estas pruebas verán la comprobación de Arrays, La comprobación de los booleans y las diferentes formas de hacer esto

Ej. `expect(resp).toBe(true) expect(resp).toBeTrue() expect(resp).toBeTruthy()`

`// la Negación puede ser asi o usar uno que evalué un false`

`expect(resp).not.toBeTruthy()`

También muestro el cómo hacer un test de funciones que están dentro de una class, probando el return de la misma, Pruebas con números usando toBe, string uso de toContain expect(typeof resp).toBe('string') familiarización con la evaluación de expect, siclos de vida del describe de [Jasmine](https://jasmine.github.io/api/3.10/global), tales como beforeAll, beforeEach, afterAll, afterEach y en que caso usar cada uno de ellos.

## Intermedias

Esta sección trabaja con pruebas un poco más complejas y reales:

1.  Pruebas sobre Event Emitter
2.  Formularios
3.  Validaciones
4.  Saltar pruebas
5.  Espías
6.  Simular retornos de servicios
7.  Simular llamado de funciones

Esta sección da fundamentos muy valiosos para realizar pruebas unitarias y de integración Se hacen comprobaciones simples de un componente haciendo usos de cosas simples como estas component = `new Form(new FormBuilder())`, aquí ya se empieza a ver los `spyOn()` para espiar algunos métodos de algunos servicios y hacer a las pruebas en relación con los resultados de estos métodos.

## Intermedias 2

Esta sección se enfoca en las pruebas de integración:

1.  Aprender la configuración básica de una prueba de integración
2.  Comprobación básica de un componente
3.  TestingModule
4.  Archivos SPEC generados automáticamente por el AngularCLI
5.  Pruebas en el HTML
6.  Revisar inputs y elementos HTML
7.  Separación entre pruebas unitarias y pruebas de integración

Ya aquí empiezo a usar a `TestBed`, `ComponentFixture`, `configureTestingModule` que es una copia limitada de lo que sería el `@NgModule`, pero para las pruebas y donde se va a poder insertar módulos componentes y servicios, también controlo ya aquí lo que es el siclo de control de cambios de Angular mediante el uso de detectChanges para que se puedan hacer pruebas de integración, ya que con esto se actualiza el HTML.

En esta sesión ya empiezo a usar a `debugElement.query()` y `By.css` para acceder al HTML y hacer las comprobaciones necesarias en una prueba de integración.

## Avanzadas

Esta sección es un verdadero reto, especialmente entre más te vas acercando al final de la misma. Aquí veremos temas como:

1.  Revisar la existencia de una ruta
2.  Confirmar una directiva de Angular (router-outlet y routerLink)
3.  Errores por selectores desconocidos
4.  Reemplazar servicios de Angular por servicios falsos controlados por nosotros
5.  Comprobar parámetros de elementos que retornen observables
6.  Subject
7.  Gets

En estas pruebas haremos comprobaciones de los params del ActivatedRoute, y comprobaremos la navegación del Router, con toHaveBeenCalledWith Verificando que se llame con los parámetros indicados para la ruta ruta en cuestion.



-FIN-