From db00fc8d4b0aae73c3a52d2b42e58c1abcb9a5b3 Mon Sep 17 00:00:00 2001 From: scissorsneedfoodtoo Date: Thu, 22 Jun 2023 15:23:13 +0900 Subject: [PATCH 1/9] fix: use AlphaVantage stock API --- apps/stock-price-checker-proxy/api/v1.js | 45 +++++++++++++++++++----- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/apps/stock-price-checker-proxy/api/v1.js b/apps/stock-price-checker-proxy/api/v1.js index 6ff3ade30..131ed9425 100644 --- a/apps/stock-price-checker-proxy/api/v1.js +++ b/apps/stock-price-checker-proxy/api/v1.js @@ -12,7 +12,7 @@ const db = new Datastore({ // cleaning cache data on app restart db.remove( - { $or: [{ data: {} }, { data: "Unknown symbol" }] }, + { $or: [{ stockData: {} }, { stockData: "Unknown symbol" }] }, { multi: true }, (err, count) => { console.log("\nremoving garbage from cache..."); @@ -31,7 +31,7 @@ const getUID = (n = 8, symbols = _symbols) => .map(() => symbols[Math.floor(Math.random() * symbols.length)]) .join(""); -const { IEX_API_KEY = "", CACHE_TTL_MINUTES = 10 } = process.env; +const { ALPHA_VANTAGE_API_KEY = "", CACHE_TTL_MINUTES = 10 } = process.env; const validTickerRegExp = /^[a-z]{1,6}$/; const isValidStock = stock => validTickerRegExp.test(stock); @@ -60,30 +60,59 @@ router.get("/stock/:stock/quote", (req, res, next) => { if (err) return next(err); if (cached) { console.log(`rid: ${req_id} ** ${stock} from cache **`); - return res.json(cached.data); + return res.json(cached.stockData); } try { const { data } = await axios.get( - `https://cloud.iexapis.com/stable/stock/${stock}/quote?token=${IEX_API_KEY}` + `https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=${stock}&apikey=${ALPHA_VANTAGE_API_KEY}` ); console.log(`rid: ${req_id} !! ${stock} from api !!`); - res.json(data); + const temp = {...data?.['Global Quote']}; + let stockData; + if (Object.keys(temp).length === 0 && temp.constructor === Object) { + stockData = "Unknown symbol"; // Mimic IEX API response for this case + } else { + const symbol = temp["01. symbol"]; + const open = temp["02. open"]; + const high = temp["03. high"]; + const low = temp["04. low"]; + const close = temp["05. price"]; + const volume = temp["06. volume"]; + const latestTradingDay = temp["07. latest trading day"]; + const previousClose = temp["08. previous close"]; + const change = temp["09. change"]; + + const parseFloatAndRound = (value) => Number(parseFloat(value).toFixed(2)); + + stockData = { + symbol, + open: parseFloatAndRound(open), + high: parseFloatAndRound(high), + low: parseFloatAndRound(low), + close: parseFloatAndRound(close), + volume: Number(volume), + latestTradingDay: new Date(latestTradingDay), + previousClose: parseFloatAndRound(previousClose), + change: parseFloatAndRound(change), + }; + } + res.json(stockData); db.update( { _id: stock }, - { _id: stock, data, updatedAt: Date.now() }, + { _id: stock, stockData, updatedAt: Date.now() }, { upsert: true }, () => console.log(`rid: ${req_id} ++ ${stock} stored ++`) ); } catch (e) { if (e.response) { - res.status(e.response.status).json(e.response.data); + res.status(e.response.status).json(e.response.stockData); db.update( { _id: stock }, - { _id: stock, data: e.response.data, updatedAt: Date.now() }, + { _id: stock, stockData: e.response.stockData, updatedAt: Date.now() }, { upsert: true } ); } else { From 0d6bc5df0b4cc58343dc04cc1f630412e0e099c3 Mon Sep 17 00:00:00 2001 From: scissorsneedfoodtoo Date: Mon, 26 Jun 2023 15:36:59 +0900 Subject: [PATCH 2/9] fix: error handler in server.js --- apps/stock-price-checker-proxy/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/stock-price-checker-proxy/server.js b/apps/stock-price-checker-proxy/server.js index 28ace7bfa..890f025d7 100644 --- a/apps/stock-price-checker-proxy/server.js +++ b/apps/stock-price-checker-proxy/server.js @@ -12,7 +12,7 @@ app.get("/", (req, res) => { res.sendFile(__dirname + "/views/index.html"); }); -app.use((err, req, res) => { +app.use((err, req, res, next) => { if (err) { console.log(err.message, err.stack); res.status(500).json({ status: "internal server error" }); From 4bd8f033a5751c4ca8305b702085a9d94ca3bffb Mon Sep 17 00:00:00 2001 From: scissorsneedfoodtoo Date: Mon, 26 Jun 2023 16:58:03 +0900 Subject: [PATCH 3/9] fix: lint error for unused next argument --- apps/stock-price-checker-proxy/server.js | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/stock-price-checker-proxy/server.js b/apps/stock-price-checker-proxy/server.js index 890f025d7..7cd76780e 100644 --- a/apps/stock-price-checker-proxy/server.js +++ b/apps/stock-price-checker-proxy/server.js @@ -12,6 +12,7 @@ app.get("/", (req, res) => { res.sendFile(__dirname + "/views/index.html"); }); +// eslint-disable-next-line no-unused-vars app.use((err, req, res, next) => { if (err) { console.log(err.message, err.stack); From ed977efe3e175032581d421a231e70350b8398ce Mon Sep 17 00:00:00 2001 From: scissorsneedfoodtoo Date: Mon, 26 Jun 2023 16:59:14 +0900 Subject: [PATCH 4/9] feat: transform response to match the IEX response as closely as possible --- apps/stock-price-checker-proxy/api/v1.js | 42 +++++++++++++----------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/apps/stock-price-checker-proxy/api/v1.js b/apps/stock-price-checker-proxy/api/v1.js index 131ed9425..2d297c95e 100644 --- a/apps/stock-price-checker-proxy/api/v1.js +++ b/apps/stock-price-checker-proxy/api/v1.js @@ -35,6 +35,7 @@ const { ALPHA_VANTAGE_API_KEY = "", CACHE_TTL_MINUTES = 10 } = process.env; const validTickerRegExp = /^[a-z]{1,6}$/; const isValidStock = stock => validTickerRegExp.test(stock); +const parseFloatAndRound = (value, digits) => Number(parseFloat(value).toFixed(digits)); router.use(cors()); @@ -67,33 +68,36 @@ router.get("/stock/:stock/quote", (req, res, next) => { `https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=${stock}&apikey=${ALPHA_VANTAGE_API_KEY}` ); console.log(`rid: ${req_id} !! ${stock} from api !!`); - const temp = {...data?.['Global Quote']}; + const temp = {...data?.["Global Quote"]}; let stockData; if (Object.keys(temp).length === 0 && temp.constructor === Object) { stockData = "Unknown symbol"; // Mimic IEX API response for this case } else { const symbol = temp["01. symbol"]; - const open = temp["02. open"]; - const high = temp["03. high"]; - const low = temp["04. low"]; - const close = temp["05. price"]; - const volume = temp["06. volume"]; - const latestTradingDay = temp["07. latest trading day"]; - const previousClose = temp["08. previous close"]; - const change = temp["09. change"]; - - const parseFloatAndRound = (value) => Number(parseFloat(value).toFixed(2)); + const open = parseFloatAndRound(temp["02. open"], 2); + const high = parseFloatAndRound(temp["03. high"], 2); + const low = parseFloatAndRound(temp["04. low"], 2); + const close = parseFloatAndRound(temp["05. price"], 2); + const volume = Number(parseFloatAndRound(temp["06. volume"]), 2); + const latestTime = new Date(temp["07. latest trading day"]).toLocaleString("en-US", { month: "long", day: "numeric", year: "numeric" }); + const previousClose = parseFloatAndRound(temp["08. previous close"], 2); + const change = parseFloatAndRound(temp["09. change"], 2); + // Transform the response to match the IEX's as closely as possible + // with the available data stockData = { + change, + changePercent: parseFloatAndRound(((close - previousClose) / previousClose), 5), + close, + high, + latestPrice: close, + latestTime, + latestVolume: volume, + low, + open, + previousClose, symbol, - open: parseFloatAndRound(open), - high: parseFloatAndRound(high), - low: parseFloatAndRound(low), - close: parseFloatAndRound(close), - volume: Number(volume), - latestTradingDay: new Date(latestTradingDay), - previousClose: parseFloatAndRound(previousClose), - change: parseFloatAndRound(change), + volume }; } res.json(stockData); From e80303886b244166a6f1e2fa92b6ef03dfa30b5b Mon Sep 17 00:00:00 2001 From: scissorsneedfoodtoo Date: Mon, 26 Jun 2023 17:01:28 +0900 Subject: [PATCH 5/9] fix: run code through prettier --- apps/stock-price-checker-proxy/api/v1.js | 39 +++++++++++++++++------- apps/stock-price-checker-proxy/server.js | 2 +- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/apps/stock-price-checker-proxy/api/v1.js b/apps/stock-price-checker-proxy/api/v1.js index 2d297c95e..a9e33ae17 100644 --- a/apps/stock-price-checker-proxy/api/v1.js +++ b/apps/stock-price-checker-proxy/api/v1.js @@ -7,7 +7,7 @@ const cors = require("cors"); const Datastore = require("@seald-io/nedb"); const db = new Datastore({ filename: "./cache/db", - autoload: true + autoload: true, }); // cleaning cache data on app restart @@ -34,8 +34,9 @@ const getUID = (n = 8, symbols = _symbols) => const { ALPHA_VANTAGE_API_KEY = "", CACHE_TTL_MINUTES = 10 } = process.env; const validTickerRegExp = /^[a-z]{1,6}$/; -const isValidStock = stock => validTickerRegExp.test(stock); -const parseFloatAndRound = (value, digits) => Number(parseFloat(value).toFixed(digits)); +const isValidStock = (stock) => validTickerRegExp.test(stock); +const parseFloatAndRound = (value, digits) => + Number(parseFloat(value).toFixed(digits)); router.use(cors()); @@ -68,7 +69,7 @@ router.get("/stock/:stock/quote", (req, res, next) => { `https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=${stock}&apikey=${ALPHA_VANTAGE_API_KEY}` ); console.log(`rid: ${req_id} !! ${stock} from api !!`); - const temp = {...data?.["Global Quote"]}; + const temp = { ...data?.["Global Quote"] }; let stockData; if (Object.keys(temp).length === 0 && temp.constructor === Object) { stockData = "Unknown symbol"; // Mimic IEX API response for this case @@ -79,15 +80,27 @@ router.get("/stock/:stock/quote", (req, res, next) => { const low = parseFloatAndRound(temp["04. low"], 2); const close = parseFloatAndRound(temp["05. price"], 2); const volume = Number(parseFloatAndRound(temp["06. volume"]), 2); - const latestTime = new Date(temp["07. latest trading day"]).toLocaleString("en-US", { month: "long", day: "numeric", year: "numeric" }); - const previousClose = parseFloatAndRound(temp["08. previous close"], 2); + const latestTime = new Date( + temp["07. latest trading day"] + ).toLocaleString("en-US", { + month: "long", + day: "numeric", + year: "numeric", + }); + const previousClose = parseFloatAndRound( + temp["08. previous close"], + 2 + ); const change = parseFloatAndRound(temp["09. change"], 2); // Transform the response to match the IEX's as closely as possible // with the available data stockData = { change, - changePercent: parseFloatAndRound(((close - previousClose) / previousClose), 5), + changePercent: parseFloatAndRound( + (close - previousClose) / previousClose, + 5 + ), close, high, latestPrice: close, @@ -97,13 +110,13 @@ router.get("/stock/:stock/quote", (req, res, next) => { open, previousClose, symbol, - volume + volume, }; } res.json(stockData); db.update( { - _id: stock + _id: stock, }, { _id: stock, stockData, updatedAt: Date.now() }, { upsert: true }, @@ -114,9 +127,13 @@ router.get("/stock/:stock/quote", (req, res, next) => { res.status(e.response.status).json(e.response.stockData); db.update( { - _id: stock + _id: stock, + }, + { + _id: stock, + stockData: e.response.stockData, + updatedAt: Date.now(), }, - { _id: stock, stockData: e.response.stockData, updatedAt: Date.now() }, { upsert: true } ); } else { diff --git a/apps/stock-price-checker-proxy/server.js b/apps/stock-price-checker-proxy/server.js index 7cd76780e..52af8b557 100644 --- a/apps/stock-price-checker-proxy/server.js +++ b/apps/stock-price-checker-proxy/server.js @@ -1,4 +1,4 @@ -require('dotenv').config(); +require("dotenv").config(); const express = require("express"); const app = express(); From c82067bc5550cce0c8ac5392c22d44387d9c081c Mon Sep 17 00:00:00 2001 From: scissorsneedfoodtoo Date: Mon, 26 Jun 2023 17:03:49 +0900 Subject: [PATCH 6/9] feat: replace IEX API key with Alpha Vantage API key in sample.env in the proxy directory --- apps/stock-price-checker-proxy/sample.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/stock-price-checker-proxy/sample.env b/apps/stock-price-checker-proxy/sample.env index 7249a1175..3bb5a1cc5 100644 --- a/apps/stock-price-checker-proxy/sample.env +++ b/apps/stock-price-checker-proxy/sample.env @@ -1,3 +1,3 @@ PORT=3000 CACHE_TTL_MINUTES= -IEX_API_KEY= +ALPHA_VANTAGE_API_KEY= From d0844a56f0b91d8e5efc7a534bd3271c92577aa0 Mon Sep 17 00:00:00 2001 From: scissorsneedfoodtoo Date: Mon, 26 Jun 2023 17:08:51 +0900 Subject: [PATCH 7/9] feat: replace IEX API key with Alpha Vantage API key in main Docker Compose sample.env --- sample.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sample.env b/sample.env index c1090e902..fd32b1466 100644 --- a/sample.env +++ b/sample.env @@ -42,7 +42,7 @@ STOCK_PRICE_CHECKER_DB_URI=mongodb://mongo:27017/stock-price-checker # Stock Price Checker Proxy STOCK_PRICE_CHECKER_PROXY_CACHE_TTL_MINUTES=30 -STOCK_PRICE_CHECKER_PROXY_IEX_API_KEY=api_key_from_iex_dashboard +STOCK_PRICE_CHECKER_PROXY_ALPHA_VANTAGE_API_KEY=api_key_from_alpha_vantage # Twitch Proxy TWITCH_PROXY_TWITCH_CLIENT_ID=client_id_from_twitch_dashboard From 6b91335ac8bc02268b6b4187849509f663db6450 Mon Sep 17 00:00:00 2001 From: scissorsneedfoodtoo Date: Mon, 26 Jun 2023 17:09:38 +0900 Subject: [PATCH 8/9] feat: update Docker Compose file to use the Alpha Vantage API key --- docker-compose.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 26856ef5b..e32b46888 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,7 @@ networks: proxy: + services: caddy: @@ -12,8 +13,8 @@ services: volumes: - ./Caddyfile:/etc/caddy/Caddyfile ports: - - 80:80 - - 443:443 + - 80:80 + - 443:443 mongo: image: mongo @@ -373,7 +374,7 @@ services: dockerfile: ./Dockerfile environment: - CACHE_TTL_MINUTES=${STOCK_PRICE_CHECKER_PROXY_CACHE_TTL_MINUTES} - - IEX_API_KEY=${STOCK_PRICE_CHECKER_PROXY_IEX_API_KEY} + - ALPHA_VANTAGE_API_KEY=${STOCK_PRICE_CHECKER_PROXY_ALPHA_VANTAGE_API_KEY} ports: - 50130:3000 From accac27ea85775b01af6208a5fac9f17ea423f88 Mon Sep 17 00:00:00 2001 From: scissorsneedfoodtoo Date: Mon, 26 Jun 2023 23:07:49 +0900 Subject: [PATCH 9/9] fix: remove trailing commas --- apps/stock-price-checker-proxy/api/v1.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/stock-price-checker-proxy/api/v1.js b/apps/stock-price-checker-proxy/api/v1.js index a9e33ae17..461e033ce 100644 --- a/apps/stock-price-checker-proxy/api/v1.js +++ b/apps/stock-price-checker-proxy/api/v1.js @@ -7,7 +7,7 @@ const cors = require("cors"); const Datastore = require("@seald-io/nedb"); const db = new Datastore({ filename: "./cache/db", - autoload: true, + autoload: true }); // cleaning cache data on app restart @@ -85,7 +85,7 @@ router.get("/stock/:stock/quote", (req, res, next) => { ).toLocaleString("en-US", { month: "long", day: "numeric", - year: "numeric", + year: "numeric" }); const previousClose = parseFloatAndRound( temp["08. previous close"], @@ -110,13 +110,13 @@ router.get("/stock/:stock/quote", (req, res, next) => { open, previousClose, symbol, - volume, + volume }; } res.json(stockData); db.update( { - _id: stock, + _id: stock }, { _id: stock, stockData, updatedAt: Date.now() }, { upsert: true }, @@ -127,12 +127,12 @@ router.get("/stock/:stock/quote", (req, res, next) => { res.status(e.response.status).json(e.response.stockData); db.update( { - _id: stock, + _id: stock }, { _id: stock, stockData: e.response.stockData, - updatedAt: Date.now(), + updatedAt: Date.now() }, { upsert: true } );