Skip to content

Story: 상품 복구 API

메타

항목
Story IDE-09-S-03
EpicE-09 미사용 상품 정리
상태ready-for-dev
우선순위P1
규모S
담당 개발자BE

사용자 스토리

As a 셀러,
I want 실수로 삭제한 상품을 복구하고 싶다,
So that 다시 원가를 관리할 수 있다.


수락 기준 (Acceptance Criteria)

AC-01: 복구 가능 조건

항목내용
Given삭제 후 7일 이내인 상품에 대해
When복구 API를 호출하면
Then상품이 복구되어 목록에 다시 표시된다

AC-02: 복구 불가 조건

항목내용
Given삭제 후 7일이 지난 상품에 대해
When복구 API를 호출하면
Then"복구 기간이 만료되었습니다" 에러 반환

AC-03: 삭제된 상품 목록 조회

항목내용
Given셀러가 삭제한 상품이 있을 때
When삭제 목록 API를 호출하면
Then7일 이내 삭제된 상품 목록이 반환된다

태스크 분해

Task 1: 삭제 상품 목록 API

  • [ ] 1.1: GET /products/deleted
    • 7일 이내 삭제된 상품만
    • 삭제일, 남은 복구 기간 포함
  • [ ] 1.2: 페이지네이션 지원

Task 2: 개별 복구 API

  • [ ] 2.1: POST /products/{productId}/restore
    • deleted_at = NULL
    • deleted_by = NULL
  • [ ] 2.2: 복구 가능 기간 검증 (7일)

Task 3: 일괄 복구 API

  • [ ] 3.1: POST /products/restore/batch
    • Request:
  • [ ] 3.2: 부분 복구 처리 (일부만 복구 가능한 경우)

API 스펙

삭제 상품 목록

GET /api/products/deleted

Response 200:
{
  "products": [
    {
      "id": "prod_001",
      "name": "테스트 상품",
      "sku": "SKU-001",
      "deletedAt": "2026-01-20T10:00:00Z",
      "recoveryDeadline": "2026-01-27T10:00:00Z",
      "daysUntilPermanentDelete": 5
    }
  ],
  "total": 15,
  "page": 1,
  "limit": 20
}

개별 복구

POST /api/products/{productId}/restore

Response 200:
{
  "success": true,
  "message": "상품이 복구되었습니다.",
  "product": {
    "id": "prod_001",
    "name": "테스트 상품"
  }
}

Response 400 (복구 기간 만료):
{
  "success": false,
  "error": "RECOVERY_PERIOD_EXPIRED",
  "message": "복구 기간이 만료되었습니다. 다음 데이터 수집 시 자동으로 재등록됩니다."
}

일괄 복구

POST /api/products/restore/batch

Request:
{
  "productIds": ["prod_001", "prod_002", "prod_003"]
}

Response 200:
{
  "success": true,
  "restoredCount": 2,
  "failedCount": 1,
  "failures": [
    {
      "productId": "prod_003",
      "reason": "RECOVERY_PERIOD_EXPIRED"
    }
  ]
}

개발 노트

복구 로직

typescript
async restoreProduct(productId: string, sellerId: string) {
  const product = await this.productRepo.findOne({
    id: productId,
    sellerId,
    deletedAt: Not(IsNull())
  });

  if (!product) {
    throw new NotFoundException('삭제된 상품을 찾을 수 없습니다');
  }

  // 7일 경과 체크
  const daysSinceDelete = daysDiff(product.deletedAt, new Date());
  if (daysSinceDelete >= 7) {
    throw new BadRequestException('복구 기간이 만료되었습니다');
  }

  // 복구
  await this.productRepo.update(productId, {
    deletedAt: null,
    deletedBy: null
  });

  return { success: true };
}

이벤트 로깅

이벤트파라미터
product_restoreseller_id, product_count
product_restore_failedseller_id, product_id, reason

생성일: 2026-01-20

장사왕 Product Team