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,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 롯데마트 GET API 핸들러
|
|
3
|
+
*/
|
|
4
|
+
import { errorResponse, successResponse } from './response.js';
|
|
5
|
+
import { fetchLotteMartStores, searchLotteMartProducts } from '../services/lottemart/client.js';
|
|
6
|
+
import { DEFAULT_LOTTEMART_TIMEOUT_MS } from '../services/lottemart/config.js';
|
|
7
|
+
import { probeLotteMartUpstream } from '../services/lottemart/debug.js';
|
|
8
|
+
function parseLotteMartProductSource(value) {
|
|
9
|
+
if (!value || value === 'auto') {
|
|
10
|
+
return 'auto';
|
|
11
|
+
}
|
|
12
|
+
if (value === 'legacy' || value === 'zetta') {
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 롯데마트 매장 검색 API 핸들러
|
|
19
|
+
* GET /api/lottemart/stores?keyword={키워드}&lat={위도}&lng={경도}
|
|
20
|
+
*/
|
|
21
|
+
export async function handleLotteMartFindStores(c) {
|
|
22
|
+
const keyword = c.req.query('keyword') || '';
|
|
23
|
+
const area = c.req.query('area') || '';
|
|
24
|
+
const brandVariant = c.req.query('brandVariant') || '';
|
|
25
|
+
const limit = parseInt(c.req.query('limit') || '20', 10);
|
|
26
|
+
const rawLat = c.req.query('lat');
|
|
27
|
+
const rawLng = c.req.query('lng');
|
|
28
|
+
const parsedLat = rawLat ? parseFloat(rawLat) : undefined;
|
|
29
|
+
const parsedLng = rawLng ? parseFloat(rawLng) : undefined;
|
|
30
|
+
const latitude = typeof parsedLat === 'number' && Number.isFinite(parsedLat) ? parsedLat : undefined;
|
|
31
|
+
const longitude = typeof parsedLng === 'number' && Number.isFinite(parsedLng) ? parsedLng : undefined;
|
|
32
|
+
try {
|
|
33
|
+
const result = await fetchLotteMartStores({
|
|
34
|
+
area,
|
|
35
|
+
keyword,
|
|
36
|
+
brandVariant,
|
|
37
|
+
latitude,
|
|
38
|
+
longitude,
|
|
39
|
+
limit,
|
|
40
|
+
}, {
|
|
41
|
+
timeout: DEFAULT_LOTTEMART_TIMEOUT_MS,
|
|
42
|
+
googleMapsApiKey: c.env?.GOOGLE_MAPS_API_KEY,
|
|
43
|
+
zyteApiKey: c.env?.ZYTE_API_KEY,
|
|
44
|
+
});
|
|
45
|
+
return successResponse(c, {
|
|
46
|
+
area: area || null,
|
|
47
|
+
keyword,
|
|
48
|
+
brandVariant: brandVariant || null,
|
|
49
|
+
geocodeUsed: result.geocodeUsed,
|
|
50
|
+
location: result.location,
|
|
51
|
+
count: result.stores.length,
|
|
52
|
+
stores: result.stores,
|
|
53
|
+
}, {
|
|
54
|
+
total: result.stores.length,
|
|
55
|
+
page: 1,
|
|
56
|
+
pageSize: limit,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
const message = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.';
|
|
61
|
+
return errorResponse(c, 'LOTTEMART_STORE_SEARCH_FAILED', message, 500);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 롯데마트 상품 검색 API 핸들러
|
|
66
|
+
* GET /api/lottemart/products?keyword={검색어}&storeCode={매장코드}
|
|
67
|
+
*/
|
|
68
|
+
export async function handleLotteMartSearchProducts(c) {
|
|
69
|
+
const keyword = c.req.query('keyword') || '';
|
|
70
|
+
const area = c.req.query('area') || '';
|
|
71
|
+
const storeCode = c.req.query('storeCode') || '';
|
|
72
|
+
const storeName = c.req.query('storeName') || '';
|
|
73
|
+
const pageLimit = parseInt(c.req.query('pageLimit') || '3', 10);
|
|
74
|
+
const parsedTimeoutMs = parseInt(c.req.query('timeoutMs') || String(DEFAULT_LOTTEMART_TIMEOUT_MS), 10);
|
|
75
|
+
const timeoutMs = Number.isFinite(parsedTimeoutMs) && parsedTimeoutMs > 0 ? parsedTimeoutMs : DEFAULT_LOTTEMART_TIMEOUT_MS;
|
|
76
|
+
const source = parseLotteMartProductSource(c.req.query('source'));
|
|
77
|
+
if (keyword.trim().length === 0) {
|
|
78
|
+
return errorResponse(c, 'MISSING_QUERY', '검색어(keyword)를 입력해주세요.');
|
|
79
|
+
}
|
|
80
|
+
if (!source) {
|
|
81
|
+
return errorResponse(c, 'INVALID_LOTTEMART_SOURCE', 'source는 auto, legacy, zetta 중 하나여야 합니다.');
|
|
82
|
+
}
|
|
83
|
+
if (storeCode.trim().length === 0 && storeName.trim().length === 0) {
|
|
84
|
+
return errorResponse(c, 'MISSING_STORE', 'storeCode 또는 storeName 중 하나를 입력해주세요.');
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const result = await searchLotteMartProducts({
|
|
88
|
+
area,
|
|
89
|
+
storeCode,
|
|
90
|
+
storeName,
|
|
91
|
+
keyword,
|
|
92
|
+
pageLimit,
|
|
93
|
+
source,
|
|
94
|
+
}, {
|
|
95
|
+
timeout: timeoutMs,
|
|
96
|
+
zyteApiKey: c.env?.ZYTE_API_KEY,
|
|
97
|
+
});
|
|
98
|
+
return successResponse(c, {
|
|
99
|
+
area: result.area,
|
|
100
|
+
storeCode: result.storeCode,
|
|
101
|
+
storeName: result.storeName,
|
|
102
|
+
keyword,
|
|
103
|
+
pageLimit,
|
|
104
|
+
totalPages: result.totalPages,
|
|
105
|
+
products: result.products,
|
|
106
|
+
}, {
|
|
107
|
+
total: result.totalCount,
|
|
108
|
+
page: 1,
|
|
109
|
+
pageSize: result.products.length,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
const message = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.';
|
|
114
|
+
return errorResponse(c, 'LOTTEMART_PRODUCT_SEARCH_FAILED', message, 500);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* 롯데마트 업스트림 단독 진단 API 핸들러
|
|
119
|
+
* GET /api/lottemart/debug?target=market-options|stores|products|product-page
|
|
120
|
+
*/
|
|
121
|
+
export async function handleLotteMartDebug(c) {
|
|
122
|
+
const target = (c.req.query('target') || '');
|
|
123
|
+
const allowedTargets = ['market-options', 'stores', 'products', 'product-page'];
|
|
124
|
+
if (!allowedTargets.includes(target)) {
|
|
125
|
+
return errorResponse(c, 'INVALID_TARGET', 'target은 market-options, stores, products, product-page 중 하나여야 합니다.');
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const result = await probeLotteMartUpstream({
|
|
129
|
+
target,
|
|
130
|
+
area: c.req.query('area') || undefined,
|
|
131
|
+
type: (c.req.query('type') || undefined),
|
|
132
|
+
storeCode: c.req.query('storeCode') || undefined,
|
|
133
|
+
keyword: c.req.query('keyword') || undefined,
|
|
134
|
+
page: c.req.query('page') ? parseInt(c.req.query('page'), 10) : undefined,
|
|
135
|
+
timeout: c.req.query('timeoutMs') ? parseInt(c.req.query('timeoutMs'), 10) : DEFAULT_LOTTEMART_TIMEOUT_MS,
|
|
136
|
+
zyteApiKey: c.env?.ZYTE_API_KEY,
|
|
137
|
+
});
|
|
138
|
+
return successResponse(c, result);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
const message = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.';
|
|
142
|
+
return errorResponse(c, 'LOTTEMART_DEBUG_FAILED', message, 500);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -1,63 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 메가박스 GET API 핸들러
|
|
3
3
|
*/
|
|
4
|
-
import { fetchMegaboxBookingList,
|
|
4
|
+
import { fetchMegaboxBookingList, toYyyymmdd } from '../services/megabox/client.js';
|
|
5
|
+
import { fetchMegaboxNearbyTheaters, resolveMegaboxNearestTheater } from '../services/megabox/location.js';
|
|
5
6
|
import { errorResponse, successResponse } from './response.js';
|
|
6
|
-
function
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
13
|
-
return 6371 * c;
|
|
7
|
+
function parseOptionalNumber(value) {
|
|
8
|
+
if (!value) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
const parsed = Number.parseFloat(value);
|
|
12
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
14
13
|
}
|
|
15
14
|
/**
|
|
16
15
|
* 메가박스 주변 지점 조회 API 핸들러
|
|
17
16
|
* GET /api/megabox/theaters?lat={위도}&lng={경도}&playDate={YYYYMMDD}&areaCode={지역코드}
|
|
18
17
|
*/
|
|
19
18
|
export async function handleMegaboxFindNearbyTheaters(c) {
|
|
20
|
-
const
|
|
21
|
-
const
|
|
19
|
+
const keyword = c.req.query('keyword') || undefined;
|
|
20
|
+
const lat = parseOptionalNumber(c.req.query('lat'));
|
|
21
|
+
const lng = parseOptionalNumber(c.req.query('lng'));
|
|
22
22
|
const playDate = c.req.query('playDate') || toYyyymmdd();
|
|
23
|
-
const areaCode = c.req.query('areaCode') ||
|
|
23
|
+
const areaCode = c.req.query('areaCode') || undefined;
|
|
24
24
|
const limit = parseInt(c.req.query('limit') || '10');
|
|
25
25
|
const timeoutMs = parseInt(c.req.query('timeoutMs') || '15000');
|
|
26
26
|
try {
|
|
27
|
-
const
|
|
27
|
+
const result = await fetchMegaboxNearbyTheaters({
|
|
28
|
+
keyword,
|
|
29
|
+
latitude: lat,
|
|
30
|
+
longitude: lng,
|
|
28
31
|
playDate,
|
|
29
32
|
areaCode,
|
|
33
|
+
limit,
|
|
34
|
+
timeout: timeoutMs,
|
|
35
|
+
}, {
|
|
36
|
+
googleMapsApiKey: c.env?.GOOGLE_MAPS_API_KEY,
|
|
30
37
|
timeout: timeoutMs,
|
|
31
38
|
});
|
|
32
|
-
|
|
33
|
-
const merged = theaters
|
|
34
|
-
.map((theater, index) => {
|
|
35
|
-
const infoResult = infoResults[index];
|
|
36
|
-
if (infoResult.status !== 'fulfilled') {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
if (infoResult.value.latitude === null || infoResult.value.longitude === null) {
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
const distanceKm = calculateDistanceKm(lat, lng, infoResult.value.latitude, infoResult.value.longitude);
|
|
43
|
-
return {
|
|
44
|
-
theaterId: theater.theaterId,
|
|
45
|
-
theaterName: theater.theaterName,
|
|
46
|
-
address: infoResult.value.address,
|
|
47
|
-
latitude: infoResult.value.latitude,
|
|
48
|
-
longitude: infoResult.value.longitude,
|
|
49
|
-
distanceKm: Number(distanceKm.toFixed(2)),
|
|
50
|
-
};
|
|
51
|
-
})
|
|
52
|
-
.filter((theater) => theater !== null)
|
|
53
|
-
.sort((a, b) => a.distanceKm - b.distanceKm)
|
|
54
|
-
.slice(0, limit);
|
|
55
|
-
return successResponse(c, {
|
|
56
|
-
location: { latitude: lat, longitude: lng },
|
|
57
|
-
playDate,
|
|
58
|
-
areaCode,
|
|
59
|
-
theaters: merged,
|
|
60
|
-
}, { total: merged.length, pageSize: limit });
|
|
39
|
+
return successResponse(c, result, { total: result.count, pageSize: limit });
|
|
61
40
|
}
|
|
62
41
|
catch (error) {
|
|
63
42
|
const message = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.';
|
|
@@ -70,16 +49,38 @@ export async function handleMegaboxFindNearbyTheaters(c) {
|
|
|
70
49
|
*/
|
|
71
50
|
export async function handleMegaboxListNowShowing(c) {
|
|
72
51
|
const playDate = c.req.query('playDate') || toYyyymmdd();
|
|
73
|
-
const
|
|
52
|
+
const keyword = c.req.query('keyword') || undefined;
|
|
53
|
+
const lat = parseOptionalNumber(c.req.query('lat'));
|
|
54
|
+
const lng = parseOptionalNumber(c.req.query('lng'));
|
|
55
|
+
let theaterId = c.req.query('theaterId') || undefined;
|
|
74
56
|
const movieId = c.req.query('movieId') || undefined;
|
|
75
|
-
const areaCode = c.req.query('areaCode') ||
|
|
57
|
+
const areaCode = c.req.query('areaCode') || undefined;
|
|
76
58
|
const timeoutMs = parseInt(c.req.query('timeoutMs') || '15000');
|
|
77
59
|
try {
|
|
60
|
+
let resolvedTheater = null;
|
|
61
|
+
let resolvedLocation = null;
|
|
62
|
+
if (!theaterId && (keyword || typeof lat === 'number' || typeof lng === 'number')) {
|
|
63
|
+
const resolved = await resolveMegaboxNearestTheater({
|
|
64
|
+
keyword,
|
|
65
|
+
latitude: lat,
|
|
66
|
+
longitude: lng,
|
|
67
|
+
areaCode,
|
|
68
|
+
playDate,
|
|
69
|
+
timeout: timeoutMs,
|
|
70
|
+
}, {
|
|
71
|
+
googleMapsApiKey: c.env?.GOOGLE_MAPS_API_KEY,
|
|
72
|
+
timeout: timeoutMs,
|
|
73
|
+
});
|
|
74
|
+
theaterId = resolved.theater?.theaterId;
|
|
75
|
+
resolvedTheater = resolved.theater;
|
|
76
|
+
resolvedLocation = resolved.location;
|
|
77
|
+
}
|
|
78
|
+
const resolvedAreaCode = resolvedLocation?.areaCode || areaCode || '11';
|
|
78
79
|
const result = await fetchMegaboxBookingList({
|
|
79
80
|
playDate,
|
|
80
81
|
theaterId,
|
|
81
82
|
movieId,
|
|
82
|
-
areaCode,
|
|
83
|
+
areaCode: resolvedAreaCode,
|
|
83
84
|
timeout: timeoutMs,
|
|
84
85
|
});
|
|
85
86
|
return successResponse(c, {
|
|
@@ -87,8 +88,12 @@ export async function handleMegaboxListNowShowing(c) {
|
|
|
87
88
|
filters: {
|
|
88
89
|
theaterId: theaterId || null,
|
|
89
90
|
movieId: movieId || null,
|
|
90
|
-
|
|
91
|
+
keyword: keyword || null,
|
|
92
|
+
latitude: lat ?? null,
|
|
93
|
+
longitude: lng ?? null,
|
|
94
|
+
areaCode: resolvedAreaCode,
|
|
91
95
|
},
|
|
96
|
+
resolvedTheater,
|
|
92
97
|
theaters: result.theaters,
|
|
93
98
|
movies: result.movies,
|
|
94
99
|
showtimes: result.showtimes,
|
|
@@ -105,17 +110,39 @@ export async function handleMegaboxListNowShowing(c) {
|
|
|
105
110
|
*/
|
|
106
111
|
export async function handleMegaboxGetRemainingSeats(c) {
|
|
107
112
|
const playDate = c.req.query('playDate') || toYyyymmdd();
|
|
108
|
-
const
|
|
113
|
+
const keyword = c.req.query('keyword') || undefined;
|
|
114
|
+
const lat = parseOptionalNumber(c.req.query('lat'));
|
|
115
|
+
const lng = parseOptionalNumber(c.req.query('lng'));
|
|
116
|
+
let theaterId = c.req.query('theaterId') || undefined;
|
|
109
117
|
const movieId = c.req.query('movieId') || undefined;
|
|
110
|
-
const areaCode = c.req.query('areaCode') ||
|
|
118
|
+
const areaCode = c.req.query('areaCode') || undefined;
|
|
111
119
|
const limit = parseInt(c.req.query('limit') || '50');
|
|
112
120
|
const timeoutMs = parseInt(c.req.query('timeoutMs') || '15000');
|
|
113
121
|
try {
|
|
122
|
+
let resolvedTheater = null;
|
|
123
|
+
let resolvedLocation = null;
|
|
124
|
+
if (!theaterId && (keyword || typeof lat === 'number' || typeof lng === 'number')) {
|
|
125
|
+
const resolved = await resolveMegaboxNearestTheater({
|
|
126
|
+
keyword,
|
|
127
|
+
latitude: lat,
|
|
128
|
+
longitude: lng,
|
|
129
|
+
areaCode,
|
|
130
|
+
playDate,
|
|
131
|
+
timeout: timeoutMs,
|
|
132
|
+
}, {
|
|
133
|
+
googleMapsApiKey: c.env?.GOOGLE_MAPS_API_KEY,
|
|
134
|
+
timeout: timeoutMs,
|
|
135
|
+
});
|
|
136
|
+
theaterId = resolved.theater?.theaterId;
|
|
137
|
+
resolvedTheater = resolved.theater;
|
|
138
|
+
resolvedLocation = resolved.location;
|
|
139
|
+
}
|
|
140
|
+
const resolvedAreaCode = resolvedLocation?.areaCode || areaCode || '11';
|
|
114
141
|
const { showtimes } = await fetchMegaboxBookingList({
|
|
115
142
|
playDate,
|
|
116
143
|
theaterId,
|
|
117
144
|
movieId,
|
|
118
|
-
areaCode,
|
|
145
|
+
areaCode: resolvedAreaCode,
|
|
119
146
|
timeout: timeoutMs,
|
|
120
147
|
});
|
|
121
148
|
const seats = showtimes
|
|
@@ -133,8 +160,12 @@ export async function handleMegaboxGetRemainingSeats(c) {
|
|
|
133
160
|
filters: {
|
|
134
161
|
theaterId: theaterId || null,
|
|
135
162
|
movieId: movieId || null,
|
|
136
|
-
|
|
163
|
+
keyword: keyword || null,
|
|
164
|
+
latitude: lat ?? null,
|
|
165
|
+
longitude: lng ?? null,
|
|
166
|
+
areaCode: resolvedAreaCode,
|
|
137
167
|
},
|
|
168
|
+
resolvedTheater,
|
|
138
169
|
seats,
|
|
139
170
|
}, { total: seats.length, pageSize: limit });
|
|
140
171
|
}
|
package/dist/api/response.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* API 공통 응답 유틸리티
|
|
3
3
|
*/
|
|
4
|
+
import { toStandardErrorDiagnostics } from '../core/errors.js';
|
|
4
5
|
/**
|
|
5
6
|
* 성공 응답 생성
|
|
6
7
|
*/
|
|
@@ -18,5 +19,6 @@ export function errorResponse(c, code, message, status = 400) {
|
|
|
18
19
|
return c.json({
|
|
19
20
|
success: false,
|
|
20
21
|
error: { code, message },
|
|
22
|
+
diagnostics: toStandardErrorDiagnostics(code, message, { status }),
|
|
21
23
|
}, status);
|
|
22
24
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CU GET API 라우트 등록
|
|
3
|
+
*/
|
|
4
|
+
import { withEdgeCache } from '../../utils/cache.js';
|
|
5
|
+
import { handleCuFindStores, handleCuCheckInventory } from '../handlers.js';
|
|
6
|
+
export function registerCuRoutes(app) {
|
|
7
|
+
app.get('/api/cu/stores', async (c) => withEdgeCache(c.req.url, {
|
|
8
|
+
ttlSeconds: 60 * 60 * 24,
|
|
9
|
+
staleWhileRevalidateSeconds: 60 * 5,
|
|
10
|
+
keyPrefix: 'cu-stores-v1',
|
|
11
|
+
}, () => handleCuFindStores(c)));
|
|
12
|
+
app.get('/api/cu/inventory', async (c) => withEdgeCache(c.req.url, {
|
|
13
|
+
ttlSeconds: 60 * 10,
|
|
14
|
+
staleWhileRevalidateSeconds: 60,
|
|
15
|
+
keyPrefix: 'cu-inventory-v1',
|
|
16
|
+
}, () => handleCuCheckInventory(c)));
|
|
17
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* 다이소 GET API 라우트 등록
|
|
3
3
|
*/
|
|
4
4
|
import { withEdgeCache } from '../../utils/cache.js';
|
|
5
|
-
import { handleSearchProducts, handleGetProduct, handleFindStores, handleCheckInventory, } from '../handlers.js';
|
|
5
|
+
import { handleSearchProducts, handleGetProduct, handleFindStores, handleCheckInventory, handleGetDisplayLocation, } from '../handlers.js';
|
|
6
6
|
export function registerDaisoRoutes(app) {
|
|
7
7
|
app.get('/api/daiso/products', async (c) => withEdgeCache(c.req.url, {
|
|
8
8
|
ttlSeconds: 60 * 30,
|
|
@@ -24,4 +24,9 @@ export function registerDaisoRoutes(app) {
|
|
|
24
24
|
staleWhileRevalidateSeconds: 60,
|
|
25
25
|
keyPrefix: 'daiso-inventory-v1',
|
|
26
26
|
}, () => handleCheckInventory(c)));
|
|
27
|
+
app.get('/api/daiso/display-location', async (c) => withEdgeCache(c.req.url, {
|
|
28
|
+
ttlSeconds: 60 * 10,
|
|
29
|
+
staleWhileRevalidateSeconds: 60,
|
|
30
|
+
keyPrefix: 'daiso-display-location-v1',
|
|
31
|
+
}, () => handleGetDisplayLocation(c)));
|
|
27
32
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 이마트24 GET API 라우트 등록
|
|
3
|
+
*/
|
|
4
|
+
import { withEdgeCache } from '../../utils/cache.js';
|
|
5
|
+
import { handleEmart24CheckInventory, handleEmart24FindStores, handleEmart24SearchProducts, } from '../emart24Handlers.js';
|
|
6
|
+
export function registerEmart24Routes(app) {
|
|
7
|
+
app.get('/api/emart24/stores', async (c) => withEdgeCache(c.req.url, {
|
|
8
|
+
ttlSeconds: 60 * 30,
|
|
9
|
+
staleWhileRevalidateSeconds: 60 * 3,
|
|
10
|
+
keyPrefix: 'emart24-stores-v1',
|
|
11
|
+
}, () => handleEmart24FindStores(c)));
|
|
12
|
+
app.get('/api/emart24/products', async (c) => withEdgeCache(c.req.url, {
|
|
13
|
+
ttlSeconds: 60 * 10,
|
|
14
|
+
staleWhileRevalidateSeconds: 60,
|
|
15
|
+
keyPrefix: 'emart24-products-v1',
|
|
16
|
+
}, () => handleEmart24SearchProducts(c)));
|
|
17
|
+
app.get('/api/emart24/inventory', async (c) => withEdgeCache(c.req.url, {
|
|
18
|
+
ttlSeconds: 60 * 5,
|
|
19
|
+
staleWhileRevalidateSeconds: 60,
|
|
20
|
+
keyPrefix: 'emart24-inventory-v1',
|
|
21
|
+
}, () => handleEmart24CheckInventory(c)));
|
|
22
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GS25 GET API 라우트 등록
|
|
3
|
+
*/
|
|
4
|
+
import { withEdgeCache } from '../../utils/cache.js';
|
|
5
|
+
import { handleGs25CheckInventory, handleGs25FindStores, handleGs25SearchProducts, } from '../gs25Handlers.js';
|
|
6
|
+
export function registerGs25Routes(app) {
|
|
7
|
+
app.get('/api/gs25/stores', async (c) => withEdgeCache(c.req.url, {
|
|
8
|
+
ttlSeconds: 60 * 10,
|
|
9
|
+
staleWhileRevalidateSeconds: 60,
|
|
10
|
+
keyPrefix: 'gs25-stores-v1',
|
|
11
|
+
}, () => handleGs25FindStores(c)));
|
|
12
|
+
app.get('/api/gs25/products', async (c) => withEdgeCache(c.req.url, {
|
|
13
|
+
ttlSeconds: 60 * 3,
|
|
14
|
+
staleWhileRevalidateSeconds: 60,
|
|
15
|
+
keyPrefix: 'gs25-products-v1',
|
|
16
|
+
}, () => handleGs25SearchProducts(c)));
|
|
17
|
+
app.get('/api/gs25/inventory', async (c) => withEdgeCache(c.req.url, {
|
|
18
|
+
ttlSeconds: 60 * 2,
|
|
19
|
+
staleWhileRevalidateSeconds: 30,
|
|
20
|
+
keyPrefix: 'gs25-inventory-v1',
|
|
21
|
+
}, () => handleGs25CheckInventory(c)));
|
|
22
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 운영용 상세 헬스 체크 라우트
|
|
3
|
+
*/
|
|
4
|
+
import { errorResponse } from '../response.js';
|
|
5
|
+
import { runHealthChecks } from '../healthChecks.js';
|
|
6
|
+
function parseBoolean(value) {
|
|
7
|
+
const normalized = (value || '').trim().toLowerCase();
|
|
8
|
+
return normalized === 'true' || normalized === '1' || normalized === 'y';
|
|
9
|
+
}
|
|
10
|
+
function readHealthCheckToken(headers) {
|
|
11
|
+
const authorization = headers.get('Authorization') || '';
|
|
12
|
+
if (authorization.toLowerCase().startsWith('bearer ')) {
|
|
13
|
+
return authorization.slice('bearer '.length).trim();
|
|
14
|
+
}
|
|
15
|
+
return (headers.get('x-health-check-key') || '').trim();
|
|
16
|
+
}
|
|
17
|
+
function parseMode(value) {
|
|
18
|
+
if (!value || value === 'quick') {
|
|
19
|
+
return 'quick';
|
|
20
|
+
}
|
|
21
|
+
if (value === 'deep' || value === 'full') {
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
export function registerHealthRoutes(app) {
|
|
27
|
+
app.get('/api/health/checks', async (c) => {
|
|
28
|
+
const secret = c.env?.HEALTH_CHECK_SECRET?.trim();
|
|
29
|
+
if (!secret) {
|
|
30
|
+
return errorResponse(c, 'HEALTH_CHECK_SECRET_NOT_CONFIGURED', 'HEALTH_CHECK_SECRET이 설정되어 있지 않습니다.', 503);
|
|
31
|
+
}
|
|
32
|
+
if (readHealthCheckToken(c.req.raw.headers) !== secret) {
|
|
33
|
+
return errorResponse(c, 'UNAUTHORIZED_HEALTH_CHECK', '유효한 헬스 체크 시크릿 키가 필요합니다.', 401);
|
|
34
|
+
}
|
|
35
|
+
const mode = parseMode(c.req.query('mode'));
|
|
36
|
+
if (!mode) {
|
|
37
|
+
return errorResponse(c, 'INVALID_HEALTH_CHECK_MODE', 'mode는 quick, deep, full 중 하나여야 합니다.', 400);
|
|
38
|
+
}
|
|
39
|
+
const timeoutMs = Number.parseInt(c.req.query('timeoutMs') || '3000', 10);
|
|
40
|
+
const slowThresholdMs = Number.parseInt(c.req.query('slowThresholdMs') || c.env?.HEALTH_CHECK_SLOW_THRESHOLD_MS || '0', 10);
|
|
41
|
+
const baseUrl = c.req.query('baseUrl')?.trim() || c.env?.HEALTH_CHECK_BASE_URL?.trim() || new URL(c.req.url).origin;
|
|
42
|
+
const transport = c.req.query('transport') || c.env?.HEALTH_CHECK_TRANSPORT || 'internal';
|
|
43
|
+
const cacheBust = parseBoolean(c.req.query('cacheBust')) || transport === 'network';
|
|
44
|
+
const fetchImpl = c.env?.HEALTH_CHECK_BASE_URL && transport !== 'network'
|
|
45
|
+
? async (input, init) => app.fetch(new Request(input, init), c.env)
|
|
46
|
+
: undefined;
|
|
47
|
+
const result = await runHealthChecks({
|
|
48
|
+
baseUrl,
|
|
49
|
+
service: c.req.query('service') || undefined,
|
|
50
|
+
check: c.req.query('check') || undefined,
|
|
51
|
+
mode,
|
|
52
|
+
timeoutMs,
|
|
53
|
+
slowThresholdMs,
|
|
54
|
+
includeSamples: parseBoolean(c.req.query('includeSamples')),
|
|
55
|
+
fresh: parseBoolean(c.req.query('fresh')),
|
|
56
|
+
cacheBust,
|
|
57
|
+
fetchImpl,
|
|
58
|
+
});
|
|
59
|
+
return c.json(result);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 롯데시네마 GET API 라우트 등록
|
|
3
|
+
*/
|
|
4
|
+
import { withEdgeCache } from '../../utils/cache.js';
|
|
5
|
+
import { handleLotteCinemaFindNearbyTheaters, handleLotteCinemaGetRemainingSeats, handleLotteCinemaListNowShowing, } from '../lottecinemaHandlers.js';
|
|
6
|
+
export function registerLotteCinemaRoutes(app) {
|
|
7
|
+
app.get('/api/lottecinema/theaters', async (c) => withEdgeCache(c.req.url, {
|
|
8
|
+
ttlSeconds: 60 * 60 * 24,
|
|
9
|
+
staleWhileRevalidateSeconds: 60 * 5,
|
|
10
|
+
keyPrefix: 'lottecinema-theaters-v1',
|
|
11
|
+
}, () => handleLotteCinemaFindNearbyTheaters(c)));
|
|
12
|
+
app.get('/api/lottecinema/movies', async (c) => withEdgeCache(c.req.url, {
|
|
13
|
+
ttlSeconds: 60 * 10,
|
|
14
|
+
staleWhileRevalidateSeconds: 60,
|
|
15
|
+
keyPrefix: 'lottecinema-movies-v1',
|
|
16
|
+
}, () => handleLotteCinemaListNowShowing(c)));
|
|
17
|
+
app.get('/api/lottecinema/seats', async (c) => withEdgeCache(c.req.url, {
|
|
18
|
+
ttlSeconds: 60 * 3,
|
|
19
|
+
staleWhileRevalidateSeconds: 30,
|
|
20
|
+
keyPrefix: 'lottecinema-seats-v1',
|
|
21
|
+
}, () => handleLotteCinemaGetRemainingSeats(c)));
|
|
22
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 롯데마트 GET API 라우트 등록
|
|
3
|
+
*/
|
|
4
|
+
import { withEdgeCache } from '../../utils/cache.js';
|
|
5
|
+
import { handleLotteMartDebug, handleLotteMartFindStores, handleLotteMartSearchProducts } from '../lottemartHandlers.js';
|
|
6
|
+
export function registerLotteMartRoutes(app) {
|
|
7
|
+
app.get('/api/lottemart/stores', async (c) => withEdgeCache(c.req.url, {
|
|
8
|
+
ttlSeconds: 60 * 30,
|
|
9
|
+
staleWhileRevalidateSeconds: 60 * 3,
|
|
10
|
+
keyPrefix: 'lottemart-stores-v1',
|
|
11
|
+
}, () => handleLotteMartFindStores(c)));
|
|
12
|
+
app.get('/api/lottemart/products', async (c) => withEdgeCache(c.req.url, {
|
|
13
|
+
ttlSeconds: 60 * 5,
|
|
14
|
+
staleWhileRevalidateSeconds: 60,
|
|
15
|
+
keyPrefix: 'lottemart-products-v1',
|
|
16
|
+
}, () => handleLotteMartSearchProducts(c)));
|
|
17
|
+
app.get('/api/lottemart/debug', (c) => handleLotteMartDebug(c));
|
|
18
|
+
}
|
|
@@ -2,8 +2,13 @@
|
|
|
2
2
|
* 올리브영 GET API 라우트 등록
|
|
3
3
|
*/
|
|
4
4
|
import { withEdgeCache } from '../../utils/cache.js';
|
|
5
|
-
import { handleOliveyoungFindStores, handleOliveyoungCheckInventory } from '../handlers.js';
|
|
5
|
+
import { handleOliveyoungFindStores, handleOliveyoungCheckInventory, handleOliveyoungSearchProducts, } from '../handlers.js';
|
|
6
6
|
export function registerOliveyoungRoutes(app) {
|
|
7
|
+
app.get('/api/oliveyoung/products', async (c) => withEdgeCache(c.req.url, {
|
|
8
|
+
ttlSeconds: 60 * 5,
|
|
9
|
+
staleWhileRevalidateSeconds: 60,
|
|
10
|
+
keyPrefix: 'oliveyoung-products-v1',
|
|
11
|
+
}, () => handleOliveyoungSearchProducts(c)));
|
|
7
12
|
app.get('/api/oliveyoung/stores', async (c) => withEdgeCache(c.req.url, {
|
|
8
13
|
ttlSeconds: 60 * 60 * 24,
|
|
9
14
|
staleWhileRevalidateSeconds: 60 * 5,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 세븐일레븐 GET API 라우트 등록
|
|
3
|
+
*/
|
|
4
|
+
import { withEdgeCache } from '../../utils/cache.js';
|
|
5
|
+
import { handleSevenElevenCheckInventory, handleSevenElevenGetCatalogSnapshot, handleSevenElevenGetSearchPopwords, handleSevenElevenSearchStores, handleSevenElevenSearchProducts, } from '../sevenelevenHandlers.js';
|
|
6
|
+
export function registerSevenElevenRoutes(app) {
|
|
7
|
+
app.get('/api/seveneleven/products', async (c) => withEdgeCache(c.req.url, {
|
|
8
|
+
ttlSeconds: 60 * 3,
|
|
9
|
+
staleWhileRevalidateSeconds: 60,
|
|
10
|
+
keyPrefix: 'seveneleven-products-v1',
|
|
11
|
+
}, () => handleSevenElevenSearchProducts(c)));
|
|
12
|
+
app.get('/api/seveneleven/stores', async (c) => withEdgeCache(c.req.url, {
|
|
13
|
+
ttlSeconds: 60 * 3,
|
|
14
|
+
staleWhileRevalidateSeconds: 60,
|
|
15
|
+
keyPrefix: 'seveneleven-stores-v1',
|
|
16
|
+
}, () => handleSevenElevenSearchStores(c)));
|
|
17
|
+
app.get('/api/seveneleven/inventory', async (c) => withEdgeCache(c.req.url, {
|
|
18
|
+
ttlSeconds: 60 * 10,
|
|
19
|
+
staleWhileRevalidateSeconds: 60,
|
|
20
|
+
keyPrefix: 'seveneleven-inventory-v1',
|
|
21
|
+
}, () => handleSevenElevenCheckInventory(c)));
|
|
22
|
+
app.get('/api/seveneleven/popwords', async (c) => withEdgeCache(c.req.url, {
|
|
23
|
+
ttlSeconds: 60 * 5,
|
|
24
|
+
staleWhileRevalidateSeconds: 60,
|
|
25
|
+
keyPrefix: 'seveneleven-popwords-v1',
|
|
26
|
+
}, () => handleSevenElevenGetSearchPopwords(c)));
|
|
27
|
+
app.get('/api/seveneleven/catalog', async (c) => withEdgeCache(c.req.url, {
|
|
28
|
+
ttlSeconds: 60 * 5,
|
|
29
|
+
staleWhileRevalidateSeconds: 60,
|
|
30
|
+
keyPrefix: 'seveneleven-catalog-v1',
|
|
31
|
+
}, () => handleSevenElevenGetCatalogSnapshot(c)));
|
|
32
|
+
}
|