· 4 years ago · Jul 12, 2021, 08:40 AM
1const functions = require("firebase-functions");
2const admin = require("firebase-admin");
3const express = require('express');
4const cors = require('cors');
5const fetch = require('node-fetch');
6
7// initialize the app
8if (!admin.apps.length) admin.initializeApp({ databaseURL: 'http://localhost:9000?ns=demo-take-home-assignment' });
9// // Create and Deploy Your First Cloud Functions
10// // https://firebase.google.com/docs/functions/write-firebase-functions
11//
12const app = express();
13// Automatically allow cross-origin requests
14app.use(cors({ origin: true }));
15/*
16 Challenge 1
17*/
18/*
19- My code worked just fine originally and displayed on the frontend but for some reason throughout my testing I started getting an error that I could not get passed.
20- FetchError: request to https://api.coincap.io/v2/assets?limit=25 failed, reason: connect ECONNREFUSED 104.17.164.77:443
21- This api even failed when testing from Postman.
22- This error prevented me from pulling from the api. I checked by changing the URL to another api and it tested just fine.
23- I am assuming it may have been something to do with my ports or proxies but I couldn't get around it.
24- So overall this code was unfortunately built without fully being able to test it properly.
25*/
26
27//call db and get the reference
28const db = admin.database()
29const dbRef = db.ref()
30
31// GET fetch function using async/await
32const apiFetch = async (params) => {
33 const response = await fetch(`https://api.coincap.io/v2/assets${params}`).catch(err=>console.error(err));
34 const data = await response.json();
35 console.log(data)
36 return data;
37};
38
39//new function call api and save top 25 coins to DB under /assets/top25
40 const saveToDb = (req,res) => {
41 apiFetch('?limit=25').then(data => {
42 const dataArr = data.data
43 const top25 = []
44
45 //loop over coins and save the ID to the top 25 array.
46 dataArr.forEach((coin) => top25.push(coin.id))
47 //console.log(top25);
48
49 //reference the db and specifically the child "assets" and set /top25 with the top25 array
50 dbRef.child('assets').set({top25: top25});
51
52 //send data to the frontend
53 res.send(dataArr)
54 }).catch(err => console.error(err));
55};
56
57// saveToDb();
58
59/*
60 Challenge 1 Comments:
61 - For a small application like this, calling the api from the front end shouldn't pose too many problems, but as the application grows you potentially will run into an issue with scaling.
62 - Depending on the api there might be restrictions to how many calls you are able to complete or how it can support a large request volume.
63 - Vulnerable to potential security issues and redirects.
64 - If you need to pass credentials to the API that would be better handled on the backend
65 - running on the front end can be faster since it has fewer application layers to pass through. (latency)
66 - if it's a 3rd party api it could possibly change and that could expose issues if its directly called from the front end.
67*/
68
69/*
70 Challenge 2
71*/
72
73// const crytpoPriceTicker = () => {
74// setInterval(() => {
75// //set new variable ref in DB now to the assets child
76// const newDbRef = db.ref('assets')
77
78// //query db for the snapshot of the saved values in /top25
79// newDbRef.on("value", (snapshot)=>{
80// const data = snapshot.val();
81// const top25 = data.top25
82// updateCoins(top25);
83// });
84
85// const updateCoins = (coins) => {
86// //loop thru each coin and call the api again based on the coins id (/assets/{id})
87// coins.forEach((coin) => {
88// apiFetch(`/${coin}`).then((data) => {
89// const dataArr = data.data
90// //if coin exists already then do not push data.
91// if (newDbRef.ref(`/${coin}`) !== NULL) {
92// console.log('already exists')
93// } else {
94// newDbRef.child(coin).push().set(dataArr)
95// }
96// })
97// });
98// };
99// //set interval for 10 seconds
100// }, 10000);
101// };
102
103// crytpoPriceTicker();
104
105/*
106 Challenge 2 Comments:
107 - Push IDs are a good choice when working with lists of data. It creates a unique key and timestamp.
108 - Plus the push() function is already optimized for performance.
109 - If you used another post method it would require more work and then you could still run into write issues with a larger scale app.
110 - Multiple submissions at the same time could happen and if two users write to assets/favcoins/2 then you run the risk of one post being deleted by another user.
111*/
112
113/*
114 Challenge 3
115*/
116// YOUR CODE GOES HERE
117/*
118- attempted to build this with more vanilla js...
119- although found a rate-limiting npm package for express that potentially could be used.
120- https://www.npmjs.com/package/express-rate-limit
121**EXPRESS RATE LIMIT***
122const rateLimit = require("express-rate-limit");
123const apiLimiter = rateLimit({
124 windowMs: 15 * 60 * 1000, // 15 minutes
125 max: 100
126});
127app.use("/api/", apiLimiter);
128*/
129
130 const crytpoPriceTicker = () => {
131 setInterval(() => {
132 //set new variable ref in DB now to the assets child
133 const newDbRef = db.ref('assets')
134
135 //query db for the snapshot of the saved values in /top25
136 newDbRef.on("value", (snapshot)=>{
137 const data = snapshot.val();
138 const top25 = data.top25
139 updateCoins(top25);
140 });
141
142 //new code inside this function
143 const updateCoins = (coins) => {
144 //copy of the original large array that will be modified and spliced into batches
145 let originalArr = coins;
146 //make new array that will hold the batches
147 let smallArr = [];
148
149 //loop for only grabbing 5 elements at a time and save to smallArr
150 for (let i = 0; i < originalArr.length; i+=5){
151 //remove the 5 coins from the orignalArr and put them in the smallArr
152 smallArr.push(originalArr.splice(i, i + 5))
153 //loop thru smallArr 5 coins and call the api again based on the coins id (/assets/{id})
154 smallArr.forEach((coin) => {
155 apiFetch(`/${coin}`).then((data) => {
156 const dataArr = data.data
157 //if coin exists already then do not push data.
158 if (newDbRef.ref(`/${coin}`) !== NULL) {
159 console.log('already exists')
160 } else {
161 //update that coins data
162 newDbRef.child(coin).push().set(dataArr)
163 }
164 })
165 });
166 //empty out the smallArr before the next batch
167 smallArr = [];
168 }
169 };
170 //set interval for 10 seconds
171 }, 10000);
172 };
173
174// crytpoPriceTicker();
175
176// Expose Express API as a single Cloud Function:
177exports.api = functions.https.onRequest(app);