
Cloud Functions for Firebase Functions es una de las características que más me emocionan sobre la plataforma de Firebase. Si no sabes que son las Firebase Cloud Functions, puedes ver éste post que Carlos ya ha escrito.
Hola a todos! Soy Carlos Rojas (@carlosrojas_o) y voy a ser su guia el dia de hoy en como utilizar los activadores del Storage con Firebase Functions.
¿Qué vamos hacer?
Vamos a crear el siguiente flujo. Cuando una imagen sea subida a Firebase Storage va a realizar lo siguiente.
- Va a convertir las imágenes que no sean JPG a este formato.
Algo muy útil y muy común en la actualidad con todas las apps de imágenes.
Preparación
Ok, para utilizar estas características y en general Firebase, debes tener lo siguiente instalado.
-
Firebase Tools. Para esto desde un terminal ejecuta.
$ npm install -g firebase-tools
-
Debemos comenzar un proyecto en la consola web de Firebase. Debes crear una cuenta desde aquí. y luego comenzar un proyecto.
-
Una vez creamos el proyecto, volvemos a nuestra terminal y ejecutamos el siguiente comando desde la carpeta de nuestro proyecto:
$firebase init functions
-
Listo, ya debemos tener nuestro proyecto listo y conectado.
Consideraciones
Firebase Functions es una característica muy poderosa ya que nos da el poder de computación del Backend solo creando pequeñas funciones. Pero para utilizarlo correctamente debemos tener claro algunas cosas.
- Las funciones se escriben con NodeJS.
- El API de la librería de functions es muy poderoso, échale un vistazo para saber cómo te puede ayudar aquí.
Continuemos
Hasta este punto deberías tener algo así.
Lo importante acá, es que vamos a trabajar sobre el archivo index.js en la carpeta functions.
Vamos al código
Primero debemos importar los módulos que vamos a utilizar.
'use strict';
const functions = require('firebase-functions');
const mkdirp = require('mkdirp-promise');
const gcs = require('@google-cloud/storage')();
const spawn = require('child-process-promise').spawn;
const path = require('path');
const os = require('os');
const fs = require('fs');
// File extension for the created PNG files.
const PNG_EXTENSION = '.png;
Aca es importante ver que estamos utilizando modulos adicionales para el trabajo y por lo tanto debemos agregarlo a nuestro proyecto en la carpeta /functions.
Agrego aca la lista:
- Cloud Storage. Se va a utilizar para procesar la imagen.
- Mkdirp-promise. Para la creación de los directorios.
- child-process-promise. Para saber el resultado de nuestras instrucciones.
- path. Para manejar las rutas.
Nota: debes instalar cada requerimiento con npm install
, mira el link para hacerlo.
Ya con esto listo creamos nuestra función.
exports.imageToPNG = functions.storage.object().onChange(event => {
const object = event.data;
const filePath = object.name;
const baseFileName = path.basename(filePath, path.extname(filePath));
const fileDir = path.dirname(filePath);
const PNGFilePath = path.normalize(path.format({dir: fileDir, name: baseFileName, ext: PNG_EXTENSION}));
const tempLocalFile = path.join(os.tmpdir(), filePath);
const tempLocalDir = path.dirname(tempLocalFile);
const tempLocalPNGFile = path.join(os.tmpdir(), PNGFilePath);
// Verifica si el elemento subido al Storage es una imagen.
if (!object.contentType.startsWith('image/')) {
console.log('No es imagen.');
return;
}
// Verifica si la imagen ya es un PNG.
if (object.contentType.startsWith('image/png')){
console.log('Es un PNG.');
return;
}
// El evento se activo por un borrado o movida del elemento.
if (object.resourceState === 'not_exists') {
console.log('El elemento fue borrado.');
return;
}
const bucket = gcs.bucket(object.bucket);
// Crea una ruta temporal donde el archivo sera guardado temporalmente.
return mkdirp(tempLocalDir).then(() => {
// Descarga un archivo desde el bucket.
return bucket.file(filePath).download({destination: tempLocalFile});
}).then(() => {
console.log('El archivo ha sido descargado a',
tempLocalFile);
// convierte la imagen a PNG usando ImageMagic
return spawn('convert', [tempLocalFile, tempLocalPNGFile]);
}).then(() => {
console.log('La imagen en PNG ha sido creada ', tempLocalPNGFile);
// Subiendo la imagen PNG
return bucket.upload(tempLocalPNGFile, {destination: PNGFilePath});
}).then(() => {
console.log('imagen nueva subida a ', PNGFilePath);
// Liberamos el espacio de los archivos temporales.
fs.unlinkSync(tempLocalPNGFile);
fs.unlinkSync(tempLocalFile);
});
});
Analicemos varias cosas.
Primero el uso de functions.storage.object().onChange()
para escuchar los eventos que se realicen sobre el storage.
El uso de const gcs = require('@google-cloud/storage')();
para descargar la imagen en el storage y convertirla con ImageMagick
.
El resto del codigo creo que se explica por si mismo, realizamos las verificaciones pertinentes y si cumple el requerimiento ( Ser imagen y no estar en PNG ) realizamos la operación.
Despliegue en Firebase
Ahora que ya tenemos nuestra función lista, simplemente realizamos el despliegue. En este punto puedes usar dos enfoques.
$ firebase deploy
este comando te envia todo (Firebase Hosting, Functions, etc ) a firebase, para este ejemplo no deberias sentir la diferencia, pero si tienen una app robusta en hosting, si se demorara mucho en actualizar tus funciones. Para solucionar esto, el segundo enfoque es.
$ firebase deploy --only functions
este comando solo actualizará las funciones.
Verificación y depuración.
Para probar nuestras funciones es muy simple, solo ingresa a la consola web de Firebase y sube una imagen en JPG.
y después de unos segundos debe aparecer la nueva imagen.
Perfecto.
Puedes ver los mensajes de consola que colocamos en la función en la consola web de firebase en Functions.
Próximos pasos
- Ingresa a la documentación e intenta agregar otros activadores a tu código.
- Aprende un poco de los fundamentos de Node, te serán de utilidad en este servicio.
Bueno espero que este tutorial sea de ayuda en sus proyectos y se sientan tan emocionados como yo sobre esto. Si tienen preguntas mi twitter es éste y los invitó a la comunidad de Ionic en español ion-book donde pasó la mayoría de mi tiempo. Muchas gracias por la oportunidad de escribir en este blog y espero hacerlo en otra oportunidad. Hasta Pronto :)