| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 |
- import { loadConfig } from './config';
- import { createServer } from './api/server';
- import { JobQueue } from './queue/JobQueue';
- import { WebshopScraper } from './scraper/WebshopScraper';
- import { logger } from './utils/logger';
- import { ShopDatabase } from './database/Database';
- import { ScrapeScheduler } from './scheduler/ScrapeScheduler';
- import { WebhookManager } from './webhooks/WebhookManager';
- async function main() {
- try {
- logger.info('Starting Webshop Scraper application...');
- // Load configuration
- const config = loadConfig();
- // Initialize database
- const db = new ShopDatabase();
- // Initialize job queue
- const jobQueue = new JobQueue(config.maxConcurrentJobs);
- // Initialize scraper with database
- const scraper = new WebshopScraper(db);
- // Initialize scheduler
- const scheduler = new ScrapeScheduler(db, jobQueue);
- // Initialize webhook manager
- const webhookManager = new WebhookManager(db);
- // Handle job processing
- jobQueue.on('process-job', async (job) => {
- logger.info(`Processing job ${job.id}`);
- const startTime = Date.now();
- let scrapeHistoryId: string | undefined;
- try {
- // Create scrape history record if shopId is available
- if (job.shopId) {
- scrapeHistoryId = db.createScrapeHistory(job.shopId, job.id);
- db.updateScrapeHistory(scrapeHistoryId, { status: 'processing' });
- // Trigger webhook: scrape_started
- await webhookManager.notifyScrapeStarted(
- job.shopId,
- job.id,
- job.sitemapUrl
- );
- }
- const result = await scraper.scrape(job.sitemapUrl, job.shopId);
- jobQueue.updateJob(job.id, {
- status: 'completed',
- result
- });
- // Update scrape history
- if (scrapeHistoryId) {
- const scrapeTimeMs = Date.now() - startTime;
- db.updateScrapeHistory(scrapeHistoryId, {
- status: 'completed',
- completed_at: new Date().toISOString(),
- scrape_time_ms: scrapeTimeMs
- });
- }
- // Trigger webhook: scrape_completed
- if (job.shopId) {
- const totalUrls = (result.shipping_informations?.length || 0) +
- (result.contacts?.length || 0) +
- (result.terms_of_conditions?.length || 0) +
- (result.faq?.length || 0);
- const totalPages = totalUrls;
- await webhookManager.notifyScrapeCompleted(
- job.shopId,
- job.id,
- {
- scrape_time_ms: Date.now() - startTime,
- urls_found: totalUrls,
- pages_scraped: totalPages
- }
- );
- }
- // Schedule next scrape if this is a new shop or first scrape
- if (job.shopId) {
- const existingSchedules = db.getScheduledJobs(job.shopId);
- const hasQueuedJobs = existingSchedules.some(s => s.status === 'queued');
- if (!hasQueuedJobs) {
- // Create initial schedule for this shop
- scheduler.createInitialSchedule(job.shopId, 'weekly', null);
- logger.info(`Created initial schedule for shop ${job.shopId}`);
- }
- }
- logger.info(`Job ${job.id} completed successfully`);
- } catch (error) {
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
- jobQueue.updateJob(job.id, {
- status: 'failed',
- error: errorMessage
- });
- // Update scrape history with error
- if (scrapeHistoryId) {
- db.updateScrapeHistory(scrapeHistoryId, {
- status: 'failed',
- completed_at: new Date().toISOString(),
- error: errorMessage
- });
- }
- // Trigger webhook: scrape_failed
- if (job.shopId) {
- await webhookManager.notifyScrapeFailed(
- job.shopId,
- job.id,
- errorMessage
- );
- }
- logger.error(`Job ${job.id} failed`, error);
- }
- });
- // Create and start HTTP server
- const app = createServer(config.apiKey, jobQueue, db);
- const server = app.listen(config.port, () => {
- logger.info(`Server listening on port ${config.port}`);
- logger.info('API endpoints:');
- logger.info(` POST /api/jobs - Create new scraping job`);
- logger.info(` GET /api/jobs/:id - Get job status and result`);
- logger.info(` GET /api/jobs - List all jobs`);
- logger.info(` GET /api/shops - List all shops`);
- logger.info(` GET /api/shops/:id - Get shop details and analytics`);
- logger.info(` GET /health - Health check`);
- logger.info('');
- logger.info('Authentication: Bearer token required for /api/* endpoints');
- });
- // Start scheduler
- scheduler.start();
- // Graceful shutdown
- const shutdown = async () => {
- logger.info('Shutting down gracefully...');
- // Stop scheduler
- scheduler.stop();
- // Close database
- db.close();
- server.close(() => {
- logger.info('Server closed');
- process.exit(0);
- });
- // Force shutdown after 10 seconds
- setTimeout(() => {
- logger.error('Forced shutdown after timeout');
- process.exit(1);
- }, 10000);
- };
- process.on('SIGTERM', shutdown);
- process.on('SIGINT', shutdown);
- } catch (error) {
- logger.error('Failed to start application', error);
- process.exit(1);
- }
- }
- main();
|