diff --git a/src/app/api/auction/route.ts b/src/app/api/auction/route.ts index 1cf66fb..5374fa2 100644 --- a/src/app/api/auction/route.ts +++ b/src/app/api/auction/route.ts @@ -15,29 +15,57 @@ export async function GET(request: Request) { const auctionItemCategory = searchParams.get("auction_item_category"); const itemName = searchParams.get("item_name"); - let url = `${NXOPEN_API_URL}/mabinogi/v1/auction/list?`; - if (auctionItemCategory) { - url += `auction_item_category=${auctionItemCategory}&`; - } - if (itemName) { - url += `item_name=${encodeURIComponent(itemName)}`; - } + let allItems: any[] = []; + let nextCursor: string | null = ""; + + try { + do { + let url = `${NXOPEN_API_URL}/mabinogi/v1/auction/list?`; + if (auctionItemCategory) { + url += `auction_item_category=${auctionItemCategory}&`; + } + if (itemName) { + url += `item_name=${encodeURIComponent(itemName)}&`; + } + if (nextCursor) { + url += `cursor=${nextCursor}`; + } + + const response = await fetch(url, { + headers: { + "Content-Type": "application/json", + "x-nxopen-api-key": NXOPEN_API_KEY || "", + }, + }); - const response = await fetch(url, { - headers: { - "Content-Type": "application/json", - "x-nxopen-api-key": NXOPEN_API_KEY || "", - }, - }); + if (!response.ok) { + console.error(await response.json()); + return NextResponse.json( + { error: "Failed to fetch data" }, + { status: 500 } + ); + } - if (!response.ok) { - console.error(await response.json()); + const data = await response.json(); + + // 결과 데이터 누적 + if (data.auction_item.length > 0) { + allItems = [...allItems, ...data.auction_item]; + } + + // 다음 페이지를 위한 cursor 업데이트 + nextCursor = data.next_cursor; + } while (nextCursor !== null); + + // 전체 데이터 반환 + return NextResponse.json({ + items: allItems, + }); + } catch (error) { + console.error("Error fetching auction data:", error); return NextResponse.json( { error: "Failed to fetch data" }, { status: 500 } ); } - - const data = await response.json(); - return NextResponse.json(data); } diff --git a/src/app/auction/page.tsx b/src/app/auction/page.tsx index 8856c30..41f610b 100644 --- a/src/app/auction/page.tsx +++ b/src/app/auction/page.tsx @@ -67,11 +67,11 @@ export default function AuctionPage() { } const data = await response.json(); - data.auction_item.sort( + data.items.sort( (a: any, b: any) => a.auction_price_per_unit - b.auction_price_per_unit ); - setFilteredItems(data.auction_item); + setFilteredItems(data.items); setErrorMessage(null); } catch (error) { console.error("API 호출 중 오류가 발생했습니다:", error); diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 060de18..305358d 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -2,13 +2,34 @@ import "@/styles/globals.css"; import { Analytics } from "@vercel/analytics/next"; import { SpeedInsights } from "@vercel/speed-insights/next"; +import { Metadata } from "next"; import Footer from "@/components/footer"; import { Providers } from "@/components/providers"; import Topbar from "@/components/topbar"; -import { metadata as defaultMetadata } from "@/constant/metadata"; -export const metadata = defaultMetadata; +export const metadata: Metadata = { + title: "마비노기 경매장 - 실시간 아이템 시세 검색 | Erinn.me", + description: + "마비노기 경매장의 실시간 아이템 시세를 검색하고 가격을 비교해보세요. 카테고리별 검색과 아이템 이름 검색을 지원합니다. 실시간 가격 정보와 아이템 옵션을 확인할 수 있습니다.", + keywords: + "마비노기, 경매장, 아이템 시세, 마비노기 경매장, 마비노기 시세, 마비노기 아이템, Erinn.me", + openGraph: { + title: "마비노기 경매장 - 실시간 아이템 시세 검색 | Erinn.me", + description: + "마비노기 경매장의 실시간 아이템 시세를 검색하고 가격을 비교해보세요. 카테고리별 검색과 아이템 이름 검색을 지원합니다.", + type: "website", + locale: "ko_KR", + siteName: "Erinn.me", + }, + robots: { + index: true, + follow: true, + }, + alternates: { + canonical: "https://erinn.me", + }, +}; export default function RootLayout({ children, @@ -16,7 +37,7 @@ export default function RootLayout({ children: React.ReactNode; }) { return ( - +
diff --git a/src/lib/api/auction.ts b/src/lib/api/auction.ts index b85a30f..4e2feb2 100644 --- a/src/lib/api/auction.ts +++ b/src/lib/api/auction.ts @@ -25,99 +25,30 @@ export interface AuctionResponse { next_cursor: string; } -// API 호출 간 딜레이를 위한 sleep 함수 -const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); - -async function fetchAllAuctionPages(params: { - item_name?: string; - auction_item_category?: string; -}): Promise { - let allItems: AuctionItem[] = []; - let nextCursor = ""; - let prevCursor = ""; // 이전 cursor 저장 - let retryCount = 0; - const MAX_RETRIES = 2; - - try { - do { - try { - // API 호출 간 1초 딜레이 추가 - await sleep(1000); - - const queryParams = new URLSearchParams(); - if (params.item_name) { - queryParams.append("item_name", params.item_name); - } - if (params.auction_item_category) { - queryParams.append( - "auction_item_category", - params.auction_item_category - ); - } - if (nextCursor) { - queryParams.append("cursor", nextCursor); - } - - const response = await fetch( - `/api/auction?${queryParams.toString()}` - ); - if (!response.ok) { - throw new Error("경매장 API 호출 중 오류가 발생했습니다."); - } - - const data: AuctionResponse = await response.json(); - - // 새로운 아이템이 없거나 이전과 같은 cursor가 반환되면 중단 - if ( - data.auction_item.length === 0 || - data.next_cursor === prevCursor - ) { - break; - } - - allItems = [...allItems, ...data.auction_item]; - prevCursor = nextCursor; // 현재 cursor를 이전 cursor로 저장 - nextCursor = data.next_cursor; - retryCount = 0; - } catch (error) { - retryCount++; - if (retryCount >= MAX_RETRIES || allItems.length === 0) { - throw error; - } - console.warn( - `데이터 조회 중 오류 발생 (${retryCount}번째 시도): `, - error - ); - break; - } - } while (nextCursor); - - return allItems; - } catch (error) { - if (allItems.length > 0) { - console.warn("일부 데이터만 조회되었습니다:", error); - return allItems; - } - throw error; - } -} - export async function getItemPrice( itemName: string ): Promise { try { - const allItems = await fetchAllAuctionPages({ item_name: itemName }); + let url = "/api/auction?"; + if (itemName !== "") { + url += `&item_name=${encodeURIComponent(itemName).replace(/\+/g, "%2B")}`; + } + const response = await fetch(url); + const data = await response.json(); - if (allItems.length === 0) { + if (data.items === undefined) { return { unitPrice: 0, averagePrice: 0, }; } - const prices = allItems.map(item => item.auction_price_per_unit); + const prices = data.items.map( + (item: any) => item.auction_price_per_unit + ); const unitPrice = Math.min(...prices); - const averagePrice = prices.reduce((a, b) => a + b, 0) / prices.length; + const averagePrice = + prices.reduce((a: any, b: any) => a + b, 0) / prices.length; return { unitPrice: unitPrice || 0, @@ -131,10 +62,3 @@ export async function getItemPrice( }; } } - -export async function searchAuctionItems(params: { - item_name?: string; - auction_item_category?: string; -}): Promise { - return fetchAllAuctionPages(params); -}