@hamjimin/xplat-back 0.6.3 → 0.7.0
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 +426 -103
- package/bin/xplat-back +57 -41
- package/dist/cli/db-setup.d.ts +1 -0
- package/dist/cli/db-setup.js +329 -0
- package/package.json +13 -10
- package/templates/ERD.md +133 -0
- package/templates/ddl/01_user.sql +30 -0
- package/templates/ddl/02_product.sql +76 -0
- package/templates/ddl/03_package.sql +67 -0
- package/templates/ddl/04_order.sql +62 -0
- package/templates/ddl/05_payment.sql +32 -0
- package/templates/ddl/06_delivery.sql +29 -0
- package/templates/ddl/07_cart.sql +27 -0
- package/templates/ddl/08_campaign.sql +45 -0
- package/templates/ddl/09_review.sql +61 -0
- package/templates/ddl/10_chain.sql +90 -0
package/README.md
CHANGED
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
# @hamjimin/xplat-back
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Express + TypeScript 백엔드 프레임워크 & CLI 스캐폴딩 도구
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
커머스 백엔드에 필요한 **인증, 스토리지, 결제, 배송, 쿼리 빌더** 등의 모듈을 `xplatSystem` 싱글턴으로 통합 제공하며,
|
|
6
|
+
CLI를 통해 프로젝트 초기화와 **MySQL DDL 자동 설치**까지 지원합니다.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
## 특징
|
|
11
|
-
|
|
12
|
-
- 🧩 **NestJS 모듈 구조**: Store/Admin API 분리, 도메인 모듈 확장 전제
|
|
13
|
-
- 🏷️ **버저닝/Prefix 설계**: `/store/v1`, `/admin/v1` 형태의 라우팅 분리
|
|
14
|
-
- 🔌 **xplatSystem 싱글턴**: 인증/JWT, 스토리지(S3), 결제/배송 등 공통 기능 접근
|
|
15
|
-
- 🛠️ **확장 포인트**: 이벤트/잡/도메인 서비스 등 확장 가능한 구조
|
|
16
|
-
- 🔧 **TypeScript 지원**: 타입 정의 포함
|
|
8
|
+
---
|
|
17
9
|
|
|
18
10
|
## 설치
|
|
19
11
|
|
|
@@ -23,138 +15,446 @@ npm install @hamjimin/xplat-back
|
|
|
23
15
|
yarn add @hamjimin/xplat-back
|
|
24
16
|
```
|
|
25
17
|
|
|
26
|
-
## 빠른 시작
|
|
18
|
+
## 빠른 시작
|
|
27
19
|
|
|
28
|
-
### 1.
|
|
20
|
+
### 1. 프로젝트 스캐폴딩
|
|
29
21
|
|
|
30
|
-
|
|
31
|
-
|
|
22
|
+
```bash
|
|
23
|
+
npx @hamjimin/xplat-back my-project
|
|
24
|
+
```
|
|
32
25
|
|
|
33
|
-
|
|
26
|
+
자동 생성되는 파일:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
my-project/
|
|
30
|
+
├── src/
|
|
31
|
+
│ ├── app.ts # Express 앱 진입점
|
|
32
|
+
│ ├── server.ts # 서버 실행 파일
|
|
33
|
+
│ ├── restapi/
|
|
34
|
+
│ │ └── health.ts # 예시 API 라우트
|
|
35
|
+
│ ├── const/
|
|
36
|
+
│ │ ├── api_code.ts # API 응답 코드 상수
|
|
37
|
+
│ │ └── constants.ts # 공통 상수
|
|
38
|
+
│ └── util/
|
|
39
|
+
│ └── dbconfig/
|
|
40
|
+
│ └── dbConnections.ts # DB 연결 설정 (TypeORM)
|
|
41
|
+
├── tsconfig.json
|
|
42
|
+
├── package.json
|
|
43
|
+
└── .env
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 2. Express 앱 생성
|
|
34
47
|
|
|
35
48
|
```typescript
|
|
36
|
-
import {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
+
import { createApp } from '@hamjimin/xplat-back';
|
|
50
|
+
import * as path from 'path';
|
|
51
|
+
|
|
52
|
+
const app = createApp({
|
|
53
|
+
cors: {
|
|
54
|
+
origin: ['http://localhost:3000'],
|
|
55
|
+
credentials: true,
|
|
56
|
+
},
|
|
57
|
+
router: {
|
|
58
|
+
directory: path.join(__dirname, 'restapi'),
|
|
59
|
+
basePath: '/restapi',
|
|
60
|
+
},
|
|
61
|
+
trustProxy: true,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
app.listen(4000, () => {
|
|
65
|
+
console.log('Server running on port 4000');
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`createApp` 옵션:
|
|
70
|
+
|
|
71
|
+
| 옵션 | 타입 | 기본값 | 설명 |
|
|
72
|
+
|------|------|--------|------|
|
|
73
|
+
| `cors.origin` | `string[]` | `['*']` | CORS 허용 origin |
|
|
74
|
+
| `cors.credentials` | `boolean` | `true` | 자격 증명 허용 |
|
|
75
|
+
| `json.limit` | `string` | `'1mb'` | JSON body 크기 제한 |
|
|
76
|
+
| `router.directory` | `string` | `'src/restapi'` | 라우트 파일 디렉토리 |
|
|
77
|
+
| `router.basePath` | `string` | `'/restapi'` | 라우터 base path |
|
|
78
|
+
| `trustProxy` | `boolean` | `true` | 프록시 신뢰 |
|
|
79
|
+
| `middleware` | `Function[]` | `[]` | 커스텀 미들웨어 |
|
|
80
|
+
|
|
81
|
+
### 3. 파일 기반 라우팅
|
|
82
|
+
|
|
83
|
+
디렉토리 구조가 API endpoint에 자동 매핑됩니다.
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
src/restapi/
|
|
87
|
+
├── health.ts → GET /restapi/health
|
|
88
|
+
├── users.ts → /restapi/users
|
|
89
|
+
└── users/
|
|
90
|
+
└── profile.ts → /restapi/users/profile
|
|
49
91
|
```
|
|
50
92
|
|
|
51
93
|
```typescript
|
|
52
|
-
import {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
94
|
+
import { createRouter } from '@hamjimin/xplat-back';
|
|
95
|
+
|
|
96
|
+
const router = createRouter(path.join(__dirname, 'restapi'));
|
|
97
|
+
app.use('/restapi', router);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## CLI
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
npx @hamjimin/xplat-back [project-name] [options]
|
|
106
|
+
npx @hamjimin/xplat-back db:setup
|
|
65
107
|
```
|
|
66
108
|
|
|
67
|
-
###
|
|
109
|
+
### 프로젝트 생성 옵션
|
|
110
|
+
|
|
111
|
+
| 옵션 | 단축 | 설명 | 기본값 |
|
|
112
|
+
|------|------|------|--------|
|
|
113
|
+
| `--port <port>` | `-p` | 서버 포트 | `4000` |
|
|
114
|
+
| `--cors <origins>` | `-c` | CORS origins (쉼표 구분) | `http://localhost:3000` |
|
|
115
|
+
| `--router-dir <dir>` | `-r` | 라우터 디렉토리 | `src/restapi` |
|
|
116
|
+
| `--router-path <path>` | | 라우터 base path | `/restapi` |
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
npx @hamjimin/xplat-back my-project --port 3000 --cors "http://localhost:3000,http://localhost:5173"
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### db:setup - MySQL DDL 자동 설치
|
|
123
|
+
|
|
124
|
+
인터랙티브 UI로 MySQL 접속 정보를 입력하고, 필요한 테이블을 선택하여 바로 생성합니다.
|
|
68
125
|
|
|
69
|
-
|
|
126
|
+
```bash
|
|
127
|
+
npx @hamjimin/xplat-back db:setup
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**실행 흐름:**
|
|
70
131
|
|
|
71
132
|
```
|
|
72
|
-
|
|
73
|
-
|
|
133
|
+
🗄️ xplat-back 데이터베이스 설정
|
|
134
|
+
|
|
135
|
+
? DB Host: localhost
|
|
136
|
+
? DB User: root
|
|
137
|
+
? DB Password: ****
|
|
138
|
+
? DB Name: my_shop
|
|
139
|
+
? DB Port: 3306
|
|
140
|
+
|
|
141
|
+
✅ MySQL 연결 성공!
|
|
142
|
+
|
|
143
|
+
? 설치할 DDL 모듈을 선택하세요 (스페이스바로 선택):
|
|
144
|
+
◉ 사용자 (User / Employee / Partner)
|
|
145
|
+
◉ 상품 (Category / Product / Variants / Brand / Side_Product)
|
|
146
|
+
◯ 패키지 (Product_Package / Detail / Variant)
|
|
147
|
+
◉ 주문 (Orders / Order_Items / Order_Manage)
|
|
148
|
+
...
|
|
149
|
+
|
|
150
|
+
📎 의존성으로 자동 추가된 모듈:
|
|
151
|
+
→ 사용자 (01_user.sql)
|
|
152
|
+
|
|
153
|
+
📋 실행 대상 DDL (3개 파일):
|
|
154
|
+
📦 01_user.sql - 사용자
|
|
155
|
+
📦 02_product.sql - 상품
|
|
156
|
+
📦 04_order.sql - 주문
|
|
157
|
+
|
|
158
|
+
? 위 3개 DDL 파일을 실행하시겠습니까? Yes
|
|
159
|
+
|
|
160
|
+
✅ 01_user.sql → user, employee, partner
|
|
161
|
+
✅ 02_product.sql → category, product, variants, brand, side_product
|
|
162
|
+
✅ 04_order.sql → orders, order_items, order_manage, order_manage_comment
|
|
163
|
+
|
|
164
|
+
🎉 데이터베이스 설정이 완료되었습니다!
|
|
74
165
|
```
|
|
75
166
|
|
|
76
|
-
|
|
167
|
+
**제공되는 DDL 모듈 (10개):**
|
|
168
|
+
|
|
169
|
+
| # | 모듈 | 테이블 |
|
|
170
|
+
|---|------|--------|
|
|
171
|
+
| 1 | 사용자 | `user`, `employee`, `partner` |
|
|
172
|
+
| 2 | 상품 | `category`, `product`, `variants`, `brand`, `side_product` |
|
|
173
|
+
| 3 | 패키지 | `product_package`, `product_package_detail`, `product_package_variant` |
|
|
174
|
+
| 4 | 주문 | `orders`, `order_items`, `order_manage`, `order_manage_comment` |
|
|
175
|
+
| 5 | 결제 | `payment`, `calcurate` |
|
|
176
|
+
| 6 | 배송 | `delivery`, `reservation` |
|
|
177
|
+
| 7 | 장바구니 | `cart`, `cart_items` |
|
|
178
|
+
| 8 | 캠페인 | `campaign`, `promotion_detail`, `event_detail` |
|
|
179
|
+
| 9 | 리뷰 | `product_review`, `review_images`, `package_product_review`, `package_review_images` |
|
|
180
|
+
| 10 | 연결 테이블 | `chain_category_detail`, `chain_product_detail`, `chain_variant_detail` 외 5개 |
|
|
181
|
+
|
|
182
|
+
- FK 의존성이 자동 해결됩니다 (예: 주문 선택 시 사용자 자동 포함)
|
|
183
|
+
- `IF NOT EXISTS`로 멱등성이 보장되어 중복 실행해도 안전합니다
|
|
184
|
+
- 이미 존재하는 테이블은 체크박스에 상태가 표시됩니다
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## xplatSystem 싱글턴
|
|
189
|
+
|
|
190
|
+
모든 모듈은 `xplatSystem` 인스턴스를 통해 접근할 수 있습니다.
|
|
77
191
|
|
|
78
192
|
```typescript
|
|
79
193
|
import { xplatSystem } from '@hamjimin/xplat-back';
|
|
80
194
|
|
|
81
|
-
|
|
82
|
-
//
|
|
83
|
-
//
|
|
195
|
+
xplatSystem.auth // 인증/JWT
|
|
196
|
+
xplatSystem.storage // S3 스토리지
|
|
197
|
+
xplatSystem.payment // 결제
|
|
198
|
+
xplatSystem.shipping // 배송
|
|
199
|
+
xplatSystem.orm // 쿼리 빌더
|
|
200
|
+
xplatSystem.middleware // 미들웨어
|
|
201
|
+
xplatSystem.commerce // NestJS 커머스 모듈
|
|
84
202
|
```
|
|
85
203
|
|
|
86
|
-
|
|
204
|
+
---
|
|
87
205
|
|
|
88
|
-
|
|
89
|
-
- `xplatSystem.router` (레거시 파일 기반 라우팅)
|
|
90
|
-
- `xplatSystem.auth` (JWT/인증 유틸)
|
|
91
|
-
- `xplatSystem.storage` (S3 유틸)
|
|
92
|
-
- `xplatSystem.payment` (Toss/KakaoPay)
|
|
93
|
-
- `xplatSystem.shipping` (CJ/Logen/CVS)
|
|
94
|
-
- `xplatSystem.orm` (쿼리 빌더/ORM 유틸)
|
|
206
|
+
## 모듈 상세
|
|
95
207
|
|
|
96
|
-
|
|
208
|
+
### Auth - 인증/JWT
|
|
97
209
|
|
|
98
|
-
|
|
99
|
-
|
|
210
|
+
```typescript
|
|
211
|
+
import { xplatSystem } from '@hamjimin/xplat-back';
|
|
100
212
|
|
|
101
|
-
|
|
102
|
-
|
|
213
|
+
// 토큰 생성
|
|
214
|
+
const token = xplatSystem.auth.generateToken(
|
|
215
|
+
{ id: 'user-1', role: 'admin' },
|
|
216
|
+
{ secret: process.env.JWT_SECRET!, expiresIn: '1h' }
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
// Access + Refresh 토큰 쌍 생성
|
|
220
|
+
const tokenPair = xplatSystem.auth.createTokenPair(
|
|
221
|
+
{ id: 'user-1' },
|
|
222
|
+
{ secret: process.env.JWT_SECRET!, expiresIn: '15m' }, // access
|
|
223
|
+
{ secret: process.env.JWT_SECRET!, expiresIn: '7d' } // refresh
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
// 인증 미들웨어
|
|
227
|
+
const authMiddleware = xplatSystem.auth.createMiddleware({
|
|
228
|
+
secret: process.env.JWT_SECRET!,
|
|
229
|
+
cookieName: 'accessToken',
|
|
230
|
+
onSuccess: (req, payload) => { req.user = payload; },
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
app.use('/api/protected', authMiddleware);
|
|
103
234
|
```
|
|
104
235
|
|
|
105
|
-
|
|
236
|
+
토큰은 **Cookie** 또는 **Authorization: Bearer** 헤더에서 자동 추출됩니다.
|
|
106
237
|
|
|
107
|
-
|
|
108
|
-
|
|
238
|
+
### Storage - S3 파일 관리
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
const storage = xplatSystem.storage;
|
|
242
|
+
|
|
243
|
+
// S3 설정 (환경변수 또는 직접 전달)
|
|
244
|
+
// S3_BUCKET, S3_ACCESS_KEY, S3_SECRET_KEY, AWS_REGION
|
|
245
|
+
|
|
246
|
+
// 업로드
|
|
247
|
+
await storage.upload({
|
|
248
|
+
key: 'uploads/2025/01/image.png',
|
|
249
|
+
body: fileBuffer,
|
|
250
|
+
bucket: 'my-bucket',
|
|
251
|
+
contentType: 'image/png',
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Presigned 다운로드 URL 생성
|
|
255
|
+
const url = await storage.getDownloadUrl({
|
|
256
|
+
key: 'uploads/2025/01/image.png',
|
|
257
|
+
bucket: 'my-bucket',
|
|
258
|
+
expiresInSec: 3600,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// 삭제
|
|
262
|
+
await storage.delete({ key: 'uploads/2025/01/image.png', bucket: 'my-bucket' });
|
|
109
263
|
```
|
|
110
264
|
|
|
111
|
-
|
|
112
|
-
-
|
|
113
|
-
-
|
|
114
|
-
-
|
|
115
|
-
|
|
116
|
-
|
|
265
|
+
유틸 함수:
|
|
266
|
+
- `buildKey(prefix, fileName, date?)` - 날짜 기반 S3 key 생성 (`prefix/YYYY/MM/DD/fileName`)
|
|
267
|
+
- `extractKey(url)` - S3 URL에서 key 추출
|
|
268
|
+
- `getDatePrefix()` - `YYYY/MM/DD` 형태의 접두사
|
|
269
|
+
|
|
270
|
+
### Payment - 결제
|
|
271
|
+
|
|
272
|
+
Toss Payments, KakaoPay를 플러그인 방식으로 지원합니다.
|
|
117
273
|
|
|
118
|
-
|
|
274
|
+
```typescript
|
|
275
|
+
import { TossPaymentProvider, KakaoPayProvider } from '@hamjimin/xplat-back';
|
|
276
|
+
|
|
277
|
+
const payment = xplatSystem.payment;
|
|
278
|
+
|
|
279
|
+
// 결제 프로바이더 등록
|
|
280
|
+
payment.registerProvider(new TossPaymentProvider({
|
|
281
|
+
secretKey: process.env.TOSS_SECRET_KEY!,
|
|
282
|
+
testMode: true,
|
|
283
|
+
}), true); // true = 기본 프로바이더로 설정
|
|
284
|
+
|
|
285
|
+
payment.registerProvider(new KakaoPayProvider({
|
|
286
|
+
adminKey: process.env.KAKAO_ADMIN_KEY!,
|
|
287
|
+
cid: 'TC0ONETIME',
|
|
288
|
+
}));
|
|
289
|
+
|
|
290
|
+
// 결제 요청
|
|
291
|
+
const response = await payment.requestPayment({
|
|
292
|
+
orderId: 'order-123',
|
|
293
|
+
amount: 15000,
|
|
294
|
+
customerName: '홍길동',
|
|
295
|
+
});
|
|
119
296
|
|
|
120
|
-
|
|
297
|
+
// 결제 승인
|
|
298
|
+
const details = await payment.confirmPayment({
|
|
299
|
+
paymentKey: response.paymentKey,
|
|
300
|
+
orderId: 'order-123',
|
|
301
|
+
amount: 15000,
|
|
302
|
+
});
|
|
121
303
|
|
|
122
|
-
|
|
304
|
+
// 결제 취소
|
|
305
|
+
await payment.cancelPayment({
|
|
306
|
+
paymentKey: response.paymentKey,
|
|
307
|
+
cancelReason: '고객 요청',
|
|
308
|
+
});
|
|
309
|
+
```
|
|
123
310
|
|
|
124
|
-
|
|
311
|
+
### Shipping - 배송
|
|
312
|
+
|
|
313
|
+
CJ대한통운, 로젠, 편의점 택배를 플러그인 방식으로 지원합니다.
|
|
125
314
|
|
|
126
315
|
```typescript
|
|
127
|
-
import {
|
|
128
|
-
import * as path from 'path';
|
|
316
|
+
import { CJLogisticsProvider, LogenProvider } from '@hamjimin/xplat-back';
|
|
129
317
|
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
318
|
+
const shipping = xplatSystem.shipping;
|
|
319
|
+
|
|
320
|
+
shipping.registerProvider(new CJLogisticsProvider({
|
|
321
|
+
apiKey: process.env.CJ_API_KEY!,
|
|
322
|
+
customerCode: 'CUSTOMER-001',
|
|
323
|
+
}), true);
|
|
324
|
+
|
|
325
|
+
// 배송비 계산
|
|
326
|
+
const fee = await shipping.calculateFee({
|
|
327
|
+
destinationZip: '06234',
|
|
328
|
+
weight: 2.5,
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// 운송장 생성
|
|
332
|
+
const label = await shipping.createLabel({
|
|
333
|
+
orderId: 'order-123',
|
|
334
|
+
toName: '홍길동',
|
|
335
|
+
toPhone: '010-1234-5678',
|
|
336
|
+
toAddress: '서울시 강남구 테헤란로 123',
|
|
337
|
+
toZip: '06234',
|
|
135
338
|
});
|
|
339
|
+
|
|
340
|
+
// 배송 조회
|
|
341
|
+
const tracking = await shipping.track(label.trackingNumber);
|
|
136
342
|
```
|
|
137
343
|
|
|
138
|
-
###
|
|
344
|
+
### ORM - 쿼리 빌더
|
|
139
345
|
|
|
140
|
-
|
|
346
|
+
SQL WHERE / ORDER BY 절을 구조화된 데이터로 생성합니다.
|
|
141
347
|
|
|
142
348
|
```typescript
|
|
143
|
-
import {
|
|
144
|
-
import * as path from 'path';
|
|
349
|
+
import { xplatSystem } from '@hamjimin/xplat-back';
|
|
145
350
|
|
|
146
|
-
const
|
|
147
|
-
|
|
351
|
+
const orm = xplatSystem.orm;
|
|
352
|
+
|
|
353
|
+
// WHERE 절 생성
|
|
354
|
+
const whereQuery = orm.createWhereQuery({
|
|
355
|
+
condition: 'AND',
|
|
356
|
+
searchData: [
|
|
357
|
+
{ column: 'name', operator: 'LIKE', value: '홍' },
|
|
358
|
+
{ column: 'status', operator: 'IN', value: ['active', 'pending'] },
|
|
359
|
+
{ column: 'created_at', operator: 'BETWEEN', value: ['2025-01-01', '2025-12-31'] },
|
|
360
|
+
],
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// ORDER BY 절 생성
|
|
364
|
+
const sortQuery = orm.createSortQuery([
|
|
365
|
+
{ column: 'created_at', operator: 'DESC' },
|
|
366
|
+
{ column: 'name', operator: 'ASC' },
|
|
367
|
+
]);
|
|
368
|
+
|
|
369
|
+
// 기존 쿼리에 적용
|
|
370
|
+
let query = 'SELECT * FROM user WHERE 1=1';
|
|
371
|
+
query = orm.appendWhereClause(query, whereQuery);
|
|
372
|
+
query = orm.appendOrderClause(query, sortQuery);
|
|
148
373
|
```
|
|
149
374
|
|
|
375
|
+
지원 연산자: `=`, `!=`, `LIKE`, `IN`, `NOTIN`, `BETWEEN`, `ISNULL`, `ISNOTNULL`
|
|
376
|
+
|
|
377
|
+
### Middleware - 미들웨어
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
const mw = xplatSystem.middleware;
|
|
381
|
+
|
|
382
|
+
// 요청 로깅
|
|
383
|
+
app.use(mw.logger());
|
|
384
|
+
|
|
385
|
+
// 상세 로깅 (body 포함)
|
|
386
|
+
app.use(mw.detailedLogger());
|
|
387
|
+
|
|
388
|
+
// 필드 검증
|
|
389
|
+
app.post('/users', mw.requireFields(['email', 'password']));
|
|
390
|
+
|
|
391
|
+
// 커스텀 검증
|
|
392
|
+
app.post('/users', mw.validator({
|
|
393
|
+
body: {
|
|
394
|
+
email: (v) => typeof v === 'string' && v.includes('@') || 'Invalid email',
|
|
395
|
+
age: (v) => typeof v === 'number' && v > 0 || 'Age must be positive',
|
|
396
|
+
},
|
|
397
|
+
}));
|
|
398
|
+
|
|
399
|
+
// 에러 핸들러 + 404 (마지막에 등록)
|
|
400
|
+
app.use(mw.notFound());
|
|
401
|
+
app.use(mw.errorHandler());
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
## NestJS 커머스 모듈
|
|
407
|
+
|
|
408
|
+
Store/Admin API 분리 구조를 제공합니다.
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
import { Module } from '@nestjs/common';
|
|
412
|
+
import { CommerceModule } from '@hamjimin/xplat-back';
|
|
413
|
+
|
|
414
|
+
@Module({
|
|
415
|
+
imports: [CommerceModule],
|
|
416
|
+
})
|
|
417
|
+
export class AppModule {}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
```
|
|
421
|
+
Store API: /store/v1/*
|
|
422
|
+
Admin API: /admin/v1/*
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
const { storePrefix, adminPrefix } = xplatSystem.commerce.getVersioning();
|
|
427
|
+
// storePrefix: /store/v1
|
|
428
|
+
// adminPrefix: /admin/v1
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
150
433
|
## 환경변수
|
|
151
434
|
|
|
152
|
-
`.env` 파일에서
|
|
435
|
+
`.env` 파일에서 설정 가능한 환경변수:
|
|
153
436
|
|
|
154
437
|
```env
|
|
438
|
+
# 앱
|
|
155
439
|
NODE_ENV=development
|
|
156
440
|
PORT=4000
|
|
157
|
-
CORS_ALLOW_ORIGINS=http://localhost:3000
|
|
441
|
+
CORS_ALLOW_ORIGINS=http://localhost:3000
|
|
442
|
+
|
|
443
|
+
# 데이터베이스
|
|
444
|
+
DB_HOST=localhost
|
|
445
|
+
DB_PORT=3306
|
|
446
|
+
DB_USER=root
|
|
447
|
+
DB_PASSWORD=
|
|
448
|
+
DB_DATABASE=my_database
|
|
449
|
+
|
|
450
|
+
# AWS S3
|
|
451
|
+
AWS_REGION=ap-northeast-2
|
|
452
|
+
S3_BUCKET=my-bucket
|
|
453
|
+
S3_ACCESS_KEY=
|
|
454
|
+
S3_SECRET_KEY=
|
|
455
|
+
|
|
456
|
+
# JWT
|
|
457
|
+
JWT_SECRET=your-secret-key
|
|
158
458
|
```
|
|
159
459
|
|
|
160
460
|
## TypeScript 설정
|
|
@@ -173,23 +473,46 @@ CORS_ALLOW_ORIGINS=http://localhost:3000,http://localhost:5173
|
|
|
173
473
|
"moduleResolution": "node",
|
|
174
474
|
"sourceMap": true,
|
|
175
475
|
"baseUrl": "./src",
|
|
176
|
-
"paths": {
|
|
177
|
-
"@/*": ["*"]
|
|
178
|
-
}
|
|
476
|
+
"paths": { "@/*": ["*"] }
|
|
179
477
|
},
|
|
180
478
|
"include": ["src/**/*"],
|
|
181
479
|
"exclude": ["node_modules", "dist"]
|
|
182
480
|
}
|
|
183
481
|
```
|
|
184
482
|
|
|
185
|
-
##
|
|
186
|
-
|
|
187
|
-
ISC
|
|
188
|
-
|
|
189
|
-
## 기여
|
|
483
|
+
## Export 목록
|
|
190
484
|
|
|
191
|
-
|
|
485
|
+
```typescript
|
|
486
|
+
// 싱글턴
|
|
487
|
+
export { xplatSystem, XplatSystem } from '@hamjimin/xplat-back';
|
|
488
|
+
|
|
489
|
+
// 앱/라우터
|
|
490
|
+
export { createApp, createRouter } from '@hamjimin/xplat-back';
|
|
491
|
+
|
|
492
|
+
// 모듈
|
|
493
|
+
export {
|
|
494
|
+
AppModule, RouterModule, AuthModule,
|
|
495
|
+
UtilsModule, MiddlewareModule, ConstantsModule,
|
|
496
|
+
StorageModule, ORMModule, PaymentModule, ShippingModule,
|
|
497
|
+
} from '@hamjimin/xplat-back';
|
|
498
|
+
|
|
499
|
+
// 결제 프로바이더
|
|
500
|
+
export { TossPaymentProvider, KakaoPayProvider } from '@hamjimin/xplat-back';
|
|
501
|
+
|
|
502
|
+
// 배송 프로바이더
|
|
503
|
+
export { CJLogisticsProvider, LogenProvider, CvsProvider } from '@hamjimin/xplat-back';
|
|
504
|
+
|
|
505
|
+
// 타입
|
|
506
|
+
export type {
|
|
507
|
+
AppConfig, ExtendedRequest, ExtendedResponse, AuthenticatedRequest,
|
|
508
|
+
TokenPayload, TokenOptions, TokenPair,
|
|
509
|
+
DetailSearch, SortQueryData, SearchDataItem, ColumnMapper,
|
|
510
|
+
S3Config,
|
|
511
|
+
PaymentRequestParams, PaymentResponse, PaymentConfirmParams, PaymentDetails,
|
|
512
|
+
ShippingFeeParams, ShippingFee, CreateLabelParams, ShippingLabel, TrackingInfo,
|
|
513
|
+
} from '@hamjimin/xplat-back';
|
|
514
|
+
```
|
|
192
515
|
|
|
193
|
-
##
|
|
516
|
+
## 라이센스
|
|
194
517
|
|
|
195
|
-
|
|
518
|
+
ISC
|