Skip to content

Commit c6e1504

Browse files
committed
feat: 인챈트 정보를 검색 필터로 추가
1 parent da7e396 commit c6e1504

17 files changed

Lines changed: 557 additions & 10 deletions

File tree

src/app/(main)/admin/page.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
RefreshCcw,
1111
ServerCog,
1212
ShieldCheck,
13+
Sparkles,
1314
Waves,
1415
} from "lucide-react";
1516
import { Button } from "@/components/ui/button";
@@ -60,6 +61,14 @@ const ADMIN_TASKS = [
6061
category: "Sync",
6162
icon: Activity,
6263
},
64+
{
65+
key: "enchant-info-sync",
66+
title: "인챈트 정보 동기화",
67+
description: "auction_history_item_option을 기반으로 enchant_info를 업서트합니다.",
68+
backendEndpoint: "POST /oab/api/enchant-infos/sync",
69+
category: "Sync",
70+
icon: Sparkles,
71+
},
6372
] as const;
6473

6574
type AdminTaskKey = (typeof ADMIN_TASKS)[number]["key"];

src/app/(main)/auction-history/page.tsx

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ export default function Page() {
429429
useState(false);
430430

431431
const [mobileFilterType, setMobileFilterType] = useState<
432-
"category" | "price" | "date" | "options" | null
432+
"category" | "price" | "date" | "options" | "enchant" | null
433433
>(null);
434434
const [mobilePriceMin, setMobilePriceMin] = useState("");
435435
const [mobilePriceMax, setMobilePriceMax] = useState("");
@@ -439,6 +439,8 @@ export default function Page() {
439439
const [mobileActiveFilters, setMobileActiveFilters] = useState<ActiveFilter[]>(
440440
[],
441441
);
442+
const [mobileEnchantPrefix, setMobileEnchantPrefix] = useState<string | null>(null);
443+
const [mobileEnchantSuffix, setMobileEnchantSuffix] = useState<string | null>(null);
442444

443445
const { data: categories = [], isLoading: isCategoriesLoading } =
444446
useItemCategories();
@@ -549,6 +551,8 @@ export default function Page() {
549551
setMobilePriceMax(parsed.priceSearchRequest?.priceTo?.toString() ?? "");
550552
setMobileDateFrom(parsed.dateAuctionBuyRequest?.dateAuctionBuyFrom ?? "");
551553
setMobileDateTo(parsed.dateAuctionBuyRequest?.dateAuctionBuyTo ?? "");
554+
setMobileEnchantPrefix(parsed.enchantSearchRequest?.enchantPrefix ?? null);
555+
setMobileEnchantSuffix(parsed.enchantSearchRequest?.enchantSuffix ?? null);
552556
}, [urlSearchParams]);
553557

554558
const applyExactItemNameChange = useCallback((value: boolean) => {
@@ -670,6 +674,12 @@ export default function Page() {
670674
delete next.itemOptionSearchRequest;
671675
}
672676

677+
if (filters.enchantSearchRequest) {
678+
next.enchantSearchRequest = filters.enchantSearchRequest;
679+
} else {
680+
delete next.enchantSearchRequest;
681+
}
682+
673683
return normalizeCategorySelection(next);
674684
});
675685
// isExactItemName은 SearchFilterCard에서 직접 onExactItemNameChange로 관리
@@ -678,6 +688,8 @@ export default function Page() {
678688
setMobilePriceMax(filters.priceSearchRequest?.priceTo?.toString() ?? "");
679689
setMobileDateFrom(filters.dateAuctionBuyRequest?.dateAuctionBuyFrom ?? "");
680690
setMobileDateTo(filters.dateAuctionBuyRequest?.dateAuctionBuyTo ?? "");
691+
setMobileEnchantPrefix(filters.enchantSearchRequest?.enchantPrefix ?? null);
692+
setMobileEnchantSuffix(filters.enchantSearchRequest?.enchantSuffix ?? null);
681693
};
682694

683695
const handleMobileFilterApply = (data: {
@@ -687,6 +699,8 @@ export default function Page() {
687699
dateFrom?: string;
688700
dateTo?: string;
689701
activeFilters?: ActiveFilter[];
702+
enchantPrefix?: string | null;
703+
enchantSuffix?: string | null;
690704
}) => {
691705
if (data.selectedCategory !== undefined) {
692706
setSelectedCategory(data.selectedCategory);
@@ -705,6 +719,8 @@ export default function Page() {
705719
if (data.activeFilters !== undefined) {
706720
setMobileActiveFilters(data.activeFilters);
707721
}
722+
if (data.enchantPrefix !== undefined) setMobileEnchantPrefix(data.enchantPrefix);
723+
if (data.enchantSuffix !== undefined) setMobileEnchantSuffix(data.enchantSuffix);
708724

709725
setSearchParams((prev) => {
710726
const next: AuctionHistorySearchParams = {
@@ -800,6 +816,16 @@ export default function Page() {
800816
delete next.itemOptionSearchRequest;
801817
}
802818

819+
const enchantPrefixVal = data.enchantPrefix !== undefined ? data.enchantPrefix : mobileEnchantPrefix;
820+
const enchantSuffixVal = data.enchantSuffix !== undefined ? data.enchantSuffix : mobileEnchantSuffix;
821+
if (enchantPrefixVal || enchantSuffixVal) {
822+
next.enchantSearchRequest = {};
823+
if (enchantPrefixVal) next.enchantSearchRequest.enchantPrefix = enchantPrefixVal;
824+
if (enchantSuffixVal) next.enchantSearchRequest.enchantSuffix = enchantSuffixVal;
825+
} else {
826+
delete next.enchantSearchRequest;
827+
}
828+
803829
return normalizeCategorySelection(next);
804830
});
805831
};
@@ -859,12 +885,14 @@ export default function Page() {
859885
hasPrice: !!(mobilePriceMin || mobilePriceMax),
860886
hasDate: !!(mobileDateFrom || mobileDateTo),
861887
hasOptions: mobileActiveFilters.length > 0,
888+
hasEnchant: !!(mobileEnchantPrefix || mobileEnchantSuffix),
862889
}}
863890
onExactItemNameToggle={() => handleExactItemNameChange(!searchParams.isExactItemName)}
864891
onCategoryClick={() => setMobileFilterType("category")}
865892
onPriceClick={() => setMobileFilterType("price")}
866893
onDateClick={() => setMobileFilterType("date")}
867894
onOptionsClick={() => setMobileFilterType("options")}
895+
onEnchantClick={() => setMobileFilterType("enchant")}
868896
/>
869897
</div>
870898
)}
@@ -1009,6 +1037,8 @@ export default function Page() {
10091037
dateFrom: mobileDateFrom,
10101038
dateTo: mobileDateTo,
10111039
activeFilters: mobileActiveFilters,
1040+
enchantPrefix: mobileEnchantPrefix,
1041+
enchantSuffix: mobileEnchantSuffix,
10121042
}}
10131043
categories={categories}
10141044
onApply={handleMobileFilterApply}

src/app/(main)/auction-realtime/page.tsx

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,13 +401,15 @@ export default function Page() {
401401
useState(false);
402402

403403
const [mobileFilterType, setMobileFilterType] = useState<
404-
"category" | "price" | "options" | null
404+
"category" | "price" | "options" | "enchant" | null
405405
>(null);
406406
const [mobilePriceMin, setMobilePriceMin] = useState("");
407407
const [mobilePriceMax, setMobilePriceMax] = useState("");
408408
const [mobileActiveFilters, setMobileActiveFilters] = useState<ActiveFilter[]>(
409409
[],
410410
);
411+
const [mobileEnchantPrefix, setMobileEnchantPrefix] = useState<string | null>(null);
412+
const [mobileEnchantSuffix, setMobileEnchantSuffix] = useState<string | null>(null);
411413

412414
const { data: categories = [], isLoading: isCategoriesLoading } =
413415
useItemCategories();
@@ -514,6 +516,8 @@ export default function Page() {
514516
setSelectedCategory(categoryIdFromSearchParams(parsed));
515517
setMobilePriceMin(parsed.priceSearchRequest?.priceFrom?.toString() ?? "");
516518
setMobilePriceMax(parsed.priceSearchRequest?.priceTo?.toString() ?? "");
519+
setMobileEnchantPrefix(parsed.enchantSearchRequest?.enchantPrefix ?? null);
520+
setMobileEnchantSuffix(parsed.enchantSearchRequest?.enchantSuffix ?? null);
517521
}, [urlSearchParams]);
518522

519523
const applyExactItemNameChange = useCallback((value: boolean) => {
@@ -628,18 +632,28 @@ export default function Page() {
628632
delete next.itemOptionSearchRequest;
629633
}
630634

635+
if (filters.enchantSearchRequest) {
636+
next.enchantSearchRequest = filters.enchantSearchRequest;
637+
} else {
638+
delete next.enchantSearchRequest;
639+
}
640+
631641
return normalizeCategorySelection(next);
632642
});
633643

634644
setMobilePriceMin(filters.priceSearchRequest?.priceFrom?.toString() ?? "");
635645
setMobilePriceMax(filters.priceSearchRequest?.priceTo?.toString() ?? "");
646+
setMobileEnchantPrefix(filters.enchantSearchRequest?.enchantPrefix ?? null);
647+
setMobileEnchantSuffix(filters.enchantSearchRequest?.enchantSuffix ?? null);
636648
};
637649

638650
const handleMobileFilterApply = (data: {
639651
selectedCategory?: string;
640652
priceMin?: string;
641653
priceMax?: string;
642654
activeFilters?: ActiveFilter[];
655+
enchantPrefix?: string | null;
656+
enchantSuffix?: string | null;
643657
}) => {
644658
if (data.selectedCategory !== undefined) {
645659
setSelectedCategory(data.selectedCategory);
@@ -656,6 +670,8 @@ export default function Page() {
656670
if (data.activeFilters !== undefined) {
657671
setMobileActiveFilters(data.activeFilters);
658672
}
673+
if (data.enchantPrefix !== undefined) setMobileEnchantPrefix(data.enchantPrefix);
674+
if (data.enchantSuffix !== undefined) setMobileEnchantSuffix(data.enchantSuffix);
659675

660676
setSearchParams((prev) => {
661677
const next: AuctionRealtimeSearchParams = {
@@ -741,6 +757,16 @@ export default function Page() {
741757
delete next.itemOptionSearchRequest;
742758
}
743759

760+
const enchantPrefixVal = data.enchantPrefix !== undefined ? data.enchantPrefix : mobileEnchantPrefix;
761+
const enchantSuffixVal = data.enchantSuffix !== undefined ? data.enchantSuffix : mobileEnchantSuffix;
762+
if (enchantPrefixVal || enchantSuffixVal) {
763+
next.enchantSearchRequest = {};
764+
if (enchantPrefixVal) next.enchantSearchRequest.enchantPrefix = enchantPrefixVal;
765+
if (enchantSuffixVal) next.enchantSearchRequest.enchantSuffix = enchantSuffixVal;
766+
} else {
767+
delete next.enchantSearchRequest;
768+
}
769+
744770
return normalizeCategorySelection(next);
745771
});
746772
};
@@ -799,11 +825,13 @@ export default function Page() {
799825
hasCategory: selectedCategory !== "all",
800826
hasPrice: !!(mobilePriceMin || mobilePriceMax),
801827
hasOptions: mobileActiveFilters.length > 0,
828+
hasEnchant: !!(mobileEnchantPrefix || mobileEnchantSuffix),
802829
}}
803830
onExactItemNameToggle={() => handleExactItemNameChange(!searchParams.isExactItemName)}
804831
onCategoryClick={() => setMobileFilterType("category")}
805832
onPriceClick={() => setMobileFilterType("price")}
806833
onOptionsClick={() => setMobileFilterType("options")}
834+
onEnchantClick={() => setMobileFilterType("enchant")}
807835
/>
808836
</div>
809837
)}
@@ -946,6 +974,8 @@ export default function Page() {
946974
priceMin: mobilePriceMin,
947975
priceMax: mobilePriceMax,
948976
activeFilters: mobileActiveFilters,
977+
enchantPrefix: mobileEnchantPrefix,
978+
enchantSuffix: mobileEnchantSuffix,
949979
}}
950980
categories={categories}
951981
onApply={handleMobileFilterApply}

src/app/api/admin/tasks/[task]/route.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { NextRequest, NextResponse } from "next/server";
33
import {
44
AUCTION_HISTORY_BATCH_ENDPOINT,
5+
ENCHANT_INFO_SYNC_ENDPOINT,
56
HORN_BUGLE_BATCH_ENDPOINT,
67
ITEM_INFO_SYNC_ENDPOINT,
78
METALWARE_ATTRIBUTE_SYNC_ENDPOINT,
@@ -40,6 +41,11 @@ const TASK_CONFIG = {
4041
label: "금속 변환 속성 정보 동기화",
4142
successMessage: "금속 변환 속성 정보 동기화가 실행되었습니다.",
4243
},
44+
"enchant-info-sync": {
45+
endpoint: ENCHANT_INFO_SYNC_ENDPOINT,
46+
label: "인챈트 정보 동기화",
47+
successMessage: "인챈트 정보 동기화가 실행되었습니다.",
48+
},
4349
} as const;
4450

4551
type TaskKey = keyof typeof TASK_CONFIG;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { NextRequest, NextResponse } from "next/server";
2+
3+
export const dynamic = "force-dynamic";
4+
export const revalidate = 1800; // 30분 캐싱
5+
6+
export async function GET(request: NextRequest) {
7+
try {
8+
const gatewayUrl = process.env.GATEWAY_URL;
9+
if (!gatewayUrl) {
10+
throw new Error("GATEWAY_URL 환경 변수가 설정되지 않았습니다.");
11+
}
12+
13+
const { searchParams } = new URL(request.url);
14+
const affixPosition = searchParams.get("affix_position");
15+
16+
const upstreamUrl = new URL(`${gatewayUrl}/oab/api/enchant-infos/fullnames`);
17+
if (affixPosition) {
18+
upstreamUrl.searchParams.set("affix_position", affixPosition);
19+
}
20+
21+
const response = await fetch(upstreamUrl.toString(), {
22+
next: { revalidate: 1800 },
23+
});
24+
25+
if (!response.ok) {
26+
throw new Error(`API 호출 실패: ${response.status}`);
27+
}
28+
29+
const data = await response.json();
30+
31+
return NextResponse.json(data, {
32+
status: 200,
33+
headers: {
34+
"Cache-Control": "public, s-maxage=1800, stale-while-revalidate=3600",
35+
},
36+
});
37+
} catch (error) {
38+
const err = error instanceof Error ? error : new Error("Unknown error");
39+
console.error("Enchant fullnames API error:", err);
40+
return NextResponse.json(
41+
{
42+
success: false,
43+
code: "INTERNAL_SERVER_ERROR",
44+
message: err.message,
45+
data: null,
46+
timestamp: new Date().toISOString(),
47+
},
48+
{ status: 500 },
49+
);
50+
}
51+
}

0 commit comments

Comments
 (0)