diff --git a/src/lib/components/PriceAlert.svelte b/src/lib/components/PriceAlert.svelte index ee8bb61b..df46e817 100644 --- a/src/lib/components/PriceAlert.svelte +++ b/src/lib/components/PriceAlert.svelte @@ -44,7 +44,7 @@ }; // Make the POST request to the endpoint - const response = await fetch("/api/fastify-post-data", { + const response = await fetch("/api/create-price-alert", { method: "POST", headers: { "Content-Type": "application/json", @@ -52,7 +52,7 @@ body: JSON.stringify(postData), }); - const output = (await response.json())?.items; + const output = await response.json(); if (output === "success") { toast.success(`Successfully created price alert`, { diff --git a/src/routes/api/community-sentiment/+server.ts b/src/routes/api/community-sentiment/+server.ts deleted file mode 100644 index a80d3678..00000000 --- a/src/routes/api/community-sentiment/+server.ts +++ /dev/null @@ -1,66 +0,0 @@ -import type { RequestHandler } from "./$types"; - -function secondsUntilEndOfDay() { - const now = new Date(); - const endOfDay = new Date( - now.getFullYear(), - now.getMonth(), - now.getDate() + 1, - ); - const secondsUntilEndOfDay = (endOfDay - now) / 1000; - return secondsUntilEndOfDay; -} - -export const POST = (async ({ request, cookies, locals }) => { - let output = "error"; - const data = await request.json(); - const sentiment = data?.sentiment; - const ticker = data?.ticker; - const sentimentId = data?.sentimentId; - const maxAge = secondsUntilEndOfDay(); - - let newData; - - if (cookies?.get("community-sentiment-" + ticker)) { - //console.log('already voted') - return new Response(JSON.stringify(output)); - } else { - try { - if (sentimentId) { - if (sentiment === "upvote") { - await locals?.pb - ?.collection("sentiment") - .update(sentimentId, { "upvote+": 1 }); - } else if (sentiment === "downvote") { - await locals?.pb - ?.collection("sentiment") - .update(sentimentId, { "downvote+": 1 }); - } - } else { - if (sentiment === "upvote") { - newData = await locals?.pb - ?.collection("sentiment") - .create({ ticker: ticker, upvote: 1 }); - } else if (sentiment === "downvote") { - newData = await locals?.pb - ?.collection("sentiment") - .create({ ticker: ticker, downvote: 1 }); - } - } - - output = "success"; - - cookies.set("community-sentiment-" + ticker, sentiment, { - httpOnly: true, - sameSite: "lax", - secure: true, - path: "/", - maxAge: maxAge, // End of day expiry - }); - } catch (e) { - console.log(e); - } - } - - return new Response(JSON.stringify(output)); -}) satisfies RequestHandler; diff --git a/src/routes/api/create-comment/+server.ts b/src/routes/api/create-comment/+server.ts deleted file mode 100644 index e8010485..00000000 --- a/src/routes/api/create-comment/+server.ts +++ /dev/null @@ -1,189 +0,0 @@ -import type { RequestHandler } from "./$types"; -import { serialize } from "object-to-formdata"; -import { validateData } from "$lib/utils"; -import { - createCommentTextSchema, - createCommentImageSchema, -} from "$lib/schemas"; -import { error } from "@sveltejs/kit"; -//import sharp from 'sharp'; -//import { marked } from 'marked'; - -/* -export const config = { - runtime: 'nodejs20.x', -}; -*/ - -/* -function removeDuplicateClasses(str) { - return str.replace(/class="([^"]*)"/g, (match, classAttr) => { - return `class="${[...new Set(classAttr.split(' '))].join(' ')}"`; - }); -} - -function addClassesToHtml(htmlString) { - // Helper function to add a class to a specific tag - function addClassToTag(tag, className) { - // Add class if the tag doesn't already have a class attribute - const regex = new RegExp(`<${tag}(?![^>]*\\bclass=)([^>]*)>`, 'g'); - htmlString = htmlString.replace(regex, `<${tag} class="${className}"$1>`); - - // Append the new class to tags that already have a class attribute, ensuring no duplicates - const regexWithClass = new RegExp(`(<${tag}[^>]*\\bclass=["'][^"']*)(?!.*\\b${className}\\b)([^"']*)["']`, 'g'); - htmlString = htmlString.replace(regexWithClass, `$1 ${className}$2"`); - } - - // Add classes to headings - addClassToTag('h1', 'text-lg'); - addClassToTag('h2', 'text-lg'); - addClassToTag('h3', 'text-lg'); - addClassToTag('h4', 'text-lg'); - addClassToTag('h5', 'text-lg'); - addClassToTag('h6', 'text-lg'); - - // Add classes to anchor tags - addClassToTag('a', 'text-blue-400 hover:text-white underline'); - - // Add classes to ordered lists - addClassToTag('ol', 'list-decimal ml-10 text-sm'); - - // Add classes to unordered lists - addClassToTag('ul', 'list-disc ml-10 text-sm -mt-5'); - - // Add classes to blockquotes and their paragraphs - function addClassToBlockquote() { - // Add class to blockquote - htmlString = htmlString.replace( - /
]*)>\s*

