import { NextRequest, NextResponse } from 'next/server' /** * eBay API Proxy Route * Proxies requests to eBay Finding API to avoid CORS issues */ interface eBaySearchParams { keywords: string categoryId?: string sortOrder?: string maxResults?: number } interface eBaySearchResult { itemId: string title: string price: string currency: string imageUrl?: string viewItemUrl: string condition?: string shippingCost?: string affiliateLink: string } type MaybeArray = T | T[] const SEARCH_CACHE_TTL = 15 * 60 * 1000 // 15 minutes const searchResponseCache = new Map() const inFlightSearchResponses = new Map>() // Affiliate campaign ID for generating links const AFFILIATE_CAMPAIGN_ID = process.env.EBAY_AFFILIATE_CAMPAIGN_ID?.trim() || '' // Generate eBay affiliate link function generateAffiliateLink(viewItemUrl: string): string { if (!AFFILIATE_CAMPAIGN_ID) { return viewItemUrl } try { const url = new URL(viewItemUrl) url.searchParams.set('mkcid', '1') url.searchParams.set('mkrid', '711-53200-19255-0') url.searchParams.set('siteid', '0') url.searchParams.set('campid', AFFILIATE_CAMPAIGN_ID) url.searchParams.set('toolid', '10001') url.searchParams.set('mkevt', '1') return url.toString() } catch { return viewItemUrl } } function first(value: MaybeArray | undefined): T | undefined { if (!value) { return undefined } return Array.isArray(value) ? value[0] : value } function normalizeItem(item: any): eBaySearchResult { const currentPrice = first(item.sellingStatus?.currentPrice) const shippingCost = first(item.shippingInfo?.shippingServiceCost) const condition = first(item.condition) const viewItemUrl = item.viewItemURL || item.viewItemUrl || '' return { itemId: item.itemId || '', title: item.title || 'Unknown Item', price: `${currentPrice?.value || '0'} ${currentPrice?.currencyId || 'USD'}`, currency: currentPrice?.currencyId || 'USD', imageUrl: first(item.galleryURL) || undefined, viewItemUrl, condition: condition?.conditionDisplayName || undefined, shippingCost: shippingCost?.value ? `${shippingCost.value} ${shippingCost.currencyId || currentPrice?.currencyId || 'USD'}` : undefined, affiliateLink: generateAffiliateLink(viewItemUrl), } } async function readEbayErrorMessage(response: Response) { const text = await response.text().catch(() => '') if (!text.trim()) { return `eBay API error: ${response.status}` } try { const parsed = JSON.parse(text) as any const messages = parsed?.errorMessage?.[0]?.error?.[0] const message = Array.isArray(messages?.message) ? messages.message[0] : messages?.message if (typeof message === 'string' && message.trim()) { const errorId = Array.isArray(messages?.errorId) ? messages.errorId[0] : messages?.errorId return errorId ? `eBay API error ${errorId}: ${message}` : `eBay API error: ${message}` } } catch { // Fall through to returning the raw text below. } return text.trim() || `eBay API error: ${response.status}` } function buildCacheKey( keywords: string, categoryId: string | undefined, sortOrder: string, maxResults: number, ): string { return [ keywords.trim().toLowerCase(), categoryId || '', sortOrder || 'BestMatch', String(maxResults), ].join('|') } function getCachedSearchResults(cacheKey: string): eBaySearchResult[] | null { const cached = searchResponseCache.get(cacheKey) if (!cached) { return null } if (Date.now() - cached.timestamp > SEARCH_CACHE_TTL) { searchResponseCache.delete(cacheKey) return null } return cached.results } function setCachedSearchResults(cacheKey: string, results: eBaySearchResult[]) { searchResponseCache.set(cacheKey, { results, timestamp: Date.now(), }) } export async function GET(request: NextRequest) { const { searchParams } = new URL(request.url) const keywords = searchParams.get('keywords') const categoryId = searchParams.get('categoryId') || undefined const sortOrder = searchParams.get('sortOrder') || 'BestMatch' const maxResults = parseInt(searchParams.get('maxResults') || '6', 10) const cacheKey = buildCacheKey(keywords || '', categoryId, sortOrder, maxResults) if (!keywords) { return NextResponse.json({ error: 'Keywords parameter is required' }, { status: 400 }) } const appId = process.env.EBAY_APP_ID?.trim() if (!appId) { console.error('EBAY_APP_ID not configured') return NextResponse.json( { error: 'eBay API not configured. Please set EBAY_APP_ID environment variable.' }, { status: 503 } ) } const cachedResults = getCachedSearchResults(cacheKey) if (cachedResults) { return NextResponse.json(cachedResults) } const inFlight = inFlightSearchResponses.get(cacheKey) if (inFlight) { try { const results = await inFlight return NextResponse.json(results) } catch (error) { return NextResponse.json( { error: error instanceof Error ? error.message : 'Failed to fetch products from eBay' }, { status: 500 } ) } } // Build eBay Finding API URL const baseUrl = 'https://svcs.ebay.com/services/search/FindingService/v1' const url = new URL(baseUrl) url.searchParams.set('OPERATION-NAME', 'findItemsAdvanced') url.searchParams.set('SERVICE-VERSION', '1.0.0') url.searchParams.set('SECURITY-APPNAME', appId) url.searchParams.set('RESPONSE-DATA-FORMAT', 'JSON') url.searchParams.set('REST-PAYLOAD', 'true') url.searchParams.set('keywords', keywords) url.searchParams.set('sortOrder', sortOrder) url.searchParams.set('paginationInput.entriesPerPage', maxResults.toString()) if (categoryId) { url.searchParams.set('categoryId', categoryId) } try { const request = (async () => { const response = await fetch(url.toString(), { method: 'GET', headers: { 'Accept': 'application/json', }, }) if (!response.ok) { const errorMessage = await readEbayErrorMessage(response) throw new Error(errorMessage) } const data = await response.json() // Parse eBay API response const findItemsAdvancedResponse = data.findItemsAdvancedResponse?.[0] if (!findItemsAdvancedResponse) { return [] } const searchResult = findItemsAdvancedResponse.searchResult?.[0] if (!searchResult || !searchResult.item || searchResult.item.length === 0) { return [] } const items = Array.isArray(searchResult.item) ? searchResult.item : [searchResult.item] return items.map((item: any) => normalizeItem(item)) })() inFlightSearchResponses.set(cacheKey, request) const results = await request setCachedSearchResults(cacheKey, results) return NextResponse.json(results) } catch (error) { console.error('Error fetching from eBay API:', error) return NextResponse.json( { error: error instanceof Error ? error.message : 'Failed to fetch products from eBay' }, { status: 500 } ) } finally { inFlightSearchResponses.delete(cacheKey) } }