@hamjimin/xplat-back 0.6.2 → 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 +393 -164
- 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,16 +1,11 @@
|
|
|
1
1
|
# @hamjimin/xplat-back
|
|
2
2
|
|
|
3
|
-
Express + TypeScript 백엔드 스캐폴딩 도구
|
|
3
|
+
Express + TypeScript 백엔드 프레임워크 & CLI 스캐폴딩 도구
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
커머스 백엔드에 필요한 **인증, 스토리지, 결제, 배송, 쿼리 빌더** 등의 모듈을 `xplatSystem` 싱글턴으로 통합 제공하며,
|
|
6
|
+
CLI를 통해 프로젝트 초기화와 **MySQL DDL 자동 설치**까지 지원합니다.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- 🚀 **파일 기반 라우팅**: 파일명과 디렉토리 구조가 API endpoint로 자동 매핑
|
|
10
|
-
- 📁 **자동 라우터 생성**: 디렉토리 구조를 재귀적으로 탐색하여 라우터 자동 생성
|
|
11
|
-
- ⚡ **빠른 프로젝트 설정**: CLI를 통한 프로젝트 초기화
|
|
12
|
-
- 🔧 **TypeScript 지원**: 완전한 TypeScript 타입 정의 포함
|
|
13
|
-
- 🎯 **zium-backend 기반**: 검증된 프로젝트 구조 기반
|
|
8
|
+
---
|
|
14
9
|
|
|
15
10
|
## 설치
|
|
16
11
|
|
|
@@ -22,43 +17,39 @@ yarn add @hamjimin/xplat-back
|
|
|
22
17
|
|
|
23
18
|
## 빠른 시작
|
|
24
19
|
|
|
25
|
-
###
|
|
26
|
-
```bash
|
|
27
|
-
npx @hamjimin/xplat-back [project-name]
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
옵션:
|
|
20
|
+
### 1. 프로젝트 스캐폴딩
|
|
31
21
|
|
|
32
22
|
```bash
|
|
33
|
-
npx @hamjimin/xplat-back my-project
|
|
23
|
+
npx @hamjimin/xplat-back my-project
|
|
34
24
|
```
|
|
35
25
|
|
|
36
|
-
|
|
37
|
-
- `-p, --port <port>`: 서버 포트 (기본값: 4000)
|
|
38
|
-
- `-c, --cors <origins>`: CORS 허용 origins (쉼표로 구분)
|
|
39
|
-
- `-r, --router-dir <directory>`: 라우터 디렉토리 (기본값: src/restapi)
|
|
40
|
-
- `--router-path <path>`: 라우터 base path (기본값: /restapi)
|
|
41
|
-
- `--with-next`: Next.js 프론트엔드(web) 템플릿 추가
|
|
26
|
+
자동 생성되는 파일:
|
|
42
27
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
50
44
|
```
|
|
51
45
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
`src/app.ts`:
|
|
46
|
+
### 2. Express 앱 생성
|
|
55
47
|
|
|
56
48
|
```typescript
|
|
57
|
-
import express, { Application } from 'express';
|
|
58
|
-
import * as path from 'path';
|
|
59
49
|
import { createApp } from '@hamjimin/xplat-back';
|
|
50
|
+
import * as path from 'path';
|
|
60
51
|
|
|
61
|
-
const app
|
|
52
|
+
const app = createApp({
|
|
62
53
|
cors: {
|
|
63
54
|
origin: ['http://localhost:3000'],
|
|
64
55
|
credentials: true,
|
|
@@ -70,185 +61,400 @@ const app: Application = createApp({
|
|
|
70
61
|
trustProxy: true,
|
|
71
62
|
});
|
|
72
63
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
app.listen(port, () => {
|
|
76
|
-
console.log(`🚀 Server is running on port ${port}`);
|
|
64
|
+
app.listen(4000, () => {
|
|
65
|
+
console.log('Server running on port 4000');
|
|
77
66
|
});
|
|
78
|
-
|
|
79
|
-
export default app;
|
|
80
67
|
```
|
|
81
68
|
|
|
82
|
-
|
|
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. 파일 기반 라우팅
|
|
83
82
|
|
|
84
|
-
|
|
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
|
|
91
|
+
```
|
|
85
92
|
|
|
86
93
|
```typescript
|
|
87
|
-
import
|
|
94
|
+
import { createRouter } from '@hamjimin/xplat-back';
|
|
95
|
+
|
|
96
|
+
const router = createRouter(path.join(__dirname, 'restapi'));
|
|
97
|
+
app.use('/restapi', router);
|
|
98
|
+
```
|
|
88
99
|
|
|
89
|
-
|
|
100
|
+
---
|
|
90
101
|
|
|
91
|
-
|
|
92
|
-
res.json({
|
|
93
|
-
success: true,
|
|
94
|
-
message: 'Server is healthy',
|
|
95
|
-
timestamp: new Date().toISOString(),
|
|
96
|
-
});
|
|
97
|
-
});
|
|
102
|
+
## CLI
|
|
98
103
|
|
|
99
|
-
|
|
104
|
+
```bash
|
|
105
|
+
npx @hamjimin/xplat-back [project-name] [options]
|
|
106
|
+
npx @hamjimin/xplat-back db:setup
|
|
100
107
|
```
|
|
101
108
|
|
|
102
|
-
|
|
109
|
+
### 프로젝트 생성 옵션
|
|
103
110
|
|
|
104
|
-
|
|
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` |
|
|
105
117
|
|
|
106
118
|
```bash
|
|
107
|
-
|
|
108
|
-
# 또는
|
|
109
|
-
ts-node -r tsconfig-paths/register src/app.ts
|
|
119
|
+
npx @hamjimin/xplat-back my-project --port 3000 --cors "http://localhost:3000,http://localhost:5173"
|
|
110
120
|
```
|
|
111
121
|
|
|
112
|
-
|
|
122
|
+
### db:setup - MySQL DDL 자동 설치
|
|
113
123
|
|
|
114
|
-
|
|
124
|
+
인터랙티브 UI로 MySQL 접속 정보를 입력하고, 필요한 테이블을 선택하여 바로 생성합니다.
|
|
115
125
|
|
|
116
|
-
|
|
126
|
+
```bash
|
|
127
|
+
npx @hamjimin/xplat-back db:setup
|
|
128
|
+
```
|
|
117
129
|
|
|
118
|
-
|
|
130
|
+
**실행 흐름:**
|
|
119
131
|
|
|
120
|
-
```typescript
|
|
121
|
-
interface AppConfig {
|
|
122
|
-
cors?: {
|
|
123
|
-
origin?: string[] | ((origin: string | undefined, callback: (err: Error | null, allow?: boolean) => void) => void);
|
|
124
|
-
credentials?: boolean;
|
|
125
|
-
};
|
|
126
|
-
json?: {
|
|
127
|
-
limit?: string;
|
|
128
|
-
};
|
|
129
|
-
urlencoded?: {
|
|
130
|
-
extended?: boolean;
|
|
131
|
-
};
|
|
132
|
-
router?: {
|
|
133
|
-
directory?: string;
|
|
134
|
-
basePath?: string;
|
|
135
|
-
};
|
|
136
|
-
trustProxy?: boolean;
|
|
137
|
-
middleware?: Array<(req: Request, res: Response, next: NextFunction) => void>;
|
|
138
|
-
}
|
|
139
132
|
```
|
|
133
|
+
🗄️ xplat-back 데이터베이스 설정
|
|
140
134
|
|
|
141
|
-
|
|
135
|
+
? DB Host: localhost
|
|
136
|
+
? DB User: root
|
|
137
|
+
? DB Password: ****
|
|
138
|
+
? DB Name: my_shop
|
|
139
|
+
? DB Port: 3306
|
|
142
140
|
|
|
143
|
-
|
|
144
|
-
import { createApp } from '@hamjimin/xplat-back';
|
|
141
|
+
✅ MySQL 연결 성공!
|
|
145
142
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
+
🎉 데이터베이스 설정이 완료되었습니다!
|
|
163
165
|
```
|
|
164
166
|
|
|
165
|
-
|
|
167
|
+
**제공되는 DDL 모듈 (10개):**
|
|
166
168
|
|
|
167
|
-
|
|
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개 |
|
|
168
181
|
|
|
169
|
-
|
|
182
|
+
- FK 의존성이 자동 해결됩니다 (예: 주문 선택 시 사용자 자동 포함)
|
|
183
|
+
- `IF NOT EXISTS`로 멱등성이 보장되어 중복 실행해도 안전합니다
|
|
184
|
+
- 이미 존재하는 테이블은 체크박스에 상태가 표시됩니다
|
|
170
185
|
|
|
171
|
-
|
|
186
|
+
---
|
|
172
187
|
|
|
173
|
-
|
|
188
|
+
## xplatSystem 싱글턴
|
|
174
189
|
|
|
175
|
-
|
|
176
|
-
import { createRouter } from '@hamjimin/xplat-back';
|
|
177
|
-
import * as path from 'path';
|
|
190
|
+
모든 모듈은 `xplatSystem` 인스턴스를 통해 접근할 수 있습니다.
|
|
178
191
|
|
|
179
|
-
|
|
180
|
-
|
|
192
|
+
```typescript
|
|
193
|
+
import { xplatSystem } from '@hamjimin/xplat-back';
|
|
194
|
+
|
|
195
|
+
xplatSystem.auth // 인증/JWT
|
|
196
|
+
xplatSystem.storage // S3 스토리지
|
|
197
|
+
xplatSystem.payment // 결제
|
|
198
|
+
xplatSystem.shipping // 배송
|
|
199
|
+
xplatSystem.orm // 쿼리 빌더
|
|
200
|
+
xplatSystem.middleware // 미들웨어
|
|
201
|
+
xplatSystem.commerce // NestJS 커머스 모듈
|
|
181
202
|
```
|
|
182
203
|
|
|
183
|
-
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## 모듈 상세
|
|
184
207
|
|
|
185
|
-
|
|
208
|
+
### Auth - 인증/JWT
|
|
186
209
|
|
|
210
|
+
```typescript
|
|
211
|
+
import { xplatSystem } from '@hamjimin/xplat-back';
|
|
212
|
+
|
|
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);
|
|
187
234
|
```
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
235
|
+
|
|
236
|
+
토큰은 **Cookie** 또는 **Authorization: Bearer** 헤더에서 자동 추출됩니다.
|
|
237
|
+
|
|
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' });
|
|
195
263
|
```
|
|
196
264
|
|
|
197
|
-
|
|
198
|
-
-
|
|
199
|
-
-
|
|
265
|
+
유틸 함수:
|
|
266
|
+
- `buildKey(prefix, fileName, date?)` - 날짜 기반 S3 key 생성 (`prefix/YYYY/MM/DD/fileName`)
|
|
267
|
+
- `extractKey(url)` - S3 URL에서 key 추출
|
|
268
|
+
- `getDatePrefix()` - `YYYY/MM/DD` 형태의 접두사
|
|
200
269
|
|
|
201
|
-
|
|
270
|
+
### Payment - 결제
|
|
202
271
|
|
|
203
|
-
|
|
272
|
+
Toss Payments, KakaoPay를 플러그인 방식으로 지원합니다.
|
|
204
273
|
|
|
205
274
|
```typescript
|
|
206
|
-
import
|
|
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
|
+
});
|
|
207
296
|
|
|
208
|
-
|
|
297
|
+
// 결제 승인
|
|
298
|
+
const details = await payment.confirmPayment({
|
|
299
|
+
paymentKey: response.paymentKey,
|
|
300
|
+
orderId: 'order-123',
|
|
301
|
+
amount: 15000,
|
|
302
|
+
});
|
|
209
303
|
|
|
210
|
-
//
|
|
211
|
-
|
|
212
|
-
|
|
304
|
+
// 결제 취소
|
|
305
|
+
await payment.cancelPayment({
|
|
306
|
+
paymentKey: response.paymentKey,
|
|
307
|
+
cancelReason: '고객 요청',
|
|
213
308
|
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Shipping - 배송
|
|
312
|
+
|
|
313
|
+
CJ대한통운, 로젠, 편의점 택배를 플러그인 방식으로 지원합니다.
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
import { CJLogisticsProvider, LogenProvider } from '@hamjimin/xplat-back';
|
|
214
317
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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',
|
|
218
338
|
});
|
|
219
339
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
export default router;
|
|
340
|
+
// 배송 조회
|
|
341
|
+
const tracking = await shipping.track(label.trackingNumber);
|
|
223
342
|
```
|
|
224
343
|
|
|
225
|
-
|
|
344
|
+
### ORM - 쿼리 빌더
|
|
345
|
+
|
|
346
|
+
SQL WHERE / ORDER BY 절을 구조화된 데이터로 생성합니다.
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
import { xplatSystem } from '@hamjimin/xplat-back';
|
|
350
|
+
|
|
351
|
+
const orm = xplatSystem.orm;
|
|
226
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);
|
|
227
373
|
```
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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/*
|
|
242
423
|
```
|
|
243
424
|
|
|
425
|
+
```typescript
|
|
426
|
+
const { storePrefix, adminPrefix } = xplatSystem.commerce.getVersioning();
|
|
427
|
+
// storePrefix: /store/v1
|
|
428
|
+
// adminPrefix: /admin/v1
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
244
433
|
## 환경변수
|
|
245
434
|
|
|
246
|
-
`.env` 파일에서
|
|
435
|
+
`.env` 파일에서 설정 가능한 환경변수:
|
|
247
436
|
|
|
248
437
|
```env
|
|
438
|
+
# 앱
|
|
249
439
|
NODE_ENV=development
|
|
250
440
|
PORT=4000
|
|
251
|
-
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
|
|
252
458
|
```
|
|
253
459
|
|
|
254
460
|
## TypeScript 설정
|
|
@@ -267,23 +473,46 @@ CORS_ALLOW_ORIGINS=http://localhost:3000,http://localhost:5173
|
|
|
267
473
|
"moduleResolution": "node",
|
|
268
474
|
"sourceMap": true,
|
|
269
475
|
"baseUrl": "./src",
|
|
270
|
-
"paths": {
|
|
271
|
-
"@/*": ["*"]
|
|
272
|
-
}
|
|
476
|
+
"paths": { "@/*": ["*"] }
|
|
273
477
|
},
|
|
274
478
|
"include": ["src/**/*"],
|
|
275
479
|
"exclude": ["node_modules", "dist"]
|
|
276
480
|
}
|
|
277
481
|
```
|
|
278
482
|
|
|
279
|
-
##
|
|
280
|
-
|
|
281
|
-
ISC
|
|
282
|
-
|
|
283
|
-
## 기여
|
|
483
|
+
## Export 목록
|
|
284
484
|
|
|
285
|
-
|
|
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
|
+
```
|
|
286
515
|
|
|
287
|
-
##
|
|
516
|
+
## 라이센스
|
|
288
517
|
|
|
289
|
-
|
|
518
|
+
ISC
|