recoder-code
Version:
🚀 AI-powered development platform - Chat with 32+ models, build projects, automate workflows. Free models included!
229 lines (198 loc) • 6.64 kB
text/typescript
/**
* Queue Service - Handles background job processing
*/
import Bull, { Job } from 'bull';
import { Package } from '../entities/Package';
import { PackageVersion } from '../entities/PackageVersion';
import { User } from '../entities/User';
export interface JobData {
type: string;
payload: Record<string, any>;
priority?: number;
delay?: number;
attempts?: number;
}
export class QueueService {
private queues: Map<string, Bull.Queue> = new Map();
private logger = {
info: (msg: string, ...args: any[]) => console.log(`[INFO] ${msg}`, ...args),
error: (msg: string, ...args: any[]) => console.error(`[ERROR] ${msg}`, ...args),
warn: (msg: string, ...args: any[]) => console.warn(`[WARN] ${msg}`, ...args)
};
constructor() {
this.initializeQueues();
}
private initializeQueues(): void {
// Initialize different queues for different job types
const queueNames = [
'package-processing',
'notifications',
'security-scanning',
'analytics',
'cleanup'
];
queueNames.forEach(name => {
const queue = new Bull(name, {
redis: {
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379'),
password: process.env.REDIS_PASSWORD
},
defaultJobOptions: {
removeOnComplete: 10,
removeOnFail: 5,
attempts: 3,
backoff: {
type: 'exponential',
delay: 2000
}
}
});
this.queues.set(name, queue);
this.setupJobProcessors(name, queue);
});
}
private setupJobProcessors(queueName: string, queue: Bull.Queue): void {
queue.process('*', async (job: Job) => {
try {
this.logger.info(`Processing job ${job.data.type} in queue ${queueName}`);
switch (job.data.type) {
case 'package-published':
await this.processPackagePublished(job.data);
break;
case 'package-updated':
await this.processPackageUpdated(job.data);
break;
case 'security-scan':
await this.processSecurityScan(job.data);
break;
case 'analytics-update':
await this.processAnalyticsUpdate(job.data);
break;
case 'cleanup-temp-files':
await this.processCleanupTempFiles(job.data);
break;
default:
this.logger.warn(`Unknown job type: ${job.data.type}`);
}
this.logger.info(`Completed job ${job.data.type} in queue ${queueName}`);
} catch (error) {
this.logger.error(`Failed to process job ${job.data.type}:`, error);
throw error;
}
});
queue.on('completed', (job: Job) => {
this.logger.info(`Job ${job.id} completed in queue ${queueName}`);
});
queue.on('failed', (job: Job, err: Error) => {
this.logger.error(`Job ${job.id} failed in queue ${queueName}:`, err);
});
}
async addJob(queueName: string, jobType: string, payload: Record<string, any>, options?: {
priority?: number;
delay?: number;
attempts?: number;
}): Promise<Job> {
const queue = this.queues.get(queueName);
if (!queue) {
throw new Error(`Queue ${queueName} not found`);
}
const jobData: JobData = {
type: jobType,
payload,
...options
};
const job = await queue.add(jobType, jobData, {
priority: options?.priority,
delay: options?.delay,
attempts: options?.attempts
});
this.logger.info(`Added job ${jobType} to queue ${queueName} with ID ${job.id}`);
return job;
}
// Specific job processors
private async processPackagePublished(data: any): Promise<void> {
// Process package publication
// - Send notifications
// - Update search indexes
// - Update statistics
// - Trigger security scans
this.logger.info(`Processing package published: ${data.packageName}@${data.version}`);
}
private async processPackageUpdated(data: any): Promise<void> {
// Process package updates
// - Send notifications to followers
// - Update search indexes
// - Check for breaking changes
this.logger.info(`Processing package updated: ${data.packageName}@${data.version}`);
}
private async processSecurityScan(data: any): Promise<void> {
// Process security scanning
// - Run security scanners
// - Check for vulnerabilities
// - Send alerts if issues found
this.logger.info(`Processing security scan: ${data.packageName}@${data.version}`);
}
private async processAnalyticsUpdate(data: any): Promise<void> {
// Process analytics updates
// - Update download counts
// - Calculate popularity metrics
// - Update trends
this.logger.info(`Processing analytics update: ${data.type}`);
}
private async processCleanupTempFiles(data: any): Promise<void> {
// Clean up temporary files
// - Remove old upload files
// - Clean cache files
// - Remove expired tokens
this.logger.info(`Processing cleanup: ${data.type}`);
}
async getQueueStats(queueName: string): Promise<{
waiting: number;
active: number;
completed: number;
failed: number;
delayed: number;
}> {
const queue = this.queues.get(queueName);
if (!queue) {
throw new Error(`Queue ${queueName} not found`);
}
const [waiting, active, completed, failed, delayed] = await Promise.all([
queue.getWaiting(),
queue.getActive(),
queue.getCompleted(),
queue.getFailed(),
queue.getDelayed()
]);
return {
waiting: waiting.length,
active: active.length,
completed: completed.length,
failed: failed.length,
delayed: delayed.length
};
}
async pauseQueue(queueName: string): Promise<void> {
const queue = this.queues.get(queueName);
if (!queue) {
throw new Error(`Queue ${queueName} not found`);
}
await queue.pause();
this.logger.info(`Paused queue ${queueName}`);
}
async resumeQueue(queueName: string): Promise<void> {
const queue = this.queues.get(queueName);
if (!queue) {
throw new Error(`Queue ${queueName} not found`);
}
await queue.resume();
this.logger.info(`Resumed queue ${queueName}`);
}
async shutdown(): Promise<void> {
this.logger.info('Shutting down queue service...');
const closePromises = Array.from(this.queues.values()).map(queue => queue.close());
await Promise.all(closePromises);
this.logger.info('Queue service shut down successfully');
}
}