better protection against bot

This commit is contained in:
MuslemRahimi 2025-02-12 19:08:58 +01:00
parent efccac0ba4
commit b071412f29
4 changed files with 203 additions and 109 deletions

View File

@ -15,44 +15,70 @@
} }
async function handleCreateAlert() { async function handleCreateAlert() {
// Validate input locally.
if (targetPrice < 0) { if (targetPrice < 0) {
toast.error(`Target Price must be above zero`, { toast.error("Target Price must be above zero", {
style: style:
"border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;", "border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;",
}); });
} else { return;
const closePopup = document.getElementById("priceAlertModal"); }
closePopup?.dispatchEvent(new MouseEvent("click"));
const postData = { // Optionally close the modal popup.
userId: data?.user?.id, const closePopup = document.getElementById("priceAlertModal");
symbol: ticker, closePopup?.dispatchEvent(new MouseEvent("click"));
name: data?.getStockQuote?.name,
assetType: assetType,
priceWhenCreated: currentPrice,
condition: condition,
targetPrice: targetPrice,
};
// Make the POST request to the endpoint // Prepare data for the POST request.
const postData = {
userId: data?.user?.id,
symbol: ticker,
name: data?.getStockQuote?.name,
assetType: assetType,
priceWhenCreated: currentPrice,
condition: condition,
targetPrice: targetPrice,
};
toast.success(`Successfully created price alert`, { // Create a promise for the POST request.
const promise = fetch("/api/create-price-alert", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(postData),
}).then(async (response) => {
const result = await response.json();
// If the response is not ok, throw an error to be caught by toast.promise.
if (!response.ok) {
throw new Error(result.error || "Failed to create price alert");
}
return result;
});
// Use toast.promise to handle pending, success, and error states.
toast.promise(
promise,
{
loading: "Creating price alert...",
success: "Successfully created price alert",
error: (err) => err.message || "Failed to create price alert",
},
{
style: style:
"border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;", "border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;",
}); },
);
const response = await fetch("/api/create-price-alert", { // Await the promise and handle the result.
method: "POST", try {
headers: { const newPriceAlertData = await promise;
"Content-Type": "application/json", // Update reactive store or state as needed.
}, $newPriceAlertData = newPriceAlertData;
body: JSON.stringify(postData), // Optionally reset targetPrice or perform further actions.
});
$newPriceAlertData = await response?.json();
//const output = await response.json();
targetPrice = currentPrice; targetPrice = currentPrice;
} catch (error) {
// The error is already handled by toast.promise, but you can log it here.
console.error("Error creating price alert:", error);
} }
} }

View File

@ -1,30 +1,60 @@
import type { RequestHandler } from "./$types"; import type { RequestHandler } from "./$types";
export const POST: RequestHandler = async ({ request, locals }) => { export const POST: RequestHandler = async ({ request, locals }) => {
const { pb } = locals; const { user, pb } = locals;
const data = await request.json(); const data = await request.json();
let output; if (user?.id) {
let newAlert = {
'user': data['userId'], // If the user is not Pro, check the current number of active price alerts.
'symbol': data['symbol']?.toUpperCase(), if (user?.tier !== 'Pro') {
'name': data['name'], const totalAlerts = await pb.collection("priceAlert").getFullList({
'assetType': data['assetType']?.toLowerCase(), // Ensure the filter checks for a boolean false.
'targetPrice': Number(data['targetPrice']), filter: `user="${user?.id}" && triggered=false`
'condition': data['condition']?.toLowerCase(), });
'priceWhenCreated': Number(data['priceWhenCreated']),
'triggered': false, // If the user already has 3 or more active alerts, return an error.
if (totalAlerts?.length >= 3) {
return new Response(
JSON.stringify({ error: "Price alert limit reached for non-Pro users" }),
{ status: 403 }
);
} }
}
// Prepare the new alert data.
try { const newAlert = {
user: user?.id,
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,
};
output = await pb.collection("priceAlert")?.create(newAlert) let output;
try {
} catch (err) { output = await pb.collection("priceAlert").create(newAlert);
output = {} } catch (err) {
} // Return an error response if the alert could not be created.
return new Response(
JSON.stringify({ error: "Error creating price alert" }),
{ status: 500 }
);
}
return new Response(JSON.stringify(output)); return new Response(JSON.stringify(output));
}
else {
return new Response(
JSON.stringify({ error: "Error creating price alert" }),
{ status: 500 }
);
}
}; };

View File

@ -1,17 +1,33 @@
import type { RequestHandler } from "./$types"; import type { RequestHandler } from "./$types";
export const POST: RequestHandler = async ({ request, locals }) => { export const POST: RequestHandler = async ({ request, locals }) => {
const { pb } = locals; const { user, pb } = locals;
const data = await request.json(); const data = await request.json();
let output; let output;
try { // For non-Pro users, ensure they can only have 1 watchlist.
output = await pb.collection("watchlist").create(data) if (user?.tier !== 'Pro') {
// Get the current watchlists for the user.
const existingWatchlists = await pb.collection("watchlist").getFullList({
filter: `user="${user.id}"`
});
// If the user already has a watchlist, return an error response.
if (existingWatchlists.length >= 1) {
return new Response(
JSON.stringify({ error: "Non-Pro users can only have 1 watchlist" }),
{ status: 403 }
);
} }
catch(e) { }
//console.log(e)
output = {}; // If the user is Pro or doesn't have a watchlist yet, attempt to create one.
} try {
console.log(output) output = await pb.collection("watchlist").create(data);
} catch (err) {
// Optionally, log the error or adjust the error message as needed.
output = { error: "Failed to create watchlist" };
}
return new Response(JSON.stringify(output)); return new Response(JSON.stringify(output));
}; };

View File

