@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/bin/xplat-back
CHANGED
|
@@ -1,38 +1,52 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// Directly invoke initializeProject to ensure CLI runs when called from the bin entry.
|
|
4
|
-
const { initializeProject } = require('../dist/cli/init.js');
|
|
5
|
-
|
|
6
3
|
const args = process.argv.slice(2);
|
|
7
|
-
const
|
|
4
|
+
const subCommand = args[0];
|
|
5
|
+
|
|
6
|
+
if (subCommand === 'db:setup') {
|
|
7
|
+
const { setupDatabase } = require('../dist/cli/db-setup.js');
|
|
8
|
+
setupDatabase().catch((error) => {
|
|
9
|
+
console.error('❌ DB 설정 중 오류가 발생했습니다:', error.message);
|
|
10
|
+
if (process.env.DEBUG) {
|
|
11
|
+
console.error(error);
|
|
12
|
+
}
|
|
13
|
+
process.exit(1);
|
|
14
|
+
});
|
|
15
|
+
} else {
|
|
16
|
+
const { initializeProject } = require('../dist/cli/init.js');
|
|
8
17
|
|
|
9
|
-
|
|
10
|
-
for (let i = 0; i < args.length; i++) {
|
|
11
|
-
const arg = args[i];
|
|
18
|
+
const config = {};
|
|
12
19
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
20
|
+
for (let i = 0; i < args.length; i++) {
|
|
21
|
+
const arg = args[i];
|
|
22
|
+
|
|
23
|
+
switch (arg) {
|
|
24
|
+
case '--port':
|
|
25
|
+
case '-p':
|
|
26
|
+
config.port = args[++i];
|
|
27
|
+
break;
|
|
28
|
+
case '--cors':
|
|
29
|
+
case '-c':
|
|
30
|
+
config.corsOrigins = args[++i];
|
|
31
|
+
break;
|
|
32
|
+
case '--router-dir':
|
|
33
|
+
case '-r':
|
|
34
|
+
config.routerDirectory = args[++i];
|
|
35
|
+
break;
|
|
36
|
+
case '--router-path':
|
|
37
|
+
config.routerBasePath = args[++i];
|
|
38
|
+
break;
|
|
39
|
+
case '--help':
|
|
40
|
+
case '-h':
|
|
41
|
+
console.log(`
|
|
32
42
|
xplat-back CLI
|
|
33
43
|
|
|
34
44
|
Usage:
|
|
35
45
|
xplat-back [options] [project-name]
|
|
46
|
+
xplat-back db:setup MySQL DDL 설치 (인터랙티브)
|
|
47
|
+
|
|
48
|
+
Commands:
|
|
49
|
+
db:setup 데이터베이스 테이블 생성 (MySQL 접속 정보 입력 후 DDL 실행)
|
|
36
50
|
|
|
37
51
|
Options:
|
|
38
52
|
-p, --port <port> 서버 포트 (기본값: 4000)
|
|
@@ -44,21 +58,23 @@ Options:
|
|
|
44
58
|
Examples:
|
|
45
59
|
xplat-back
|
|
46
60
|
xplat-back my-project
|
|
61
|
+
xplat-back db:setup
|
|
47
62
|
xplat-back --port 3000 --cors "http://localhost:3000,http://localhost:5173"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
63
|
+
`);
|
|
64
|
+
process.exit(0);
|
|
65
|
+
break;
|
|
66
|
+
default:
|
|
67
|
+
if (!arg.startsWith('-')) {
|
|
68
|
+
config.projectName = arg;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
55
71
|
}
|
|
56
|
-
}
|
|
57
72
|
|
|
58
|
-
initializeProject(config).catch((error) => {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
});
|
|
73
|
+
initializeProject(config).catch((error) => {
|
|
74
|
+
console.error('❌ 초기화 중 오류가 발생했습니다:', error.message);
|
|
75
|
+
if (process.env.DEBUG) {
|
|
76
|
+
console.error(error);
|
|
77
|
+
}
|
|
78
|
+
process.exit(1);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function setupDatabase(): Promise<void>;
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.setupDatabase = setupDatabase;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
43
|
+
const mysql = __importStar(require("mysql2/promise"));
|
|
44
|
+
const DDL_MODULES = [
|
|
45
|
+
{
|
|
46
|
+
file: '01_user.sql',
|
|
47
|
+
name: '사용자 (User / Employee / Partner)',
|
|
48
|
+
tables: ['user', 'employee', 'partner'],
|
|
49
|
+
deps: [],
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
file: '02_product.sql',
|
|
53
|
+
name: '상품 (Category / Product / Variants / Brand / Side_Product)',
|
|
54
|
+
tables: ['category', 'product', 'variants', 'brand', 'side_product'],
|
|
55
|
+
deps: ['01_user.sql'],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
file: '03_package.sql',
|
|
59
|
+
name: '패키지 (Product_Package / Detail / Variant)',
|
|
60
|
+
tables: ['product_package', 'product_package_detail', 'product_package_variant'],
|
|
61
|
+
deps: ['02_product.sql'],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
file: '04_order.sql',
|
|
65
|
+
name: '주문 (Orders / Order_Items / Order_Manage)',
|
|
66
|
+
tables: ['orders', 'order_items', 'order_manage', 'order_manage_comment'],
|
|
67
|
+
deps: ['01_user.sql'],
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
file: '05_payment.sql',
|
|
71
|
+
name: '결제 (Payment / Calcurate)',
|
|
72
|
+
tables: ['payment', 'calcurate'],
|
|
73
|
+
deps: ['01_user.sql', '04_order.sql'],
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
file: '06_delivery.sql',
|
|
77
|
+
name: '배송 (Delivery / Reservation)',
|
|
78
|
+
tables: ['delivery', 'reservation'],
|
|
79
|
+
deps: ['04_order.sql'],
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
file: '07_cart.sql',
|
|
83
|
+
name: '장바구니 (Cart / Cart_Items)',
|
|
84
|
+
tables: ['cart', 'cart_items'],
|
|
85
|
+
deps: ['01_user.sql', '02_product.sql'],
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
file: '08_campaign.sql',
|
|
89
|
+
name: '캠페인 (Campaign / Promotion / Event)',
|
|
90
|
+
tables: ['campaign', 'promotion_detail', 'event_detail'],
|
|
91
|
+
deps: ['03_package.sql'],
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
file: '09_review.sql',
|
|
95
|
+
name: '리뷰 (Product_Review / Package_Review)',
|
|
96
|
+
tables: ['product_review', 'review_images', 'package_product_review', 'package_review_images'],
|
|
97
|
+
deps: ['01_user.sql', '02_product.sql', '03_package.sql'],
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
file: '10_chain.sql',
|
|
101
|
+
name: '연결 테이블 (Chain_*)',
|
|
102
|
+
tables: [
|
|
103
|
+
'chain_category_detail', 'chain_product_detail', 'chain_variant_detail',
|
|
104
|
+
'chain_product_brand', 'chain_order_delivery', 'chain_order_cart',
|
|
105
|
+
'chain_order_reservation', 'chain_campaign_package',
|
|
106
|
+
],
|
|
107
|
+
deps: [
|
|
108
|
+
'01_user.sql', '02_product.sql', '03_package.sql',
|
|
109
|
+
'04_order.sql', '06_delivery.sql', '07_cart.sql', '08_campaign.sql',
|
|
110
|
+
],
|
|
111
|
+
},
|
|
112
|
+
];
|
|
113
|
+
function resolveDependencies(selectedFiles) {
|
|
114
|
+
const resolved = new Set();
|
|
115
|
+
function addWithDeps(file) {
|
|
116
|
+
if (resolved.has(file))
|
|
117
|
+
return;
|
|
118
|
+
const mod = DDL_MODULES.find(m => m.file === file);
|
|
119
|
+
if (!mod)
|
|
120
|
+
return;
|
|
121
|
+
for (const dep of mod.deps) {
|
|
122
|
+
addWithDeps(dep);
|
|
123
|
+
}
|
|
124
|
+
resolved.add(file);
|
|
125
|
+
}
|
|
126
|
+
for (const file of selectedFiles) {
|
|
127
|
+
addWithDeps(file);
|
|
128
|
+
}
|
|
129
|
+
return DDL_MODULES
|
|
130
|
+
.filter(m => resolved.has(m.file))
|
|
131
|
+
.map(m => m.file);
|
|
132
|
+
}
|
|
133
|
+
function getDdlDirectory() {
|
|
134
|
+
const candidates = [
|
|
135
|
+
path.join(__dirname, '..', '..', 'templates', 'ddl'),
|
|
136
|
+
path.join(__dirname, '..', 'templates', 'ddl'),
|
|
137
|
+
path.join(__dirname, '..', '..', '..', 'templates', 'ddl'),
|
|
138
|
+
];
|
|
139
|
+
for (const dir of candidates) {
|
|
140
|
+
if (fs.existsSync(dir))
|
|
141
|
+
return dir;
|
|
142
|
+
}
|
|
143
|
+
const packageRoot = path.resolve(__dirname);
|
|
144
|
+
let current = packageRoot;
|
|
145
|
+
for (let i = 0; i < 5; i++) {
|
|
146
|
+
const ddlDir = path.join(current, 'templates', 'ddl');
|
|
147
|
+
if (fs.existsSync(ddlDir))
|
|
148
|
+
return ddlDir;
|
|
149
|
+
current = path.dirname(current);
|
|
150
|
+
}
|
|
151
|
+
throw new Error('templates/ddl 디렉토리를 찾을 수 없습니다.');
|
|
152
|
+
}
|
|
153
|
+
async function setupDatabase() {
|
|
154
|
+
console.log('\n🗄️ xplat-back 데이터베이스 설정\n');
|
|
155
|
+
console.log('─'.repeat(50));
|
|
156
|
+
// Step 1: MySQL 접속 정보
|
|
157
|
+
const { dbConfig } = await inquirer_1.default.prompt([
|
|
158
|
+
{
|
|
159
|
+
type: 'input',
|
|
160
|
+
name: 'dbConfig.host',
|
|
161
|
+
message: 'DB Host:',
|
|
162
|
+
default: 'localhost',
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
type: 'input',
|
|
166
|
+
name: 'dbConfig.user',
|
|
167
|
+
message: 'DB User:',
|
|
168
|
+
default: 'root',
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
type: 'password',
|
|
172
|
+
name: 'dbConfig.password',
|
|
173
|
+
message: 'DB Password:',
|
|
174
|
+
mask: '*',
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
type: 'input',
|
|
178
|
+
name: 'dbConfig.database',
|
|
179
|
+
message: 'DB Name:',
|
|
180
|
+
validate: (input) => input.trim() ? true : 'DB 이름을 입력해주세요.',
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
type: 'number',
|
|
184
|
+
name: 'dbConfig.port',
|
|
185
|
+
message: 'DB Port:',
|
|
186
|
+
default: 3306,
|
|
187
|
+
},
|
|
188
|
+
]);
|
|
189
|
+
// Step 2: 연결 테스트
|
|
190
|
+
console.log('\n⏳ MySQL 연결 테스트 중...');
|
|
191
|
+
let connection;
|
|
192
|
+
try {
|
|
193
|
+
connection = await mysql.createConnection({
|
|
194
|
+
host: dbConfig.host,
|
|
195
|
+
user: dbConfig.user,
|
|
196
|
+
password: dbConfig.password,
|
|
197
|
+
port: dbConfig.port,
|
|
198
|
+
multipleStatements: true,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
console.error(`\n❌ MySQL 연결 실패: ${err.message}`);
|
|
203
|
+
console.error(' 접속 정보를 확인하고 다시 시도해주세요.\n');
|
|
204
|
+
process.exit(1);
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
await connection.query(`CREATE DATABASE IF NOT EXISTS \`${dbConfig.database}\``);
|
|
208
|
+
await connection.query(`USE \`${dbConfig.database}\``);
|
|
209
|
+
console.log('✅ MySQL 연결 성공!\n');
|
|
210
|
+
}
|
|
211
|
+
catch (err) {
|
|
212
|
+
console.error(`\n❌ 데이터베이스 설정 실패: ${err.message}\n`);
|
|
213
|
+
await connection.end();
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
// Step 3: 기존 테이블 확인
|
|
217
|
+
let existingTables = [];
|
|
218
|
+
try {
|
|
219
|
+
const [rows] = await connection.query('SHOW TABLES');
|
|
220
|
+
existingTables = rows.map((row) => Object.values(row)[0]);
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
// ignore
|
|
224
|
+
}
|
|
225
|
+
// Step 4: DDL 모듈 선택 (체크박스)
|
|
226
|
+
console.log('─'.repeat(50));
|
|
227
|
+
const choices = DDL_MODULES.map(mod => {
|
|
228
|
+
const existCount = mod.tables.filter(t => existingTables.includes(t)).length;
|
|
229
|
+
const statusTag = existCount === mod.tables.length
|
|
230
|
+
? ' (✅ 이미 설치됨)'
|
|
231
|
+
: existCount > 0
|
|
232
|
+
? ` (⚠️ 일부 ${existCount}/${mod.tables.length} 존재)`
|
|
233
|
+
: '';
|
|
234
|
+
return {
|
|
235
|
+
name: `${mod.name}${statusTag}\n 테이블: ${mod.tables.join(', ')}`,
|
|
236
|
+
value: mod.file,
|
|
237
|
+
short: mod.name,
|
|
238
|
+
};
|
|
239
|
+
});
|
|
240
|
+
const { selectedModules } = await inquirer_1.default.prompt([
|
|
241
|
+
{
|
|
242
|
+
type: 'checkbox',
|
|
243
|
+
name: 'selectedModules',
|
|
244
|
+
message: '설치할 DDL 모듈을 선택하세요 (스페이스바로 선택):',
|
|
245
|
+
choices,
|
|
246
|
+
pageSize: 20,
|
|
247
|
+
},
|
|
248
|
+
]);
|
|
249
|
+
if (selectedModules.length === 0) {
|
|
250
|
+
console.log('\n⚠️ 선택된 모듈이 없습니다. 종료합니다.\n');
|
|
251
|
+
await connection.end();
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
// Step 5: 의존성 해결
|
|
255
|
+
const resolvedFiles = resolveDependencies(selectedModules);
|
|
256
|
+
const addedDeps = resolvedFiles.filter(f => !selectedModules.includes(f));
|
|
257
|
+
if (addedDeps.length > 0) {
|
|
258
|
+
const depNames = addedDeps.map(f => {
|
|
259
|
+
const mod = DDL_MODULES.find(m => m.file === f);
|
|
260
|
+
return mod ? ` → ${mod.name} (${f})` : ` → ${f}`;
|
|
261
|
+
});
|
|
262
|
+
console.log('\n📎 의존성으로 자동 추가된 모듈:');
|
|
263
|
+
depNames.forEach(n => console.log(n));
|
|
264
|
+
}
|
|
265
|
+
// Step 6: 실행할 테이블 목록 표시 및 최종 확인
|
|
266
|
+
console.log('\n─'.repeat(50));
|
|
267
|
+
console.log(`\n📋 실행 대상 DDL (${resolvedFiles.length}개 파일):\n`);
|
|
268
|
+
for (const file of resolvedFiles) {
|
|
269
|
+
const mod = DDL_MODULES.find(m => m.file === file);
|
|
270
|
+
const icon = selectedModules.includes(file) ? '📦' : '🔗';
|
|
271
|
+
console.log(` ${icon} ${file} - ${mod.name}`);
|
|
272
|
+
}
|
|
273
|
+
const { confirmExec } = await inquirer_1.default.prompt([
|
|
274
|
+
{
|
|
275
|
+
type: 'confirm',
|
|
276
|
+
name: 'confirmExec',
|
|
277
|
+
message: `\n위 ${resolvedFiles.length}개 DDL 파일을 실행하시겠습니까?`,
|
|
278
|
+
default: false,
|
|
279
|
+
},
|
|
280
|
+
]);
|
|
281
|
+
if (!confirmExec) {
|
|
282
|
+
console.log('\n❌ 취소되었습니다.\n');
|
|
283
|
+
await connection.end();
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
// Step 7: DDL 순차 실행
|
|
287
|
+
const ddlDir = getDdlDirectory();
|
|
288
|
+
console.log('\n⏳ DDL 실행 중...\n');
|
|
289
|
+
let successCount = 0;
|
|
290
|
+
let skipCount = 0;
|
|
291
|
+
const errors = [];
|
|
292
|
+
for (const file of resolvedFiles) {
|
|
293
|
+
const filePath = path.join(ddlDir, file);
|
|
294
|
+
if (!fs.existsSync(filePath)) {
|
|
295
|
+
console.log(` ⚠️ ${file} 파일을 찾을 수 없습니다. 건너뜁니다.`);
|
|
296
|
+
skipCount++;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
const sql = fs.readFileSync(filePath, 'utf-8');
|
|
300
|
+
try {
|
|
301
|
+
await connection.query(sql);
|
|
302
|
+
const mod = DDL_MODULES.find(m => m.file === file);
|
|
303
|
+
console.log(` ✅ ${file} → ${mod.tables.join(', ')}`);
|
|
304
|
+
successCount++;
|
|
305
|
+
}
|
|
306
|
+
catch (err) {
|
|
307
|
+
if (err.code === 'ER_TABLE_EXISTS_ERROR') {
|
|
308
|
+
console.log(` ⚠️ ${file} → 이미 존재하는 테이블이 있어 건너뜁니다.`);
|
|
309
|
+
skipCount++;
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
console.log(` ❌ ${file} → ${err.message}`);
|
|
313
|
+
errors.push({ file, error: err.message });
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
await connection.end();
|
|
318
|
+
// Step 8: 결과 요약
|
|
319
|
+
console.log('\n' + '─'.repeat(50));
|
|
320
|
+
console.log('\n📊 실행 결과:\n');
|
|
321
|
+
console.log(` ✅ 성공: ${successCount}개`);
|
|
322
|
+
if (skipCount > 0)
|
|
323
|
+
console.log(` ⚠️ 건너뜀: ${skipCount}개`);
|
|
324
|
+
if (errors.length > 0) {
|
|
325
|
+
console.log(` ❌ 실패: ${errors.length}개`);
|
|
326
|
+
errors.forEach(e => console.log(` - ${e.file}: ${e.error}`));
|
|
327
|
+
}
|
|
328
|
+
console.log('\n🎉 데이터베이스 설정이 완료되었습니다!\n');
|
|
329
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hamjimin/xplat-back",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Express + TypeScript 백엔드 스캐폴딩 도구 (zium-backend 기반)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -35,33 +35,36 @@
|
|
|
35
35
|
"author": "xplat",
|
|
36
36
|
"license": "ISC",
|
|
37
37
|
"dependencies": {
|
|
38
|
+
"@aws-sdk/client-s3": "^3.922.0",
|
|
39
|
+
"@aws-sdk/s3-request-presigner": "^3.922.0",
|
|
38
40
|
"@nestjs/common": "^10.4.15",
|
|
39
41
|
"@nestjs/core": "^10.4.15",
|
|
40
42
|
"@nestjs/platform-express": "^10.4.15",
|
|
43
|
+
"bcrypt": "^5.1.1",
|
|
41
44
|
"class-transformer": "^0.5.1",
|
|
42
45
|
"class-validator": "^0.14.1",
|
|
43
46
|
"cookie-parser": "^1.4.6",
|
|
44
47
|
"cors": "^2.8.5",
|
|
48
|
+
"crypto-js": "^4.2.0",
|
|
45
49
|
"express": "^4.18.2",
|
|
50
|
+
"inquirer": "^8.2.7",
|
|
46
51
|
"jsonwebtoken": "^9.0.2",
|
|
47
|
-
"bcrypt": "^5.1.1",
|
|
48
|
-
"crypto-js": "^4.2.0",
|
|
49
52
|
"lodash": "^4.17.21",
|
|
53
|
+
"mysql2": "^3.17.2",
|
|
50
54
|
"reflect-metadata": "^0.2.2",
|
|
51
55
|
"rxjs": "^7.8.1",
|
|
52
|
-
"uuid": "^9.0.1"
|
|
53
|
-
"@aws-sdk/client-s3": "^3.922.0",
|
|
54
|
-
"@aws-sdk/s3-request-presigner": "^3.922.0"
|
|
56
|
+
"uuid": "^9.0.1"
|
|
55
57
|
},
|
|
56
58
|
"devDependencies": {
|
|
57
|
-
"@types/
|
|
58
|
-
"@types/node": "^20.10.0",
|
|
59
|
+
"@types/bcrypt": "^5.0.2",
|
|
59
60
|
"@types/cookie-parser": "^1.4.6",
|
|
60
61
|
"@types/cors": "^2.8.17",
|
|
61
|
-
"@types/jsonwebtoken": "^9.0.5",
|
|
62
|
-
"@types/bcrypt": "^5.0.2",
|
|
63
62
|
"@types/crypto-js": "^4.2.2",
|
|
63
|
+
"@types/express": "^4.17.21",
|
|
64
|
+
"@types/inquirer": "^8.2.12",
|
|
65
|
+
"@types/jsonwebtoken": "^9.0.5",
|
|
64
66
|
"@types/lodash": "^4.14.202",
|
|
67
|
+
"@types/node": "^20.10.0",
|
|
65
68
|
"@types/uuid": "^9.0.8",
|
|
66
69
|
"typescript": "^5.3.3"
|
|
67
70
|
}
|
package/templates/ERD.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# E-Commerce Platform ERD
|
|
2
|
+
|
|
3
|
+
> 36 tables / MySQL / DDL: `templates/ddl/`
|
|
4
|
+
|
|
5
|
+
## Table Overview
|
|
6
|
+
|
|
7
|
+
### User (01_user.sql)
|
|
8
|
+
| Table | PK | Columns |
|
|
9
|
+
|-------|----|----|
|
|
10
|
+
| user | id | name, phone |
|
|
11
|
+
| employee | id | name, phone |
|
|
12
|
+
| partner | id | name, phone |
|
|
13
|
+
|
|
14
|
+
### Product (02_product.sql)
|
|
15
|
+
| Table | PK | FK | Columns |
|
|
16
|
+
|-------|----|----|---------|
|
|
17
|
+
| category | id | - | name |
|
|
18
|
+
| product | id | category_id | name, description, price, status, type, count, discount_start_date, discount_end_date, discount_price, discount_percent, delivery_price, delivery_type, delivery_remaining_price |
|
|
19
|
+
| variants | id | product_id | name, description, price, type, count, delivery_price, delivery_remaining_price, delivery_type |
|
|
20
|
+
| brand | id | employee_id | name, description |
|
|
21
|
+
| side_product | id | product_id | name, discount_percent, discount_price, price, count, type |
|
|
22
|
+
|
|
23
|
+
### Package (03_package.sql)
|
|
24
|
+
| Table | PK | FK | Columns |
|
|
25
|
+
|-------|----|----|---------|
|
|
26
|
+
| product_package | id | order_id | status, campaign_type, payment_type, discount_percent, discount_price, point, payment_price, delivery_price, delivery_type, delivery_remaining_price |
|
|
27
|
+
| product_package_detail | id | product_package_id, product_id | name, price, type, count, payment_price, point, discount_price, discount_percent, delivery_price, delivery_type, delivery_remaining_price |
|
|
28
|
+
| product_package_variant | id | product_package_detail_id, variant_id, product_package_id | name, price, type, count, payment_price, point, discount_price, discount_percent, delivery_price, delivery_type, delivery_remaining_price |
|
|
29
|
+
|
|
30
|
+
### Order (04_order.sql)
|
|
31
|
+
| Table | PK | FK | Columns |
|
|
32
|
+
|-------|----|----|---------|
|
|
33
|
+
| orders | id | user_id | status |
|
|
34
|
+
| order_items | id | order_id | product_id, variant_id, description, price, campaign_yn, campaign_id, type, count |
|
|
35
|
+
| order_manage | id | order_id, user_id, order_item_id | delivery_status, card_info(JSON), price, payment_type, payment_date |
|
|
36
|
+
| order_manage_comment | id | order_manage_id, partner_id, employee_id | context |
|
|
37
|
+
|
|
38
|
+
### Payment (05_payment.sql)
|
|
39
|
+
| Table | PK | FK | Columns |
|
|
40
|
+
|-------|----|----|---------|
|
|
41
|
+
| payment | id | order_id | payment_type, card_info(JSON), price, refund_info(JSON), payment_date |
|
|
42
|
+
| calcurate | id | partner_id, order_id, payment_id | calcurate_price, calcurate_date |
|
|
43
|
+
|
|
44
|
+
### Delivery (06_delivery.sql)
|
|
45
|
+
| Table | PK | FK | Columns |
|
|
46
|
+
|-------|----|----|---------|
|
|
47
|
+
| delivery | id | - | status, delivery_number, company, location, address, address_detail, zip_code, price |
|
|
48
|
+
| reservation | id | order_item_id | status, date_time |
|
|
49
|
+
|
|
50
|
+
### Cart (07_cart.sql)
|
|
51
|
+
| Table | PK | FK | Columns |
|
|
52
|
+
|-------|----|----|---------|
|
|
53
|
+
| cart | id | user_id | status |
|
|
54
|
+
| cart_items | id | cart_id, product_id, variant_id | count |
|
|
55
|
+
|
|
56
|
+
### Campaign (08_campaign.sql)
|
|
57
|
+
| Table | PK | FK | Columns |
|
|
58
|
+
|-------|----|----|---------|
|
|
59
|
+
| campaign | id | - | title, description, start_date, end_date, type, status, condition |
|
|
60
|
+
| promotion_detail | id | campaign_id, package_id | status, discount_price, discount_percent |
|
|
61
|
+
| event_detail | id | campaign_id, package_id | status, discount_price, discount_percent |
|
|
62
|
+
|
|
63
|
+
### Review (09_review.sql)
|
|
64
|
+
| Table | PK | FK | Columns |
|
|
65
|
+
|-------|----|----|---------|
|
|
66
|
+
| product_review | id | product_id, user_id | status, user_pwd, context, image_id |
|
|
67
|
+
| review_images | id | review_id | file_name, file_size, file_url, file_ext |
|
|
68
|
+
| package_product_review | id | package_id, user_id | status, user_pwd, context, image_id, score |
|
|
69
|
+
| package_review_images | id | review_id | file_name, file_size, file_url, file_ext |
|
|
70
|
+
|
|
71
|
+
### Chain (10_chain.sql)
|
|
72
|
+
| Table | FK1 | FK2 |
|
|
73
|
+
|-------|-----|-----|
|
|
74
|
+
| chain_category_detail | category_id | detail_id |
|
|
75
|
+
| chain_product_detail | product_id | detail_id |
|
|
76
|
+
| chain_variant_detail | variant_id | detail_id |
|
|
77
|
+
| chain_product_brand | product_id | brand_id |
|
|
78
|
+
| chain_order_delivery | order_id, order_item_id | delivery_id |
|
|
79
|
+
| chain_order_cart | order_id | cart_id |
|
|
80
|
+
| chain_order_reservation | order_id | reservation_id |
|
|
81
|
+
| chain_campaign_package | campaign_id | package_id |
|
|
82
|
+
|
|
83
|
+
## Relationship Diagram (Text)
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
user ──1:N──> orders
|
|
87
|
+
user ──1:N──> cart
|
|
88
|
+
user ──1:N──> product_review
|
|
89
|
+
user ──1:N──> package_product_review
|
|
90
|
+
|
|
91
|
+
category ──1:N──> product
|
|
92
|
+
product ──1:N──> variants
|
|
93
|
+
product ──1:N──> side_product
|
|
94
|
+
product ──1:N──> product_review
|
|
95
|
+
|
|
96
|
+
product_package ──1:N──> product_package_detail ──1:N──> product_package_variant
|
|
97
|
+
|
|
98
|
+
orders ──1:N──> order_items
|
|
99
|
+
orders ──1:N──> payment
|
|
100
|
+
orders ──1:N──> order_manage ──1:N──> order_manage_comment
|
|
101
|
+
orders ──1:N──> calcurate
|
|
102
|
+
|
|
103
|
+
order_items ──1:N──> reservation
|
|
104
|
+
|
|
105
|
+
cart ──1:N──> cart_items
|
|
106
|
+
|
|
107
|
+
campaign ──1:N──> promotion_detail
|
|
108
|
+
campaign ──1:N──> event_detail
|
|
109
|
+
|
|
110
|
+
brand ──1:N──> chain_product_brand <──N:1── product
|
|
111
|
+
|
|
112
|
+
-- Chain tables (M:N resolvers)
|
|
113
|
+
orders <──> delivery via chain_order_delivery
|
|
114
|
+
orders <──> cart via chain_order_cart
|
|
115
|
+
orders <──> reservation via chain_order_reservation
|
|
116
|
+
campaign <──> product_package via chain_campaign_package
|
|
117
|
+
category/product/variants <──> detail via chain_*_detail
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## DDL Execution Order
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
01_user.sql -- user, employee, partner (no deps)
|
|
124
|
+
02_product.sql -- category, product, variants, brand, side_product
|
|
125
|
+
03_package.sql -- product_package, product_package_detail, product_package_variant
|
|
126
|
+
04_order.sql -- orders, order_items, order_manage, order_manage_comment
|
|
127
|
+
05_payment.sql -- payment, calcurate
|
|
128
|
+
06_delivery.sql -- delivery, reservation
|
|
129
|
+
07_cart.sql -- cart, cart_items
|
|
130
|
+
08_campaign.sql -- campaign, promotion_detail, event_detail
|
|
131
|
+
09_review.sql -- product_review, review_images, package_product_review, package_review_images
|
|
132
|
+
10_chain.sql -- all chain/junction tables
|
|
133
|
+
```
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
-- =============================================
|
|
2
|
+
-- User / Employee / Partner
|
|
3
|
+
-- =============================================
|
|
4
|
+
|
|
5
|
+
CREATE TABLE IF NOT EXISTS `user` (
|
|
6
|
+
`id` VARCHAR(255) NOT NULL,
|
|
7
|
+
`name` VARCHAR(100) NOT NULL,
|
|
8
|
+
`phone` VARCHAR(20),
|
|
9
|
+
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
10
|
+
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
11
|
+
PRIMARY KEY (`id`)
|
|
12
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
13
|
+
|
|
14
|
+
CREATE TABLE IF NOT EXISTS `employee` (
|
|
15
|
+
`id` VARCHAR(255) NOT NULL,
|
|
16
|
+
`name` VARCHAR(100) NOT NULL,
|
|
17
|
+
`phone` VARCHAR(20),
|
|
18
|
+
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
19
|
+
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
20
|
+
PRIMARY KEY (`id`)
|
|
21
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
22
|
+
|
|
23
|
+
CREATE TABLE IF NOT EXISTS `partner` (
|
|
24
|
+
`id` VARCHAR(255) NOT NULL,
|
|
25
|
+
`name` VARCHAR(100),
|
|
26
|
+
`phone` VARCHAR(20),
|
|
27
|
+
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
28
|
+
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
29
|
+
PRIMARY KEY (`id`)
|
|
30
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|