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

View File

@@ -0,0 +1,37 @@
-- ============================================================
-- 이베이 부품 검색 이력 테이블
-- 목적: 검색 결과 캐싱 + 사용자별 검색 이력 관리
-- ============================================================
CREATE TABLE IF NOT EXISTS ebay_search_history (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES auth.users(id) ON DELETE SET NULL,
part_number VARCHAR(100) NOT NULL,
part_name VARCHAR(500),
result JSONB NOT NULL,
sources_checked TEXT[] DEFAULT '{}',
processing_time VARCHAR(20),
ai_model VARCHAR(100),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- 인덱스
CREATE INDEX IF NOT EXISTS idx_ebay_search_part_number ON ebay_search_history(part_number);
CREATE INDEX IF NOT EXISTS idx_ebay_search_user ON ebay_search_history(user_id);
CREATE INDEX IF NOT EXISTS idx_ebay_search_created ON ebay_search_history(created_at DESC);
-- RLS (로그인 사용자는 본인 이력만 조회)
ALTER TABLE ebay_search_history ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users view own search history"
ON ebay_search_history FOR SELECT
TO authenticated
USING (auth.uid() = user_id);
CREATE POLICY "Users insert own search history"
ON ebay_search_history FOR INSERT
TO authenticated
WITH CHECK (auth.uid() = user_id);
-- 서버 사이드 관리자 작업은 service_role 키를 사용하여 RLS를 우회합니다
-- anon 역할에 전체 권한을 부여하지 않습니다