Skip to content

Task 1-2: Extract Shared Webhook Failure Helper

Phase: 1 Priority: Medium Module: marketplace Depends on: Không có Reference: docs/bountyhunter-blockchain-p2/details/feature-marketplace/SPEC.md

Background

Mỗi @Process() handler trong MarketplaceProcessor đều có đoạn error recovery gần giống nhau: kiểm tra job.attemptsMade === QUEUE_MAX_RETRY - 1, nếu đúng thì gọi postWithRetry(webhook, failurePayload), nếu không thì job.retry(). Pattern này được copy-paste ít nhất 10 lần, làm cho code khó maintain và dễ sai khi cần thay đổi logic retry. Cần extract thành shared helper.

Tasks

Note: Helper nên là method của MarketplaceProcessor (private) hoặc util function. postWithRetryQUEUE_MAX_RETRY phải được inject/import đúng cách trong scope của method. Job type import từ bull. Kiểm tra type của failurePayload — thường là object với { status: 'FAILED', error: string, txHash?: string }.

  • [ ] Định nghĩa helper method notifyWebhookOrRetry trong MarketplaceProcessor:
    private async notifyWebhookOrRetry(
      job: Job,
      webhookUrl: string,
      failurePayload: Record<string, unknown>,
    ): Promise<void> {
      if (job.attemptsMade >= QUEUE_MAX_RETRY - 1) {
        this.logger.warn(
          `[MARKETPLACE] jobId=${job.id} maxRetries=${QUEUE_MAX_RETRY} reached → sending failure webhook`
        );
        await postWithRetry(webhookUrl, failurePayload);
      } else {
        this.logger.log(
          `[MARKETPLACE] jobId=${job.id} attempt=${job.attemptsMade + 1}/${QUEUE_MAX_RETRY} → retrying`
        );
        await job.retry();
      }
    }
    
  • [ ] Áp dụng notifyWebhookOrRetry thay thế đoạn error recovery trong tất cả handler:
  • handleSendAdminSellNft
  • handleUserBuyNft
  • handleListingNft
  • handleCancelListingNft
  • handleBurnTicket
  • Và tất cả handler còn lại (kiểm tra đủ 10+)
  • [ ] Mỗi handler sau khi refactor có cấu trúc:
    @Process(QUEUE_NAME.ADMIN_SELL_NFT)
    async handleSendAdminSellNft(job: Job): Promise<void> {
      const { webhookUrl, ...data } = job.data;
      try {
        // ... business logic ...
        await postWithRetry(webhookUrl, successPayload);
      } catch (error) {
        this.logger.error(`[MARKETPLACE] action=ADMIN_SELL_NFT jobId=${job.id} error=${error.message}`);
        await this.notifyWebhookOrRetry(job, webhookUrl, {
          status: 'FAILED',
          error: error.message,
        });
      }
    }
    
  • [ ] Verify không còn inline retry/webhook logic trong catch block nào

Verification / Acceptance Criteria

  • [ ] grep -n "job.attemptsMade" src/marketplace/marketplace.processor.ts chỉ trả về kết quả trong method notifyWebhookOrRetry (không còn inline)
  • [ ] grep -n "job.retry()" src/marketplace/marketplace.processor.ts chỉ còn trong notifyWebhookOrRetry
  • [ ] Tất cả handler đều gọi notifyWebhookOrRetry trong catch block
  • [ ] Khi attemptsMade < QUEUE_MAX_RETRY - 1job.retry() được gọi, webhook không được gọi
  • [ ] Khi attemptsMade === QUEUE_MAX_RETRY - 1 → webhook failure được gọi, job.retry() không được gọi
  • [ ] TypeScript compile không có lỗi

Files to Modify

  • src/marketplace/marketplace.processor.ts