DB refactor for easier testsing
Some checks failed
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Failing after 5m16s

App.ts refactor into hooks
unit tests
This commit is contained in:
2025-12-08 20:46:12 -08:00
parent 0eda796fad
commit 158778c2ec
67 changed files with 4666 additions and 3599 deletions

View File

@@ -11,6 +11,7 @@ import { aiService } from './aiService.server';
import * as emailService from './emailService.server';
import * as db from './db/index.db';
import { FlyerProcessingService, type FlyerJobData, type IFileSystem } from './flyerProcessingService.server';
import { FlyerDataTransformer } from './flyerDataTransformer';
// --- Start: Interfaces for Dependency Injection ---
// --- End: Interfaces for Dependency Injection ---
@@ -68,6 +69,19 @@ export const analyticsQueue = new Queue<AnalyticsJobData>('analytics-reporting',
},
});
export const weeklyAnalyticsQueue = new Queue<WeeklyAnalyticsJobData>('weekly-analytics-reporting', {
connection,
defaultJobOptions: {
attempts: 2,
backoff: {
type: 'exponential',
delay: 3600000, // 1 hour delay for retries
},
removeOnComplete: true,
removeOnFail: 50,
},
});
export const cleanupQueue = new Queue<CleanupJobData>('file-cleanup', {
connection,
defaultJobOptions: {
@@ -95,6 +109,14 @@ interface AnalyticsJobData {
reportDate: string; // e.g., '2024-10-26'
}
/**
* Defines the data for a weekly analytics job.
*/
interface WeeklyAnalyticsJobData {
reportYear: number;
reportWeek: number; // ISO week number (1-53)
}
interface CleanupJobData {
flyerId: number;
// An array of absolute file paths to be deleted. Made optional for manual cleanup triggers.
@@ -115,7 +137,8 @@ const flyerProcessingService = new FlyerProcessingService(
db,
fsAdapter,
execAsync,
cleanupQueue // Inject the cleanup queue to break the circular dependency
cleanupQueue, // Inject the cleanup queue to break the circular dependency
new FlyerDataTransformer() // Inject the new transformer
);
/**
@@ -154,9 +177,12 @@ export const emailWorker = new Worker<EmailJobData>(
try {
await emailService.sendEmail(job.data);
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : 'An unknown email error occurred';
// Standardize error logging to capture the full error object, including the stack trace.
// This provides more context for debugging than just logging the message.
logger.error(`[EmailWorker] Job ${job.id} failed. Attempt ${job.attemptsMade}/${job.opts.attempts}.`, {
error: errorMessage,
// Log the full error object for better diagnostics.
error: error instanceof Error ? error : new Error(String(error)),
// Also include the job data for context.
jobData: job.data,
});
// Re-throw to let BullMQ handle the failure and retry.
@@ -189,9 +215,9 @@ export const analyticsWorker = new Worker<AnalyticsJobData>(
await new Promise(resolve => setTimeout(resolve, 10000)); // Simulate a 10-second task
logger.info(`[AnalyticsWorker] Successfully generated report for ${reportDate}.`);
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : 'An unknown analytics error occurred';
// Standardize error logging.
logger.error(`[AnalyticsWorker] Job ${job.id} failed. Attempt ${job.attemptsMade}/${job.opts.attempts}.`, {
error: errorMessage,
error: error instanceof Error ? error : new Error(String(error)),
jobData: job.data,
});
throw error; // Re-throw to let BullMQ handle the failure and retry.
@@ -238,8 +264,10 @@ export const cleanupWorker = new Worker<CleanupJobData>(
}
logger.info(`[CleanupWorker] Successfully cleaned up ${paths.length} file(s) for flyer ${flyerId}.`);
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : 'An unknown cleanup error occurred';
logger.error(`[CleanupWorker] Job ${job.id} for flyer ${flyerId} failed. Attempt ${job.attemptsMade}/${job.opts.attempts}.`, { error: errorMessage });
// Standardize error logging.
logger.error(`[CleanupWorker] Job ${job.id} for flyer ${flyerId} failed. Attempt ${job.attemptsMade}/${job.opts.attempts}.`, {
error: error instanceof Error ? error : new Error(String(error)),
});
throw error; // Re-throw to let BullMQ handle the failure and retry.
}
},
@@ -249,11 +277,40 @@ export const cleanupWorker = new Worker<CleanupJobData>(
}
);
/**
* A dedicated worker for generating weekly analytics reports.
* This is a placeholder for the actual report generation logic.
*/
export const weeklyAnalyticsWorker = new Worker<WeeklyAnalyticsJobData>(
'weekly-analytics-reporting',
async (job: Job<WeeklyAnalyticsJobData>) => {
const { reportYear, reportWeek } = job.data;
logger.info(`[WeeklyAnalyticsWorker] Starting weekly report generation for job ${job.id}`, { reportYear, reportWeek });
try {
// Simulate a longer-running task for weekly reports
await new Promise(resolve => setTimeout(resolve, 30000)); // Simulate 30-second task
logger.info(`[WeeklyAnalyticsWorker] Successfully generated weekly report for week ${reportWeek}, ${reportYear}.`);
} catch (error: unknown) {
// Standardize error logging.
logger.error(`[WeeklyAnalyticsWorker] Job ${job.id} failed. Attempt ${job.attemptsMade}/${job.opts.attempts}.`, {
error: error instanceof Error ? error : new Error(String(error)),
jobData: job.data,
});
throw error; // Re-throw to let BullMQ handle the failure and retry.
}
},
{
connection,
concurrency: parseInt(process.env.WEEKLY_ANALYTICS_WORKER_CONCURRENCY || '1', 10),
}
);
// --- Attach Event Listeners to All Workers ---
attachWorkerEventListeners(flyerWorker);
attachWorkerEventListeners(emailWorker);
attachWorkerEventListeners(analyticsWorker);
attachWorkerEventListeners(cleanupWorker);
attachWorkerEventListeners(weeklyAnalyticsWorker);
logger.info('All workers started and listening for jobs.');