\documentclass[10pt,a4paper]{article} % Packages \usepackage{fancyhdr} % For header and footer \usepackage{multicol} % Allows multicols in tables \usepackage{tabularx} % Intelligent column widths \usepackage{tabulary} % Used in header and footer \usepackage{hhline} % Border under tables \usepackage{graphicx} % For images \usepackage{xcolor} % For hex colours %\usepackage[utf8x]{inputenc} % For unicode character support \usepackage[T1]{fontenc} % Without this we get weird character replacements \usepackage{colortbl} % For coloured tables \usepackage{setspace} % For line height \usepackage{lastpage} % Needed for total page number \usepackage{seqsplit} % Splits long words. %\usepackage{opensans} % Can't make this work so far. Shame. Would be lovely. \usepackage[normalem]{ulem} % For underlining links % Most of the following are not required for the majority % of cheat sheets but are needed for some symbol support. \usepackage{amsmath} % Symbols \usepackage{MnSymbol} % Symbols \usepackage{wasysym} % Symbols %\usepackage[english,german,french,spanish,italian]{babel} % Languages % Document Info \author{Cash (CashM)} \pdfinfo{ /Title (firebase-express-template-with-user-auth.pdf) /Creator (Cheatography) /Author (Cash (CashM)) /Subject (Firebase-Express Template with User Auth Cheat Sheet) } % Lengths and widths \addtolength{\textwidth}{6cm} \addtolength{\textheight}{-1cm} \addtolength{\hoffset}{-3cm} \addtolength{\voffset}{-2cm} \setlength{\tabcolsep}{0.2cm} % Space between columns \setlength{\headsep}{-12pt} % Reduce space between header and content \setlength{\headheight}{85pt} % If less, LaTeX automatically increases it \renewcommand{\footrulewidth}{0pt} % Remove footer line \renewcommand{\headrulewidth}{0pt} % Remove header line \renewcommand{\seqinsert}{\ifmmode\allowbreak\else\-\fi} % Hyphens in seqsplit % This two commands together give roughly % the right line height in the tables \renewcommand{\arraystretch}{1.3} \onehalfspacing % Commands \newcommand{\SetRowColor}[1]{\noalign{\gdef\RowColorName{#1}}\rowcolor{\RowColorName}} % Shortcut for row colour \newcommand{\mymulticolumn}[3]{\multicolumn{#1}{>{\columncolor{\RowColorName}}#2}{#3}} % For coloured multi-cols \newcolumntype{x}[1]{>{\raggedright}p{#1}} % New column types for ragged-right paragraph columns \newcommand{\tn}{\tabularnewline} % Required as custom column type in use % Font and Colours \definecolor{HeadBackground}{HTML}{333333} \definecolor{FootBackground}{HTML}{666666} \definecolor{TextColor}{HTML}{333333} \definecolor{DarkBackground}{HTML}{A30000} \definecolor{LightBackground}{HTML}{FCF7F7} \renewcommand{\familydefault}{\sfdefault} \color{TextColor} % Header and Footer \pagestyle{fancy} \fancyhead{} % Set header to blank \fancyfoot{} % Set footer to blank \fancyhead[L]{ \noindent \begin{multicols}{3} \begin{tabulary}{5.8cm}{C} \SetRowColor{DarkBackground} \vspace{-7pt} {\parbox{\dimexpr\textwidth-2\fboxsep\relax}{\noindent \hspace*{-6pt}\includegraphics[width=5.8cm]{/web/www.cheatography.com/public/images/cheatography_logo.pdf}} } \end{tabulary} \columnbreak \begin{tabulary}{11cm}{L} \vspace{-2pt}\large{\bf{\textcolor{DarkBackground}{\textrm{Firebase-Express Template with User Auth Cheat Sheet}}}} \\ \normalsize{by \textcolor{DarkBackground}{Cash (CashM)} via \textcolor{DarkBackground}{\uline{cheatography.com/123081/cs/26637/}}} \end{tabulary} \end{multicols}} \fancyfoot[L]{ \footnotesize \noindent \begin{multicols}{3} \begin{tabulary}{5.8cm}{LL} \SetRowColor{FootBackground} \mymulticolumn{2}{p{5.377cm}}{\bf\textcolor{white}{Cheatographer}} \\ \vspace{-2pt}Cash (CashM) \\ \uline{cheatography.com/cashm} \\ \end{tabulary} \vfill \columnbreak \begin{tabulary}{5.8cm}{L} \SetRowColor{FootBackground} \mymulticolumn{1}{p{5.377cm}}{\bf\textcolor{white}{Cheat Sheet}} \\ \vspace{-2pt}Published 17th February, 2021.\\ Updated 17th February, 2021.\\ Page {\thepage} of \pageref{LastPage}. \end{tabulary} \vfill \columnbreak \begin{tabulary}{5.8cm}{L} \SetRowColor{FootBackground} \mymulticolumn{1}{p{5.377cm}}{\bf\textcolor{white}{Sponsor}} \\ \SetRowColor{white} \vspace{-5pt} %\includegraphics[width=48px,height=48px]{dave.jpeg} Measure your website readability!\\ www.readability-score.com \end{tabulary} \end{multicols}} \begin{document} \raggedright \raggedcolumns % Set font size to small. Switch to any value % from this page to resize cheat sheet text: % www.emerson.emory.edu/services/latex/latex_169.html \footnotesize % Small font. \begin{multicols*}{2} \begin{tabularx}{8.4cm}{X} \SetRowColor{DarkBackground} \mymulticolumn{1}{x{8.4cm}}{\bf\textcolor{white}{Overview}} \tn \SetRowColor{white} \mymulticolumn{1}{x{8.4cm}}{This "template" allows you to set up all the necessary code for a Google Firebase (Node.js-like) server using their "Functions" option. \newline % Row Count 3 (+ 3) For initial development/testing, you can use the locally installed firebase server tool and it will update your database. Once you are ready for production, simply {\emph{deploy}} your functions to Firebase which will return an endpoint to use. \newline % Row Count 8 (+ 5) This template focuses on the code snippet functions needed to have user signup, login, and authentication. \newline % Row Count 11 (+ 3) Basically Firebase will have the functions operate like a backend server being hosted on their site. The database resides in their "Cloud Firestore". \newline % Row Count 14 (+ 3) Once this project is running, you can now focus on creating all of the other functions for your project database.% Row Count 17 (+ 3) } \tn \hhline{>{\arrayrulecolor{DarkBackground}}-} \end{tabularx} \par\addvspace{1.3em} \begin{tabularx}{8.4cm}{x{4 cm} x{4 cm} } \SetRowColor{DarkBackground} \mymulticolumn{2}{x{8.4cm}}{\bf\textcolor{white}{Creating a startup environment}} \tn % Row 0 \SetRowColor{LightBackground} \mymulticolumn{2}{x{8.4cm}}{create project on firebase console} \tn % Row Count 1 (+ 1) % Row 1 \SetRowColor{white} register your new app: {\emph{\textless{}project-name\textgreater{}}} & Take note of the SDK/config data \tn % Row Count 3 (+ 2) % Row 2 \SetRowColor{LightBackground} npm install -g firebase-tools & Global install of CLI \tn % Row Count 5 (+ 2) % Row 3 \SetRowColor{white} firebase login & Login to Firebase (may launch browser window) \tn % Row Count 8 (+ 3) % Row 4 \SetRowColor{LightBackground} mkdir {\emph{\textless{}project-name\textgreater{}}} & Create your project directory \tn % Row Count 10 (+ 2) % Row 5 \SetRowColor{white} cd {\emph{\textless{}project-name\textgreater{}}} & Move into your project directory \tn % Row Count 12 (+ 2) % Row 6 \SetRowColor{LightBackground} firebase init & Choose "functions" \tn % Row Count 13 (+ 1) % Row 7 \SetRowColor{white} JavaScript or Typescript & Choose your coding language preference. (JavaScript) \tn % Row Count 16 (+ 3) % Row 8 \SetRowColor{LightBackground} eslint ? & Do you wish to use eslint? Suggest - 'No" \tn % Row Count 19 (+ 3) % Row 9 \SetRowColor{white} code . & run VS Code \tn % Row Count 20 (+ 1) % Row 10 \SetRowColor{LightBackground} Add two folders under the "functions" folder & util, handlers \tn % Row Count 23 (+ 3) % Row 11 \SetRowColor{white} Copy the code snippets to the {\emph{util}} folder each in its own .js file. & admin, config, validators, and fbAuth \tn % Row Count 27 (+ 4) % Row 12 \SetRowColor{LightBackground} Copy the {\emph{users}} code to user.js file in the {\emph{handlers}} folder & users \tn % Row Count 31 (+ 4) \end{tabularx} \par\addvspace{1.3em} \vfill \columnbreak \begin{tabularx}{8.4cm}{x{4 cm} x{4 cm} } \SetRowColor{DarkBackground} \mymulticolumn{2}{x{8.4cm}}{\bf\textcolor{white}{Creating a startup environment (cont)}} \tn % Row 13 \SetRowColor{LightBackground} Replace the contents of index.js with the code listed here. & index.js \tn % Row Count 3 (+ 3) % Row 14 \SetRowColor{white} - & - \tn % Row Count 4 (+ 1) % Row 15 \SetRowColor{LightBackground} firebase serve & CLI command to test code on your local host. \tn % Row Count 7 (+ 3) % Row 16 \SetRowColor{white} \mymulticolumn{2}{x{8.4cm}}{firebase deploy} \tn % Row Count 8 (+ 1) \hhline{>{\arrayrulecolor{DarkBackground}}--} \SetRowColor{LightBackground} \mymulticolumn{2}{x{8.4cm}}{For a list of comands for the CLI, see the npm webpage: \{\{link="https://www.npmjs.com/package/firebase-tools"\}\}Firebase CLI\{\{/link\}\}} \tn \hhline{>{\arrayrulecolor{DarkBackground}}--} \end{tabularx} \par\addvspace{1.3em} \begin{tabularx}{8.4cm}{X} \SetRowColor{DarkBackground} \mymulticolumn{1}{x{8.4cm}}{\bf\textcolor{white}{Firebase Functions vs. Node.js server}} \tn \SetRowColor{white} \mymulticolumn{1}{x{8.4cm}}{Firebase "Functions" are written in Node.js format, so for example you will have the standard request and response options with a callback: \newline % Row Count 3 (+ 3) `exports.helloworld = \seqsplit{functions.https.onRequest(} (request, response) =\textgreater{} \{` \newline % Row Count 5 (+ 2) ` response.send('Hello world!') \});` \newline % Row Count 6 (+ 1) However it is not necessary to code the full blown Node.js server. Once you have written your function handlers, you will {\emph{deploy}} them up to firebase and then receive valid endpoint(s) that will serve your the response to your request. - In the example above, you would receive an endpoint similar to the following: \newline % Row Count 13 (+ 7) `https://\textless{}...firebase assigned server...\textgreater{}.\textless{}...project name...\textgreater{}-\textless{}...some random letters ...\textgreater{}.cloudfunctions.net/helloWorld` \newline % Row Count 16 (+ 3) This endpoint would then return "Hello world!"% Row Count 17 (+ 1) } \tn \hhline{>{\arrayrulecolor{DarkBackground}}-} \end{tabularx} \par\addvspace{1.3em} \begin{tabularx}{8.4cm}{X} \SetRowColor{DarkBackground} \mymulticolumn{1}{x{8.4cm}}{\bf\textcolor{white}{Utility - Admin}} \tn \SetRowColor{LightBackground} \mymulticolumn{1}{x{8.4cm}}{// Import admin \newline const admn = \seqsplit{require('firebase-admin')} \newline \newline // Initialize app \newline admin.initializeApp \newline \newline // Setup a 'shortcut' variable to save keystrokes \newline const db = admin.firestore() \newline \newline // Export \newline module.exports = \{ admin, db \}} \tn \hhline{>{\arrayrulecolor{DarkBackground}}-} \SetRowColor{LightBackground} \mymulticolumn{1}{x{8.4cm}}{This utility file setups up the import statements, app initialization, and a short cut variable.} \tn \hhline{>{\arrayrulecolor{DarkBackground}}-} \end{tabularx} \par\addvspace{1.3em} \begin{tabularx}{8.4cm}{X} \SetRowColor{DarkBackground} \mymulticolumn{1}{x{8.4cm}}{\bf\textcolor{white}{Utility - Config}} \tn \SetRowColor{LightBackground} \mymulticolumn{1}{x{8.4cm}}{const config = \{ \newline apiKey: "{\emph{\textless{}...your key goes here...\textgreater{}}}", \newline authDomain: "{\emph{\textless{}...project id...\textgreater{}}}.firebaseapp.com", \newline databaseURL: "https://{\emph{\textless{}...project id...\textgreater{}}}-default-rtdb.firebaseio.com", \newline projectId: "{\emph{\textless{}...project id...\textgreater{}}}", \newline storageBucket: "{\emph{\textless{}...project id...\textgreater{}}}.appspot.com", \newline messagingSenderId: "{\emph{\textless{} ...messaging sender id...\textgreater{}}}", \newline appId: "{\emph{\textless{}...app id...\textgreater{}}}" \newline \}; \newline \newline module.exports = \{ config \}} \tn \hhline{>{\arrayrulecolor{DarkBackground}}-} \SetRowColor{LightBackground} \mymulticolumn{1}{x{8.4cm}}{Replace all "fields" with the actual data. \newline The easiest way is to simply copy the entire block from the firebase screen and paste it in place. \newline This information is found under the project overview, in the Firebase SDK Snippet under your registered "app".} \tn \hhline{>{\arrayrulecolor{DarkBackground}}-} \end{tabularx} \par\addvspace{1.3em} \begin{tabularx}{8.4cm}{X} \SetRowColor{DarkBackground} \mymulticolumn{1}{x{8.4cm}}{\bf\textcolor{white}{Utility - fbAuth}} \tn \SetRowColor{LightBackground} \mymulticolumn{1}{x{8.4cm}}{const \{ admin, db \} = require('./admin') \newline \newline module.exports = (req, res, next) =\textgreater{} \{ \newline let idToken; \newline if \seqsplit{(req.headers.authorization} \&\& \seqsplit{req.headers.authorization.startsWith("Bearer} ")) \{ \newline idToken = \seqsplit{req.headers.authorization.split("Bearer} "){[}1{]}; \newline \} else \{ \newline console.error("No token found"); \newline return res.status(403).json(\{error: "Unauthorized"\}); \newline \} \newline \newline \seqsplit{admin.auth().verifyIdToken(idToken)} \newline .then((decodedToken) =\textgreater{} \{ \newline req.user = decodedToken; \newline return db.collection("users") \newline .where("userId", "==", req.user.uid) \newline .limit(1) \newline .get(); \newline \}) \newline .then((data) =\textgreater{} \{ \newline req.user.userHandle = data.docs{[}0{]}.data().userHandle; \newline return next(); \newline \}) \newline .catch((err) =\textgreater{}\{ \newline console.error("Error while verifying token", err); \newline return \seqsplit{res.status(403).json(err);} \newline \}); \newline \};} \tn \hhline{>{\arrayrulecolor{DarkBackground}}-} \SetRowColor{LightBackground} \mymulticolumn{1}{x{8.4cm}}{This code snippet allows for security based routes. \newline Simply place the import in the code with your {\emph{Express}} import. \newline \newline Since {\emph{Express}} allows for chained {\emph{middleware}} calls, we add a callback to FBAuth between the route endpoint and the function handler. \newline \newline (See "Protected Routes" section below.)} \tn \hhline{>{\arrayrulecolor{DarkBackground}}-} \end{tabularx} \par\addvspace{1.3em} \begin{tabularx}{8.4cm}{X} \SetRowColor{DarkBackground} \mymulticolumn{1}{x{8.4cm}}{\bf\textcolor{white}{Protected Routes (Using "fbAuth")}} \tn \SetRowColor{LightBackground} \mymulticolumn{1}{x{8.4cm}}{// -{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-- Route without authentication -{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-- // \newline app.post('/signup', signup); \newline \newline // -{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-- Route with authentication -{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-- // \newline // (e.g. requires a valid token) \newline app.post('/user', FBAuth, addUserDetails);} \tn \hhline{>{\arrayrulecolor{DarkBackground}}-} \SetRowColor{LightBackground} \mymulticolumn{1}{x{8.4cm}}{For any route that you wish to be protected (e.g. only valid/logged-in users can see or use), simply add the callback to FBAuth between the route and the callback function that handles the route. \newline \newline Of course the imports will require {\emph{firebase-functions}}, {\emph{express}}, and the {\emph{fbAuth}} file. \newline For a more complete example - see the Index.js section.} \tn \hhline{>{\arrayrulecolor{DarkBackground}}-} \end{tabularx} \par\addvspace{1.3em} \begin{tabularx}{8.4cm}{X} \SetRowColor{DarkBackground} \mymulticolumn{1}{x{8.4cm}}{\bf\textcolor{white}{Utility - Basic "User" validators}} \tn \SetRowColor{LightBackground} \mymulticolumn{1}{x{8.4cm}}{// -{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}- HELPER FUNCTIONS -{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}- \newline \newline // -{}-{}-{}-- Email validation using a regex expression -{}-{}-{}-- \newline const isEmail = (email) =\textgreater{} \{ \newline const regEx = /\textasciicircum{}(({[}\textasciicircum{}\textless{}\textgreater{}()\textbackslash{}{[}\textbackslash{}{]}\textbackslash{}\textbackslash{}.,;:\textbackslash{}s@"{]}+(\textbackslash{}.{[}\textasciicircum{}\textless{}\textgreater{}()\textbackslash{}{[}\textbackslash{}{]}\textbackslash{}\textbackslash{}.,;:\textbackslash{}s@"{]}+)*)|(".+"))@((\textbackslash{}{[}{[}0-9{]}\{1,3\}\textbackslash{}.{[}0-9{]}\{1,3\}\textbackslash{}.{[}0-9{]}\{1,3\}\textbackslash{}.{[}0-9{]}\{1,3\}\textbackslash{}{]})|(({[}a-zA-Z\textbackslash{}-0-9{]}+\textbackslash{}.)+{[}a-zA-Z{]}\{2,\}))\$/ \newline if (email.match(regEx)) return true \newline else return false \newline \} \newline \newline // -{}-{}-{}-- Non-blank/Required entry -{}-{}-{}-- \newline const isEmpty = (string) =\textgreater{} \{ \newline if (string.trim() === '') return true; \newline else return false; \newline \}; \newline \newline // -{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}- SIGNUP and LOGIN Functions -{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}- \newline \newline // -{}-{}-{}-{}-{}-{}-{}-{}-{}- Validate "SignUp" fields: userid, password, confirm password, \& email \newline exports.validateSignupData = (data) =\textgreater{} \{ \newline const errors = \{\} \newline \newline // validate email \newline if (isEmpty(data.email)) \{ \newline errors.email = "Must not be empty" \newline \} else if (!isEmail(data.email)) \{ \newline errors.email = "Must be a valid email address" \newline \} \newline \newline // validate password \newline if (isEmpty(data.password)) errors.password = "Must not be empty"; \newline \newline // validate confirmpassword \newline if (data.confirmPassword !== data.password) errors.confirmPassword = "Passwords must match"; \newline \newline // validate userHandle \newline if \seqsplit{(isEmpty(data.userHandle))} errors.userHandle = "Must not be empty"; \newline \newline return \{ \newline errors, \newline valid: \seqsplit{Object.keys(errors).length} === 0 ? true : false \newline \} \newline \} \newline \newline // -{}-{}-{}-{}-{}-{}-{}-{}-{}- Validate "Login" fields: email \& password \newline exports.validateLoginData = (data) =\textgreater{} \{ \newline const errors = \{\} \newline \newline // validate email \newline if (isEmpty(data.email)) errors.email = "Must not be empty" \newline // validate password \newline if (isEmpty(data.password)) errors.password = "Must not be empty"; \newline \newline return \{ \newline errors, \newline valid: \seqsplit{Object.keys(errors).length} === 0 ? true : false \newline \} \newline \} \newline \newline exports.reduceUserDetails = (data) =\textgreater{} \{ \newline let userDetails = \{\} \newline \newline // strip out empty string fields so that they are not added \newline // to the document \newline if(!isEmpty(data.firstName.trim())) userDetails.firstName = data.firstName \newline if(!isEmpty(data.lastName.trim())) userDetails.lastName = data.lastName \newline if(userDetails.length \textgreater{} 0 )\{ \newline userDetails.updatedAt = new Date().toISOString() \newline \} \newline return userDetails \newline \}} \tn \hhline{>{\arrayrulecolor{DarkBackground}}-} \SetRowColor{LightBackground} \mymulticolumn{1}{x{8.4cm}}{This code section validates the "fields" for non-blank and correct formatting prior to sending them to the Firebase server for authentication.} \tn \hhline{>{\arrayrulecolor{DarkBackground}}-} \end{tabularx} \par\addvspace{1.3em} \begin{tabularx}{8.4cm}{X} \SetRowColor{DarkBackground} \mymulticolumn{1}{x{8.4cm}}{\bf\textcolor{white}{Users Handler}} \tn \SetRowColor{LightBackground} \mymulticolumn{1}{x{8.4cm}}{// Import Admin \newline const \{ admin, db \} = require ('../util/admin') \newline const \{ config \} = \seqsplit{require("../util/config")} \newline const \{ v4: uuidv4 \} = require('uuid') \newline \newline const firebase = require('firebase') \newline firebase.initializeApp(config) \newline \newline const \{ validateSignupData, \newline validateLoginData, \newline reduceUserDetails \newline \} = \seqsplit{require('../util/validators')} \newline \newline // -{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}- SIGNUP -{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}- // \newline exports.signup = (req, res) =\textgreater{} \{ \newline const newUser = \{ \newline email: req.body.email, \newline password: req.body.password, \newline confirmPassword: req.body.confirmPassword, \newline userHandle: req.body.userHandle, \newline firstName: req.body.firstName, \newline lastName: req.body.lastName, \newline \}; \newline \newline const \{ valid, errors \} = \seqsplit{validateSignupData(newUser)} \newline if(!valid) return \seqsplit{res.status(400).json(errors)} \newline \newline const noImg = 'no-img.png' \newline \newline let token; \newline let userId; \newline \newline db.doc(`/users/\$\{newUser.userHandle\}`).get() \newline .then((doc) =\textgreater{} \{ \newline if (doc.exists) \{ \newline return res.status(400).json(\{userHandle: "This handle is already taken."\}); \newline \} else \{ \newline return firebase \newline .auth() \newline \seqsplit{.createUserWithEmailAndPassword(newUser.email}, newUser.password); \newline \} \newline \}) \newline .then((data) =\textgreater{} \{ \newline userId = data.user.uid; \newline return data.user.getIdToken(); \newline \}) \newline .then((idToken) =\textgreater{} \{ \newline token = idToken; \newline const userCredentials = \{ \newline // userHandle: newUser.userHandle, \newline email: newUser.email, \newline createdAT: new Date().toISOString(), \newline updatedAt: new Date().toISOString(), \newline userId, \newline firstName: newUser.firstName, \newline lastName: newUser.lastName, \newline imageUrl: \seqsplit{`https://firebasestorage.googleapis}.com/v0/b/\$\{config.storageBucket\}/o/\$\{noImg\}?alt=media` \newline \}; \newline db.doc(`/users/\$\{newUser.userHandle\}`).set(userCredentials); \newline \}) \newline .then(() =\textgreater{} \{ \newline return res.status(201).json(\{token\}); \newline \} \newline \newline ) \newline .catch((err) =\textgreater{} \{ \newline console.error(err); \newline if (err.code === \seqsplit{"auth/email-already-in-use")} \{ \newline return res.status(400).json(\{email: "Email is already in use."\}); \newline \} else \{ \newline return res.status(500).json(\{error: err.code\}); \newline \} \newline \}); \newline \} \newline \newline // -{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}- LOGIN -{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}- // \newline exports.login = (req, res) =\textgreater{} \{ \newline const user = \{ \newline email: req.body.email, \newline password: req.body.password, \newline \}; \newline \newline const \{ valid, errors \} = validateLoginData(user) \newline if(!valid) return \seqsplit{res.status(400).json(errors)} \newline \newline firebase \newline .auth() \newline \seqsplit{.signInWithEmailAndPassword(user.email}, user.password) \newline .then((data) =\textgreater{} \{ \newline // TODO: Rtv userId from user's file (i.e. userHandle) \newline return data.user.getIdToken(); \newline \}) \newline .then((token) =\textgreater{} \{ \newline return res.json(\{token\}); \newline \}) \newline .catch((err) =\textgreater{} \{ \newline console.error(err); \newline if (err.code === "auth/wrong-password") \{ \newline return res.status(403).json(\{general: "Wrong credentials, please try again"\}); \newline \} else return res.status(500).json(\{error: err.code\}); \newline \}); \newline \} \newline \newline // -{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}- GET ALL USERS -{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}- // \newline exports.getAllUsers = (req, res) =\textgreater{} \{ \newline db \newline .collection("users") \newline .orderBy("userId", "asc") \newline .get() \newline .then((data) =\textgreater{} \{ \newline const users = {[}{]} \newline console.log(data.doc) \newline data.forEach((doc) =\textgreater{} \{ \newline users.push(\{ \newline userId: doc.id, \newline userHandle: doc.data().userHandle, \newline firstName: doc.data().firstName, \newline lastName: doc.data().lastName, \newline email: doc.data().email, \newline createdAt: doc.data().createdAt, \newline updatedAt: doc.data().updatedAt, \newline \}); \newline \}); \newline return res.json(users); \newline \}) \newline .catch((err) =\textgreater{} console.error(err)); \newline \} \newline \newline // -{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}- ADD USER DETAILS -{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}- // \newline exports.addUserDetails = (req, res) =\textgreater{} \{ \newline let userDetails = \seqsplit{reduceUserDetails(req.body)} \newline \newline // TODO: Replace userHandle with userId once rtvd \newline db.doc(`/user/\$\{req.user.userHandle\}`) \newline .update(userDetails) \newline .then(() =\textgreater{} \{ \newline return res.json(\{ message: 'Details added successfully'\}) \newline \}) \newline .catch(err =\textgreater{} \{ \newline console.error(err) \newline return res.status(500).json(\{ error: err.code\}) \newline \}) \newline \} \newline \newline // -{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}- UPLOAD AN IMAGE -{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}- // \newline exports.uploadImage = (req, res) =\textgreater{} \{ \newline const BusBoy = require("busboy"); \newline const path = require("path"); \newline const os = require("os"); \newline const fs = require("fs"); \newline \newline const busboy = new BusBoy(\{ headers: req.headers \}); \newline \newline let imageToBeUploaded = \{\}; \newline let imageFileName; \newline // String for image token \newline let generatedToken = uuidv4(); \newline \newline busboy.on("file", (fieldname, file, filename, encoding, mimetype) =\textgreater{} \{ \newline console.log(fieldname, file, filename, encoding, mimetype); \newline if (mimetype !== "image/jpeg" \&\& mimetype !== "image/png") \{ \newline return res.status(400).json(\{ error: "Wrong file type submitted" \}); \newline \} \newline // my.image.png =\textgreater{} {[}'my', 'image', 'png'{]} \newline const imageExtension = filename.split("."){[}filename.split(".").length - 1{]}; \newline // 32756238461724837.png \newline imageFileName = `\$\{Math.round( \newline Math.random() {\emph{ 1000000000000 \newline ).toString()\}.\$\{imageExtension\}`; \newline const filepath = path.join(os.tmpdir(), imageFileName); \newline imageToBeUploaded = \{ filepath, mimetype \}; \newline \seqsplit{file.pipe(fs.createWriteStream(filepath));} \newline \}); \newline busboy.on("finish", () =\textgreater{} \{ \newline admin \newline .storage() \newline .bucket() \newline \seqsplit{.upload(imageToBeUploaded.filepath}, \{ \newline resumable: false, \newline metadata: \{ \newline metadata: \{ \newline contentType: \seqsplit{imageToBeUploaded.mimetype}, \newline //Generate token to be appended to imageUrl \newline \seqsplit{firebaseStorageDownloadTokens:} generatedToken, \newline \}, \newline \}, \newline \}) \newline .then(() =\textgreater{} \{ \newline // Append token to url \newline const imageUrl = \seqsplit{`https://firebasestorage.googleapis}.com/v0/b/\$\{config.storageBucket\}/o/\$\{imageFileName\}?alt=media\&token=\$\{generatedToken\}`; \newline // console.log('{\bf{}}}} User info {\bf{{\emph{}}}}: ', req.user) \newline // TODO: Replace userHandle with userId/uid \newline // Then remove userHandle from database documents \newline return db.doc(`/users/\$\{req.user.userHandle\}`).update(\{ imageUrl \}); \newline \}) \newline .then(() =\textgreater{} \{ \newline return res.json(\{ message: "image uploaded successfully" \}); \newline \}) \newline .catch((err) =\textgreater{} \{ \newline console.error(err); \newline return res.status(500).json(\{ error: "something went wrong" \}); \newline \}); \newline \}); \newline busboy.end(req.rawBody); \newline \};} \tn \hhline{>{\arrayrulecolor{DarkBackground}}-} \SetRowColor{LightBackground} \mymulticolumn{1}{x{8.4cm}}{This code section has the following functions: \newline signup, \newline login, \newline getAllUsers, \newline addUserDetails, \newline uploadImage \newline \newline The {\emph{uploadImage}} function uses the third party library "busboy" to parse and upload a file to your image store. For more info on this package see\{\{link="https://www.npmjs.com/package/busboy"\}\} NPM Busboy\{\{/link\}\}} \tn \hhline{>{\arrayrulecolor{DarkBackground}}-} \end{tabularx} \par\addvspace{1.3em} \begin{tabularx}{8.4cm}{X} \SetRowColor{DarkBackground} \mymulticolumn{1}{x{8.4cm}}{\bf\textcolor{white}{Index.js (main server app)}} \tn \SetRowColor{LightBackground} \mymulticolumn{1}{x{8.4cm}}{// Imports \newline const functions = \seqsplit{require("firebase-functions");} \newline const app = require('express')(); \newline const FBAuth = require('./util/fbAuth') \newline \newline // Import Handlers \newline const \{ signup, \newline login, \newline getAllUsers, \newline uploadImage, \newline addUserDetails \newline \} = \seqsplit{require('./handlers/users')} \newline \newline \newline // ** -{}-{}-{}-- Routes -{}-{}-{}-- // \newline \newline // -{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-- USERS -{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-- // \newline app.post('/signup', signup); \newline app.post('/login', login); \newline app.post('/user/image', FBAuth, uploadImage) \newline app.post('/user', FBAuth, addUserDetails) \newline app.get('/users', FBAuth, getAllUsers); \newline \newline // -{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}- EXPORT ALL FUNCS -{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-- // \newline exports.api = \seqsplit{functions.https.onRequest(app)} \newline ;} \tn \hhline{>{\arrayrulecolor{DarkBackground}}-} \SetRowColor{LightBackground} \mymulticolumn{1}{x{8.4cm}}{{\emph{Of course you can now add your own additional handlers and and routes for project database.}}} \tn \hhline{>{\arrayrulecolor{DarkBackground}}-} \end{tabularx} \par\addvspace{1.3em} % That's all folks \end{multicols*} \end{document}