├── ci-cd ├── cloudbuild.md └── github-actions.md ├── .github └── FUNDING.yml ├── send-email-with-sendgrid.js ├── csv-json-export.js ├── schedule.ts ├── send-sms-with-twilio.js ├── send-email-with-mailgun.js ├── local-test-easy.js ├── http-request.js ├── send-notifications-with-slack.js ├── README.md └── revisions.ts /ci-cd/cloudbuild.md: -------------------------------------------------------------------------------- 1 | # Deploy Firebase Via Cloud Build 2 | 3 | Deploy firebase hosting or cloud functions via Google Cloud Build. 4 | 5 | ``` 6 | ### cloudbuid.yaml 7 | 8 | steps: 9 | # Step 1 10 | - name: node:10 11 | entrypoint: yarn 12 | args: 13 | - 'install' 14 | 15 | # Step 2 16 | - name: node:10 17 | entrypoint: yarn 18 | args: 19 | - build 20 | 21 | # Step 3 22 | - name: 'gcr.io/cloud-builders/gcloud' 23 | entrypoint: /bin/bash 24 | args: ['-c', 'gcloud auth application-default print-access-token > FIREBASE_TOKEN'] 25 | 26 | # Step 4 27 | - name: 'node:10' 28 | entrypoint: yarn 29 | args: 30 | - deploy 31 | 32 | timeout: 1200s # 20min 33 | 34 | ``` 35 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: dalenguyen # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: dalenguyen # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /send-email-with-sendgrid.js: -------------------------------------------------------------------------------- 1 | // TypeScript 2 | import * as sgMail from '@sendgrid/mail'; // Version ^6.2.1 3 | 4 | // JavaScript 5 | const sgMail = require('@sendgrid/mail'); 6 | 7 | // Sendgrid setting 8 | const SENDGRID_API_KEY = 'SENDGRID_API_KEY'; 9 | sgMail.setApiKey(SENDGRID_API_KEY); 10 | 11 | const senderEmail = 'Dale Nguyen '; 12 | const sendToEmails = ['receiver-1@example.com', 'receiver-2@example.com']; 13 | 14 | // Prepare email content 15 | const msg = { 16 | to: sendToEmails, 17 | from: senderEmail, 18 | subject: 'Email Subject', 19 | html: 'Hey! I got an email!', 20 | }; 21 | 22 | // Start sending meails 23 | sgMail.send(msg) 24 | .then(() => console.log(`Email sent! to ${msg.to}`)) 25 | .catch(err => console.log('Error: ', err)) -------------------------------------------------------------------------------- /csv-json-export.js: -------------------------------------------------------------------------------- 1 | const json2csv = require("json2csv").parse; 2 | 3 | exports.csvJsonReport = functions.https.onRequest((request, response) => { 4 | 5 | // You should you how to prepare an object 6 | // It could be anything that you like from your collections for example. 7 | var report = {'a': 0, 'b': 1}; 8 | 9 | // Return JSON to screen 10 | response.status(200).json(report); 11 | 12 | // If you want to download a CSV file, you need to convert the object to CSV format 13 | // Please read this for other usages of json2csv - https://github.com/zemirco/json2csv 14 | const csv = json2csv(report) 15 | response.setHeader( 16 | "Content-disposition", 17 | "attachment; filename=report.csv" 18 | ) 19 | response.set("Content-Type", "text/csv") 20 | response.status(200).send(csv) 21 | 22 | }) 23 | -------------------------------------------------------------------------------- /schedule.ts: -------------------------------------------------------------------------------- 1 | // https://firebase.google.com/docs/functions/schedule-functions 2 | 3 | import { RuntimeOptions, runWith, EventContext } from 'firebase-functions' 4 | 5 | const runtimeOpts: RuntimeOptions = { 6 | memory: '256MB', 7 | timeoutSeconds: 540, // max timeout 8 | } 9 | 10 | const catchErrorOnRun = (handler: (context) => Promise) => async ( 11 | context: EventContext 12 | ): Promise => { 13 | try { 14 | await handler(context) 15 | } catch (error) { 16 | console.error(error) 17 | } 18 | } 19 | 20 | export const handleSchedule = async () => { 21 | console.log('Schedule functions is running') 22 | return 23 | } 24 | 25 | // Function will run every 60 minutes 26 | export const scheduleFunction = runWith(runtimeOpts) 27 | .pubsub.schedule('every 60 minutes') 28 | .onRun(catchErrorOnRun(handleSchedule)) 29 | -------------------------------------------------------------------------------- /send-sms-with-twilio.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Typescript 3 | * Twilio version: ^3.15.0 4 | */ 5 | 6 | import * as Twilio from 'twilio'; 7 | 8 | // getting ready 9 | const twilioNumber = 'your-twilio-number'; 10 | const accountSid = 'AC-something'; 11 | const authToken = 'something-something'; 12 | 13 | const client = new Twilio(accountSid, authToken); 14 | 15 | // start sending message 16 | 17 | function sendText(){ 18 | const phoneNumbers = [ 'phone-number-1', 'phone-number-2'] 19 | 20 | phoneNumbers.map(phoneNumber => { 21 | console.log(phoneNumber); 22 | 23 | if ( !validE164(phoneNumber) ) { 24 | throw new Error('number must be E164 format!') 25 | } 26 | 27 | const textContent = { 28 | body: `You have a new sms from Dale Nguyen :)`, 29 | to: phoneNumber, 30 | from: twilioNumber 31 | } 32 | 33 | client.messages.create(textContent) 34 | .then((message) => console.log(message.to)) 35 | }) 36 | } 37 | 38 | // Validate E164 format 39 | function validE164(num) { 40 | return /^\+?[1-9]\d{1,14}$/.test(num) 41 | } 42 | -------------------------------------------------------------------------------- /ci-cd/github-actions.md: -------------------------------------------------------------------------------- 1 | # Deploy Firebase Via Github Actions 2 | 3 | Deploy firebase hosting or cloud functions via Github Actions. 4 | 5 | Get the Firebase token by running `firebase login:ci` and store it as the `FIREBASE_TOKEN` secret. 6 | 7 | ``` 8 | # .github/workflows/dev.yml 9 | 10 | name: Push to Dev Branch 11 | 12 | on: 13 | push: 14 | branches: 15 | - dev 16 | 17 | jobs: 18 | deploy-function: 19 | name: Build and Deploy functions to Dev 20 | runs-on: ubuntu-latest 21 | env: 22 | FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }} 23 | 24 | steps: 25 | - name: Checkout Repo 26 | uses: actions/checkout@v2 27 | with: 28 | ref: dev 29 | 30 | - name: Install Firebase CLI 31 | run: sudo npm install -g firebase-tools 32 | 33 | - name: Install Dependencies 34 | working-directory: functions 35 | run: sudo npm install 36 | 37 | - name: Build dev 38 | working-directory: functions 39 | run: sudo npm run build 40 | 41 | - name: Deploy to dev 42 | working-directory: functions 43 | run: sudo firebase deploy --only functions --token $FIREBASE_TOKEN 44 | ``` 45 | -------------------------------------------------------------------------------- /send-email-with-mailgun.js: -------------------------------------------------------------------------------- 1 | // JavaScript 2 | import nodemailer from 'nodemailer'; 3 | 4 | // nodemailer settings 5 | const mailer = nodemailer.createTransport({ 6 | host: 'smtp.mailgun.org', 7 | port: 587, 8 | secure: false, // true for 465, false for other ports 9 | auth: { 10 | user: process.env.MAILGUN_EMAIL, 11 | pass: process.env.MAILGUN_PASSWORD 12 | } 13 | }); 14 | 15 | exports.sendByeEmail = functions.https.onRequest((request, response) => { 16 | const email = request.query.email; 17 | const firstname = request.query.firstname; 18 | return sendEmail(email, firstname); 19 | }); 20 | 21 | async function sendEmail(email, firstname) { 22 | 23 | const mailOptions = { 24 | from: 'Daniel N. ', 25 | to: `${validEmail(email)}` 26 | }; 27 | 28 | mailOptions.subject = `Mailgun is awesome!`; 29 | mailOptions.text = `Hello ${firstname}, Greeting from Buenos Aires, Argentina!`; 30 | await mailer.sendMail(mailOptions); 31 | 32 | return null; 33 | } 34 | 35 | 36 | // Validate email 37 | function validEmail(email) { 38 | const regex = `/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/g`; 39 | return regex.test(email); 40 | } -------------------------------------------------------------------------------- /local-test-easy.js: -------------------------------------------------------------------------------- 1 | // You don't have to deploy a function everytime you need to test. Do it locally instead! 2 | // https://firebase.google.com/docs/admin/setup#prerequisites 3 | 4 | /** 5 | * Typescript version (index.ts) 6 | */ 7 | 8 | import * as admin from 'firebase-admin'; 9 | import { serviceAccount } from './serviceAccountKey'; 10 | 11 | admin.initializeApp({ 12 | credential: admin.credential.cert({ 13 | projectId: serviceAccount.project_id, 14 | clientEmail: serviceAccount.client_email, 15 | privateKey: serviceAccount.private_key 16 | }), 17 | databaseURL: serviceAccount.database_url 18 | }) 19 | 20 | // serviceAccountKey.ts 21 | 22 | export const serviceAccount = { 23 | "project_id": "your-project-id", 24 | "private_key": "-----BEGIN PRIVATE KEY-----blah-blah-blah=\n-----END PRIVATE KEY-----\n", 25 | "client_email": "dalenguyen@.iam.gserviceaccount.com", 26 | "database_url": "https://dalenguyen.firebaseio.com" 27 | } 28 | 29 | /** 30 | * Javascrit verrsion (index.js) 31 | */ 32 | 33 | var admin = require('firebase-admin'); 34 | var serviceAccount = require('path/to/serviceAccountKey.json'); 35 | 36 | admin.initializeApp({ 37 | credential: admin.credential.cert(serviceAccount), 38 | databaseURL: 'https://.firebaseio.com' 39 | }); 40 | 41 | -------------------------------------------------------------------------------- /http-request.js: -------------------------------------------------------------------------------- 1 | // https://medium.com/@dalenguyen/working-with-firebase-functions-http-request-22fd1ab644d3 2 | 3 | // Start a HTTP Request function 4 | 5 | exports.request = functions.https.onRequest((request, response) => { 6 | // ... 7 | }); 8 | 9 | //----------------------------------- 10 | 11 | // Enable CORS (Allow ‘Access-Control-Allow-Origin’) 12 | 13 | // Add CORS to your index.js 14 | const cors = require('cors')({origin: true}); 15 | // Put this line to your function 16 | // Automatically allow cross-origin requests 17 | cors(req, res, () => {}); 18 | 19 | //----------------------------------- 20 | 21 | // Check for request methods 22 | 23 | if(request.method !== "POST"){ 24 | res.status(400).send('Please send a POST request'); 25 | return; 26 | } 27 | 28 | //----------------------------------- 29 | 30 | // Get data from POST request 31 | 32 | let data = request.body; 33 | 34 | /* 35 | * Send GET request with parameters from Angular 36 | * https://angular.io/api/http/RequestOptionsArgs 37 | */ 38 | 39 | import { Http, Headers } from '@angular/http'; 40 | 41 | // Prepare the header 42 | let headers: Headers = new Headers(); 43 | headers.set('parameter-name' , 'parameter-value'); 44 | 45 | // Send request with parameters 46 | this.http.get('url', { 47 | headers: headers 48 | }).subscribe(res => resolve(res.json())); 49 | 50 | // In order get the request value 51 | let params = req.headers['parameter-name']; 52 | 53 | -------------------------------------------------------------------------------- /send-notifications-with-slack.js: -------------------------------------------------------------------------------- 1 | const request = require('request-promise'); 2 | const moment = require('moment'); 3 | 4 | exports.orderPizza = functions.https.onRequest((request, response) => { 5 | const order = request.order; // The order of the user. 6 | return orderReceived(order); 7 | }); 8 | 9 | // Sends a welcome email to the given user. 10 | async function orderReceived(order) { 11 | 12 | await orderToSlack(` 13 | =========================== 14 | Order No. ${order.orderId} 15 | Created at: ${moment.unix(order.created_at).format('MM/DD/YYYY HH:MM')} 16 | *************************** 17 | Product: ${order.product} 18 | Quantity: ${order.qty} 19 | SKU: ${order.sku} 20 | --------------------------- 21 | Comments: ${order.ingredients} 22 | *************************** 23 | Price: $${order.price} 24 | Discount: $${order.discount} 25 | --------------------------- 26 | Total: $${order.total} 27 | *************************** 28 | Name: ${order.name} 29 | E-mail: ${order.email} 30 | Delivery to: ${order.address} 31 | =========================== 32 | `); 33 | return null; 34 | 35 | } 36 | 37 | // Helper function that posts notifications to Slack 38 | function orderToSlack(slackMessage) { 39 | 40 | return request({ 41 | method: 'POST', 42 | uri: slackWebhook, 43 | body: { 44 | text: slackMessage, 45 | channel: '#orders', 46 | username: `Daniel`, 47 | icon_emoji: ':pizza:' 48 | }, 49 | json: true 50 | }); 51 | 52 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Awesome Firebase Functions Snippets 2 | 3 | Here contains all my works that involves with writing firebase functions 4 | 5 | In order to use this, you should know how to create and deploy firebase functions. If you don't, please read [Get started](https://firebase.google.com/docs/functions/get-started) 6 | 7 | ## CI / CD 8 | 9 | - [Google Cloud Build](/ci-cd/cloudbuild.md) 10 | - [Github Actions](/ci-cd/github-actions.md) 11 | 12 | ## Others 13 | 14 | - [Easy to test Firebase Functions locally](https://github.com/dalenguyen/firebase-functions-snippets/blob/master/local-test-easy.js) 15 | - [Export from Firebase Functions to CSV or JSON](https://github.com/dalenguyen/firebase-functions-snippets/blob/master/csv-json-export.js) 16 | - [Dealing with HTTP Request](https://github.com/dalenguyen/firebase-functions-snippets/blob/master/http-request.js) 17 | - [Send SMS with Twilio](https://github.com/dalenguyen/firebase-functions-snippets/blob/master/send-sms-with-twilio.js) 18 | - [Send Email with Sendgrid](https://github.com/dalenguyen/firebase-functions-snippets/blob/master/send-email-with-sendgrid.js) 19 | - [Send Email with Mailgun](https://github.com/dalenguyen/firebase-functions-snippets/blob/master/send-email-with-mailgun.js) 20 | - [Send notifications with Slack](https://github.com/dalenguyen/firebase-functions-snippets/blob/master/send-notifications-with-slack.js) 21 | - [Create a revison on every updates](https://github.com/dalenguyen/firebase-functions-snippets/blob/master/revisions.ts) 22 | - [Schedule a function](https://github.com/dalenguyen/firebase-functions-snippets/blob/master/schedule.ts) 23 | 24 | The CSV or JSON code will get a HTTP request and return a JSON or CSV file that user wants. You can either call the request direction from your browser or you can user [cron-job.org](https://cron-job.org/en/members/jobs/details/?jobid=919859). 25 | -------------------------------------------------------------------------------- /revisions.ts: -------------------------------------------------------------------------------- 1 | // A new revision is created on every update under revision collection 2 | 3 | import { DeepDiff } from 'deep-diff' 4 | import { 5 | Change, 6 | EventContext, 7 | runWith, 8 | RuntimeOptions, 9 | } from 'firebase-functions' 10 | import { DocumentSnapshot } from '@google-cloud/firestore' 11 | 12 | export interface RevisionChange { 13 | kind: string 14 | path: string 15 | lhs: any 16 | rhs: any 17 | } 18 | export interface Revision { 19 | changes: RevisionChange[] 20 | createdAt: string 21 | } 22 | 23 | export const handleOnUpdateEvent = async ( 24 | documentSnapshot: Change, 25 | context: EventContext 26 | ) => { 27 | const previousSnapshot = documentSnapshot.before 28 | const updatedSnapshot = documentSnapshot.after 29 | 30 | const revision = createNewRevision(previousSnapshot, updatedSnapshot) 31 | 32 | await addRevision(updatedSnapshot, revision) 33 | } 34 | 35 | export const createNewRevision = ( 36 | previousDocument: DocumentSnapshot, 37 | updatedDocument: DocumentSnapshot 38 | ): Revision => { 39 | const previousEntity = previousDocument.data() as T 40 | const updatedEntity = updatedDocument.data() as T 41 | 42 | return createRevision(previousEntity, updatedEntity) 43 | } 44 | 45 | export const addRevision = async ( 46 | document: DocumentSnapshot, 47 | revision: Revision 48 | ) => { 49 | return document.ref.collection(`revisions`).doc().set(revision) 50 | } 51 | 52 | export const createRevision = (obj1: any, obj2: any): Revision => { 53 | const changes: any[] = [] 54 | 55 | const differences: any[] = DeepDiff.diff(obj1, obj2) 56 | differences.forEach(difference => { 57 | let change = { 58 | kind: difference.kind, 59 | path: difference.path.join('.'), 60 | lhs: difference.lhs, 61 | rhs: difference.rhs, 62 | } 63 | change = JSON.parse(JSON.stringify(change)) 64 | changes.push(change) 65 | }) 66 | 67 | const revision: Revision = { 68 | createdAt: new Date().toISOString(), 69 | changes: changes, 70 | } 71 | return revision 72 | } 73 | 74 | const runtimeOpts: RuntimeOptions = { 75 | memory: '256MB', 76 | timeoutSeconds: 540, // max timeout 77 | } 78 | 79 | // This is how we create a function that listen to 80 | // User changes and create a revision 81 | export const createRevisions = runWith(runtimeOpts) 82 | .firestore.document(`user/{userId}`) 83 | .onUpdate(handleOnUpdateEvent) 84 | --------------------------------------------------------------------------------