@cartanova/qgrid-cli 1.0.1 → 1.0.3
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/bundle/dist/dist/_virtual/_@oxc-project_runtime@0.122.0/helpers/decorate.js +9 -0
- package/bundle/dist/dist/_virtual/_@oxc-project_runtime@0.122.0/helpers/decorateMetadata.js +6 -0
- package/bundle/dist/dist/_virtual/_rolldown/runtime.js +15 -0
- package/bundle/dist/dist/application/qgrid/oauth.js +140 -0
- package/bundle/dist/dist/application/qgrid/pool.js +100 -0
- package/bundle/dist/dist/application/qgrid/qgrid.frame.js +280 -0
- package/bundle/dist/dist/application/qgrid/qgrid.types.js +79 -0
- package/bundle/dist/dist/application/qgrid/worker.js +183 -0
- package/bundle/dist/dist/application/queries.generated.js +31 -0
- package/bundle/dist/dist/application/request-log/request-log.model.js +113 -0
- package/bundle/dist/dist/application/request-log/request-log.types.js +13 -0
- package/bundle/dist/dist/application/sonamu.generated.js +99 -0
- package/bundle/dist/dist/application/sonamu.generated.sso.js +34 -0
- package/bundle/dist/dist/application/token/token.model.js +157 -0
- package/bundle/dist/dist/application/token/token.types.js +22 -0
- package/bundle/dist/dist/i18n/en.js +63 -0
- package/bundle/dist/dist/i18n/ko.js +62 -0
- package/bundle/dist/dist/i18n/sd.generated.js +272 -0
- package/bundle/dist/dist/index.js +11 -0
- package/bundle/dist/dist/migrations/20260410171405_create__request_logs.js +26 -0
- package/bundle/dist/dist/migrations/20260410171406_create__tokens.js +24 -0
- package/bundle/dist/dist/migrations/20260411120624_alter_tokens_alter2.js +16 -0
- package/bundle/dist/dist/node_modules/.pnpm/@jridgewell_sourcemap-codec@1.5.5/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.js +155 -0
- package/bundle/dist/dist/node_modules/.pnpm/@vitest_expect@4.1.2/node_modules/@vitest/expect/dist/index.js +1484 -0
- package/bundle/dist/dist/node_modules/.pnpm/@vitest_pretty-format@4.1.2/node_modules/@vitest/pretty-format/dist/index.js +887 -0
- package/bundle/dist/dist/node_modules/.pnpm/@vitest_runner@4.1.2/node_modules/@vitest/runner/dist/chunk-artifact.js +2746 -0
- package/bundle/dist/dist/node_modules/.pnpm/@vitest_runner@4.1.2/node_modules/@vitest/runner/dist/index.js +8 -0
- package/bundle/dist/dist/node_modules/.pnpm/@vitest_runner@4.1.2/node_modules/@vitest/runner/dist/utils.js +8 -0
- package/bundle/dist/dist/node_modules/.pnpm/@vitest_snapshot@4.1.2/node_modules/@vitest/snapshot/dist/index.js +662 -0
- package/bundle/dist/dist/node_modules/.pnpm/@vitest_spy@4.1.2/node_modules/@vitest/spy/dist/index.js +386 -0
- package/bundle/dist/dist/node_modules/.pnpm/@vitest_utils@4.1.2/node_modules/@vitest/utils/dist/chunk-pathe.M-eThtNZ.js +82 -0
- package/bundle/dist/dist/node_modules/.pnpm/@vitest_utils@4.1.2/node_modules/@vitest/utils/dist/constants.js +48 -0
- package/bundle/dist/dist/node_modules/.pnpm/@vitest_utils@4.1.2/node_modules/@vitest/utils/dist/diff.js +1307 -0
- package/bundle/dist/dist/node_modules/.pnpm/@vitest_utils@4.1.2/node_modules/@vitest/utils/dist/display.js +558 -0
- package/bundle/dist/dist/node_modules/.pnpm/@vitest_utils@4.1.2/node_modules/@vitest/utils/dist/error.js +33 -0
- package/bundle/dist/dist/node_modules/.pnpm/@vitest_utils@4.1.2/node_modules/@vitest/utils/dist/helpers.js +226 -0
- package/bundle/dist/dist/node_modules/.pnpm/@vitest_utils@4.1.2/node_modules/@vitest/utils/dist/offset.js +27 -0
- package/bundle/dist/dist/node_modules/.pnpm/@vitest_utils@4.1.2/node_modules/@vitest/utils/dist/serialize.js +77 -0
- package/bundle/dist/dist/node_modules/.pnpm/@vitest_utils@4.1.2/node_modules/@vitest/utils/dist/source-map.js +374 -0
- package/bundle/dist/dist/node_modules/.pnpm/@vitest_utils@4.1.2/node_modules/@vitest/utils/dist/timers.js +37 -0
- package/bundle/dist/dist/node_modules/.pnpm/chai@6.2.2/node_modules/chai/index.js +2886 -0
- package/bundle/dist/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/branding.js +10 -0
- package/bundle/dist/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/index.js +109 -0
- package/bundle/dist/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/messages.js +10 -0
- package/bundle/dist/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/overloads.js +10 -0
- package/bundle/dist/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/utils.js +10 -0
- package/bundle/dist/dist/node_modules/.pnpm/magic-string@0.30.21/node_modules/magic-string/dist/magic-string.es.js +1127 -0
- package/bundle/dist/dist/node_modules/.pnpm/pathe@2.0.3/node_modules/pathe/dist/index.js +21 -0
- package/bundle/dist/dist/node_modules/.pnpm/pathe@2.0.3/node_modules/pathe/dist/shared/pathe.M-eThtNZ.js +432 -0
- package/bundle/dist/dist/node_modules/.pnpm/tinybench@2.9.0/node_modules/tinybench/dist/index.js +506 -0
- package/bundle/dist/dist/node_modules/.pnpm/tinyrainbow@3.1.0/node_modules/tinyrainbow/dist/index.js +86 -0
- package/bundle/dist/dist/node_modules/.pnpm/vite@8.0.3_@emnapi_core@1.9.2_@emnapi_runtime@1.9.2_@types_node@25.0.7_esbuild@0.27.4_jiti@2.6.1_sass@1.98.0_tsx@4.21.0/node_modules/vite/dist/node/module-runner.js +426 -0
- package/bundle/dist/dist/node_modules/.pnpm/vitest@4.1.2_@opentelemetry_api@1.9.0_@types_node@25.0.7_vite@8.0.3_@emnapi_core@1.9.2__bb31087278f97a39b31b92010beab980/node_modules/vitest/dist/chunks/_commonjsHelpers.D26ty3Ew.js +6 -0
- package/bundle/dist/dist/node_modules/.pnpm/vitest@4.1.2_@opentelemetry_api@1.9.0_@types_node@25.0.7_vite@8.0.3_@emnapi_core@1.9.2__bb31087278f97a39b31b92010beab980/node_modules/vitest/dist/chunks/benchmark.CX_oY03V.js +41 -0
- package/bundle/dist/dist/node_modules/.pnpm/vitest@4.1.2_@opentelemetry_api@1.9.0_@types_node@25.0.7_vite@8.0.3_@emnapi_core@1.9.2__bb31087278f97a39b31b92010beab980/node_modules/vitest/dist/chunks/evaluatedModules.Dg1zASAC.js +19 -0
- package/bundle/dist/dist/node_modules/.pnpm/vitest@4.1.2_@opentelemetry_api@1.9.0_@types_node@25.0.7_vite@8.0.3_@emnapi_core@1.9.2__bb31087278f97a39b31b92010beab980/node_modules/vitest/dist/chunks/index.Chj8NDwU.js +202 -0
- package/bundle/dist/dist/node_modules/.pnpm/vitest@4.1.2_@opentelemetry_api@1.9.0_@types_node@25.0.7_vite@8.0.3_@emnapi_core@1.9.2__bb31087278f97a39b31b92010beab980/node_modules/vitest/dist/chunks/index.DOa3dzoN.js +16 -0
- package/bundle/dist/dist/node_modules/.pnpm/vitest@4.1.2_@opentelemetry_api@1.9.0_@types_node@25.0.7_vite@8.0.3_@emnapi_core@1.9.2__bb31087278f97a39b31b92010beab980/node_modules/vitest/dist/chunks/rpc.MzXet3jl.js +59 -0
- package/bundle/dist/dist/node_modules/.pnpm/vitest@4.1.2_@opentelemetry_api@1.9.0_@types_node@25.0.7_vite@8.0.3_@emnapi_core@1.9.2__bb31087278f97a39b31b92010beab980/node_modules/vitest/dist/chunks/test.p_J6dB8a.js +2910 -0
- package/bundle/dist/dist/node_modules/.pnpm/vitest@4.1.2_@opentelemetry_api@1.9.0_@types_node@25.0.7_vite@8.0.3_@emnapi_core@1.9.2__bb31087278f97a39b31b92010beab980/node_modules/vitest/dist/chunks/utils.BX5Fg8C4.js +44 -0
- package/bundle/dist/dist/node_modules/.pnpm/vitest@4.1.2_@opentelemetry_api@1.9.0_@types_node@25.0.7_vite@8.0.3_@emnapi_core@1.9.2__bb31087278f97a39b31b92010beab980/node_modules/vitest/dist/index.js +28 -0
- package/bundle/dist/dist/sonamu.config.js +196 -0
- package/bundle/dist/dist/testing/fixture.js +8 -0
- package/bundle/dist/dist/testing/global.js +9 -0
- package/bundle/dist/dist/testing/setup-mocks.js +17 -0
- package/bundle/dist/dist/utils/subset-loaders.js +12 -0
- package/bundle/src/application/qgrid/oauth.ts +181 -0
- package/bundle/src/application/qgrid/pool.ts +132 -0
- package/bundle/src/application/qgrid/qgrid.frame.ts +275 -0
- package/bundle/src/application/qgrid/qgrid.types.ts +117 -0
- package/bundle/src/application/qgrid/worker.ts +234 -0
- package/bundle/src/application/queries.generated.ts +59 -0
- package/bundle/src/application/request-log/request-log.model.ts +155 -0
- package/bundle/src/application/request-log/request-log.types.ts +15 -0
- package/bundle/src/application/sonamu.generated.http +187 -0
- package/bundle/src/application/sonamu.generated.sso.ts +66 -0
- package/bundle/src/application/sonamu.generated.ts +135 -0
- package/bundle/src/application/token/token.model.ts +160 -0
- package/bundle/src/application/token/token.types.ts +26 -0
- package/bundle/src/i18n/en.ts +59 -0
- package/bundle/src/i18n/ko.ts +59 -0
- package/bundle/src/i18n/sd.generated.ts +384 -0
- package/bundle/src/index.ts +6 -0
- package/bundle/src/sonamu.config.ts +248 -0
- package/bundle/src/src/application/qgrid/oauth.ts +181 -0
- package/bundle/src/src/application/qgrid/pool.ts +132 -0
- package/bundle/src/src/application/qgrid/qgrid.frame.ts +275 -0
- package/bundle/src/src/application/qgrid/qgrid.types.ts +117 -0
- package/bundle/src/src/application/qgrid/worker.ts +234 -0
- package/bundle/src/src/application/queries.generated.ts +59 -0
- package/bundle/src/src/application/request-log/request-log.entity.json +75 -0
- package/bundle/src/src/application/request-log/request-log.model.ts +155 -0
- package/bundle/src/src/application/request-log/request-log.types.ts +15 -0
- package/bundle/src/src/application/sonamu.generated.http +187 -0
- package/bundle/src/src/application/sonamu.generated.sso.ts +66 -0
- package/bundle/src/src/application/sonamu.generated.ts +135 -0
- package/bundle/src/src/application/token/token.entity.json +37 -0
- package/bundle/src/src/application/token/token.model.ts +160 -0
- package/bundle/src/src/application/token/token.types.ts +26 -0
- package/bundle/src/src/i18n/en.ts +59 -0
- package/bundle/src/src/i18n/ko.ts +59 -0
- package/bundle/src/src/i18n/sd.generated.ts +384 -0
- package/bundle/src/src/index.ts +6 -0
- package/bundle/src/src/migrations/20260410171405_create__request_logs.ts +23 -0
- package/bundle/src/src/migrations/20260410171406_create__tokens.ts +21 -0
- package/bundle/src/src/migrations/20260411120624_alter_tokens_alter2.ts +15 -0
- package/bundle/src/src/sonamu.config.ts +248 -0
- package/bundle/src/src/testing/fixture.ts +6 -0
- package/bundle/src/src/testing/global.ts +6 -0
- package/bundle/src/src/testing/setup-mocks.ts +44 -0
- package/bundle/src/src/typings/fastify.d.ts +8 -0
- package/bundle/src/src/typings/sonamu.d.ts +17 -0
- package/bundle/src/src/utils/subset-loaders.ts +11 -0
- package/bundle/src/testing/fixture.ts +6 -0
- package/bundle/src/testing/global.ts +6 -0
- package/bundle/src/testing/setup-mocks.ts +44 -0
- package/bundle/src/typings/fastify.d.ts +8 -0
- package/bundle/src/typings/sonamu.d.ts +17 -0
- package/bundle/src/utils/subset-loaders.ts +11 -0
- package/bundle/web-dist/web-dist/client/assets/index-DlPchHBY.js +116 -0
- package/bundle/web-dist/web-dist/client/assets/index-vZsvyuFn.css +1 -0
- package/bundle/web-dist/web-dist/client/assets/rolldown-runtime-Dw2cE7zH.js +1 -0
- package/bundle/web-dist/web-dist/client/assets/routes-CagDz23B.js +1 -0
- package/bundle/web-dist/web-dist/client/assets/sd.generated-BrQoQXvv.js +47 -0
- package/bundle/web-dist/web-dist/client/assets/services.generated-BspO2L0j.js +1 -0
- package/bundle/web-dist/web-dist/client/assets/show-aXUGa0gm.js +29 -0
- package/bundle/web-dist/web-dist/client/assets/tokens-DRkXtZ3Q.js +1 -0
- package/bundle/web-dist/web-dist/client/assets/vendor-react-DH4kUfK2.js +9 -0
- package/bundle/web-dist/web-dist/client/assets/vendor-tanstack-CUxlo6YN.js +1 -0
- package/bundle/web-dist/web-dist/client/index.html +18 -0
- package/bundle/web-dist/web-dist/server/assets/react-dom-CGIzOSuj.js +335 -0
- package/bundle/web-dist/web-dist/server/assets/routes-ClzReRep.js +490 -0
- package/bundle/web-dist/web-dist/server/assets/sd.generated-BnonqqKr.js +21694 -0
- package/bundle/web-dist/web-dist/server/assets/services.generated-D5ABD5fr.js +836 -0
- package/bundle/web-dist/web-dist/server/assets/show-C9pcdSxc.js +23364 -0
- package/bundle/web-dist/web-dist/server/assets/tokens-JH6FsnWZ.js +860 -0
- package/bundle/web-dist/web-dist/server/entry-server.generated.js +44646 -0
- package/package.json +11 -10
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @generated
|
|
3
|
+
* 직접 수정하지 마세요.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Sonamu } from "sonamu";
|
|
7
|
+
|
|
8
|
+
const DEFAULT_LOCALE = "ko" as const;
|
|
9
|
+
const SUPPORTED_LOCALES = ["ko", "en"] as const;
|
|
10
|
+
function getCurrentLocale(): (typeof SUPPORTED_LOCALES)[number] {
|
|
11
|
+
try {
|
|
12
|
+
const ctx = Sonamu.getContext();
|
|
13
|
+
return (ctx.locale as (typeof SUPPORTED_LOCALES)[number]) ?? DEFAULT_LOCALE;
|
|
14
|
+
} catch (_) {
|
|
15
|
+
return DEFAULT_LOCALE;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
import en from "./en";
|
|
20
|
+
import ko from "./ko";
|
|
21
|
+
|
|
22
|
+
// react-components i18n keys
|
|
23
|
+
const rcKeysKo = {
|
|
24
|
+
// AsyncSelect Component
|
|
25
|
+
"rc.asyncSelect.loading": "로딩 중...",
|
|
26
|
+
"rc.asyncSelect.noOptions": "옵션이 없습니다",
|
|
27
|
+
"rc.asyncSelect.noResults": "검색 결과가 없습니다",
|
|
28
|
+
"rc.asyncSelect.selectPlaceholder": "선택하세요...",
|
|
29
|
+
|
|
30
|
+
// Combobox Component
|
|
31
|
+
"rc.combobox.noResults": "검색 결과가 없습니다",
|
|
32
|
+
"rc.combobox.selectPlaceholder": "선택하세요...",
|
|
33
|
+
|
|
34
|
+
// DatePicker Component
|
|
35
|
+
"rc.datePicker.pickDate": "날짜 선택",
|
|
36
|
+
"rc.datePicker.placeholder": "날짜 선택",
|
|
37
|
+
"rc.datePicker.selectDate": "날짜 선택",
|
|
38
|
+
|
|
39
|
+
// DateSelectorMultiple Component
|
|
40
|
+
"rc.dateSelectorMultiple.addDate": "날짜 추가",
|
|
41
|
+
"rc.dateSelectorMultiple.dateRange": "기간",
|
|
42
|
+
"rc.dateSelectorMultiple.placeholder": "날짜 선택",
|
|
43
|
+
"rc.dateSelectorMultiple.singleDate": "단일",
|
|
44
|
+
|
|
45
|
+
// FileInput Component
|
|
46
|
+
"rc.fileInput.browseFiles": "파일 찾기",
|
|
47
|
+
"rc.fileInput.dropZone": "파일을 드래그하거나 클릭하여 업로드",
|
|
48
|
+
"rc.fileInput.filePlaceholder": "파일",
|
|
49
|
+
"rc.fileInput.imagePlaceholder": "이미지",
|
|
50
|
+
"rc.fileInput.maxFilesExceeded": (maxFiles: number) =>
|
|
51
|
+
`최대 ${maxFiles}개 파일만 업로드 가능합니다`,
|
|
52
|
+
"rc.fileInput.pending": "대기 중",
|
|
53
|
+
"rc.fileInput.remove": "제거",
|
|
54
|
+
"rc.fileInput.uploadFailed": "업로드 실패",
|
|
55
|
+
"rc.fileInput.uploading": "업로드 중...",
|
|
56
|
+
|
|
57
|
+
// MonthPickerMultiple Component
|
|
58
|
+
"rc.monthPickerMultiple.addMonth": "월 추가",
|
|
59
|
+
"rc.monthPickerMultiple.dateRange": "기간",
|
|
60
|
+
"rc.monthPickerMultiple.endDate": "종료일",
|
|
61
|
+
"rc.monthPickerMultiple.placeholder": "월 선택",
|
|
62
|
+
"rc.monthPickerMultiple.singleDate": "단일",
|
|
63
|
+
"rc.monthPickerMultiple.startDate": "시작일",
|
|
64
|
+
|
|
65
|
+
// MultiSelect Component
|
|
66
|
+
"rc.multiSelect.clear": "전체 해제",
|
|
67
|
+
"rc.multiSelect.close": "닫기",
|
|
68
|
+
"rc.multiSelect.moreItems": (count: number) => `+${count}개 더보기`,
|
|
69
|
+
"rc.multiSelect.noOptions": "옵션이 없습니다",
|
|
70
|
+
"rc.multiSelect.noOptionsSelected": "선택된 옵션이 없습니다",
|
|
71
|
+
"rc.multiSelect.noResults": "검색 결과가 없습니다",
|
|
72
|
+
"rc.multiSelect.optionsCount": (count: number) => `${count}개 옵션`,
|
|
73
|
+
"rc.multiSelect.selectAll": "전체 선택",
|
|
74
|
+
"rc.multiSelect.selectPlaceholder": "선택하세요...",
|
|
75
|
+
|
|
76
|
+
// Pagination Component
|
|
77
|
+
"rc.pagination.next": "다음",
|
|
78
|
+
"rc.pagination.previous": "이전",
|
|
79
|
+
"rc.pagination.showing": (start: number, end: number, total: number) =>
|
|
80
|
+
`${total}개 중 ${start}-${end}`,
|
|
81
|
+
|
|
82
|
+
// Calendar Component
|
|
83
|
+
"rc.calendar.month.0": "1월",
|
|
84
|
+
"rc.calendar.month.1": "2월",
|
|
85
|
+
"rc.calendar.month.2": "3월",
|
|
86
|
+
"rc.calendar.month.3": "4월",
|
|
87
|
+
"rc.calendar.month.4": "5월",
|
|
88
|
+
"rc.calendar.month.5": "6월",
|
|
89
|
+
"rc.calendar.month.6": "7월",
|
|
90
|
+
"rc.calendar.month.7": "8월",
|
|
91
|
+
"rc.calendar.month.8": "9월",
|
|
92
|
+
"rc.calendar.month.9": "10월",
|
|
93
|
+
"rc.calendar.month.10": "11월",
|
|
94
|
+
"rc.calendar.month.11": "12월",
|
|
95
|
+
|
|
96
|
+
// Sonamu Filter Component
|
|
97
|
+
"rc.sonamuFilter.title": "소나무 필터",
|
|
98
|
+
"rc.sonamuFilter.apply": "적용",
|
|
99
|
+
"rc.sonamuFilter.reset": "초기화",
|
|
100
|
+
"rc.sonamuFilter.addRule": "규칙 추가",
|
|
101
|
+
"rc.sonamuFilter.noRulesYet": '아직 규칙이 없습니다. "+ 규칙 추가"를 클릭하여 시작하세요.',
|
|
102
|
+
"rc.sonamuFilter.selectField": "필드 선택",
|
|
103
|
+
"rc.sonamuFilter.selectOperator": "연산자 선택",
|
|
104
|
+
"rc.sonamuFilter.selectOperatorFirst": "먼저 연산자를 선택하세요",
|
|
105
|
+
"rc.sonamuFilter.enterValue": "값 입력",
|
|
106
|
+
"rc.sonamuFilter.enterNumber": "숫자 입력",
|
|
107
|
+
"rc.sonamuFilter.notSupported": "지원하지 않음",
|
|
108
|
+
"rc.sonamuFilter.startDate": "시작일",
|
|
109
|
+
"rc.sonamuFilter.endDate": "종료일",
|
|
110
|
+
"rc.sonamuFilter.appliedFilters": "🌲 적용된 소나무 필터",
|
|
111
|
+
|
|
112
|
+
// Common
|
|
113
|
+
"rc.common.cancel": "취소",
|
|
114
|
+
"rc.common.save": "저장",
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const rcKeysEn = {
|
|
118
|
+
// AsyncSelect Component
|
|
119
|
+
"rc.asyncSelect.loading": "Loading...",
|
|
120
|
+
"rc.asyncSelect.noOptions": "No options",
|
|
121
|
+
"rc.asyncSelect.noResults": "No results",
|
|
122
|
+
"rc.asyncSelect.selectPlaceholder": "Select...",
|
|
123
|
+
|
|
124
|
+
// Combobox Component
|
|
125
|
+
"rc.combobox.noResults": "No results",
|
|
126
|
+
"rc.combobox.selectPlaceholder": "Select...",
|
|
127
|
+
|
|
128
|
+
// DatePicker Component
|
|
129
|
+
"rc.datePicker.pickDate": "Pick date",
|
|
130
|
+
"rc.datePicker.placeholder": "Pick date",
|
|
131
|
+
"rc.datePicker.selectDate": "Select date",
|
|
132
|
+
|
|
133
|
+
// DateSelectorMultiple Component
|
|
134
|
+
"rc.dateSelectorMultiple.addDate": "Add date",
|
|
135
|
+
"rc.dateSelectorMultiple.dateRange": "Range",
|
|
136
|
+
"rc.dateSelectorMultiple.placeholder": "Pick date",
|
|
137
|
+
"rc.dateSelectorMultiple.singleDate": "Single",
|
|
138
|
+
|
|
139
|
+
// FileInput Component
|
|
140
|
+
"rc.fileInput.browseFiles": "Browse files",
|
|
141
|
+
"rc.fileInput.dropZone": "Drag files here or click to upload",
|
|
142
|
+
"rc.fileInput.filePlaceholder": "File",
|
|
143
|
+
"rc.fileInput.imagePlaceholder": "Image",
|
|
144
|
+
"rc.fileInput.maxFilesExceeded": (maxFiles: number) => `Maximum ${maxFiles} files allowed`,
|
|
145
|
+
"rc.fileInput.pending": "Pending",
|
|
146
|
+
"rc.fileInput.remove": "Remove",
|
|
147
|
+
"rc.fileInput.uploadFailed": "Upload failed",
|
|
148
|
+
"rc.fileInput.uploading": "Uploading...",
|
|
149
|
+
|
|
150
|
+
// MonthPickerMultiple Component
|
|
151
|
+
"rc.monthPickerMultiple.addMonth": "Add month",
|
|
152
|
+
"rc.monthPickerMultiple.dateRange": "Range",
|
|
153
|
+
"rc.monthPickerMultiple.endDate": "End date",
|
|
154
|
+
"rc.monthPickerMultiple.placeholder": "Pick month",
|
|
155
|
+
"rc.monthPickerMultiple.singleDate": "Single",
|
|
156
|
+
"rc.monthPickerMultiple.startDate": "Start date",
|
|
157
|
+
|
|
158
|
+
// MultiSelect Component
|
|
159
|
+
"rc.multiSelect.clear": "Clear all",
|
|
160
|
+
"rc.multiSelect.close": "Close",
|
|
161
|
+
"rc.multiSelect.moreItems": (count: number) => `+${count} more`,
|
|
162
|
+
"rc.multiSelect.noOptions": "No options",
|
|
163
|
+
"rc.multiSelect.noOptionsSelected": "No options selected",
|
|
164
|
+
"rc.multiSelect.noResults": "No results",
|
|
165
|
+
"rc.multiSelect.optionsCount": (count: number) => `${count} options`,
|
|
166
|
+
"rc.multiSelect.selectAll": "Select all",
|
|
167
|
+
"rc.multiSelect.selectPlaceholder": "Select...",
|
|
168
|
+
|
|
169
|
+
// Pagination Component
|
|
170
|
+
"rc.pagination.next": "Next",
|
|
171
|
+
"rc.pagination.previous": "Previous",
|
|
172
|
+
"rc.pagination.showing": (start: number, end: number, total: number) =>
|
|
173
|
+
`Showing ${start}-${end} of ${total}`,
|
|
174
|
+
|
|
175
|
+
// Calendar Component
|
|
176
|
+
"rc.calendar.month.0": "January",
|
|
177
|
+
"rc.calendar.month.1": "February",
|
|
178
|
+
"rc.calendar.month.2": "March",
|
|
179
|
+
"rc.calendar.month.3": "April",
|
|
180
|
+
"rc.calendar.month.4": "May",
|
|
181
|
+
"rc.calendar.month.5": "June",
|
|
182
|
+
"rc.calendar.month.6": "July",
|
|
183
|
+
"rc.calendar.month.7": "August",
|
|
184
|
+
"rc.calendar.month.8": "September",
|
|
185
|
+
"rc.calendar.month.9": "October",
|
|
186
|
+
"rc.calendar.month.10": "November",
|
|
187
|
+
"rc.calendar.month.11": "December",
|
|
188
|
+
|
|
189
|
+
// Sonamu Filter Component
|
|
190
|
+
"rc.sonamuFilter.title": "Sonamu Filter",
|
|
191
|
+
"rc.sonamuFilter.apply": "Apply",
|
|
192
|
+
"rc.sonamuFilter.reset": "Reset",
|
|
193
|
+
"rc.sonamuFilter.addRule": "Add Rule",
|
|
194
|
+
"rc.sonamuFilter.noRulesYet": 'No rules yet. Click "+ Add Rule" to start.',
|
|
195
|
+
"rc.sonamuFilter.selectField": "Select field",
|
|
196
|
+
"rc.sonamuFilter.selectOperator": "Operator",
|
|
197
|
+
"rc.sonamuFilter.selectOperatorFirst": "Select operator first",
|
|
198
|
+
"rc.sonamuFilter.enterValue": "Enter value",
|
|
199
|
+
"rc.sonamuFilter.enterNumber": "Enter number",
|
|
200
|
+
"rc.sonamuFilter.notSupported": "Not supported",
|
|
201
|
+
"rc.sonamuFilter.startDate": "Start date",
|
|
202
|
+
"rc.sonamuFilter.endDate": "End date",
|
|
203
|
+
"rc.sonamuFilter.appliedFilters": "🌲 Applied SonamuFilters",
|
|
204
|
+
|
|
205
|
+
// Common
|
|
206
|
+
"rc.common.cancel": "Cancel",
|
|
207
|
+
"rc.common.save": "Save",
|
|
208
|
+
} as const;
|
|
209
|
+
|
|
210
|
+
// entity.json에서 추출한 entity labels (defaultLocale 전용)
|
|
211
|
+
const entityLabels = {
|
|
212
|
+
"entity.RequestLog": "RequestLog",
|
|
213
|
+
"entity.RequestLog.id": "ID",
|
|
214
|
+
"entity.RequestLog.created_at": "등록일시",
|
|
215
|
+
"entity.RequestLog.token_name": "토큰 이름",
|
|
216
|
+
"entity.RequestLog.query": "prompt 전문",
|
|
217
|
+
"entity.RequestLog.response": "llm 응답 전문",
|
|
218
|
+
"entity.RequestLog.input_tokens": "입력 토큰",
|
|
219
|
+
"entity.RequestLog.output_tokens": "출력 토큰",
|
|
220
|
+
"entity.RequestLog.cache_read_tokens": "캐시 읽기 토큰",
|
|
221
|
+
"entity.RequestLog.cache_creation_tokens": "캐시 생성 토큰",
|
|
222
|
+
"entity.RequestLog.duration_ms": "응답 소요시간(ms)",
|
|
223
|
+
"enum.RequestLogOrderBy.id-desc": "ID최신순",
|
|
224
|
+
"enum.RequestLogSearchField.id": "ID",
|
|
225
|
+
"enum.RequestLogSearchField.token_name": "토큰이름",
|
|
226
|
+
"enum.RequestLogSearchField.query": "쿼리",
|
|
227
|
+
"entity.Token": "Token",
|
|
228
|
+
"entity.Token.id": "ID",
|
|
229
|
+
"entity.Token.created_at": "등록일시",
|
|
230
|
+
"entity.Token.token": "access_token",
|
|
231
|
+
"entity.Token.name": "토큰 이름",
|
|
232
|
+
"entity.Token.refresh_token": "refresh_token",
|
|
233
|
+
"entity.Token.expires_at": "만료 시각 (epoch ms)",
|
|
234
|
+
"entity.Token.account_uuid": "Anthropic 계정 UUID",
|
|
235
|
+
"entity.Token.active": "활성 여부",
|
|
236
|
+
"enum.TokenOrderBy.id-desc": "ID최신순",
|
|
237
|
+
"enum.TokenSearchField.id": "ID",
|
|
238
|
+
"enum.TokenSearchField.name": "이름",
|
|
239
|
+
} as const;
|
|
240
|
+
|
|
241
|
+
// defaultLocale의 dictionary를 기준으로 키 추출
|
|
242
|
+
type RCKeys = typeof rcKeysKo;
|
|
243
|
+
type ProjectDictionary = typeof ko;
|
|
244
|
+
type EntityLabels = typeof entityLabels;
|
|
245
|
+
type RawMergedDictionary = RCKeys &
|
|
246
|
+
Omit<EntityLabels, keyof (RCKeys & ProjectDictionary)> &
|
|
247
|
+
ProjectDictionary;
|
|
248
|
+
|
|
249
|
+
// 키는 유지하되, 값 타입은 string 또는 함수로 일반화 (다른 locale의 리터럴 타입 충돌 방지)
|
|
250
|
+
export type MergedDictionary = {
|
|
251
|
+
[K in keyof RawMergedDictionary]: RawMergedDictionary[K] extends (...args: infer P) => string
|
|
252
|
+
? (...args: P) => string
|
|
253
|
+
: string;
|
|
254
|
+
};
|
|
255
|
+
export type DictKey = keyof MergedDictionary;
|
|
256
|
+
export type LocalizedString = string & { __brand: "LocalizedString" };
|
|
257
|
+
|
|
258
|
+
export function defineLocale(dict: Partial<MergedDictionary>) {
|
|
259
|
+
return dict;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// 각 locale별로 rc-keys + entity labels + 프로젝트 dict 합침
|
|
263
|
+
const dictionaries: Record<string, Partial<MergedDictionary>> = {
|
|
264
|
+
ko: { ...rcKeysKo, ...entityLabels, ...ko },
|
|
265
|
+
en: { ...rcKeysEn, ...en },
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
type SDReturnType<K extends DictKey> = MergedDictionary[K] extends (...args: infer P) => string
|
|
269
|
+
? (...args: P) => LocalizedString
|
|
270
|
+
: LocalizedString;
|
|
271
|
+
|
|
272
|
+
function getDictValue<K extends DictKey>(key: K, locale: string): SDReturnType<K> {
|
|
273
|
+
// 1. 지정된 locale에서 조회
|
|
274
|
+
const dict = dictionaries[locale];
|
|
275
|
+
if (dict?.[key] !== undefined) {
|
|
276
|
+
return dict[key] as unknown as SDReturnType<K>;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// 2. default locale에서 조회
|
|
280
|
+
if (locale !== DEFAULT_LOCALE && dictionaries[DEFAULT_LOCALE]?.[key] !== undefined) {
|
|
281
|
+
return dictionaries[DEFAULT_LOCALE][key] as unknown as SDReturnType<K>;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// 3. supported locales 순회
|
|
285
|
+
for (const supportedLocale of SUPPORTED_LOCALES) {
|
|
286
|
+
if (supportedLocale !== locale && supportedLocale !== DEFAULT_LOCALE) {
|
|
287
|
+
if (dictionaries[supportedLocale]?.[key] !== undefined) {
|
|
288
|
+
return dictionaries[supportedLocale][key] as unknown as SDReturnType<K>;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// 4. 모두 실패 시 key 반환
|
|
294
|
+
return key as unknown as SDReturnType<K>;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Sonamu Dictionary 함수
|
|
299
|
+
* locale에 맞는 번역 텍스트를 반환합니다.
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* SD("common.save") // → "저장" (LocalizedString)
|
|
303
|
+
* SD("user.notFound")(1) // → "존재하지 않는 User ID 1" (LocalizedString)
|
|
304
|
+
*/
|
|
305
|
+
export function SD<K extends DictKey>(key: K): SDReturnType<K> {
|
|
306
|
+
const locale = getCurrentLocale();
|
|
307
|
+
return getDictValue(key, locale);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* 특정 locale의 번역 텍스트를 반환하는 함수를 생성합니다.
|
|
312
|
+
*
|
|
313
|
+
* @example
|
|
314
|
+
* const EN = SD.locale("en");
|
|
315
|
+
* EN("common.save") // → "Save"
|
|
316
|
+
*/
|
|
317
|
+
SD.locale =
|
|
318
|
+
(locale: (typeof SUPPORTED_LOCALES)[number]) =>
|
|
319
|
+
<K extends DictKey>(key: K): SDReturnType<K> => {
|
|
320
|
+
return getDictValue(key, locale);
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
// Localized 가능한 Column 타입 계산
|
|
324
|
+
type LocalizedBaseColumn<T> = {
|
|
325
|
+
[K in keyof T & string]: K extends `${infer Base}_${(typeof SUPPORTED_LOCALES)[number]}`
|
|
326
|
+
? Base
|
|
327
|
+
: K;
|
|
328
|
+
}[keyof T & string];
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* locale에 따라 적절한 컬럼 값을 반환합니다.
|
|
332
|
+
* DB에 name, name_ko, name_en처럼 localized column이 있을 때 사용합니다.
|
|
333
|
+
*
|
|
334
|
+
* 우선순위 (지원 로케일은 ko/jp/en이고, 서비스의 기본 로케일은 ko, 사용자의 로케일은 jp일 때): column_jp → column → column_ko → column_en
|
|
335
|
+
* 우선순위 (지원 로케일은 ko/jp/en이고, 서비스의 기본 로케일은 en, 사용자의 로케일은 ko일 때): column_ko → column → column_en → column_jp
|
|
336
|
+
*
|
|
337
|
+
* @example
|
|
338
|
+
* localizedColumn(tag, "name")
|
|
339
|
+
*/
|
|
340
|
+
export function localizedColumn<
|
|
341
|
+
T extends Record<string, unknown>,
|
|
342
|
+
K extends LocalizedBaseColumn<T>,
|
|
343
|
+
>(row: T, column: K): string | undefined {
|
|
344
|
+
const locale = getCurrentLocale();
|
|
345
|
+
const otherLocales = SUPPORTED_LOCALES.filter(
|
|
346
|
+
(l: string) => l !== locale && l !== DEFAULT_LOCALE,
|
|
347
|
+
);
|
|
348
|
+
const localizedKey = (column: K, locale: (typeof SUPPORTED_LOCALES)[number]) =>
|
|
349
|
+
`${column}_${locale}`;
|
|
350
|
+
const keys = [
|
|
351
|
+
localizedKey(column, locale),
|
|
352
|
+
column,
|
|
353
|
+
localizedKey(column, DEFAULT_LOCALE),
|
|
354
|
+
...otherLocales.map((l) => localizedKey(column, l)),
|
|
355
|
+
];
|
|
356
|
+
|
|
357
|
+
for (const key of keys) {
|
|
358
|
+
if (!(key in row)) {
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (row[key] !== null && row[key] !== undefined && row[key] !== "") {
|
|
363
|
+
return String(row[key]);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return undefined;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Enum의 localized labels를 Proxy로 반환합니다.
|
|
372
|
+
* Select 컴포넌트 등에서 EnumLabel[key] 대신 사용합니다.
|
|
373
|
+
*
|
|
374
|
+
* @example
|
|
375
|
+
* SD.enumLabels("TagOrderBy")[key] // → 현재 locale의 라벨
|
|
376
|
+
*/
|
|
377
|
+
SD.enumLabels = (enumName: string): Record<string, LocalizedString> => {
|
|
378
|
+
return new Proxy({} as Record<string, LocalizedString>, {
|
|
379
|
+
get(_, key: string) {
|
|
380
|
+
const dictKey = `enum.${enumName}.${key}` as DictKey;
|
|
381
|
+
return getDictValue(dictKey, getCurrentLocale());
|
|
382
|
+
},
|
|
383
|
+
});
|
|
384
|
+
};
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
|
|
3
|
+
import { getConsoleSink } from "@logtape/logtape";
|
|
4
|
+
import { getPrettyFormatter } from "@logtape/pretty";
|
|
5
|
+
import dotenv from "dotenv";
|
|
6
|
+
import { CachePresets, defineConfig } from "sonamu";
|
|
7
|
+
import { drivers as cacheDrivers, store } from "sonamu/cache";
|
|
8
|
+
import { drivers } from "sonamu/storage";
|
|
9
|
+
|
|
10
|
+
dotenv.config({ path: path.join(import.meta.dirname, "../.env") });
|
|
11
|
+
|
|
12
|
+
const host = process.env.HOST ?? "localhost";
|
|
13
|
+
const port = Number(process.env.PORT ?? 44900);
|
|
14
|
+
|
|
15
|
+
export default defineConfig({
|
|
16
|
+
projectName: process.env.PROJECT_NAME ?? "SonamuProject",
|
|
17
|
+
database: {
|
|
18
|
+
name: process.env.QGRID_DB_NAME ?? "qgrid",
|
|
19
|
+
defaultOptions: {
|
|
20
|
+
connection: {
|
|
21
|
+
host: process.env.QGRID_DB_HOST ?? "localhost",
|
|
22
|
+
port: Number(process.env.QGRID_DB_PORT ?? 44901),
|
|
23
|
+
user: process.env.QGRID_DB_USER ?? "postgres",
|
|
24
|
+
password: process.env.QGRID_DB_PASSWORD ?? "postgres",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
environments: {
|
|
28
|
+
production: {
|
|
29
|
+
connection: {
|
|
30
|
+
host: process.env.DEV0_DB_HOST,
|
|
31
|
+
port: Number(process.env.DEV0_DB_PORT),
|
|
32
|
+
database: process.env.DATABASE_NAME,
|
|
33
|
+
user: process.env.DEV0_DB_USER,
|
|
34
|
+
password: process.env.DEV0_DB_PASSWORD,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
fixture: {
|
|
38
|
+
connection: {
|
|
39
|
+
host: process.env.DEV0_DB_HOST,
|
|
40
|
+
port: Number(process.env.DEV0_DB_PORT),
|
|
41
|
+
database: process.env.DATABASE_NAME,
|
|
42
|
+
user: process.env.DEV0_DB_USER,
|
|
43
|
+
password: process.env.DEV0_DB_PASSWORD,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
api: {
|
|
49
|
+
dir: "api",
|
|
50
|
+
timezone: "Asia/Seoul",
|
|
51
|
+
route: {
|
|
52
|
+
prefix: "/api",
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
i18n: {
|
|
56
|
+
defaultLocale: "ko",
|
|
57
|
+
supportedLocales: ["ko", "en"],
|
|
58
|
+
},
|
|
59
|
+
sync: {
|
|
60
|
+
targets: ["web"],
|
|
61
|
+
},
|
|
62
|
+
slackConfirm:
|
|
63
|
+
process.env.SLACK_BOT_TOKEN && process.env.SLACK_CHANNEL_ID
|
|
64
|
+
? {
|
|
65
|
+
targets: ["development_master", "production_master"],
|
|
66
|
+
botToken: process.env.SLACK_BOT_TOKEN ?? "",
|
|
67
|
+
channelId: process.env.SLACK_CHANNEL_ID ?? "",
|
|
68
|
+
}
|
|
69
|
+
: undefined,
|
|
70
|
+
test: {
|
|
71
|
+
parallel: true,
|
|
72
|
+
maxWorkers: 4,
|
|
73
|
+
devRunner: { enabled: true },
|
|
74
|
+
},
|
|
75
|
+
logging: {
|
|
76
|
+
sinks: {
|
|
77
|
+
console: getConsoleSink({
|
|
78
|
+
formatter: getPrettyFormatter({
|
|
79
|
+
timestamp: "time",
|
|
80
|
+
categoryWidth: 20,
|
|
81
|
+
categoryTruncate: "middle",
|
|
82
|
+
}),
|
|
83
|
+
}),
|
|
84
|
+
},
|
|
85
|
+
loggers: [
|
|
86
|
+
{
|
|
87
|
+
category: ["sonamu"],
|
|
88
|
+
sinks: ["console"],
|
|
89
|
+
lowestLevel: process.env.NODE_ENV === "test" ? "warning" : "debug",
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
server: {
|
|
94
|
+
listen: { port, host },
|
|
95
|
+
plugins: {
|
|
96
|
+
formbody: true,
|
|
97
|
+
qs: true,
|
|
98
|
+
multipart: { limits: { fileSize: 1024 * 1024 * 30 } },
|
|
99
|
+
static: {
|
|
100
|
+
root: path.join(import.meta.dirname, "/../", "public"),
|
|
101
|
+
prefix: "/api/public",
|
|
102
|
+
},
|
|
103
|
+
custom: (server) => {
|
|
104
|
+
// OAuth 콜백 — Anthropic이 /callback으로 리다이렉트
|
|
105
|
+
server.get("/callback", async (request, reply) => {
|
|
106
|
+
const { code, state } = request.query as { code?: string; state?: string };
|
|
107
|
+
if (!code || !state) {
|
|
108
|
+
return reply.redirect("/?oauth=error&reason=missing_params");
|
|
109
|
+
}
|
|
110
|
+
// Frame의 oauthCallback으로 위임
|
|
111
|
+
try {
|
|
112
|
+
const { QgridFrame } = await import("./application/qgrid/qgrid.frame");
|
|
113
|
+
await QgridFrame.handleOAuthCallback(code, state, reply);
|
|
114
|
+
} catch (e) {
|
|
115
|
+
return reply.redirect(
|
|
116
|
+
`/?oauth=error&reason=${encodeURIComponent((e as Error).message)}`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
apiConfig: {
|
|
123
|
+
contextProvider: (defaultContext, request) => {
|
|
124
|
+
return {
|
|
125
|
+
...defaultContext,
|
|
126
|
+
ip: request.ip,
|
|
127
|
+
body: request.body,
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
guardHandler: (_guard, _request, _api) => {
|
|
131
|
+
if (_guard === "user") {
|
|
132
|
+
console.log("user guard");
|
|
133
|
+
}
|
|
134
|
+
console.log("NOTHING YET");
|
|
135
|
+
},
|
|
136
|
+
cacheControlHandler: (req) => {
|
|
137
|
+
switch (req.type) {
|
|
138
|
+
case "assets":
|
|
139
|
+
// Hash 포함된 파일: 영구 캐시
|
|
140
|
+
if (req.path.match(/-[a-f0-9]+\./)) {
|
|
141
|
+
return CachePresets.immutable;
|
|
142
|
+
}
|
|
143
|
+
return CachePresets.longLived;
|
|
144
|
+
|
|
145
|
+
case "api":
|
|
146
|
+
// GET 요청만 캐싱 고려
|
|
147
|
+
if (req.method === "GET") {
|
|
148
|
+
// 특정 경로는 짧은 캐시
|
|
149
|
+
if (req.path.startsWith("/api/static-data")) {
|
|
150
|
+
return CachePresets.shortLived;
|
|
151
|
+
}
|
|
152
|
+
if (req.path.startsWith("/api/terms")) {
|
|
153
|
+
return CachePresets.mediumLived;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// 기본: 캐시 없음
|
|
157
|
+
return CachePresets.noCache;
|
|
158
|
+
|
|
159
|
+
case "ssr":
|
|
160
|
+
// SSR 페이지: 10초 캐시
|
|
161
|
+
return CachePresets.ssr;
|
|
162
|
+
|
|
163
|
+
case "csr":
|
|
164
|
+
// CSR fallback (index.html): 1분 캐시
|
|
165
|
+
return CachePresets.shortLived;
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
storage: {
|
|
170
|
+
drivers: {
|
|
171
|
+
fs: drivers.fs({
|
|
172
|
+
location: path.join(import.meta.dirname, "/../public/uploaded"),
|
|
173
|
+
visibility: "public",
|
|
174
|
+
urlBuilder: {
|
|
175
|
+
async generateURL(key) {
|
|
176
|
+
return `/api/public/uploaded/${key}`;
|
|
177
|
+
},
|
|
178
|
+
async generateSignedURL(key) {
|
|
179
|
+
return `/api/public/uploaded/${key}`;
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
}),
|
|
183
|
+
s3: drivers.s3({
|
|
184
|
+
credentials: {
|
|
185
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? "",
|
|
186
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? "",
|
|
187
|
+
},
|
|
188
|
+
region: process.env.S3_REGION ?? "ap-northeast-2",
|
|
189
|
+
bucket: process.env.S3_BUCKET ?? "sonamu_default_bucket",
|
|
190
|
+
visibility: "private",
|
|
191
|
+
}),
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
cache: {
|
|
195
|
+
default: "main",
|
|
196
|
+
stores: {
|
|
197
|
+
main: store().useL1Layer(cacheDrivers.memory({ maxSize: "50mb" })),
|
|
198
|
+
},
|
|
199
|
+
ttl: "5m",
|
|
200
|
+
prefix: "",
|
|
201
|
+
},
|
|
202
|
+
lifecycle: {
|
|
203
|
+
onStart: async () => {
|
|
204
|
+
// DB 마이그레이션 자동 실행 (테이블 없으면 생성)
|
|
205
|
+
try {
|
|
206
|
+
const { TokenModel } = await import("./application/token/token.model");
|
|
207
|
+
const knex = TokenModel.getDB("w");
|
|
208
|
+
const migrationsDir = path.join(import.meta.dirname, "../src/migrations");
|
|
209
|
+
const [batch, log] = await knex.migrate.latest({
|
|
210
|
+
directory: migrationsDir,
|
|
211
|
+
});
|
|
212
|
+
if (log.length > 0) {
|
|
213
|
+
console.log(`✓ Migration: ${log.length} applied (batch ${batch})`);
|
|
214
|
+
log.forEach((name: string) => console.log(` ✓ ${name}`));
|
|
215
|
+
}
|
|
216
|
+
} catch (e) {
|
|
217
|
+
console.warn(`⚠ Migration skipped: ${(e as Error).message}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Pool 초기화
|
|
221
|
+
const { initPool } = await import("./application/qgrid/pool");
|
|
222
|
+
try {
|
|
223
|
+
const { TokenModel } = await import("./application/token/token.model");
|
|
224
|
+
const entries = await TokenModel.findActive("A");
|
|
225
|
+
const tokens = entries.map((e) => e.token);
|
|
226
|
+
const pool = initPool(tokens);
|
|
227
|
+
console.log(
|
|
228
|
+
`🌲 Server listening on http://${host}:${port} (${pool.workers.size} tokens loaded)`,
|
|
229
|
+
);
|
|
230
|
+
} catch (e) {
|
|
231
|
+
console.warn(`⚠ Pool init failed: ${(e as Error).message}`);
|
|
232
|
+
initPool([]);
|
|
233
|
+
console.log(`🌲 Server listening on http://${host}:${port} (no tokens)`);
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
onShutdown: () => {
|
|
237
|
+
console.log("graceful shutdown");
|
|
238
|
+
},
|
|
239
|
+
onError: (error, _request, reply) => {
|
|
240
|
+
console.error(error);
|
|
241
|
+
reply.status(500).send({
|
|
242
|
+
name: error.name,
|
|
243
|
+
message: error.message,
|
|
244
|
+
});
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
});
|