mirror of
https://github.com/clucraft/PriceGhost.git
synced 2026-05-18 13:55:16 +02:00
Add refresh controls and notification support
- Add refresh button to product list items with spinning animation - Add editable refresh interval dropdown on product detail page - Add user profile dropdown with settings link in navbar - Create Settings page for Telegram and Discord configuration - Add per-product notification options (price drop threshold, back in stock) - Integrate notifications into scheduler for automatic alerts - Add notification service supporting Telegram Bot API and Discord webhooks Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
8c5d20707d
commit
a6928a0c17
13 changed files with 1373 additions and 21 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import cron from 'node-cron';
|
||||
import { productQueries, priceHistoryQueries } from '../models';
|
||||
import { productQueries, priceHistoryQueries, userQueries } from '../models';
|
||||
import { scrapeProduct } from './scraper';
|
||||
import { sendNotifications, NotificationPayload } from './notifications';
|
||||
|
||||
let isRunning = false;
|
||||
|
||||
|
|
@ -24,12 +25,36 @@ async function checkPrices(): Promise<void> {
|
|||
|
||||
const scrapedData = await scrapeProduct(product.url);
|
||||
|
||||
// Check for back-in-stock notification
|
||||
const wasOutOfStock = product.stock_status === 'out_of_stock';
|
||||
const nowInStock = scrapedData.stockStatus === 'in_stock';
|
||||
|
||||
// Update stock status
|
||||
if (scrapedData.stockStatus !== product.stock_status) {
|
||||
await productQueries.updateStockStatus(product.id, scrapedData.stockStatus);
|
||||
console.log(
|
||||
`Stock status changed for product ${product.id}: ${product.stock_status} -> ${scrapedData.stockStatus}`
|
||||
);
|
||||
|
||||
// Send back-in-stock notification
|
||||
if (wasOutOfStock && nowInStock && product.notify_back_in_stock) {
|
||||
try {
|
||||
const userSettings = await userQueries.getNotificationSettings(product.user_id);
|
||||
if (userSettings) {
|
||||
const payload: NotificationPayload = {
|
||||
productName: product.name || 'Unknown Product',
|
||||
productUrl: product.url,
|
||||
type: 'back_in_stock',
|
||||
newPrice: scrapedData.price?.price,
|
||||
currency: scrapedData.price?.currency || 'USD',
|
||||
};
|
||||
await sendNotifications(userSettings, payload);
|
||||
console.log(`Back-in-stock notification sent for product ${product.id}`);
|
||||
}
|
||||
} catch (notifyError) {
|
||||
console.error(`Failed to send back-in-stock notification for product ${product.id}:`, notifyError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (scrapedData.price) {
|
||||
|
|
@ -38,6 +63,34 @@ async function checkPrices(): Promise<void> {
|
|||
|
||||
// Only record if price has changed or it's the first entry
|
||||
if (!latestPrice || latestPrice.price !== scrapedData.price.price) {
|
||||
// Check for price drop notification before recording
|
||||
if (latestPrice && product.price_drop_threshold) {
|
||||
const oldPrice = parseFloat(String(latestPrice.price));
|
||||
const newPrice = scrapedData.price.price;
|
||||
const priceDrop = oldPrice - newPrice;
|
||||
|
||||
if (priceDrop >= product.price_drop_threshold) {
|
||||
try {
|
||||
const userSettings = await userQueries.getNotificationSettings(product.user_id);
|
||||
if (userSettings) {
|
||||
const payload: NotificationPayload = {
|
||||
productName: product.name || 'Unknown Product',
|
||||
productUrl: product.url,
|
||||
type: 'price_drop',
|
||||
oldPrice: oldPrice,
|
||||
newPrice: newPrice,
|
||||
currency: scrapedData.price.currency,
|
||||
threshold: product.price_drop_threshold,
|
||||
};
|
||||
await sendNotifications(userSettings, payload);
|
||||
console.log(`Price drop notification sent for product ${product.id}: ${priceDrop} drop`);
|
||||
}
|
||||
} catch (notifyError) {
|
||||
console.error(`Failed to send price drop notification for product ${product.id}:`, notifyError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await priceHistoryQueries.create(
|
||||
product.id,
|
||||
scrapedData.price.price,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue