daiso 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +249 -533
- package/dist/api/actionsProxy.js +82 -0
- package/dist/api/cgvHandlers.js +103 -16
- package/dist/api/configStatus.js +19 -0
- package/dist/api/daisoHandlers.js +127 -0
- package/dist/api/emart24Handlers.js +176 -0
- package/dist/api/gs25Handlers.js +267 -0
- package/dist/api/handlers.js +209 -104
- package/dist/api/healthChecks.js +504 -0
- package/dist/api/lottecinemaHandlers.js +155 -0
- package/dist/api/lottemartHandlers.js +144 -0
- package/dist/api/megaboxHandlers.js +81 -50
- package/dist/api/response.js +2 -0
- package/dist/api/routes/cuRoutes.js +17 -0
- package/dist/api/routes/daisoRoutes.js +6 -1
- package/dist/api/routes/emart24Routes.js +22 -0
- package/dist/api/routes/gs25Routes.js +22 -0
- package/dist/api/routes/healthRoutes.js +61 -0
- package/dist/api/routes/lottecinemaRoutes.js +22 -0
- package/dist/api/routes/lottemartRoutes.js +18 -0
- package/dist/api/routes/oliveyoungRoutes.js +6 -1
- package/dist/api/routes/sevenelevenRoutes.js +32 -0
- package/dist/api/sevenelevenHandlers.js +200 -0
- package/dist/bin.js +0 -0
- package/dist/cli/args.js +75 -0
- package/dist/cli/commandOptions.js +38 -0
- package/dist/cli/commands/convenience.js +293 -0
- package/dist/cli/commands/daiso.js +112 -0
- package/dist/cli/constants.js +5 -0
- package/dist/cli/deps.js +48 -0
- package/dist/cli/http.js +156 -0
- package/dist/cli/interactiveFetch.js +31 -0
- package/dist/cli/interactiveItemSearch.js +261 -0
- package/dist/cli/interactivePrompt.js +50 -0
- package/dist/cli/interactiveTypes.js +4 -0
- package/dist/cli/types.js +4 -0
- package/dist/cli.js +87 -196
- package/dist/cliHelp.js +212 -1
- package/dist/cliInteractive.js +243 -0
- package/dist/cliPicker.js +57 -0
- package/dist/cliRenderer.js +90 -3
- package/dist/core/errors.js +66 -0
- package/dist/core/registry.js +227 -3
- package/dist/index.js +149 -9
- package/dist/pages/openapi.js +2 -1
- package/dist/pages/openapiFullSpec.js +58 -0
- package/dist/pages/openapiResponse.js +28 -0
- package/dist/pages/openapiSpec.js +2 -41
- package/dist/pages/openapiSpecActions.js +109 -0
- package/dist/pages/openapiSpecComponents.js +30 -391
- package/dist/pages/openapiSpecComponentsCgv.js +24 -0
- package/dist/pages/openapiSpecComponentsCu.js +112 -0
- package/dist/pages/openapiSpecComponentsDaisoOliveyoung.js +342 -0
- package/dist/pages/openapiSpecComponentsEmart24.js +141 -0
- package/dist/pages/openapiSpecComponentsGs25.js +109 -0
- package/dist/pages/openapiSpecComponentsLotteMart.js +85 -0
- package/dist/pages/openapiSpecComponentsLottecinema.js +163 -0
- package/dist/pages/openapiSpecComponentsMegabox.js +144 -0
- package/dist/pages/openapiSpecComponentsSeveneleven.js +82 -0
- package/dist/pages/openapiSpecPathsCgv.js +66 -3
- package/dist/pages/openapiSpecPathsCu.js +145 -0
- package/dist/pages/openapiSpecPathsDaiso.js +302 -0
- package/dist/pages/openapiSpecPathsDaisoOliveyoung.js +4 -376
- package/dist/pages/openapiSpecPathsEmart24.js +223 -0
- package/dist/pages/openapiSpecPathsGs25.js +166 -0
- package/dist/pages/openapiSpecPathsLotteMart.js +149 -0
- package/dist/pages/openapiSpecPathsLottecinema.js +212 -0
- package/dist/pages/openapiSpecPathsMegabox.js +52 -3
- package/dist/pages/openapiSpecPathsOliveyoung.js +266 -0
- package/dist/pages/openapiSpecPathsSeveneleven.js +234 -0
- package/dist/pages/prompt.js +8 -402
- package/dist/pages/promptCoreText.js +278 -0
- package/dist/pages/promptReferenceText.js +137 -0
- package/dist/pages/promptRetailCinemaText.js +274 -0
- package/dist/services/cgv/client.js +6 -1
- package/dist/services/cgv/index.js +3 -3
- package/dist/services/cgv/location.js +235 -0
- package/dist/services/cgv/tools/findTheaters.js +30 -5
- package/dist/services/cgv/tools/getTimetable.js +42 -14
- package/dist/services/cgv/tools/searchMovies.js +40 -12
- package/dist/services/cgv/transport.js +0 -3
- package/dist/services/cu/api.js +11 -0
- package/dist/services/cu/client.js +297 -0
- package/dist/services/cu/index.js +21 -0
- package/dist/services/cu/tools/checkInventory.js +123 -0
- package/dist/services/cu/tools/findNearbyStores.js +49 -0
- package/dist/services/cu/types.js +4 -0
- package/dist/services/daiso/api.js +22 -2
- package/dist/services/daiso/client.js +67 -0
- package/dist/services/daiso/index.js +4 -0
- package/dist/services/daiso/product.js +17 -0
- package/dist/services/daiso/tools/checkInventory.js +80 -43
- package/dist/services/daiso/tools/findInventoryByName.js +222 -0
- package/dist/services/daiso/tools/findStores.js +17 -7
- package/dist/services/daiso/tools/getDisplayLocation.js +75 -0
- package/dist/services/emart24/api.js +11 -0
- package/dist/services/emart24/client.js +182 -0
- package/dist/services/emart24/index.js +22 -0
- package/dist/services/emart24/inventoryLookup.js +134 -0
- package/dist/services/emart24/tools/checkInventory.js +54 -0
- package/dist/services/emart24/tools/findNearbyStores.js +72 -0
- package/dist/services/emart24/tools/searchProducts.js +73 -0
- package/dist/services/emart24/types.js +4 -0
- package/dist/services/gs25/api.js +9 -0
- package/dist/services/gs25/client.js +208 -0
- package/dist/services/gs25/index.js +22 -0
- package/dist/services/gs25/storeUtils.js +168 -0
- package/dist/services/gs25/tools/checkInventory.js +163 -0
- package/dist/services/gs25/tools/findNearbyStores.js +103 -0
- package/dist/services/gs25/tools/searchProducts.js +57 -0
- package/dist/services/gs25/types.js +4 -0
- package/dist/services/lottecinema/api.js +12 -0
- package/dist/services/lottecinema/client.js +161 -0
- package/dist/services/lottecinema/index.js +30 -0
- package/dist/services/lottecinema/location.js +191 -0
- package/dist/services/lottecinema/tools/findNearbyTheaters.js +47 -0
- package/dist/services/lottecinema/tools/getRemainingSeats.js +85 -0
- package/dist/services/lottecinema/tools/listNowShowing.js +79 -0
- package/dist/services/lottecinema/types.js +4 -0
- package/dist/services/lottemart/api.js +31 -0
- package/dist/services/lottemart/client.js +254 -0
- package/dist/services/lottemart/clientTypes.js +4 -0
- package/dist/services/lottemart/config.js +4 -0
- package/dist/services/lottemart/debug.js +92 -0
- package/dist/services/lottemart/geocode.js +49 -0
- package/dist/services/lottemart/index.js +30 -0
- package/dist/services/lottemart/keyword.js +29 -0
- package/dist/services/lottemart/parser.js +185 -0
- package/dist/services/lottemart/session.js +331 -0
- package/dist/services/lottemart/storeSearch.js +52 -0
- package/dist/services/lottemart/tools/findNearbyStores.js +67 -0
- package/dist/services/lottemart/tools/searchProducts.js +69 -0
- package/dist/services/lottemart/types.js +4 -0
- package/dist/services/lottemart/zetta.js +105 -0
- package/dist/services/megabox/location.js +219 -0
- package/dist/services/megabox/tools/findNearbyTheaters.js +13 -44
- package/dist/services/megabox/tools/getRemainingSeats.js +31 -3
- package/dist/services/megabox/tools/listNowShowing.js +31 -3
- package/dist/services/oliveyoung/api.js +2 -0
- package/dist/services/oliveyoung/client.js +225 -13
- package/dist/services/oliveyoung/index.js +3 -1
- package/dist/services/oliveyoung/tools/checkInventory.js +21 -5
- package/dist/services/oliveyoung/tools/searchProducts.js +57 -0
- package/dist/services/seveneleven/api.js +15 -0
- package/dist/services/seveneleven/client.js +286 -0
- package/dist/services/seveneleven/index.js +30 -0
- package/dist/services/seveneleven/inventory.js +302 -0
- package/dist/services/seveneleven/productKeyword.js +141 -0
- package/dist/services/seveneleven/tools/checkInventory.js +82 -0
- package/dist/services/seveneleven/tools/getCatalogSnapshot.js +55 -0
- package/dist/services/seveneleven/tools/getSearchPopwords.js +41 -0
- package/dist/services/seveneleven/tools/searchProducts.js +67 -0
- package/dist/services/seveneleven/tools/searchStores.js +45 -0
- package/dist/services/seveneleven/types.js +4 -0
- package/dist/utils/cliInteractiveHelpers.js +63 -0
- package/dist/utils/daisoKeyword.js +30 -0
- package/dist/utils/http.js +98 -12
- package/dist/utils/zyte.js +47 -26
- package/openapi.json +1979 -736
- package/openapi.yaml +1428 -541
- package/package.json +49 -18
- package/dist/tools/checkInventory.js +0 -98
- package/dist/tools/findStores.js +0 -125
- package/dist/tools/getPriceInfo.js +0 -77
- package/dist/tools/searchProducts.js +0 -61
- package/dist/types/index.js +0 -4
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GS25 GET API 핸들러
|
|
3
|
+
*/
|
|
4
|
+
/* c8 ignore start */
|
|
5
|
+
import { errorResponse, successResponse } from './response.js';
|
|
6
|
+
import { attachDistanceToGs25Stores, fetchGs25SearchProducts, fetchGs25Stores, filterGs25StoresByKeyword, geocodeGs25Address, selectGs25StoresForKeyword, sortGs25Stores, } from '../services/gs25/client.js';
|
|
7
|
+
const GS25_FALLBACK_STORE_LOOKUP_ITEM_CODE = '8801117752804';
|
|
8
|
+
/**
|
|
9
|
+
* GS25 매장 검색 API 핸들러
|
|
10
|
+
* GET /api/gs25/stores?keyword={키워드}&lat={위도}&lng={경도}
|
|
11
|
+
*/
|
|
12
|
+
export async function handleGs25FindStores(c) {
|
|
13
|
+
const keyword = c.req.query('keyword') || '';
|
|
14
|
+
const serviceCode = c.req.query('serviceCode') || '01';
|
|
15
|
+
const limit = parseInt(c.req.query('limit') || '20', 10);
|
|
16
|
+
const rawLat = c.req.query('lat');
|
|
17
|
+
const rawLng = c.req.query('lng');
|
|
18
|
+
const parsedLat = rawLat ? parseFloat(rawLat) : undefined;
|
|
19
|
+
const parsedLng = rawLng ? parseFloat(rawLng) : undefined;
|
|
20
|
+
let latitude = typeof parsedLat === 'number' && Number.isFinite(parsedLat) ? parsedLat : undefined;
|
|
21
|
+
let longitude = typeof parsedLng === 'number' && Number.isFinite(parsedLng) ? parsedLng : undefined;
|
|
22
|
+
let geocodeUsed = false;
|
|
23
|
+
try {
|
|
24
|
+
if ((typeof latitude !== 'number' || typeof longitude !== 'number') && keyword.trim().length > 0) {
|
|
25
|
+
const geocoded = await geocodeGs25Address(keyword, {
|
|
26
|
+
timeout: 15000,
|
|
27
|
+
googleMapsApiKey: c.env?.GOOGLE_MAPS_API_KEY,
|
|
28
|
+
});
|
|
29
|
+
if (geocoded) {
|
|
30
|
+
latitude = geocoded.latitude;
|
|
31
|
+
longitude = geocoded.longitude;
|
|
32
|
+
geocodeUsed = true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
let storeResult = await fetchGs25Stores({
|
|
36
|
+
serviceCode,
|
|
37
|
+
latitude,
|
|
38
|
+
longitude,
|
|
39
|
+
}, {
|
|
40
|
+
timeout: 20000,
|
|
41
|
+
});
|
|
42
|
+
let fallbackUsed = false;
|
|
43
|
+
if (storeResult.stores.length === 0 && typeof latitude === 'number' && typeof longitude === 'number') {
|
|
44
|
+
try {
|
|
45
|
+
const fallbackResult = await fetchGs25Stores({
|
|
46
|
+
serviceCode,
|
|
47
|
+
itemCode: GS25_FALLBACK_STORE_LOOKUP_ITEM_CODE,
|
|
48
|
+
realTimeStockYn: 'Y',
|
|
49
|
+
latitude,
|
|
50
|
+
longitude,
|
|
51
|
+
useCache: false,
|
|
52
|
+
}, {
|
|
53
|
+
timeout: 20000,
|
|
54
|
+
});
|
|
55
|
+
if (fallbackResult.stores.length > 0) {
|
|
56
|
+
storeResult = fallbackResult;
|
|
57
|
+
fallbackUsed = true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
fallbackUsed = false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const selected = selectGs25StoresForKeyword(storeResult.stores, keyword, {
|
|
65
|
+
relaxWhenEmpty: typeof latitude === 'number' && typeof longitude === 'number',
|
|
66
|
+
});
|
|
67
|
+
const withDistance = attachDistanceToGs25Stores(selected.stores, latitude, longitude);
|
|
68
|
+
const stores = sortGs25Stores(withDistance).slice(0, limit);
|
|
69
|
+
return successResponse(c, {
|
|
70
|
+
serviceCode,
|
|
71
|
+
keyword,
|
|
72
|
+
geocodeUsed,
|
|
73
|
+
location: typeof latitude === 'number' && typeof longitude === 'number'
|
|
74
|
+
? { latitude, longitude }
|
|
75
|
+
: null,
|
|
76
|
+
cacheHit: storeResult.cacheHit,
|
|
77
|
+
filterRelaxed: selected.filterRelaxed,
|
|
78
|
+
fallbackUsed,
|
|
79
|
+
stores,
|
|
80
|
+
}, {
|
|
81
|
+
total: selected.stores.length,
|
|
82
|
+
pageSize: limit,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
const message = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.';
|
|
87
|
+
return errorResponse(c, 'GS25_STORE_SEARCH_FAILED', message, 500);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* GS25 상품 검색 API 핸들러
|
|
92
|
+
* GET /api/gs25/products?keyword={검색어}
|
|
93
|
+
*/
|
|
94
|
+
export async function handleGs25SearchProducts(c) {
|
|
95
|
+
const keyword = c.req.query('keyword') || '';
|
|
96
|
+
const limit = parseInt(c.req.query('limit') || '20', 10);
|
|
97
|
+
if (keyword.trim().length === 0) {
|
|
98
|
+
return errorResponse(c, 'MISSING_QUERY', '검색어(keyword)를 입력해주세요.');
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
const products = await fetchGs25SearchProducts(keyword, { timeout: 20000 });
|
|
102
|
+
const limitedProducts = products.slice(0, limit);
|
|
103
|
+
return successResponse(c, {
|
|
104
|
+
keyword,
|
|
105
|
+
count: limitedProducts.length,
|
|
106
|
+
products: limitedProducts.map((p) => ({
|
|
107
|
+
itemCode: p.itemCode,
|
|
108
|
+
itemName: p.itemName,
|
|
109
|
+
shortItemName: p.shortItemName,
|
|
110
|
+
imageUrl: p.imageUrl,
|
|
111
|
+
rating: p.rating,
|
|
112
|
+
stockCheckEnabled: p.stockCheckEnabled,
|
|
113
|
+
})),
|
|
114
|
+
}, {
|
|
115
|
+
total: limitedProducts.length,
|
|
116
|
+
pageSize: limit,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
const message = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.';
|
|
121
|
+
return errorResponse(c, 'GS25_PRODUCT_SEARCH_FAILED', message, 500);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* GS25 재고 조회 API 핸들러
|
|
126
|
+
* GET /api/gs25/inventory?keyword={검색어}&storeKeyword={매장키워드}&lat={위도}&lng={경도}
|
|
127
|
+
*/
|
|
128
|
+
export async function handleGs25CheckInventory(c) {
|
|
129
|
+
const keyword = c.req.query('keyword') || '';
|
|
130
|
+
const itemCode = c.req.query('itemCode') || '';
|
|
131
|
+
const storeKeyword = c.req.query('storeKeyword') || '';
|
|
132
|
+
const serviceCode = c.req.query('serviceCode') || '01';
|
|
133
|
+
const storeLimit = parseInt(c.req.query('storeLimit') || '20', 10);
|
|
134
|
+
const rawLat = c.req.query('lat');
|
|
135
|
+
const rawLng = c.req.query('lng');
|
|
136
|
+
const parsedLat = rawLat ? parseFloat(rawLat) : undefined;
|
|
137
|
+
const parsedLng = rawLng ? parseFloat(rawLng) : undefined;
|
|
138
|
+
let latitude = typeof parsedLat === 'number' && Number.isFinite(parsedLat) ? parsedLat : undefined;
|
|
139
|
+
let longitude = typeof parsedLng === 'number' && Number.isFinite(parsedLng) ? parsedLng : undefined;
|
|
140
|
+
let geocodeUsed = false;
|
|
141
|
+
if (keyword.trim().length === 0 && itemCode.trim().length === 0) {
|
|
142
|
+
return errorResponse(c, 'MISSING_QUERY', '검색어(keyword) 또는 상품 코드(itemCode)를 입력해주세요.');
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
if ((typeof latitude !== 'number' || typeof longitude !== 'number') && storeKeyword.trim().length > 0) {
|
|
146
|
+
const directGeocoded = await geocodeGs25Address(storeKeyword, {
|
|
147
|
+
timeout: 15000,
|
|
148
|
+
googleMapsApiKey: c.env?.GOOGLE_MAPS_API_KEY,
|
|
149
|
+
});
|
|
150
|
+
if (directGeocoded) {
|
|
151
|
+
latitude = directGeocoded.latitude;
|
|
152
|
+
longitude = directGeocoded.longitude;
|
|
153
|
+
geocodeUsed = true;
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
const baseStores = await fetchGs25Stores({
|
|
157
|
+
serviceCode,
|
|
158
|
+
}, {
|
|
159
|
+
timeout: 20000,
|
|
160
|
+
});
|
|
161
|
+
const firstAddress = filterGs25StoresByKeyword(baseStores.stores, storeKeyword).find((store) => store.address.trim().length > 0)
|
|
162
|
+
?.address || '';
|
|
163
|
+
if (firstAddress.length > 0) {
|
|
164
|
+
const geocoded = await geocodeGs25Address(firstAddress, {
|
|
165
|
+
timeout: 15000,
|
|
166
|
+
googleMapsApiKey: c.env?.GOOGLE_MAPS_API_KEY,
|
|
167
|
+
});
|
|
168
|
+
if (geocoded) {
|
|
169
|
+
latitude = geocoded.latitude;
|
|
170
|
+
longitude = geocoded.longitude;
|
|
171
|
+
geocodeUsed = true;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// 1단계: totalSearch API로 키워드 → itemCode 변환
|
|
177
|
+
let firstProduct;
|
|
178
|
+
let stockResult;
|
|
179
|
+
let itemCodeUsed = itemCode.trim().length > 0;
|
|
180
|
+
let resolvedItemCode = itemCode.trim().length > 0 ? itemCode.trim() : null;
|
|
181
|
+
if (resolvedItemCode) {
|
|
182
|
+
stockResult = await fetchGs25Stores({
|
|
183
|
+
serviceCode,
|
|
184
|
+
itemCode: resolvedItemCode,
|
|
185
|
+
realTimeStockYn: 'Y',
|
|
186
|
+
latitude,
|
|
187
|
+
longitude,
|
|
188
|
+
useCache: false,
|
|
189
|
+
}, {
|
|
190
|
+
timeout: 20000,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
const searchProducts = await fetchGs25SearchProducts(keyword, { timeout: 20000 });
|
|
195
|
+
firstProduct = searchProducts.find((p) => p.itemCode.length > 0);
|
|
196
|
+
if (firstProduct) {
|
|
197
|
+
resolvedItemCode = firstProduct.itemCode;
|
|
198
|
+
itemCodeUsed = true;
|
|
199
|
+
stockResult = await fetchGs25Stores({
|
|
200
|
+
serviceCode,
|
|
201
|
+
itemCode: resolvedItemCode,
|
|
202
|
+
realTimeStockYn: 'Y',
|
|
203
|
+
latitude,
|
|
204
|
+
longitude,
|
|
205
|
+
useCache: false,
|
|
206
|
+
}, {
|
|
207
|
+
timeout: 20000,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
stockResult = await fetchGs25Stores({
|
|
212
|
+
serviceCode,
|
|
213
|
+
keyword,
|
|
214
|
+
realTimeStockYn: 'Y',
|
|
215
|
+
latitude,
|
|
216
|
+
longitude,
|
|
217
|
+
useCache: false,
|
|
218
|
+
}, {
|
|
219
|
+
timeout: 20000,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const selected = selectGs25StoresForKeyword(stockResult.stores, storeKeyword, {
|
|
224
|
+
relaxWhenEmpty: typeof latitude === 'number' && typeof longitude === 'number',
|
|
225
|
+
});
|
|
226
|
+
const filtered = selected.stores;
|
|
227
|
+
const withDistance = attachDistanceToGs25Stores(filtered, latitude, longitude);
|
|
228
|
+
const stores = sortGs25Stores(withDistance).slice(0, storeLimit);
|
|
229
|
+
// 상품 정보: searchProducts에서 먼저 가져오고, 없으면 stores에서
|
|
230
|
+
const productName = firstProduct?.itemName || firstProduct?.shortItemName ||
|
|
231
|
+
filtered.find((item) => item.searchItemName.length > 0)?.searchItemName || null;
|
|
232
|
+
const productPrice = filtered.find((item) => item.searchItemSellPrice !== null)?.searchItemSellPrice ?? null;
|
|
233
|
+
const inStockStoreCount = filtered.filter((item) => item.realStockQuantity > 0).length;
|
|
234
|
+
const totalStockQuantity = filtered.reduce((sum, item) => sum + Math.max(item.realStockQuantity, 0), 0);
|
|
235
|
+
return successResponse(c, {
|
|
236
|
+
serviceCode,
|
|
237
|
+
keyword,
|
|
238
|
+
itemCodeUsed,
|
|
239
|
+
itemCode: resolvedItemCode,
|
|
240
|
+
storeKeyword,
|
|
241
|
+
geocodeUsed,
|
|
242
|
+
filterRelaxed: selected.filterRelaxed,
|
|
243
|
+
location: typeof latitude === 'number' && typeof longitude === 'number'
|
|
244
|
+
? { latitude, longitude }
|
|
245
|
+
: null,
|
|
246
|
+
product: {
|
|
247
|
+
name: productName,
|
|
248
|
+
sellPrice: productPrice,
|
|
249
|
+
imageUrl: firstProduct?.imageUrl || null,
|
|
250
|
+
rating: firstProduct?.rating || null,
|
|
251
|
+
},
|
|
252
|
+
inventory: {
|
|
253
|
+
totalStoreCount: stockResult.totalCount,
|
|
254
|
+
matchedStoreCount: filtered.length,
|
|
255
|
+
inStockStoreCount,
|
|
256
|
+
totalStockQuantity,
|
|
257
|
+
count: stores.length,
|
|
258
|
+
stores,
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
const message = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.';
|
|
264
|
+
return errorResponse(c, 'GS25_INVENTORY_CHECK_FAILED', message, 500);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/* c8 ignore stop */
|
package/dist/api/handlers.js
CHANGED
|
@@ -4,122 +4,60 @@
|
|
|
4
4
|
* MCP 미지원 서비스를 위한 GET 기반 API 핸들러입니다.
|
|
5
5
|
* 기존 도구의 핵심 함수들을 재사용합니다.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { fetchOnlineStock, fetchStoreInventory, } from '../services/daiso/tools/checkInventory.js';
|
|
10
|
-
import { fetchProductById } from '../services/daiso/tools/getPriceInfo.js';
|
|
11
|
-
import { getImageUrl } from '../services/daiso/api.js';
|
|
12
|
-
import { fetchOliveyoungProducts, fetchOliveyoungStores, } from '../services/oliveyoung/client.js';
|
|
7
|
+
import { enrichOliveyoungProductsWithNearbyStoreInventory, fetchOliveyoungProducts, fetchOliveyoungStores, } from '../services/oliveyoung/client.js';
|
|
8
|
+
import { fetchCuStock, fetchCuStores, geocodeCuAddress } from '../services/cu/client.js';
|
|
13
9
|
import { errorResponse, successResponse } from './response.js';
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
export async function handleSearchProducts(c) {
|
|
19
|
-
const query = c.req.query('q');
|
|
20
|
-
const page = parseInt(c.req.query('page') || '1');
|
|
21
|
-
const pageSize = parseInt(c.req.query('pageSize') || '30');
|
|
22
|
-
if (!query || query.trim().length === 0) {
|
|
23
|
-
return errorResponse(c, 'MISSING_QUERY', '검색어(q)를 입력해주세요.');
|
|
24
|
-
}
|
|
25
|
-
try {
|
|
26
|
-
const { products, totalCount } = await fetchProducts(query, page, pageSize);
|
|
27
|
-
return successResponse(c, { products }, { total: totalCount, page, pageSize });
|
|
28
|
-
}
|
|
29
|
-
catch (error) {
|
|
30
|
-
const message = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.';
|
|
31
|
-
return errorResponse(c, 'SEARCH_FAILED', message, 500);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* 제품 상세 정보 API 핸들러
|
|
36
|
-
* GET /api/daiso/products/:id
|
|
37
|
-
*/
|
|
38
|
-
export async function handleGetProduct(c) {
|
|
39
|
-
const productId = c.req.param('id');
|
|
40
|
-
if (!productId) {
|
|
41
|
-
return errorResponse(c, 'MISSING_ID', '제품 ID가 필요합니다.');
|
|
10
|
+
export { handleCheckInventory, handleFindStores, handleGetDisplayLocation, handleGetProduct, handleSearchProducts, } from './daisoHandlers.js';
|
|
11
|
+
function parseBooleanQuery(value, defaultValue = false) {
|
|
12
|
+
if (!value) {
|
|
13
|
+
return defaultValue;
|
|
42
14
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return errorResponse(c, 'NOT_FOUND', '제품을 찾을 수 없습니다.', 404);
|
|
47
|
-
}
|
|
48
|
-
const result = {
|
|
49
|
-
id: product.PD_NO,
|
|
50
|
-
name: product.PDNM || product.EXH_PD_NM,
|
|
51
|
-
price: parseInt(product.PD_PRC) || 0,
|
|
52
|
-
currency: 'KRW',
|
|
53
|
-
imageUrl: getImageUrl(product.ATCH_FILE_URL),
|
|
54
|
-
brand: product.BRND_NM || undefined,
|
|
55
|
-
soldOut: product.SOLD_OUT_YN === 'Y',
|
|
56
|
-
isNew: product.NEW_PD_YN === 'Y',
|
|
57
|
-
};
|
|
58
|
-
return successResponse(c, result);
|
|
15
|
+
const normalized = value.trim().toLowerCase();
|
|
16
|
+
if (normalized === 'true' || normalized === '1' || normalized === 'y') {
|
|
17
|
+
return true;
|
|
59
18
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return errorResponse(c, 'FETCH_FAILED', message, 500);
|
|
19
|
+
if (normalized === 'false' || normalized === '0' || normalized === 'n') {
|
|
20
|
+
return false;
|
|
63
21
|
}
|
|
22
|
+
return defaultValue;
|
|
64
23
|
}
|
|
65
24
|
/**
|
|
66
|
-
*
|
|
67
|
-
* GET /api/
|
|
25
|
+
* 올리브영 상품 검색 API 핸들러
|
|
26
|
+
* GET /api/oliveyoung/products?keyword={검색어}
|
|
68
27
|
*/
|
|
69
|
-
export async function
|
|
70
|
-
const keyword = c.req.query('keyword');
|
|
71
|
-
const sido = c.req.query('sido');
|
|
72
|
-
const gugun = c.req.query('gugun');
|
|
73
|
-
const dong = c.req.query('dong');
|
|
74
|
-
const limit = parseInt(c.req.query('limit') || '50');
|
|
75
|
-
if (!keyword && !sido) {
|
|
76
|
-
return errorResponse(c, 'MISSING_PARAMS', '검색어(keyword) 또는 지역(sido)을 입력해주세요.');
|
|
77
|
-
}
|
|
78
|
-
try {
|
|
79
|
-
const stores = await fetchStores(keyword, sido, gugun, dong);
|
|
80
|
-
const limitedStores = stores.slice(0, limit);
|
|
81
|
-
return successResponse(c, { stores: limitedStores }, { total: stores.length });
|
|
82
|
-
}
|
|
83
|
-
catch (error) {
|
|
84
|
-
const message = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.';
|
|
85
|
-
return errorResponse(c, 'SEARCH_FAILED', message, 500);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* 재고 확인 API 핸들러
|
|
90
|
-
* GET /api/daiso/inventory?productId={제품ID}&lat={위도}&lng={경도}&keyword={매장검색어}
|
|
91
|
-
*/
|
|
92
|
-
export async function handleCheckInventory(c) {
|
|
93
|
-
const productId = c.req.query('productId');
|
|
94
|
-
const lat = parseFloat(c.req.query('lat') || '37.5665');
|
|
95
|
-
const lng = parseFloat(c.req.query('lng') || '126.978');
|
|
28
|
+
export async function handleOliveyoungSearchProducts(c) {
|
|
96
29
|
const keyword = c.req.query('keyword') || '';
|
|
97
30
|
const page = parseInt(c.req.query('page') || '1');
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
31
|
+
const size = parseInt(c.req.query('size') || '20');
|
|
32
|
+
const sort = c.req.query('sort') || '01';
|
|
33
|
+
const includeSoldOut = c.req.query('includeSoldOut') === 'true';
|
|
34
|
+
const parsedTimeoutMs = parseInt(c.req.query('timeoutMs') || '15000', 10);
|
|
35
|
+
const timeoutMs = Number.isFinite(parsedTimeoutMs) && parsedTimeoutMs > 0 ? parsedTimeoutMs : 15000;
|
|
36
|
+
if (keyword.trim().length === 0) {
|
|
37
|
+
return errorResponse(c, 'MISSING_QUERY', '검색어(keyword)를 입력해주세요.');
|
|
101
38
|
}
|
|
102
39
|
try {
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
40
|
+
const result = await fetchOliveyoungProducts({
|
|
41
|
+
keyword,
|
|
42
|
+
page,
|
|
43
|
+
size,
|
|
44
|
+
sort,
|
|
45
|
+
includeSoldOut,
|
|
46
|
+
}, {
|
|
47
|
+
apiKey: c.env?.ZYTE_API_KEY,
|
|
48
|
+
timeout: timeoutMs,
|
|
49
|
+
});
|
|
50
|
+
return successResponse(c, {
|
|
51
|
+
keyword,
|
|
52
|
+
totalCount: result.totalCount,
|
|
53
|
+
nextPage: result.nextPage,
|
|
54
|
+
count: result.products.length,
|
|
55
|
+
products: result.products,
|
|
56
|
+
}, { total: result.totalCount, page, pageSize: size });
|
|
119
57
|
}
|
|
120
58
|
catch (error) {
|
|
121
59
|
const message = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.';
|
|
122
|
-
return errorResponse(c, '
|
|
60
|
+
return errorResponse(c, 'OLIVEYOUNG_PRODUCT_SEARCH_FAILED', message, 500);
|
|
123
61
|
}
|
|
124
62
|
}
|
|
125
63
|
/**
|
|
@@ -132,6 +70,8 @@ export async function handleOliveyoungFindStores(c) {
|
|
|
132
70
|
const lng = parseFloat(c.req.query('lng') || '126.978');
|
|
133
71
|
const pageIdx = parseInt(c.req.query('pageIdx') || '1');
|
|
134
72
|
const limit = parseInt(c.req.query('limit') || '20');
|
|
73
|
+
const parsedTimeoutMs = parseInt(c.req.query('timeoutMs') || '15000', 10);
|
|
74
|
+
const timeoutMs = Number.isFinite(parsedTimeoutMs) && parsedTimeoutMs > 0 ? parsedTimeoutMs : 15000;
|
|
135
75
|
try {
|
|
136
76
|
const result = await fetchOliveyoungStores({
|
|
137
77
|
latitude: lat,
|
|
@@ -139,7 +79,8 @@ export async function handleOliveyoungFindStores(c) {
|
|
|
139
79
|
pageIdx,
|
|
140
80
|
searchWords: keyword,
|
|
141
81
|
}, {
|
|
142
|
-
apiKey: c.env
|
|
82
|
+
apiKey: c.env?.ZYTE_API_KEY,
|
|
83
|
+
timeout: timeoutMs,
|
|
143
84
|
});
|
|
144
85
|
return successResponse(c, {
|
|
145
86
|
stores: result.stores.slice(0, limit),
|
|
@@ -164,6 +105,10 @@ export async function handleOliveyoungCheckInventory(c) {
|
|
|
164
105
|
const sort = c.req.query('sort') || '01';
|
|
165
106
|
const includeSoldOut = c.req.query('includeSoldOut') === 'true';
|
|
166
107
|
const storeLimit = parseInt(c.req.query('storeLimit') || '10');
|
|
108
|
+
const parsedTimeoutMs = parseInt(c.req.query('timeoutMs') || '15000', 10);
|
|
109
|
+
const timeoutMs = Number.isFinite(parsedTimeoutMs) && parsedTimeoutMs > 0 ? parsedTimeoutMs : 15000;
|
|
110
|
+
const parsedStockCheckLimit = parseInt(c.req.query('stockCheckLimit') || '5', 10);
|
|
111
|
+
const stockCheckLimit = Number.isFinite(parsedStockCheckLimit) && parsedStockCheckLimit >= 0 ? parsedStockCheckLimit : 5;
|
|
167
112
|
if (!keyword || keyword.trim().length === 0) {
|
|
168
113
|
return errorResponse(c, 'MISSING_QUERY', '검색어(keyword)를 입력해주세요.');
|
|
169
114
|
}
|
|
@@ -176,6 +121,7 @@ export async function handleOliveyoungCheckInventory(c) {
|
|
|
176
121
|
searchWords: storeKeyword,
|
|
177
122
|
}, {
|
|
178
123
|
apiKey: c.env.ZYTE_API_KEY,
|
|
124
|
+
timeout: timeoutMs,
|
|
179
125
|
}),
|
|
180
126
|
fetchOliveyoungProducts({
|
|
181
127
|
keyword,
|
|
@@ -185,8 +131,19 @@ export async function handleOliveyoungCheckInventory(c) {
|
|
|
185
131
|
includeSoldOut,
|
|
186
132
|
}, {
|
|
187
133
|
apiKey: c.env.ZYTE_API_KEY,
|
|
134
|
+
timeout: timeoutMs,
|
|
188
135
|
}),
|
|
189
136
|
]);
|
|
137
|
+
const enrichedInventory = await enrichOliveyoungProductsWithNearbyStoreInventory(productResult.products, {
|
|
138
|
+
latitude: lat,
|
|
139
|
+
longitude: lng,
|
|
140
|
+
storeKeyword,
|
|
141
|
+
maxProducts: Math.min(productResult.products.length, stockCheckLimit),
|
|
142
|
+
}, {
|
|
143
|
+
apiKey: c.env.ZYTE_API_KEY,
|
|
144
|
+
timeout: timeoutMs,
|
|
145
|
+
});
|
|
146
|
+
const inStockCount = enrichedInventory.products.filter((product) => product.inStock).length;
|
|
190
147
|
return successResponse(c, {
|
|
191
148
|
keyword,
|
|
192
149
|
location: { latitude: lat, longitude: lng },
|
|
@@ -197,7 +154,11 @@ export async function handleOliveyoungCheckInventory(c) {
|
|
|
197
154
|
inventory: {
|
|
198
155
|
totalCount: productResult.totalCount,
|
|
199
156
|
nextPage: productResult.nextPage,
|
|
200
|
-
|
|
157
|
+
stockCheckedCount: enrichedInventory.checkedCount,
|
|
158
|
+
stockUncheckedCount: Math.max(0, productResult.products.length - enrichedInventory.checkedCount),
|
|
159
|
+
inStockCount,
|
|
160
|
+
outOfStockCount: enrichedInventory.products.length - inStockCount,
|
|
161
|
+
products: enrichedInventory.products,
|
|
201
162
|
},
|
|
202
163
|
}, { total: productResult.totalCount, page, pageSize: size });
|
|
203
164
|
}
|
|
@@ -206,3 +167,147 @@ export async function handleOliveyoungCheckInventory(c) {
|
|
|
206
167
|
return errorResponse(c, 'OLIVEYOUNG_INVENTORY_CHECK_FAILED', message, 500);
|
|
207
168
|
}
|
|
208
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* CU 매장 검색 API 핸들러
|
|
172
|
+
* GET /api/cu/stores?keyword={키워드}&lat={위도}&lng={경도}
|
|
173
|
+
*/
|
|
174
|
+
export async function handleCuFindStores(c) {
|
|
175
|
+
const keyword = c.req.query('keyword') || '';
|
|
176
|
+
const rawLat = c.req.query('lat');
|
|
177
|
+
const rawLng = c.req.query('lng');
|
|
178
|
+
const limit = parseInt(c.req.query('limit') || '20');
|
|
179
|
+
const parsedLat = rawLat ? parseFloat(rawLat) : undefined;
|
|
180
|
+
const parsedLng = rawLng ? parseFloat(rawLng) : undefined;
|
|
181
|
+
const lat = typeof parsedLat === 'number' && Number.isFinite(parsedLat) ? parsedLat : undefined;
|
|
182
|
+
const lng = typeof parsedLng === 'number' && Number.isFinite(parsedLng) ? parsedLng : undefined;
|
|
183
|
+
try {
|
|
184
|
+
const result = await fetchCuStores({
|
|
185
|
+
latitude: lat,
|
|
186
|
+
longitude: lng,
|
|
187
|
+
searchWord: keyword,
|
|
188
|
+
}, {
|
|
189
|
+
timeout: 15000,
|
|
190
|
+
apiKey: c.env.ZYTE_API_KEY,
|
|
191
|
+
});
|
|
192
|
+
return successResponse(c, {
|
|
193
|
+
location: typeof lat === 'number' && typeof lng === 'number' ? { latitude: lat, longitude: lng } : null,
|
|
194
|
+
keyword,
|
|
195
|
+
stores: result.stores.slice(0, limit),
|
|
196
|
+
}, { total: result.totalCount, pageSize: limit });
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
const message = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.';
|
|
200
|
+
return errorResponse(c, 'CU_STORE_SEARCH_FAILED', message, 500);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* CU 재고 검색 API 핸들러
|
|
205
|
+
* GET /api/cu/inventory?keyword={검색어}&lat={위도}&lng={경도}
|
|
206
|
+
*/
|
|
207
|
+
export async function handleCuCheckInventory(c) {
|
|
208
|
+
const keyword = c.req.query('keyword');
|
|
209
|
+
const storeKeyword = c.req.query('storeKeyword') || '';
|
|
210
|
+
const rawLat = c.req.query('lat');
|
|
211
|
+
const rawLng = c.req.query('lng');
|
|
212
|
+
const parsedLat = rawLat ? parseFloat(rawLat) : undefined;
|
|
213
|
+
const parsedLng = rawLng ? parseFloat(rawLng) : undefined;
|
|
214
|
+
const lat = typeof parsedLat === 'number' && Number.isFinite(parsedLat) ? parsedLat : undefined;
|
|
215
|
+
const lng = typeof parsedLng === 'number' && Number.isFinite(parsedLng) ? parsedLng : undefined;
|
|
216
|
+
const size = parseInt(c.req.query('size') || '20');
|
|
217
|
+
const offset = parseInt(c.req.query('offset') || '0');
|
|
218
|
+
const searchSort = c.req.query('searchSort') || 'recom';
|
|
219
|
+
const storeLimit = parseInt(c.req.query('storeLimit') || '10');
|
|
220
|
+
const storeCheck = parseBooleanQuery(c.req.query('storeCheck'), true);
|
|
221
|
+
if (!keyword || keyword.trim().length === 0) {
|
|
222
|
+
return errorResponse(c, 'MISSING_QUERY', '검색어(keyword)를 입력해주세요.');
|
|
223
|
+
}
|
|
224
|
+
try {
|
|
225
|
+
const stockResult = await fetchCuStock({
|
|
226
|
+
keyword,
|
|
227
|
+
limit: size,
|
|
228
|
+
offset,
|
|
229
|
+
searchSort,
|
|
230
|
+
}, {
|
|
231
|
+
timeout: 15000,
|
|
232
|
+
});
|
|
233
|
+
const firstStockItem = stockResult.items.find((item) => item.itemCode.trim().length > 0) || null;
|
|
234
|
+
const hasInputLocation = typeof lat === 'number' && typeof lng === 'number';
|
|
235
|
+
const resolvedLat = hasInputLocation ? lat : undefined;
|
|
236
|
+
const resolvedLng = hasInputLocation ? lng : undefined;
|
|
237
|
+
let storeResult = storeCheck
|
|
238
|
+
? null
|
|
239
|
+
: { totalCount: 0, stores: [] };
|
|
240
|
+
// 좌표 미입력 + 매장 키워드 입력 시, 키워드 기반 매장 검색 결과를 우선 사용합니다.
|
|
241
|
+
if (storeCheck && !hasInputLocation && storeKeyword.trim().length > 0) {
|
|
242
|
+
const keywordStoreResult = await fetchCuStores({
|
|
243
|
+
searchWord: storeKeyword,
|
|
244
|
+
}, {
|
|
245
|
+
timeout: 15000,
|
|
246
|
+
apiKey: c.env?.ZYTE_API_KEY,
|
|
247
|
+
});
|
|
248
|
+
const firstAddress = keywordStoreResult.stores.find((store) => store.address.trim().length > 0)?.address || '';
|
|
249
|
+
if (firstAddress.length > 0) {
|
|
250
|
+
const geocoded = await geocodeCuAddress(firstAddress, {
|
|
251
|
+
timeout: 15000,
|
|
252
|
+
googleMapsApiKey: c.env?.GOOGLE_MAPS_API_KEY,
|
|
253
|
+
});
|
|
254
|
+
if (geocoded) {
|
|
255
|
+
const hasStockSeed = !!firstStockItem?.itemCode;
|
|
256
|
+
storeResult = await fetchCuStores({
|
|
257
|
+
latitude: geocoded.latitude,
|
|
258
|
+
longitude: geocoded.longitude,
|
|
259
|
+
searchWord: storeKeyword,
|
|
260
|
+
itemCd: firstStockItem?.itemCode || '',
|
|
261
|
+
onItemNo: firstStockItem?.onItemNo || '',
|
|
262
|
+
jipCd: firstStockItem?.itemCode || '',
|
|
263
|
+
isRecommend: hasStockSeed ? 'Y' : '',
|
|
264
|
+
recommendId: hasStockSeed ? 'stock' : '',
|
|
265
|
+
pageType: hasStockSeed ? 'search_improve stock_sch_improve' : 'search_improve',
|
|
266
|
+
}, {
|
|
267
|
+
timeout: 15000,
|
|
268
|
+
apiKey: c.env?.ZYTE_API_KEY,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
storeResult ||= keywordStoreResult;
|
|
273
|
+
}
|
|
274
|
+
if (!storeResult) {
|
|
275
|
+
const hasStockSeed = !!firstStockItem?.itemCode;
|
|
276
|
+
storeResult = await fetchCuStores({
|
|
277
|
+
latitude: resolvedLat,
|
|
278
|
+
longitude: resolvedLng,
|
|
279
|
+
searchWord: storeKeyword,
|
|
280
|
+
itemCd: firstStockItem?.itemCode || '',
|
|
281
|
+
onItemNo: firstStockItem?.onItemNo || '',
|
|
282
|
+
jipCd: firstStockItem?.itemCode || '',
|
|
283
|
+
isRecommend: hasStockSeed ? 'Y' : '',
|
|
284
|
+
recommendId: hasStockSeed ? 'stock' : '',
|
|
285
|
+
pageType: hasStockSeed ? 'search_improve stock_sch_improve' : 'search_improve',
|
|
286
|
+
}, {
|
|
287
|
+
timeout: 15000,
|
|
288
|
+
apiKey: c.env?.ZYTE_API_KEY,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
return successResponse(c, {
|
|
292
|
+
keyword,
|
|
293
|
+
location: typeof resolvedLat === 'number' && typeof resolvedLng === 'number'
|
|
294
|
+
? { latitude: resolvedLat, longitude: resolvedLng }
|
|
295
|
+
: null,
|
|
296
|
+
nearbyStores: {
|
|
297
|
+
totalCount: storeResult.totalCount,
|
|
298
|
+
stockItemCode: firstStockItem?.itemCode || null,
|
|
299
|
+
stockItemName: firstStockItem?.itemName || null,
|
|
300
|
+
stores: storeResult.stores.slice(0, storeLimit),
|
|
301
|
+
},
|
|
302
|
+
inventory: {
|
|
303
|
+
totalCount: stockResult.totalCount,
|
|
304
|
+
spellModifyYn: stockResult.spellModifyYn,
|
|
305
|
+
items: stockResult.items,
|
|
306
|
+
},
|
|
307
|
+
}, { total: stockResult.totalCount, page: Math.floor(offset / Math.max(size, 1)) + 1, pageSize: size });
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
const message = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.';
|
|
311
|
+
return errorResponse(c, 'CU_INVENTORY_CHECK_FAILED', message, 500);
|
|
312
|
+
}
|
|
313
|
+
}
|