83 lines
2.6 KiB
JavaScript

import { getSettings } from "utils/config/config";
import createLogger from "utils/logger";
import { cachedRequest } from "utils/proxy/http";
const logger = createLogger("stocks");
export default async function handler(req, res) {
const { watchlist, provider, cache } = req.query;
logger.debug("Stocks API request: %o", { watchlist, provider, cache });
if (!watchlist) {
return res.status(400).json({ error: "Missing watchlist" });
}
const watchlistArr = watchlist.split(",") || [watchlist];
if (!watchlistArr.length || watchlistArr[0] === "null" || !watchlistArr[0]) {
return res.status(400).json({ error: "Missing watchlist" });
}
if (watchlistArr.length > 8) {
return res.status(400).json({ error: "Max items in watchlist is 8" });
}
const hasDuplicates = new Set(watchlistArr).size !== watchlistArr.length;
if (hasDuplicates) {
return res.status(400).json({ error: "Watchlist contains duplicates" });
}
if (!provider) {
return res.status(400).json({ error: "Missing provider" });
}
if (provider !== "finnhub") {
return res.status(400).json({ error: "Invalid provider" });
}
const providersInConfig = getSettings()?.providers;
let apiKey;
Object.entries(providersInConfig).forEach(([key, val]) => {
if (key === provider) apiKey = val;
});
if (typeof apiKey === "undefined") {
return res.status(400).json({ error: "Missing or invalid API Key for provider" });
}
if (provider === "finnhub") {
// Finnhub allows up to 30 calls/second
// https://finnhub.io/docs/api/rate-limit
const results = await Promise.all(
watchlistArr.map(async (ticker) => {
if (!ticker) {
return { ticker: null, currentPrice: null, percentChange: null };
}
// https://finnhub.io/docs/api/quote
const apiUrl = `https://finnhub.io/api/v1/quote?symbol=${ticker}&token=${apiKey}`;
// Finnhub free accounts allow up to 60 calls/minute
// https://finnhub.io/pricing
const { c, dp } = await cachedRequest(apiUrl, cache || 1);
logger.debug("Finnhub API response for %s: %o", ticker, { c, dp });
// API sometimes returns 200, but values returned are `null`
if (c === null || dp === null) {
return { ticker, currentPrice: null, percentChange: null };
}
// Rounding percentage, but we want it back to a number for comparison
return { ticker, currentPrice: c.toFixed(2), percentChange: parseFloat(dp.toFixed(2)) };
}),
);
return res.send({
stocks: results,
});
}
return res.status(400).json({ error: "Invalid configuration" });
}