@ -287,13 +287,13 @@
} }
async function createWatchList(event) { async function createWatchList(event) {
event.preventDefault(); // prevent the default form submission behavior event.preventDefault(); // Prevent the default form submission behavior
const formData = new FormData(event.target); // create a FormData object from the form
const formData = new FormData(event.target); // Create a FormData object from the form
const title = formData.get("title"); const title = formData.get("title");
if (!title || title?.length === 0) { // Validate the title input
if (!title || title.toString().trim().length === 0) {
toast.error("Title cannot be empty!", { toast.error("Title cannot be empty!", {
style: style:
"border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;", "border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;",
@ -301,7 +301,7 @@
return; return;
} }
if (title?.length > 100) { if (title.toString().length > 100) {
toast.error("Title is too long. Keep it simple and concise bruv!", { toast.error("Title is too long. Keep it simple and concise bruv!", {
style: style:
"border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;", "border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;",
@ -309,54 +309,66 @@
return; return;
} }
const postData = {}; // Build the POST data object
const postData: Record<string, any> = {};
// Iterate through the FormData entries and populate the object for (const [key, value] of formData.entries()) {
for (const [key, value] of formData?.entries()) {
postData[key] = value; postData[key] = value;
} }
try {
const response = await fetch("/api/create-watchlist", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(postData),
}); // make a POST request to the server with the FormData object
if (response?.ok) {
const output = await response.json();
try {
// Save the version along with the rules
localStorage?.setItem(
"last-watchlist-id",
JSON?.stringify(output?.id),
);
} catch (e) {
console.log("Failed saving indicator rules: ", e);
}
toast.success("Watchlist created successfully!", { // Create a promise for the POST request
style: const promise = fetch("/api/create-watchlist", {
"border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;", method: "POST",
}); headers: {
"Content-Type": "application/json",
const clicked = document.getElementById("addWatchlist"); },
clicked?.dispatchEvent(new MouseEvent("click")); body: JSON.stringify(postData),
const anchor = document.createElement("a"); }).then(async (response) => {
anchor.href = "/watchlist/stocks"; const output = await response.json();
anchor.dispatchEvent(new MouseEvent("click")); if (!response.ok) {
} else { // If the server returned an error (e.g. nonPro user already has a watchlist),
toast.error("Something went wrong. Please try again!", { // throw an error to be caught by toast.promise.
style: throw new Error(
"border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;", output.error || "Something went wrong. Please try again!",
}); );
} }
} catch (error) { return output;
console.error("Error:", error); });
toast.error("An error occurred. Please try again later.", {
// Use toast.promise to display a loading toast, then a success or error message
toast.promise(
promise,
{
loading: "Creating watchlist...",
success: "Watchlist created successfully!",
error: (err) =>
err.message || "Something went wrong. Please try again!",
},
{
style: style:
"border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;", "border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;",
}); },
);
try {
const output = await promise;
// Save the watchlist ID to localStorage (optional)
try {
localStorage.setItem("last-watchlist-id", JSON.stringify(output?.id));
} catch (e) {
console.log("Failed saving watchlist id: ", e);
}
// Dispatch events to close the modal and navigate to the watchlist page
const clicked = document.getElementById("addWatchlist");
clicked?.dispatchEvent(new MouseEvent("click"));
const anchor = document.createElement("a");
anchor.href = "/watchlist/stocks";
anchor.dispatchEvent(new MouseEvent("click"));
} catch (error) {
console.error("Error creating watchlist:", error);
// No additional toast.error is needed here since toast.promise already handles errors.
} }
} }
@ -633,6 +645,7 @@
try { try {
const savedRules = localStorage?.getItem("watchlist-ruleOfList"); const savedRules = localStorage?.getItem("watchlist-ruleOfList");
const savedLastWatchlistId = localStorage?.getItem("last-watchlist-id"); const savedLastWatchlistId = localStorage?.getItem("last-watchlist-id");
if (savedRules) { if (savedRules) {
const parsedRules = JSON.parse(savedRules); const parsedRules = JSON.parse(savedRules);
@ -666,21 +679,30 @@
} }
// Update checked items and sort the indicators // Update checked items and sort the indicators
checkedItems = new Set(ruleOfList.map((item) => item.name)); checkedItems = new Set(ruleOfList?.map((item) => item.name));
allRows = sortIndicatorCheckMarks(allRows); allRows = sortIndicatorCheckMarks(allRows);
if ( // Parse savedLastWatchlistId safely
typeof savedLastWatchlistId !== "undefined" && if (savedLastWatchlistId && savedLastWatchlistId.length > 0) {
savedLastWatchlistId?.length > 0 let parsedLastWatchlistId;
) { try {
parsedLastWatchlistId = JSON.parse(savedLastWatchlistId);
} catch (error) {
console.error(
"Error parsing last watchlist id from localStorage. Using raw value instead.",
error,
);
parsedLastWatchlistId = savedLastWatchlistId;
}
displayWatchList = allList?.find( displayWatchList = allList?.find(
(item) => item?.id === JSON?.parse(savedLastWatchlistId), (item) => item?.id === parsedLastWatchlistId,
); );
} }
// If no valid watchlist found, default to the first element of allList // If no valid watchlist is found, default to the first element of allList
if (!displayWatchList && allList?.length > 0) { if (!displayWatchList && allList?.length > 0) {
displayWatchList = allList?.at(0); displayWatchList = allList.at(0);
} else if (!displayWatchList) { } else if (!displayWatchList) {
displayWatchList = {}; displayWatchList = {};
} }
@ -696,7 +718,7 @@
isLoaded = true; isLoaded = true;
} catch (e) { } catch (e) {
console.log(e); console.error("onMount error:", e);
} }
if ($isOpen) { if ($isOpen) {