\n

{ - let output = "error"; - - const body = await request.formData(); - - if (body?.get("comment") === "undefined") { - body?.delete("comment"); - body?.append("comment", ""); - } - - if (body?.get("reply") === null) { - body?.delete("reply"); - body?.append("reply", ""); - } - - const { formData, errors } = await validateData( - body, - body?.get("image")?.length === 0 - ? createCommentTextSchema - : createCommentImageSchema, - ); - - if (errors) { - return new Response(JSON.stringify(output)); - } - //formData.comment = addClassesToHtml(marked(formData?.comment)) - - /* - if (formData?.image?.type?.includes('image')) - { - - try { - // image optimization before storing into the database - const image = formData?.image; - - - const imageBuffer = await image?.arrayBuffer(); - const imageBufferArray = new Uint8Array(imageBuffer); - - const optimizedImageBuffer = await sharp(imageBufferArray) - .resize({ - width: 800, - height: 1000, - fit: sharp.fit.inside, // Maintain aspect ratio and fit within the specified dimensions - withoutEnlargement: true, // Do not upscale the image if it's smaller than the specified dimensions - }) - .jpeg({ quality: 50 }) // Example: Convert the image to JPEG format with 50% quality - .toBuffer(); - - formData.image = new File([optimizedImageBuffer], image.name, { - type: image.type, - lastModified: image.lastModified, - }); - - } catch(err) { - console.log('Error: ', err); - error(err.status, err.message); - } - } - */ - - //Each comment gives the user +1 Karma points - - await locals.pb.collection("users").update(locals?.user?.id, { - "karma+": 1, - }); - - let newComment; - try { - newComment = await locals.pb - .collection("comments") - .create(serialize(formData), { - expand: "user,alreadyVoted(comment)", - fields: - "*,expand.user,expand.alreadyVoted(comment).user,expand.alreadyVoted(comment).type", - }); - - let postId = formData.post; - const opPost = await locals.pb.collection("posts").getOne(postId); - //create new record for notifications collections - if (locals?.user?.id !== opPost?.user) { - let formDataNotifications = new FormData(); - formDataNotifications.append("opUser", opPost?.user); - formDataNotifications.append("user", formData?.user); - formDataNotifications.append("post", postId); - formDataNotifications.append("comment", newComment?.id); - formDataNotifications.append("notifyType", "comment"); - - await locals.pb.collection("notifications").create(formDataNotifications); - } - - let formDataAlreadyVoted = new FormData(); - formDataAlreadyVoted.append("comment", newComment?.id); - formDataAlreadyVoted.append("user", newComment?.user); - formDataAlreadyVoted.append("type", "upvote"); - //console.log(formDataAlreadyVoted) - await locals.pb.collection("alreadyVoted").create(formDataAlreadyVoted); - - //User always upvotes their comment in the intial state - await locals.pb.collection("comments").update(newComment?.id, { - "upvote+": 1, - }); - - output = "success"; - } catch (err) { - console.log("Error: ", err); - error(err.status, err.message); - } - - return new Response(JSON.stringify([output, newComment])); -}) satisfies RequestHandler; diff --git a/src/routes/api/create-price-alert/+server.ts b/src/routes/api/create-price-alert/+server.ts new file mode 100644 index 00000000..ad36e6f1 --- /dev/null +++ b/src/routes/api/create-price-alert/+server.ts @@ -0,0 +1,31 @@ +import type { RequestHandler } from "./$types"; + +export const POST: RequestHandler = async ({ request, locals }) => { + const { pb } = locals; + const data = await request.json(); + + let output; + + let newAlert = { + 'user': data['userId'], + 'symbol': data['symbol']?.toUpperCase(), + 'name': data['name'], + 'assetType': data['assetType']?.toLowerCase(), + 'targetPrice': Number(data['targetPrice']), + 'condition': data['condition']?.toLowerCase(), + 'priceWhenCreated': Number(data['priceWhenCreated']), + 'triggered': false, + } + + + try { + + await pb.collection("priceAlert")?.create(newAlert) + output = 'success'; + + } catch (err) { + output = 'failure' + } + + return new Response(JSON.stringify(output)); +}; diff --git a/src/routes/api/create-strategy/+server.ts b/src/routes/api/create-strategy/+server.ts new file mode 100644 index 00000000..2b6bdaf7 --- /dev/null +++ b/src/routes/api/create-strategy/+server.ts @@ -0,0 +1,17 @@ +import type { RequestHandler } from "./$types"; + +export const POST: RequestHandler = async ({ request, locals }) => { + const { pb } = locals; + const data = await request.json(); + + + let output; + try { + output = await pb.collection("stockscreener").create(data) + } + catch(e) { + output = {}; + } + + return new Response(JSON.stringify(output)); +}; diff --git a/src/routes/api/delete-price-alert/+server.ts b/src/routes/api/delete-price-alert/+server.ts new file mode 100644 index 00000000..85a306b6 --- /dev/null +++ b/src/routes/api/delete-price-alert/+server.ts @@ -0,0 +1,24 @@ + import type { RequestHandler } from "./$types"; + +export const POST = (async ({ request, locals }) => { + const { pb } = locals; + const data = await request.json(); + const priceAlertIdList = data?.priceAlertIdList; + + + let output; + + try { + for (const item of priceAlertIdList) { + await pb.collection("priceAlert")?.delete(item) + } + output = 'success'; + } + catch(e) { + //console.log(e) + output = 'failure'; + } + + return new Response(JSON.stringify(output)); +}) satisfies RequestHandler; + diff --git a/src/routes/api/delete-strategy/+server.ts b/src/routes/api/delete-strategy/+server.ts new file mode 100644 index 00000000..a60964b3 --- /dev/null +++ b/src/routes/api/delete-strategy/+server.ts @@ -0,0 +1,20 @@ + import type { RequestHandler } from "./$types"; + +export const POST = (async ({ request, locals }) => { + const { pb } = locals; + const data = await request.json(); + + + let output; + + try { + await pb.collection("stockscreener")?.delete(data?.strategyId) + output = 'success'; + } + catch(e) { + output = 'failure'; + } + + return new Response(JSON.stringify(output)); +}) satisfies RequestHandler; + diff --git a/src/routes/api/edit-name-watchlist/+server.ts b/src/routes/api/edit-name-watchlist/+server.ts new file mode 100644 index 00000000..eb8071d6 --- /dev/null +++ b/src/routes/api/edit-name-watchlist/+server.ts @@ -0,0 +1,29 @@ +// Declare a route +module.exports = function (fastify, opts, done) { + + const pb = opts.pb; + + fastify.post('/edit-name-watchlist', async (request, reply) => { + const data = request.body; + const watchListId = data?.watchListId; + const newTitle = data?.title; + + let output; + + try { + await pb.collection("watchlist").update(watchListId, { + 'title': newTitle + }) + output = 'success'; + } + catch(e) { + //console.log(e) + output = 'failure'; + } + + reply.send({ items: output }) + + }); + + done(); +}; \ No newline at end of file diff --git a/src/routes/api/fastify-post-data/+server.ts b/src/routes/api/fastify-post-data/+server.ts deleted file mode 100644 index 9d31b7b2..00000000 --- a/src/routes/api/fastify-post-data/+server.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { RequestHandler } from "./$types"; - -export const POST: RequestHandler = async ({ request, locals }) => { - const data = await request.json(); - const { fastifyURL } = locals; - - // Destructure 'path' from data and collect the rest - const { path, ...restData } = data; - - const response = await fetch(`${fastifyURL}/${path}`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - // Pass the rest of the data (excluding path) in the body - body: JSON.stringify(restData), - }); - - const output = await response.json(); - return new Response(JSON.stringify(output)); -}; diff --git a/src/routes/api/get-one-post/+server.ts b/src/routes/api/get-one-post/+server.ts deleted file mode 100644 index 52c39e69..00000000 --- a/src/routes/api/get-one-post/+server.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { RequestHandler } from "./$types"; - -export const POST: RequestHandler = async ({ request, locals }) => { - const data = await request.json(); - const { fastifyURL } = locals; - - const response = await fetch(fastifyURL + "/get-one-post", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }); - - const output = await response.json(); - - return new Response(JSON.stringify(output)); -}; diff --git a/src/routes/api/get-post/+server.ts b/src/routes/api/get-post/+server.ts deleted file mode 100644 index 29adfaef..00000000 --- a/src/routes/api/get-post/+server.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { RequestHandler } from "./$types"; - -export const POST: RequestHandler = async ({ request, locals }) => { - const data = await request.json(); - const { fastifyURL } = locals; - - const response = await fetch(fastifyURL + "/get-post", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }); - - const output = await response.json(); - - return new Response(JSON.stringify(output)); -}; diff --git a/src/routes/api/save-strategy/+server.ts b/src/routes/api/save-strategy/+server.ts new file mode 100644 index 00000000..9f388a5b --- /dev/null +++ b/src/routes/api/save-strategy/+server.ts @@ -0,0 +1,21 @@ + import type { RequestHandler } from "./$types"; + +export const POST = (async ({ request, locals }) => { + const { pb } = locals; + const data = await request.json(); + + + let output; + + try { + output = await pb.collection("stockscreener").update(data?.strategyId, { + 'rules': data?.rules + }) + } + catch(e) { + output = {}; + } + + return new Response(JSON.stringify(output)); +}) satisfies RequestHandler; + diff --git a/src/routes/price-alert/+page.svelte b/src/routes/price-alert/+page.svelte index fd532a16..59172bc5 100644 --- a/src/routes/price-alert/+page.svelte +++ b/src/routes/price-alert/+page.svelte @@ -7,7 +7,6 @@ import { goto } from "$app/navigation"; import { screenWidth } from "$lib/store"; import MiniPlot from "$lib/components/MiniPlot.svelte"; - import { onMount } from "svelte"; import ArrowLogo from "lucide-svelte/icons/move-up-right"; export let data; @@ -133,7 +132,6 @@ }, ); - let isLoaded = false; let priceAlertList = data?.getPriceAlert; function stockSelector(symbol, assetType) { @@ -175,10 +173,9 @@ const postData = { priceAlertIdList: deletePriceAlertList, - path: "delete-price-alert", }; - const response = await fetch("/api/fastify-post-data", { + const response = await fetch("/api/delete-price-alert", { method: "POST", headers: { "Content-Type": "application/json", @@ -191,10 +188,6 @@ } } - onMount(async () => { - isLoaded = true; - }); - $: charNumber = $screenWidth < 640 ? 15 : 40; @@ -251,249 +244,232 @@ - {#if isLoaded} -

-
- Stock Indexes - {getCurrentDateFormatted()} -
- -
-
- - - - -
-
+
+
+ Stock Indexes - {getCurrentDateFormatted()}
- {#if priceAlertList?.length === 0} +
- - No Alerts set - - - - Create price alerts for your stocks that have the most - potential in your opinion. - - {#if !data?.user} - - Get Started - - - - - {/if} + + + +
- {:else} -
- {#if editMode} -
+ + {#if priceAlertList?.length === 0} +
+ + No Alerts set + + + + Create price alerts for your stocks that have the most potential + in your opinion. + + {#if !data?.user} + + Get Started + - - {numberOfChecked} - - - {/if} + + + {/if} +
+ {:else} +
+ {#if editMode} -
- -
(editMode = !editMode)} + class="border text-sm border-gray-600 ml-3 cursor-pointer inline-flex items-center justify-center space-x-1 whitespace-nowrap rounded-md py-2 pl-3 pr-4 font-semibold text-white shadow-sm bg-[#09090B] sm:hover:bg-[#09090B]/60 ease-out" > - - - - - - - - - - - - - - - {#each priceAlertList as item, index} - - - stockSelector(item?.symbol, item?.assetType)} - class="sm:hover:bg-[#245073] sm:hover:bg-opacity-[0.2] odd:bg-[#27272A] border-b-[#09090B] cursor-pointer" - > - - - - - - - - - - - - - - {/each} - -
SymbolCompanyVolumePrice when CreatedPrice TargetCurrent PriceChange
handleFilter(item?.id)} - class="text-blue-400 font-medium text-sm sm:text-[1rem] whitespace-nowrap text-start border-b-[#09090B] flex flex-row items-center" - > - - {item?.symbol} - handleFilter(item?.id)} - class="text-white text-sm sm:text-[1rem] whitespace-nowrap border-b-[#09090B]" - > - {item?.name?.length > charNumber - ? item?.name?.slice(0, charNumber) + "..." - : item?.name} - - {abbreviateNumber(item?.volume)} - - {item?.priceWhenCreated} - - {item?.targetPrice} - - {item.price?.toFixed(2)} - - {#if item?.changesPercentage >= 0} - +{item?.changesPercentage?.toFixed(2)}% - {:else} - {item?.changesPercentage?.toFixed(2)}% - - {/if} -
-
- - {/if} - {:else} -
-
- -
+ {#if !editMode} + Edit + {:else} + Cancel + {/if} +
+ +
+ + + + + + + + + + + + + + + {#each priceAlertList as item, index} + + + stockSelector(item?.symbol, item?.assetType)} + class="sm:hover:bg-[#245073] sm:hover:bg-opacity-[0.2] odd:bg-[#27272A] border-b-[#09090B] cursor-pointer" + > + + + + + + + + + + + + + + {/each} + +
SymbolCompanyVolumePrice when CreatedPrice TargetCurrent PriceChange
handleFilter(item?.id)} + class="text-blue-400 font-medium text-sm sm:text-[1rem] whitespace-nowrap text-start border-b-[#09090B] flex flex-row items-center" + > + + {item?.symbol} + handleFilter(item?.id)} + class="text-white text-sm sm:text-[1rem] whitespace-nowrap border-b-[#09090B]" + > + {item?.name?.length > charNumber + ? item?.name?.slice(0, charNumber) + "..." + : item?.name} + + {abbreviateNumber(item?.volume)} + + {item?.priceWhenCreated} + + {item?.targetPrice} + + {item.price?.toFixed(2)} + + {#if item?.changesPercentage >= 0} + +{item?.changesPercentage?.toFixed(2)}% + {:else} + {item?.changesPercentage?.toFixed(2)}% + + {/if} +
+
+ {/if} diff --git a/src/routes/stock-screener/+page.server.ts b/src/routes/stock-screener/+page.server.ts index 7019edb3..0e70389b 100644 --- a/src/routes/stock-screener/+page.server.ts +++ b/src/routes/stock-screener/+page.server.ts @@ -28,21 +28,23 @@ const ensureAllEmaParameters = (params) => { }; export const load = async ({ locals }) => { - const { apiURL, apiKey, fastifyURL, user } = locals; + const { apiURL, apiKey, user, pb } = locals; const getAllStrategies = async () => { - const postData = { userId: user?.id }; - const response = await fetch(fastifyURL + "/all-strategies", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(postData), - }); + let output = []; - const output = (await response.json())?.items; + try { + output = await pb.collection("stockscreener").getFullList({ + filter: `user="${user?.id}"`, + }); + output?.sort((a, b) => new Date(b?.updated) - new Date(a?.updated)); - output?.sort((a, b) => new Date(b?.updated) - new Date(a?.updated)); + } + catch(e) { + output = []; + } + + return output; }; diff --git a/src/routes/stock-screener/+page.svelte b/src/routes/stock-screener/+page.svelte index 87108a11..42874100 100644 --- a/src/routes/stock-screener/+page.svelte +++ b/src/routes/stock-screener/+page.svelte @@ -1305,7 +1305,7 @@ } async function handleCreateStrategy() { - if (data?.user?.tier === "Pro" && !data?.user?.freeTrial) { + if (data?.user?.tier === "Pro") { const closePopup = document.getElementById("addStrategy"); closePopup?.dispatchEvent(new MouseEvent("click")); } else { @@ -1314,9 +1314,9 @@ } async function handleDeleteStrategy() { - const postData = { strategyId: selectedStrategy, path: "delete-strategy" }; + const postData = { strategyId: selectedStrategy }; - const response = await fetch("/api/fastify-post-data", { + const response = await fetch("/api/delete-strategy", { method: "POST", headers: { "Content-Type": "application/json", @@ -1324,7 +1324,7 @@ body: JSON.stringify(postData), }); - const output = (await response.json())?.items; + const output = await response.json(); if (output === "success") { toast.success("Strategy deleted successfully!", { @@ -1398,9 +1398,8 @@ for (const [key, value] of formData.entries()) { postData[key] = value; } - postData["path"] = "create-strategy"; - const response = await fetch("/api/fastify-post-data", { + const response = await fetch("/api/create-strategy", { method: "POST", headers: { "Content-Type": "application/json", @@ -1408,7 +1407,7 @@ body: JSON.stringify(postData), }); - const output = (await response.json())?.items; + const output = await response?.json(); if (output?.id && output?.id?.length !== 0) { toast.success("Strategy created successfully!", { style: "border-radius: 200px; background: #333; color: #fff;", @@ -1686,10 +1685,9 @@ const handleKeyDown = (event) => { const postData = { strategyId: selectedStrategy, rules: ruleOfList, - path: "save-strategy", }; - const response = await fetch("/api/fastify-post-data", { + const response = await fetch("/api/save-strategy", { method: "POST", headers: { "Content-Type": "application/json", @@ -1697,18 +1695,10 @@ const handleKeyDown = (event) => { body: JSON.stringify(postData), }); - const output = (await response.json())?.items; - if (printToast === true) { - if (output?.id && output?.id?.length !== 0) { - toast.success("Strategy saved!", { - style: "border-radius: 200px; background: #333; color: #fff;", - }); - } else { - toast.error("Something went wrong. Please try again later!", { - style: "border-radius: 200px; background: #333; color: #fff;", - }); - } + toast.success("Strategy saved!", { + style: "border-radius: 200px; background: #333; color: #fff;", + }); } //isSaved = true;