bugfixing

This commit is contained in:
MuslemRahimi 2025-02-22 22:06:55 +01:00
parent 22dd3cbdc2
commit 08798d15c3
3 changed files with 226 additions and 158 deletions

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { stockTicker, displayCompanyName } from "$lib/store"; import { stockTicker, displayCompanyName } from "$lib/store";
import { abbreviateNumber } from "$lib/utils"; import { abbreviateNumber, removeCompanyStrings } from "$lib/utils";
export let data; export let data;
let rawData = {}; let rawData = {};
@ -26,7 +26,7 @@
} }
$: { $: {
if ($stockTicker && typeof window !== "undefined") { if ($stockTicker) {
rawData = data?.getEarningsSurprise; rawData = data?.getEarningsSurprise;
epsRatio = epsRatio =
rawData?.epsPrior === 0 rawData?.epsPrior === 0
@ -44,8 +44,7 @@
} }
</script> </script>
{#if Object?.keys(rawData)?.length !== 0} <div class="space-y-3 overflow-hidden">
<div class="space-y-3 overflow-hidden">
<!--Start Content--> <!--Start Content-->
<div class="w-auto lg:w-full p-1 flex flex-col m-auto"> <div class="w-auto lg:w-full p-1 flex flex-col m-auto">
<div class="flex flex-col items-center w-full mb-3"> <div class="flex flex-col items-center w-full mb-3">
@ -73,9 +72,8 @@
: 'bg-default pl-1'} " : 'bg-default pl-1'} "
> >
<div class="mt-1"> <div class="mt-1">
{$displayCompanyName} has released their quartely earnings on {new Date( {removeCompanyStrings($displayCompanyName)} has released their quartely earnings
rawData?.date, on {new Date(rawData?.date)?.toLocaleString("en-US", {
)?.toLocaleString("en-US", {
month: "short", month: "short",
day: "numeric", day: "numeric",
year: "numeric", year: "numeric",
@ -87,7 +85,7 @@
class="ml-[20px] sm:ml-[30px]" class="ml-[20px] sm:ml-[30px]"
style="color: #fff; line-height: 22px; margin-top:10px; margin-bottom: 10px; list-style-type: disc;" style="color: #fff; line-height: 22px; margin-top:10px; margin-bottom: 10px; list-style-type: disc;"
> >
Revenue of <span class="font-semibold" Revenue of <span class=""
>{abbreviateNumber(rawData?.revenue, true)}</span >{abbreviateNumber(rawData?.revenue, true)}</span
> >
{rawData?.revenueSurprise > 0 ? "exceeds" : "misses"} estimates by {abbreviateNumber( {rawData?.revenueSurprise > 0 ? "exceeds" : "misses"} estimates by {abbreviateNumber(
@ -95,7 +93,7 @@
true, true,
)}, with )}, with
<span <span
class="font-semibold {revenueRatio > 0 class=" {revenueRatio > 0
? "before:content-['+'] text-[#00FC50]" ? "before:content-['+'] text-[#00FC50]"
: 'text-[#FF2F1F]'}">{revenueRatio}%</span : 'text-[#FF2F1F]'}">{revenueRatio}%</span
> >
@ -105,12 +103,12 @@
class="ml-[20px] sm:ml-[30px]" class="ml-[20px] sm:ml-[30px]"
style="color: #fff; line-height: 22px; margin-top: 0px; margin-bottom: 0px; list-style-type: disc;" style="color: #fff; line-height: 22px; margin-top: 0px; margin-bottom: 0px; list-style-type: disc;"
> >
EPS of <span class="font-semibold">{rawData?.eps}</span> EPS of <span class="">{rawData?.eps}</span>
{rawData?.epsSurprise > 0 ? "exceeds" : "misses"} estimates by {rawData?.epsSurprise?.toFixed( {rawData?.epsSurprise > 0 ? "exceeds" : "misses"} estimates by {rawData?.epsSurprise?.toFixed(
2, 2,
)}, with )}, with
<span <span
class="font-semibold {epsRatio === null class=" {epsRatio === null
? 'text-white' ? 'text-white'
: epsRatio > 0 : epsRatio > 0
? 'text-[#00FC50]' ? 'text-[#00FC50]'
@ -122,5 +120,4 @@
</li> </li>
</div> </div>
</div> </div>
</div> </div>
{/if}

View File

@ -1,5 +1,6 @@
const cleanString = (input) => { // Pre-compile regex pattern and substrings for cleaning
const substringsToRemove = [ const REMOVE_PATTERNS = {
pattern: new RegExp(`\\b(${[
"Depositary", "Depositary",
"Inc.", "Inc.",
"Incorporated", "Incorporated",
@ -11,54 +12,26 @@ const cleanString = (input) => {
"Oyj", "Oyj",
"Company", "Company",
"The", "The",
"plc", "plc"
]; ].join("|")})\\b|,`, "gi")
const pattern = new RegExp(`\\b(${substringsToRemove.join("|")})\\b|,`, "gi");
return input?.replace(pattern, "").trim();
}; };
const fetchData = async (apiURL, apiKey, endpoint, ticker) => { // Memoized string cleaning function
try { const cleanString = (() => {
const response = await fetch(`${apiURL}${endpoint}`, { const cache = new Map();
method: "POST", return (input) => {
headers: { if (!input) return '';
"Content-Type": "application/json", if (cache.has(input)) return cache.get(input);
"X-API-KEY": apiKey, const cleaned = input.replace(REMOVE_PATTERNS.pattern, '').trim();
}, cache.set(input, cleaned);
body: JSON.stringify({ ticker }), return cleaned;
}); };
})();
if (!response.ok) { // Constants
throw new Error(`HTTP error! status: ${response.status}`); const CACHE_DURATION = 5 * 60 * 1000;
} const REQUEST_TIMEOUT = 5000;
const ENDPOINTS = Object.freeze([
const data = await response.json();
return data;
} catch (error) {
console.error(`Error fetching ${endpoint}:`, error);
return [];
}
};
const fetchWatchlist = async (pb, userId) => {
let output;
try {
output = await pb.collection("watchlist").getFullList({
filter: `user="${userId}"`,
});
} catch (e) {
//console.log(e)
output = [];
}
return output;
};
export const load = async ({ params, locals }) => {
const { apiURL, apiKey, pb, user } = locals;
const { tickerID } = params;
try {
const endpoints = [
"/etf-profile", "/etf-profile",
"/etf-holdings", "/etf-holdings",
"/etf-sector-weighting", "/etf-sector-weighting",
@ -67,17 +40,126 @@ export const load = async ({ params, locals }) => {
"/pre-post-quote", "/pre-post-quote",
"/wiim", "/wiim",
"/one-day-price", "/one-day-price",
"/stock-news", "/stock-news"
]; ]);
const promises = [ // LRU Cache implementation with automatic cleanup
...endpoints.map((endpoint) => class LRUCache {
fetchData(apiURL, apiKey, endpoint, tickerID), constructor(maxSize = 100) {
), this.cache = new Map();
fetchWatchlist(pb, user?.id), this.maxSize = maxSize;
]; }
const [ get(key) {
const item = this.cache.get(key);
if (!item) return null;
if (Date.now() - item.timestamp >= CACHE_DURATION) {
this.cache.delete(key);
return null;
}
return item.data;
}
set(key, data) {
if (this.cache.size >= this.maxSize) {
const oldestKey = this.cache.keys().next().value;
this.cache.delete(oldestKey);
}
this.cache.set(key, { data, timestamp: Date.now() });
}
}
const dataCache = new LRUCache();
// Optimized fetch function with AbortController and timeout
const fetchWithTimeout = async (url, options, timeout) => {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
...options,
signal: controller.signal
});
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
return await response.json();
} finally {
clearTimeout(timeoutId);
}
};
// Main data fetching function
const fetchData = async (apiURL, apiKey, endpoint, ticker) => {
const cacheKey = `${endpoint}-${ticker}`;
const cachedData = dataCache.get(cacheKey);
if (cachedData) return cachedData;
const options = {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-KEY": apiKey
},
body: JSON.stringify({ ticker, endpoints: ENDPOINTS })
};
try {
const data = await fetchWithTimeout(
`${apiURL}${endpoint}`,
options,
REQUEST_TIMEOUT
);
dataCache.set(cacheKey, data);
return data;
} catch (error) {
if (error.name === 'AbortError') {
throw new Error(`Request timeout for ${endpoint}`);
}
return [];
}
};
// Optimized watchlist fetching with error boundary
const fetchWatchlist = async (pb, userId) => {
if (!userId) return [];
try {
return await pb.collection("watchlist").getFullList({
filter: `user="${userId}"`
});
} catch {
return [];
}
};
// Main load function with parallel fetching
export const load = async ({ params, locals }) => {
const { apiURL, apiKey, pb, user } = locals;
const { tickerID } = params;
if (!tickerID) {
return getDefaultResponse(tickerID);
}
try {
const [bulkData, userWatchlist] = await Promise.all([
fetchData(apiURL, apiKey, "/bulk-data", tickerID),
fetchWatchlist(pb, user?.id)
]);
// Destructure with default empty arrays to prevent undefined errors
const {
'/etf-profile': getETFProfile = [],
'/etf-holdings': getETFHoldings = [],
'/etf-sector-weighting': getETFSectorWeighting = [],
'/stock-dividend': getStockDividend = [],
'/stock-quote': getStockQuote = [],
'/pre-post-quote': getPrePostQuote = [],
'/wiim': getWhyPriceMoved = [],
'/one-day-price': getOneDayPrice = [],
'/stock-news': getNews = []
} = bulkData;
return {
getETFProfile, getETFProfile,
getETFHoldings, getETFHoldings,
getETFSectorWeighting, getETFSectorWeighting,
@ -87,26 +169,17 @@ export const load = async ({ params, locals }) => {
getWhyPriceMoved, getWhyPriceMoved,
getOneDayPrice, getOneDayPrice,
getNews, getNews,
getUserWatchlist, getUserWatchlist: userWatchlist,
] = await Promise.all(promises);
return {
getETFProfile: getETFProfile || [],
getETFHoldings: getETFHoldings || [],
getETFSectorWeighting: getETFSectorWeighting || [],
getStockDividend: getStockDividend || [],
getStockQuote: getStockQuote || [],
getPrePostQuote: getPrePostQuote || [],
getWhyPriceMoved: getWhyPriceMoved || [],
getOneDayPrice: getOneDayPrice || [],
getNews: getNews || [],
getUserWatchlist: getUserWatchlist || [],
companyName: cleanString(getETFProfile?.at(0)?.name), companyName: cleanString(getETFProfile?.at(0)?.name),
getParams: params.tickerID, getParams: tickerID
}; };
} catch (error) { } catch (error) {
console.error('Error in load function:', error); return getDefaultResponse(tickerID);
return { }
};
// Helper function to generate default response
const getDefaultResponse = (tickerID) => ({
getETFProfile: [], getETFProfile: [],
getETFHoldings: [], getETFHoldings: [],
getETFSectorWeighting: [], getETFSectorWeighting: [],
@ -118,7 +191,5 @@ export const load = async ({ params, locals }) => {
getNews: [], getNews: [],
getUserWatchlist: [], getUserWatchlist: [],
companyName: '', companyName: '',
getParams: params.tickerID, getParams: tickerID
}; });
}
};

View File

@ -380,11 +380,11 @@
class="bg-default w-full max-w-screen sm:max-w-[1250px] min-h-screen overflow-hidden" class="bg-default w-full max-w-screen sm:max-w-[1250px] min-h-screen overflow-hidden"
> >
<!-- Page wrapper --> <!-- Page wrapper -->
<div class="mt-5 flex flex-col w-full relative w-full"> <div class="mt-5 flex flex-col w-full relative w-full sm:max-w-[1250px]">
<main class="grow w-full"> <main class="grow w-full">
<section class=""> <section class="">
<div class="w-full"> <div class="w-full">
<div class="sm:flex sm:justify-start w-full sm:max-w-[1250px]"> <div class="sm:flex sm:justify-start w-full">
<!--Start Mobile Navbar--> <!--Start Mobile Navbar-->
<div class="fixed top-0 left-0 right-0 z-20 bg-default sm:hidden"> <div class="fixed top-0 left-0 right-0 z-20 bg-default sm:hidden">
<div class="navbar w-full px-4 py-2"> <div class="navbar w-full px-4 py-2">