feat: 이베이 부품 AI 리스팅 툴 — 실제 크롤링·AI·가격 모듈 구현

[핵심 모듈 (lib/ebay-tools/)]
- types.ts: 검색 요청/결과/크롤링/가격 공통 타입 정의
- crawler.ts: RockAuto HTTP 크롤러 + eBay 검색 (cheerio, UA 로테이션)
- ai-analyzer.ts: Claude API Tool Use로 크롤링 결과 구조화 (lazy 클라이언트, 런타임 검증)
- pricing.ts: 환율 API 연동 + HS Code 관세 + VAT + 소액면세 계산

[검색 API]
- Mock 데이터 → 실제 크롤링+AI+가격 파이프라인으로 교체
- AI 실패 시 fallback 결과 생성
- 입력값 50자 제한 + 허용 문자 검증

[프론트엔드]
- 중복 타입 제거 → lib/ebay-tools/types import
- 가격 탭에 VAT, 총 수입비용, 면세 여부, 면책 문구 추가

[DB]
- 004_ebay_search_history.sql: 검색 이력 테이블 + RLS (anon 전체 권한 제거)

[Evaluator 반영]
- anon RLS 보안 취약점 수정
- AI 응답 런타임 필드 검증 추가
- Anthropic 클라이언트 lazy 초기화

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-02 14:04:22 +09:00
parent 244781f96a
commit 7003e8d27e
9 changed files with 831 additions and 105 deletions

74
lib/ebay-tools/types.ts Normal file
View File

@@ -0,0 +1,74 @@
export interface SearchRequest {
partNumber: string;
partName?: string;
}
export interface BasicInfo {
partNumber: string;
partName: string;
brand: string;
oemNumbers: string[];
category: string;
imageUrl?: string;
}
export interface ListingInfo {
title: string;
category: string;
itemSpecifics: Record<string, string>;
}
export interface FitmentEntry {
year: string;
make: string;
model: string;
engine: string;
confidence: 'high' | 'medium' | 'low';
source: string;
}
export interface PriceSource {
site: string;
price: number;
currency: string;
url: string;
}
export interface PricingInfo {
sources: PriceSource[];
exchangeRate: { rate: number; source: string; date: string };
customs: {
hsCode: string;
dutyRate: string;
estimatedDuty: number;
vat: number;
totalImportCost: number;
isExempt: boolean;
disclaimer: string;
};
}
export interface SearchResult {
success: boolean;
data: {
basicInfo: BasicInfo;
listing: ListingInfo;
fitment: FitmentEntry[];
pricing: PricingInfo;
rawData: Record<string, unknown>;
meta: {
searchedAt: string;
sourcesChecked: string[];
processingTime: string;
aiModel: string;
};
};
error?: string;
}
export interface CrawlResult {
source: string;
success: boolean;
data: Record<string, unknown>;
error?: string;
}