RelVDev2's picture
Deploy pre-receipt backup worker
d5c9d40
Raw
History Blame Contribute Delete
4.37 kB
import { config, validateConfig } from './config';
import { logger, setWorkerId } from './utils/logger';
import { createApp } from './server/app';
import { startWorkerManager, stopWorkerManager } from './worker/manager';
import { closeBrowser } from './worker/browser-pool';
import { runQuotaCheckCron } from './cron/quota-check';
import { runCleanupReportsCron } from './cron/cleanup-reports';
import { runStaleRecoveryCron } from './cron/stale-recovery';
import * as cron from 'node-cron';
async function main(): Promise<void> {
// Validate environment
try {
validateConfig();
} catch (err) {
console.error('Configuration error:', err instanceof Error ? err.message : err);
process.exit(1);
}
setWorkerId(config.workerId);
logger.info('Turnitin Worker starting', {
workerId: config.workerId,
maxWorkers: config.maxWorkers,
port: config.port,
enableWorker: config.enableWorker,
enableCron: config.enableCron,
});
// Start Express server
const app = createApp();
const server = app.listen(config.port, () => {
logger.info(`HTTP server listening on port ${config.port}`);
});
if (config.enableWorker) {
// Start worker manager (polls for jobs)
startWorkerManager().catch((err) => {
logger.error('Worker manager fatal error', {
error: err instanceof Error ? err.message : String(err),
});
});
} else {
logger.info('Worker manager disabled by ENABLE_WORKER=false');
}
if (config.enableCron) {
// Schedule cron jobs
// Quota check: every N minutes
const quotaCronExpr = `*/${config.quotaCheckInterval} * * * *`;
cron.schedule(quotaCronExpr, () => {
runQuotaCheckCron().catch((err) => {
logger.error('Quota check cron error', {
error: err instanceof Error ? err.message : String(err),
});
});
});
logger.info(`Quota check cron scheduled: ${quotaCronExpr}`);
// Report cleanup: every N minutes
const cleanupCronExpr = `*/${config.cleanupInterval} * * * *`;
cron.schedule(cleanupCronExpr, () => {
runCleanupReportsCron().catch((err) => {
logger.error('Report cleanup cron error', {
error: err instanceof Error ? err.message : String(err),
});
});
});
logger.info(`Report cleanup cron scheduled: ${cleanupCronExpr}`);
// Stale recovery: every N minutes
const staleCronExpr = `*/${config.staleRecoveryInterval} * * * *`;
cron.schedule(staleCronExpr, () => {
runStaleRecoveryCron().catch((err) => {
logger.error('Stale recovery cron error', {
error: err instanceof Error ? err.message : String(err),
});
});
});
logger.info(`Stale recovery cron scheduled: ${staleCronExpr}`);
} else {
logger.info('Cron jobs disabled by ENABLE_CRON=false');
}
logger.info('All systems started successfully');
// Graceful shutdown
const shutdown = async (signal: string) => {
logger.info(`Received ${signal}, shutting down gracefully...`);
// Stop accepting new jobs
stopWorkerManager();
// Close HTTP server
server.close(() => {
logger.info('HTTP server closed');
});
// Wait for active workers to finish (max 60 seconds)
const maxWait = 60000;
const start = Date.now();
const { getActiveWorkerCount } = await import('./worker/manager');
while (getActiveWorkerCount() > 0 && Date.now() - start < maxWait) {
logger.info(`Waiting for ${getActiveWorkerCount()} active workers to finish...`);
await new Promise((r) => setTimeout(r, 2000));
}
// Close browser
await closeBrowser();
logger.info('Shutdown complete');
process.exit(0);
};
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));
// Handle uncaught errors
process.on('uncaughtException', (err) => {
logger.error('Uncaught exception', {
error: err.message,
stack: err.stack,
});
// Don't exit on uncaught exceptions in worker service
// The worker loop has its own error handling
});
process.on('unhandledRejection', (reason) => {
logger.error('Unhandled promise rejection', {
reason: reason instanceof Error ? reason.message : String(reason),
});
});
}
main().catch((err) => {
console.error('Fatal startup error:', err);
process.exit(1);
});