mirror of
https://github.com/clucraft/PriceGhost.git
synced 2026-05-18 13:55:16 +02:00
Initial commit: PriceGhost price tracking application
Full-stack application for tracking product prices: - Backend: Node.js + Express + TypeScript - Frontend: React + Vite + TypeScript - Database: PostgreSQL - Price scraping with Cheerio - JWT authentication - Background price checking with node-cron - Price history charts with Recharts - Docker support with docker-compose Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
commit
10660e5626
44 changed files with 3662 additions and 0 deletions
76
backend/src/services/scheduler.ts
Normal file
76
backend/src/services/scheduler.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import cron from 'node-cron';
|
||||
import { productQueries, priceHistoryQueries } from '../models';
|
||||
import { scrapePrice } from './scraper';
|
||||
|
||||
let isRunning = false;
|
||||
|
||||
async function checkPrices(): Promise<void> {
|
||||
if (isRunning) {
|
||||
console.log('Price check already in progress, skipping...');
|
||||
return;
|
||||
}
|
||||
|
||||
isRunning = true;
|
||||
console.log('Starting scheduled price check...');
|
||||
|
||||
try {
|
||||
// Find all products that are due for a refresh
|
||||
const products = await productQueries.findDueForRefresh();
|
||||
console.log(`Found ${products.length} products to check`);
|
||||
|
||||
for (const product of products) {
|
||||
try {
|
||||
console.log(`Checking price for product ${product.id}: ${product.url}`);
|
||||
|
||||
const priceData = await scrapePrice(product.url);
|
||||
|
||||
if (priceData) {
|
||||
// Get the latest recorded price to compare
|
||||
const latestPrice = await priceHistoryQueries.getLatest(product.id);
|
||||
|
||||
// Only record if price has changed or it's the first entry
|
||||
if (!latestPrice || latestPrice.price !== priceData.price) {
|
||||
await priceHistoryQueries.create(
|
||||
product.id,
|
||||
priceData.price,
|
||||
priceData.currency
|
||||
);
|
||||
console.log(
|
||||
`Recorded new price for product ${product.id}: ${priceData.currency} ${priceData.price}`
|
||||
);
|
||||
} else {
|
||||
console.log(`Price unchanged for product ${product.id}`);
|
||||
}
|
||||
} else {
|
||||
console.warn(`Could not extract price for product ${product.id}`);
|
||||
}
|
||||
|
||||
// Update last_checked even if price extraction failed
|
||||
await productQueries.updateLastChecked(product.id);
|
||||
|
||||
// Add a small delay between requests to avoid rate limiting
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
} catch (error) {
|
||||
console.error(`Error checking product ${product.id}:`, error);
|
||||
// Continue with next product even if one fails
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error in scheduled price check:', error);
|
||||
} finally {
|
||||
isRunning = false;
|
||||
console.log('Scheduled price check complete');
|
||||
}
|
||||
}
|
||||
|
||||
export function startScheduler(): void {
|
||||
// Run every minute
|
||||
cron.schedule('* * * * *', () => {
|
||||
checkPrices().catch(console.error);
|
||||
});
|
||||
|
||||
console.log('Price check scheduler started (runs every minute)');
|
||||
}
|
||||
|
||||
// Allow manual trigger for testing
|
||||
export { checkPrices };
|
||||
Loading…
Add table
Add a link
Reference in a new issue