92 lines
2.8 KiB
TypeScript
92 lines
2.8 KiB
TypeScript
import 'dotenv/config';
|
|
import server from './api/server';
|
|
import { ScraperScheduler } from './scrapers/scheduler';
|
|
import { PriceRepository } from './database/repository';
|
|
import { broadcastPriceUpdate } from './api/routes/websocket';
|
|
|
|
const PORT = parseInt(process.env.PORT || '3000', 10);
|
|
const HOST = process.env.HOST || '0.0.0.0';
|
|
const SCRAPE_INTERVAL = parseInt(process.env.SCRAPE_INTERVAL_MINUTES || '5', 10);
|
|
|
|
const repository = new PriceRepository();
|
|
const scheduler = new ScraperScheduler(SCRAPE_INTERVAL);
|
|
|
|
// Handle scrape results
|
|
scheduler.onScrapeComplete(async (results) => {
|
|
const startTime = Date.now();
|
|
|
|
try {
|
|
// Save all listings
|
|
const allListings = results.flatMap(r => r.listings);
|
|
if (allListings.length > 0) {
|
|
await repository.saveListings(allListings);
|
|
|
|
// Find and save lowest price
|
|
const lowestListing = allListings.reduce((min, listing) =>
|
|
listing.pricePerMillion < min.pricePerMillion ? listing : min
|
|
);
|
|
|
|
await repository.savePriceIndex(
|
|
lowestListing.pricePerMillion,
|
|
lowestListing.vendor,
|
|
lowestListing.seller
|
|
);
|
|
|
|
// Broadcast update to WebSocket clients
|
|
const latestPrices = await repository.getLatestPrices();
|
|
broadcastPriceUpdate({
|
|
timestamp: new Date(),
|
|
lowestPrice: lowestListing.pricePerMillion,
|
|
platform: lowestListing.vendor,
|
|
sellerName: lowestListing.seller,
|
|
allPrices: latestPrices.map((p: any) => ({
|
|
id: p.id,
|
|
platform: p.vendor,
|
|
sellerName: p.sellerName,
|
|
pricePerMillion: Number(p.usdPerMillion),
|
|
timestamp: p.timestamp,
|
|
url: p.url
|
|
}))
|
|
});
|
|
|
|
console.log(`✓ Broadcasted price update to ${latestPrices.length} listings`);
|
|
}
|
|
|
|
// Log successful scrape
|
|
const runtimeMs = Date.now() - startTime;
|
|
await repository.logScrape('success', `Saved ${allListings.length} listings`, runtimeMs);
|
|
|
|
console.log(`✓ Saved ${allListings.length} listings to database`);
|
|
} catch (error) {
|
|
const runtimeMs = Date.now() - startTime;
|
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
await repository.logScrape('failure', message, runtimeMs);
|
|
console.error('✗ Failed to save listings:', message);
|
|
}
|
|
});
|
|
|
|
// Start server
|
|
const start = async () => {
|
|
try {
|
|
await server.listen({ port: PORT, host: HOST });
|
|
console.log(`Server listening on ${HOST}:${PORT}`);
|
|
|
|
// Start scraper
|
|
scheduler.start();
|
|
console.log(`Scraper started (interval: ${SCRAPE_INTERVAL} minutes)`);
|
|
} catch (err) {
|
|
server.log.error(err);
|
|
process.exit(1);
|
|
}
|
|
};
|
|
|
|
// Graceful shutdown
|
|
process.on('SIGINT', async () => {
|
|
console.log('\nShutting down gracefully...');
|
|
await scheduler.close();
|
|
await server.close();
|
|
process.exit(0);
|
|
});
|
|
|
|
start();
|