Skip to content

Commit 62fe8c3

Browse files
authored
Merge pull request #1181 from Musab1258/feat/1142-correlation-id-background-jobs
feat(backend): propagate correlationId to background jobs
2 parents 2a1263d + 4b911b7 commit 62fe8c3

16 files changed

Lines changed: 399 additions & 75 deletions

backend/src/modules/admin/admin-audit-logs.controller.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard';
2828
import { RolesGuard } from '../../common/guards/roles.guard';
2929
import { Roles } from '../../common/decorators/roles.decorator';
3030
import { Role } from '../../common/enums/role.enum';
31+
import { CorrelationId } from '../../common/decorators/correlation-id.decorator';
3132
import { JobQueueService } from '../job-queue/job-queue.service';
3233
import { QUEUE_NAMES } from '../job-queue/job-queue.constants';
3334
import { Job } from 'bullmq';
@@ -158,19 +159,24 @@ export class AdminAuditLogsController {
158159
async queueExport(
159160
@Body() dto: AuditLogExportDto,
160161
@Query('format') format: 'csv' | 'json' = 'csv',
162+
@CorrelationId() correlationId?: string,
161163
) {
162-
const job = await this.jobQueueService.addAuditLogExportJob({
163-
filters: {
164-
actor: dto.actor,
165-
action: dto.action,
166-
resourceType: dto.resourceType,
167-
resourceId: dto.resourceId,
168-
fromDate: dto.fromDate,
169-
toDate: dto.toDate,
164+
const job = await this.jobQueueService.addAuditLogExportJob(
165+
{
166+
filters: {
167+
actor: dto.actor,
168+
action: dto.action,
169+
resourceType: dto.resourceType,
170+
resourceId: dto.resourceId,
171+
fromDate: dto.fromDate,
172+
toDate: dto.toDate,
173+
},
174+
format,
175+
requestedBy: 'admin',
170176
},
171-
format,
172-
requestedBy: 'admin',
173-
});
177+
undefined,
178+
correlationId,
179+
);
174180

175181
return {
176182
jobId: job.id,

backend/src/modules/disputes/disputes.controller.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,8 +228,9 @@ export class DisputesController {
228228
}),
229229
)
230230
file: any,
231+
@CorrelationId() correlationId?: string,
231232
): Promise<DisputeEvidence> {
232-
return this.disputesService.uploadEvidence(id, file, dto);
233+
return this.disputesService.uploadEvidence(id, file, dto, correlationId);
233234
}
234235

235236
@Get(':id/evidence')

backend/src/modules/disputes/disputes.service.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,7 @@ export class DisputesService {
396396
disputeId: string,
397397
file: any,
398398
dto: UploadEvidenceDto,
399+
correlationId?: string,
399400
): Promise<DisputeEvidence> {
400401
// Verify dispute exists
401402
const dispute = await this.findOne(disputeId);
@@ -449,6 +450,8 @@ export class DisputesService {
449450
const savedEvidence = await this.evidenceRepository.save(evidence);
450451

451452
// Enqueue background processing job
453+
const job = await this.jobQueueService.addEvidenceProcessingJob(
454+
{
452455
let job;
453456
try {
454457
job = await this.jobQueueService.addEvidenceProcessingJob({
@@ -458,6 +461,10 @@ export class DisputesService {
458461
mimeType: file.mimetype,
459462
originalFilename: file.originalname,
460463
uploadedBy: dto.uploadedBy,
464+
},
465+
undefined,
466+
correlationId,
467+
);
461468
});
462469
} catch (err) {
463470
await this.quotaService.release(reservation.reservationId, {

backend/src/modules/job-queue/job-queue.service.spec.ts

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,23 @@ describe('JobQueueService', () => {
8686
expect(result.id).toBe('job-1');
8787
});
8888

89+
it('should propagate correlationId in job data', async () => {
8990
it('should return existing job on duplicate enqueue', async () => {
9091
const data = {
9192
userId: 'user-1',
9293
type: 'sweep_completed',
9394
title: 'Sweep Done',
9495
message: 'Swept 100 XLM',
9596
};
97+
const correlationId = 'corr-notif-123';
98+
99+
await service.addNotificationJob(data, undefined, correlationId);
100+
101+
expect(notificationQueue.add).toHaveBeenCalledWith(
102+
'send-notification',
103+
expect.objectContaining({ correlationId }),
104+
undefined,
105+
);
96106
const duplicateError = new Error('Job already exists');
97107
duplicateError.name = 'JobAlreadyExistsError';
98108
const existingJob = { id: 'existing-job', data };
@@ -138,6 +148,24 @@ describe('JobQueueService', () => {
138148
);
139149
expect(result.id).toBe('job-1');
140150
});
151+
152+
it('should propagate correlationId in job data', async () => {
153+
const data = {
154+
to: 'user@test.com',
155+
subject: 'Welcome',
156+
template: 'welcome',
157+
context: { name: 'Alice' },
158+
};
159+
const correlationId = 'corr-email-456';
160+
161+
await service.addEmailJob(data, undefined, correlationId);
162+
163+
expect(emailQueue.add).toHaveBeenCalledWith(
164+
'send-email',
165+
expect.objectContaining({ correlationId }),
166+
undefined,
167+
);
168+
});
141169
});
142170

143171
describe('addBlockchainJob', () => {
@@ -157,6 +185,24 @@ describe('JobQueueService', () => {
157185
{ jobId: 'blockchain-evt-123' },
158186
);
159187
});
188+
189+
it('should propagate correlationId in job data', async () => {
190+
const data = {
191+
eventId: 'evt-456',
192+
contractId: 'CDEF456',
193+
eventType: 'withdrawal',
194+
rawEvent: { ledger: 200 },
195+
};
196+
const correlationId = 'corr-bc-789';
197+
198+
await service.addBlockchainJob(data, undefined, correlationId);
199+
200+
expect(blockchainQueue.add).toHaveBeenCalledWith(
201+
'process-blockchain-event',
202+
expect.objectContaining({ correlationId }),
203+
expect.objectContaining({ jobId: 'blockchain-evt-456' }),
204+
);
205+
});
160206
});
161207

162208
describe('addReportJob', () => {
@@ -177,6 +223,23 @@ describe('JobQueueService', () => {
177223
}),
178224
);
179225
});
226+
227+
it('should propagate correlationId in job data', async () => {
228+
const data = {
229+
reportType: 'annual-summary',
230+
userId: 'user-2',
231+
params: { year: 2025 },
232+
};
233+
const correlationId = 'corr-report-abc';
234+
235+
await service.addReportJob(data, undefined, correlationId);
236+
237+
expect(reportQueue.add).toHaveBeenCalledWith(
238+
'generate-report',
239+
expect.objectContaining({ correlationId }),
240+
undefined,
241+
);
242+
});
180243
});
181244

182245
describe('addAvatarProcessingJob', () => {
@@ -200,6 +263,25 @@ describe('JobQueueService', () => {
200263
}),
201264
);
202265
});
266+
267+
it('should propagate correlationId in job data', async () => {
268+
const data = {
269+
uploadId: 'upload-2',
270+
userId: 'user-3',
271+
storagePath: 'avatars/user-3/raw.png',
272+
mimeType: 'image/jpeg',
273+
originalFilename: 'photo.jpg',
274+
};
275+
const correlationId = 'corr-avatar-def';
276+
277+
await service.addAvatarProcessingJob(data, undefined, correlationId);
278+
279+
expect(avatarQueue.add).toHaveBeenCalledWith(
280+
'process-avatar',
281+
expect.objectContaining({ correlationId }),
282+
expect.objectContaining({ jobId: 'avatar-upload-2' }),
283+
);
284+
});
203285
});
204286

205287
describe('addAuditLogExportJob', () => {
@@ -220,6 +302,45 @@ describe('JobQueueService', () => {
220302
}),
221303
);
222304
});
305+
306+
it('should propagate correlationId in job data', async () => {
307+
const data = {
308+
filters: { actor: 'admin-2' },
309+
format: 'json' as const,
310+
requestedBy: 'admin-2',
311+
};
312+
const correlationId = 'corr-export-ghi';
313+
314+
await service.addAuditLogExportJob(data, undefined, correlationId);
315+
316+
expect(auditLogExportQueue.add).toHaveBeenCalledWith(
317+
'export-audit-logs',
318+
expect.objectContaining({ correlationId }),
319+
expect.objectContaining({ attempts: 3 }),
320+
);
321+
});
322+
});
323+
324+
describe('addEvidenceProcessingJob', () => {
325+
it('should propagate correlationId in job data', async () => {
326+
const data = {
327+
evidenceId: 'evid-1',
328+
disputeId: 'disp-1',
329+
storagePath: 'evidence/file.pdf',
330+
mimeType: 'application/pdf',
331+
originalFilename: 'file.pdf',
332+
uploadedBy: 'user-1',
333+
};
334+
const correlationId = 'corr-evid-jkl';
335+
336+
await service.addEvidenceProcessingJob(data, undefined, correlationId);
337+
338+
expect(disputeEvidenceQueue.add).toHaveBeenCalledWith(
339+
'process-dispute-evidence',
340+
expect.objectContaining({ correlationId }),
341+
expect.objectContaining({ jobId: 'evidence-evid-1' }),
342+
);
343+
});
223344
});
224345

225346
describe('getQueueStatus', () => {

0 commit comments

Comments
 (0)