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,15 +15,20 @@
} }
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;
}
// Optionally close the modal popup.
const closePopup = document.getElementById("priceAlertModal"); const closePopup = document.getElementById("priceAlertModal");
closePopup?.dispatchEvent(new MouseEvent("click")); closePopup?.dispatchEvent(new MouseEvent("click"));
// Prepare data for the POST request.
const postData = { const postData = {
userId: data?.user?.id, userId: data?.user?.id,
symbol: ticker, symbol: ticker,
@ -34,25 +39,46 @@
targetPrice: targetPrice, targetPrice: targetPrice,
}; };
// Make the POST request to the endpoint // Create a promise for the POST request.
const promise = fetch("/api/create-price-alert", {
toast.success(`Successfully created price alert`, {
style:
"border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;",
});
const response = await fetch("/api/create-price-alert", {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify(postData), 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;
}); });
$newPriceAlertData = await response?.json(); // 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:
"border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;",
},
);
//const output = await response.json(); // Await the promise and handle the result.
try {
const newPriceAlertData = await promise;
// Update reactive store or state as needed.
$newPriceAlertData = newPriceAlertData;
// Optionally reset targetPrice or perform further actions.
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.
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,
};
let output;
try { try {
output = await pb.collection("priceAlert").create(newAlert);
output = await pb.collection("priceAlert")?.create(newAlert)
} catch (err) { } catch (err) {
output = {} // 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;
// For non-Pro users, ensure they can only have 1 watchlist.
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 }
);
}
}
// If the user is Pro or doesn't have a watchlist yet, attempt to create one.
try { try {
output = await pb.collection("watchlist").create(data) 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" };
} }
catch(e) {
//console.log(e)
output = {};
}
console.log(output)
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", { // Create a promise for the POST request
const promise = fetch("/api/create-watchlist", {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify(postData), body: JSON.stringify(postData),
}); // make a POST request to the server with the FormData object }).then(async (response) => {
if (response?.ok) {
const output = await response.json(); const output = await response.json();
try { if (!response.ok) {
// Save the version along with the rules // If the server returned an error (e.g. nonPro user already has a watchlist),
localStorage?.setItem( // throw an error to be caught by toast.promise.
"last-watchlist-id", throw new Error(
JSON?.stringify(output?.id), output.error || "Something went wrong. Please try again!",
); );
} catch (e) {
console.log("Failed saving indicator rules: ", e);
} }
return output;
toast.success("Watchlist created successfully!", {
style:
"border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;",
}); });
// 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:
"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"); const clicked = document.getElementById("addWatchlist");
clicked?.dispatchEvent(new MouseEvent("click")); clicked?.dispatchEvent(new MouseEvent("click"));
const anchor = document.createElement("a"); const anchor = document.createElement("a");
anchor.href = "/watchlist/stocks"; anchor.href = "/watchlist/stocks";
anchor.dispatchEvent(new MouseEvent("click")); anchor.dispatchEvent(new MouseEvent("click"));
} else {
toast.error("Something went wrong. Please try again!", {
style:
"border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;",
});
}
} catch (error) { } catch (error) {
console.error("Error:", error); console.error("Error creating watchlist:", error);
toast.error("An error occurred. Please try again later.", { // No additional toast.error is needed here since toast.promise already handles errors.
style:
"border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;",
});
} }
} }
@ -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) {