feat(phase-6): Bulk CSV processing and background worker implementation

- Add BulkJob model to Prisma schema with relations
- Implement BulkProcessorService for CSV parsing and job management
- Create BulkTrackingWorker for background processing with BullMQ
- Add comprehensive bulk API routes (upload, jobs, progress, export)
- Integrate multer for CSV file uploads with validation
- Add job progress tracking and estimation
- Implement CSV export functionality for results
- Add queue statistics and cleanup endpoints
- Create shared types for bulk processing
- Add comprehensive test suite for all bulk functionality
- Implement graceful worker shutdown and error handling
- Add rate limiting and authentication for all bulk endpoints

Backward compatibility: Maintained for /api/track and /api/v1/track
This commit is contained in:
Andrei
2025-08-18 14:18:13 +00:00
parent 8c8300780f
commit 9626863917
13 changed files with 2309 additions and 64 deletions

View File

@@ -222,4 +222,92 @@ export const ErrorResponseSchema = z.object({
details: z.any().optional(),
});
export type ErrorResponse = z.infer<typeof ErrorResponseSchema>;
export type ErrorResponse = z.infer<typeof ErrorResponseSchema>;
// ============================================================================
// BULK PROCESSING TYPES
// ============================================================================
export const BulkJobStatusSchema = z.enum(['pending', 'processing', 'completed', 'failed', 'cancelled']);
export const BulkJobProgressSchema = z.object({
total: z.number(),
processed: z.number(),
successful: z.number(),
failed: z.number(),
});
export const BulkJobResultSchema = z.object({
url: z.string(),
label: z.string().optional(),
checkId: z.string().optional(),
status: z.enum(['success', 'failed']),
error: z.string().optional(),
timing: z.object({
startedAt: z.date(),
finishedAt: z.date().optional(),
durationMs: z.number().optional(),
}),
});
export const BulkJobSchema = z.object({
id: z.string(),
userId: z.string(),
organizationId: z.string().optional(),
projectId: z.string().optional(),
status: BulkJobStatusSchema,
progress: BulkJobProgressSchema,
createdAt: z.date(),
startedAt: z.date().optional(),
finishedAt: z.date().optional(),
estimatedCompletionAt: z.date().optional(),
urlCount: z.number(),
options: z.object({
method: z.enum(['GET', 'POST', 'HEAD']),
userAgent: z.string().optional(),
maxHops: z.number(),
timeout: z.number(),
enableSSLAnalysis: z.boolean(),
enableSEOAnalysis: z.boolean(),
enableSecurityAnalysis: z.boolean(),
headers: z.record(z.string()).optional(),
}),
results: z.array(BulkJobResultSchema).optional(),
});
export const CreateBulkJobRequestSchema = z.object({
projectId: z.string().optional(),
urls: z.array(z.object({
url: z.string().url(),
label: z.string().optional(),
metadata: z.record(z.any()).optional(),
})).min(1).max(1000),
options: z.object({
method: z.enum(['GET', 'POST', 'HEAD']).default('GET'),
userAgent: z.string().optional(),
maxHops: z.number().min(1).max(20).default(10),
timeout: z.number().min(1000).max(30000).default(15000),
enableSSLAnalysis: z.boolean().default(true),
enableSEOAnalysis: z.boolean().default(true),
enableSecurityAnalysis: z.boolean().default(true),
headers: z.record(z.string()).optional(),
}).default({}),
});
export const BulkStatsSchema = z.object({
queue: z.object({
waiting: z.number(),
active: z.number(),
completed: z.number(),
failed: z.number(),
delayed: z.number(),
}),
timestamp: z.string(),
});
export type BulkJobStatus = z.infer<typeof BulkJobStatusSchema>;
export type BulkJobProgress = z.infer<typeof BulkJobProgressSchema>;
export type BulkJobResult = z.infer<typeof BulkJobResultSchema>;
export type BulkJob = z.infer<typeof BulkJobSchema>;
export type CreateBulkJobRequest = z.infer<typeof CreateBulkJobRequestSchema>;
export type BulkStats = z.infer<typeof BulkStatsSchema>;