Introducción

1

Qué necesitas para este curso y qué aprenderás sobre Node.js con Hapi

2

Conceptos principales de hapi y creación de nuestro primer servidor

3

Breve historia y estado actual

Creando un sitio básico con Hapi

4

El objeto h, response y sus herramientas

5

Uso de plugins - Contenido estático

6

Plantillas con Handlebars

7

Renderizado de vistas - Layout y template del home

8

Recibiendo parámetros en una ruta POST - Creación del registro

9

Definir una mejor estructura con buenas prácticas en Hapi

10

Validando la información - Implementando Joi

11

Introducción a Firebase

12

Creando un modelo y guardando en firebase

13

Implementando el login y validación del usuario

14

Autenticación de usuarios - Cookies y estado

15

Manejando errores

16

Visualización de errores

17

Controlar el error 404 en inert y el error de validación

18

Repaso - Creación del modelo y controlador para preguntas

19

Repaso - Creación de las rutas para crear preguntas

20

Listar las últimas preguntas en el home

Aplicacion de conceptos avanzados

21

Enrutamiento avanzado - visualizando una pregunta

22

Enrutamiento avanzado - respondiendo una pregunta

23

Generando la lógica de la plantilla según si es creador o contribuidor

24

Métodos de servidor - respuesta correcta

25

Usando métodos de servidor

26

Manejo del caché - Agregando el home al caché

27

Procesamiento de archivos - Aceptando imágenes

28

Logging con Good - Monitoreando el servidor

29

Creación de plugins - Teoría

30

Creación de plugins - Implementando un API REST

31

Estrategías de autenticación - Asegurando el API REST

32

Seguridad básica - Asegurando el servidor contra CSRF

33

Seguridad básica - Asegurando el servidor contra XSS

Herramientas de desarrollo

34

Depuración del proyecto

35

Ecosistema de Hapi

Aún no tienes acceso a esta clase

Crea una cuenta y continúa viendo este curso

Procesamiento de archivos - Aceptando imágenes

27/35
Recursos

En esta clase aprenderemos el manejo de archivos con Hapi. Agregaremos la posibilidad de anexar imágenes a las preguntas. Para esto será necesario modificar el método create del modelo questions.js para que guarde en la base de datos de Firebase el nombre de archivo.

Será necesario también requerir algunos módulos adicionales, o algunas funciones desde esos módulos:

const { writeFile } = require('fs')
const { promisify } = require('util')
const { join } = require('path')

Instalaremos y usaremos el módulo uuid en su versión v1 para manejar nuestros propios nombres de archivo internamente y evitar la duplicidad.

En el controlador de las preguntas incorporamos la lógica del manejo de archivos cuando se ha identificado la presencia del dato image en el buffer del con

Buffer.isBuffer( request.payload.image )

Este campo image debemos incluirlo en el formulario, en la vista con el formulario de respuesta. Finalmente, al recibir el archivo a través del buffer tendremos que escribirlo en el filesystem del servidor, para lo cual usaremos la función writeFile que hemos convertido en promesa y llamado write( args ) con los argumentos correspondientes. Ya para mostrar la imagen en la vista cuando se haya recuperado, sólo bastará con incorporar la etiqueta <img /> con la referencia al archivo almacenado.

Aportes 15

Preguntas 1

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad? Crea una cuenta o inicia sesión.

La validación de Buffer si funciona como la hizo el profesor, y así se aseguran de que solo si la imagen es un binario sea guardada.
Para que el binario de la imagen sea accesible desde el payloaddeben de asegurarse de que en el html su form tenga habilitada la opción:

enctype="multipart/form-data"

Para que no el servidor no incurra en un error al habilitar este tipo de content-type. En las opciones de la ruta agreguen:

