better protection against bot
This commit is contained in:
parent
efccac0ba4
commit
b071412f29
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 }
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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));
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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. non‑Pro 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) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user