options: {
      payload: {
        parse: true,
        multipart: true,
      },
....

Esto habilitará la cabecera que permitirá el tipo de contenido.
Y así la definición de la ruta quedará:

  {
    method: 'POST',
    path: '/new-question',
    options: {
      payload: {
        parse: true,
        multipart: true,
      },
      validate: {
        payload: questionSchema.validQuestion,
        failAction: questionController.failValidation,
      },
    },
    handler: questionController.createQuestion,
  },

Por si alguno tiene problemas con el buffer lo solucione de la siguiente manera:

var x = Buffer.from(req.payload.image);
      //console.log(Buffer.isBuffer(x));
        if(Buffer.isBuffer(x)){
          filename = `${uuid.v1()}.png`;
          console.log(filename);
          await write(join(__dirname,'..','public','uploads',filename), req.payload.image);
        }
const uuidv1 = require('uuid/v1'); // <== NOW DEPRECATED!
uuidv1();
const { v1: uuid } = require('uuid');

Para los que tengan error con el require del uuid

const uuid = require('uuid/v1')

cambiar por:

const uuid = require('uuid/dist/v1')

El modulo fs de node, en la versiones más recientes, soporta por defecto las promesas (es un feature en etapa experimental a la fecha 10/10/18). ver documentación

No funciona la validación de Buffer.

La subida de imágenes, a mi parecer es mucho más fácil con Express y usando módulos externos.

Mi ruta quedo de la siguiente manera:

{
    path: '/create-question',
    method: 'POST',
    options: {
      payload: {
        multipart: true,
      },
      validate: {
        payload: Joi.object({
          title: Joi.string().required(),
          description: Joi.string().required(),
          image: Joi.any().optional(),
        }),
        failAction: user.failValidation,
      },
    },
    handler: question.createQuestion,
  },

Tengo un problema al momento de gurardar los archivos, se guardan pero no puedo ver la imagen, dice que el archivo está dañado y

Buffer.isBuffer( req.payload.image )

Siempre me devuelve falso a pesar de que haya mandado una imagen

mi aporte:

const { v1: uuidv1 } = require('uuid')

const write = promisify(writeFile)


async function createQuestion(req, h) {
    if (!req.state.user) {
        return h.redirect('/login')
    }

    let result, filename
    try {
        if (Buffer.isBuffer(req.payload.image)) {
            filename = `${uuidv1()}.png`
            await write(join(__dirname, '..', 'public', 'uploads', filename), req.payload.image)
        }
        result = await questions.create(req.payload, req.state.user, filename)
        console.log(`Pregunta creada con el ID: ${result}`)
        return h.redirect(`/question/${result}`)
        //return h.response(`Pregunta creada con el ID: ${result}`)
    } catch (error) {
        console.error(`Ocurrio un error: ${error}`)

        return h.view('ask', {
            title: 'Crear pregunta',
            error: 'Problemas creado la pregunta'
        }).code(500).takeover()
    }
}

Hola compañero, tengo un problema que no he podido solucionar.
Tengo el mismo código que en la clase, pero en el payload se manda el nombre de la imagen y no su binario.
Alguna ayuda?

Para recibir un vídeo o un documento etc podría usar la misma lógica ?

Mi aporte:

const { writeFile, mkdirSync } = require('fs')

......
async function createQuestion (req, h) {
  if (!req.state.user) {
    return h.redirect('/login')
  }

  try {
    // Realizamos la destructuracion de la pregunta para eliminar [Object: null prototype]
    const question = { ...req.payload }

    let filename
    if (Buffer.isBuffer(question.image)) {
      filename = `${uuid()}.png`

      // Creamos la carpeta uploads si no existe 
      mkdirSync(`${__dirname}/../public/uploads`,{recursive:true});
      // Agregamos la imagen en la carpeta el uploads
      await write(join(__dirname, '..', 'public', 'uploads', filename), question.image)
    }

    const result = await questions.create(question, req.state.user, filename)
    console.log(`Pregunta creada con el ID ${result}`)

    return h.redirect(`/question/${result}`)
  } catch (error) {
    console.error(`Ocurrio un error: ${error}`)

    return h.view('ask', {
      title: 'Crear pregunta',
      error: 'Problemas creando la pregunta'
    }).code(500).takeover()
  }
}

veo en algunos comentariosque proponen arrgalar la parte del buffer con este codigo

var x = Buffer.from(req.payload.image);

sin embargo, si se emplea ese codigo se guarda una imagen vacio en la carpeta updates, propongo otra solucion para el problema del Buffer

es instalar el paquete

npm i is-buffer

y el codigo quedaría asi

const isBuffer = require('is-buffer')

async function createQuestion(req, h) {
    if (req.state.user) {
        h.redirect('/login')
    }
    let result, filename;
    try {
        const x = req.payload.image;
        //console.log(isBuffer(x.toString('binary')));
        if (isBuffer(x)) {
            filename = `${uuid()}.png`
            
            const path = join(__dirname,'..', 'public/uploads/',filename);
            await write(path, req.payload.image, (err) => {
                if (err) {
                    console.log("Error uploading file.");
                }
                console.log("File is uploaded");
            });
        }
        //console.log( req.payload, req.state.user )
        result = await questions.create(req.payload, req.state.user, filename);

    } catch (err) {
        console.error(`Ocurrio un error: ${err}`);
        return h.view('ask', {
            title: 'Crear pregunta',
            error: 'Problemas creado la pregunta',
        }).code(500).takeover();
    }
    console.log(`Pregunta creada con el ID 1 ${result}`);
    return h.redirect(`/question/${result}`);
}

Estuvo bueno el video, aunque me incline por implementar la conexión con firebase storage…

Me quedo así el router

const newQuestion: ServerRoute = {
  method: 'POST',
  path: '/ask',
  handler: controller.newAsk,
  options: {
    validate: {
      payload: Joi.object({
        title: Joi.string().required().min(3),
        description: Joi.string().required().min(10),
        image: Joi.optional(),
      }),
      failAction: failJoiValidation,
    },
    payload: {
      output: 'stream',
      parse: true,
      multipart: {
        output: 'stream',
      },
    },
  },
};

y hay que implementar el módelo de conexión al storage…

import { nanoid } from 'nanoid';
import { storage } from 'firebase-admin';

export class Images {
  private storage: storage.Storage;

  constructor(adminStore: storage.Storage) {
    this.storage = adminStore;
  }

  async newImage(streamFile: any, imagePath: string) {
    const storageBucket = this.storage.bucket();
    const arrImagePath = imagePath.split('.');
    const file = storageBucket.file(
      `${nanoid()}.${arrImagePath[arrImagePath.length - 1]}`,
    );

    await file.save(streamFile);
    await file.makePublic();
    file.publicUrl();
    return file.name;
  }
}

también es necesario agregar la info en la conexión al admin de firebase (solo 2 líneas extra)

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: process.env.DB_URL,
// esta es una línea adicional
  storageBucket: process.env.STORAGE_BUCKET
})

// y la otra línea es la exportación 
export const storage = admin.storage();

feedback al repo siempre bienvenida

repo github