@hamjimin/xplat-back 0.5.0 → 0.6.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/dist/cli/index.js +5 -0
- package/dist/cli/init.d.ts +1 -0
- package/dist/cli/init.js +93 -2
- package/dist/cli/templates/app.ts.example +3 -0
- package/dist/cli/templates/next/app/globals.css.example +15 -0
- package/dist/cli/templates/next/app/layout.tsx.example +16 -0
- package/dist/cli/templates/next/app/page.tsx.example +12 -0
- package/dist/cli/templates/next/next-env.d.ts.example +5 -0
- package/dist/cli/templates/next/next.config.js.example +9 -0
- package/dist/cli/templates/next/package.json.example +24 -0
- package/dist/cli/templates/next/tsconfig.json.example +23 -0
- package/dist/cli/templates/package.json.example +4 -1
- package/dist/cli/templates/util/dbconfig/dbConnections.ts.example +92 -0
- package/dist/commerce/commerce.module.d.ts +7 -0
- package/dist/commerce/commerce.module.js +25 -0
- package/dist/commerce/modules/admin/admin-api.module.d.ts +8 -0
- package/dist/commerce/modules/admin/admin-api.module.js +25 -0
- package/dist/commerce/modules/admin/controllers/admin-health.controller.d.ts +11 -0
- package/dist/commerce/modules/admin/controllers/admin-health.controller.js +36 -0
- package/dist/commerce/modules/store/controllers/store-health.controller.d.ts +11 -0
- package/dist/commerce/modules/store/controllers/store-health.controller.js +36 -0
- package/dist/commerce/modules/store/store-api.module.d.ts +8 -0
- package/dist/commerce/modules/store/store-api.module.js +25 -0
- package/dist/commerce/types.d.ts +33 -0
- package/dist/commerce/types.js +5 -0
- package/dist/core/XplatSystem.d.ts +2 -0
- package/dist/core/XplatSystem.js +2 -0
- package/dist/modules/commerce/index.d.ts +15 -0
- package/dist/modules/commerce/index.js +21 -0
- package/package.json +9 -3
package/dist/cli/index.js
CHANGED
|
@@ -28,6 +28,10 @@ async function main() {
|
|
|
28
28
|
case '--router-path':
|
|
29
29
|
config.routerBasePath = args[++i];
|
|
30
30
|
break;
|
|
31
|
+
case '--with-next':
|
|
32
|
+
case '--next':
|
|
33
|
+
config.withNext = true;
|
|
34
|
+
break;
|
|
31
35
|
case '--help':
|
|
32
36
|
case '-h':
|
|
33
37
|
showHelp();
|
|
@@ -64,6 +68,7 @@ Options:
|
|
|
64
68
|
-c, --cors <origins> CORS 허용 origins (쉼표로 구분, 기본값: http://localhost:3000)
|
|
65
69
|
-r, --router-dir <directory> 라우터 디렉토리 (기본값: src/restapi)
|
|
66
70
|
--router-path <path> 라우터 base path (기본값: /restapi)
|
|
71
|
+
--with-next Next.js 프론트엔드(web) 템플릿 추가
|
|
67
72
|
-h, --help 도움말 출력
|
|
68
73
|
|
|
69
74
|
Examples:
|
package/dist/cli/init.d.ts
CHANGED
package/dist/cli/init.js
CHANGED
|
@@ -48,6 +48,7 @@ async function initializeProject(config = {}) {
|
|
|
48
48
|
const corsOrigins = config.corsOrigins || 'http://localhost:3000';
|
|
49
49
|
const routerDirectory = config.routerDirectory || 'src/restapi';
|
|
50
50
|
const routerBasePath = config.routerBasePath || '/restapi';
|
|
51
|
+
const withNext = config.withNext || false;
|
|
51
52
|
console.log('\n🚀 xplat-back 프로젝트 초기화를 시작합니다...\n');
|
|
52
53
|
// src 디렉토리 생성
|
|
53
54
|
const srcDir = path.join(projectRoot, 'src');
|
|
@@ -75,6 +76,12 @@ async function initializeProject(config = {}) {
|
|
|
75
76
|
await createExampleRouteFile(restapiDir);
|
|
76
77
|
// 상수 파일 생성 (src/const 디렉토리)
|
|
77
78
|
await createConstantsFiles(srcDir);
|
|
79
|
+
// DB 설정 파일 생성 (src/util/dbconfig)
|
|
80
|
+
await createDbConfigFiles(srcDir);
|
|
81
|
+
// Next.js 앱 생성 (옵션)
|
|
82
|
+
if (withNext) {
|
|
83
|
+
await createNextApp(projectRoot);
|
|
84
|
+
}
|
|
78
85
|
console.log('\n✅ 프로젝트 초기화가 완료되었습니다!');
|
|
79
86
|
console.log(`\n생성된 파일:`);
|
|
80
87
|
console.log(` - src/app.ts`);
|
|
@@ -85,6 +92,12 @@ async function initializeProject(config = {}) {
|
|
|
85
92
|
console.log(` - ${routerDirectory}/health.ts`);
|
|
86
93
|
console.log(` - src/const/api_code.ts`);
|
|
87
94
|
console.log(` - src/const/constants.ts`);
|
|
95
|
+
console.log(` - src/util/dbconfig/dbConnections.ts`);
|
|
96
|
+
if (withNext) {
|
|
97
|
+
console.log(` - web/next.config.js`);
|
|
98
|
+
console.log(` - web/app/page.tsx`);
|
|
99
|
+
console.log(` - web/package.json`);
|
|
100
|
+
}
|
|
88
101
|
console.log(`\n다음 단계:`);
|
|
89
102
|
console.log(` 1. npm install (또는 yarn install)`);
|
|
90
103
|
console.log(` 2. npm run dev (또는 yarn dev)로 서버 실행`);
|
|
@@ -101,8 +114,11 @@ async function createAppFile(projectRoot, port, corsOrigins, routerDirectory, ro
|
|
|
101
114
|
}
|
|
102
115
|
const appContent = `import express, { Application } from 'express';
|
|
103
116
|
import * as path from 'path';
|
|
117
|
+
import dotenv from 'dotenv';
|
|
104
118
|
import { createApp } from 'xplat-back';
|
|
105
119
|
|
|
120
|
+
dotenv.config();
|
|
121
|
+
|
|
106
122
|
const app: Application = createApp({
|
|
107
123
|
cors: {
|
|
108
124
|
origin: process.env.CORS_ALLOW_ORIGINS
|
|
@@ -218,7 +234,10 @@ async function createPackageJsonFile(projectRoot, projectName) {
|
|
|
218
234
|
},
|
|
219
235
|
dependencies: {
|
|
220
236
|
express: '^4.18.2',
|
|
221
|
-
|
|
237
|
+
dotenv: '^16.4.5',
|
|
238
|
+
typeorm: '^0.3.20',
|
|
239
|
+
mysql2: '^3.9.7',
|
|
240
|
+
'xplat-back': '^0.5.1',
|
|
222
241
|
},
|
|
223
242
|
devDependencies: {
|
|
224
243
|
'@types/express': '^4.17.21',
|
|
@@ -247,7 +266,14 @@ PORT=${port}
|
|
|
247
266
|
# CORS Configuration
|
|
248
267
|
CORS_ALLOW_ORIGINS=${corsOrigins}
|
|
249
268
|
|
|
250
|
-
#
|
|
269
|
+
# Database Configuration
|
|
270
|
+
DB_HOST=localhost
|
|
271
|
+
DB_PORT=3306
|
|
272
|
+
DB_USER=root
|
|
273
|
+
DB_PASSWORD=
|
|
274
|
+
DB_DATABASE=my_database
|
|
275
|
+
DB_MYSQL_PORT=3306
|
|
276
|
+
DB_SYNC=false
|
|
251
277
|
`;
|
|
252
278
|
fs.writeFileSync(envPath, envContent, 'utf-8');
|
|
253
279
|
console.log(`✅ .env 파일이 생성되었습니다.`);
|
|
@@ -298,3 +324,68 @@ async function createConstantsFiles(srcDir) {
|
|
|
298
324
|
console.log(`⚠️ src/const/constants.ts 파일이 이미 존재합니다. 건너뜁니다.`);
|
|
299
325
|
}
|
|
300
326
|
}
|
|
327
|
+
/**
|
|
328
|
+
* DB 설정 파일 생성 (src/util/dbconfig/dbConnections.ts)
|
|
329
|
+
*/
|
|
330
|
+
async function createDbConfigFiles(srcDir) {
|
|
331
|
+
const dbConfigDir = path.join(srcDir, 'util', 'dbconfig');
|
|
332
|
+
if (!fs.existsSync(dbConfigDir)) {
|
|
333
|
+
fs.mkdirSync(dbConfigDir, { recursive: true });
|
|
334
|
+
}
|
|
335
|
+
const dbConnectionsPath = path.join(dbConfigDir, 'dbConnections.ts');
|
|
336
|
+
if (fs.existsSync(dbConnectionsPath)) {
|
|
337
|
+
console.log(`⚠️ src/util/dbconfig/dbConnections.ts 파일이 이미 존재합니다. 건너뜁니다.`);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const templatePath = path.join(__dirname, 'templates', 'util', 'dbconfig', 'dbConnections.ts.example');
|
|
341
|
+
const templateContent = fs.readFileSync(templatePath, 'utf-8');
|
|
342
|
+
fs.writeFileSync(dbConnectionsPath, templateContent, 'utf-8');
|
|
343
|
+
console.log(`✅ src/util/dbconfig/dbConnections.ts 파일이 생성되었습니다.`);
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Next.js 프로젝트 생성 (web 디렉토리)
|
|
347
|
+
*/
|
|
348
|
+
async function createNextApp(projectRoot) {
|
|
349
|
+
const webDir = path.join(projectRoot, 'web');
|
|
350
|
+
if (!fs.existsSync(webDir)) {
|
|
351
|
+
fs.mkdirSync(webDir, { recursive: true });
|
|
352
|
+
}
|
|
353
|
+
const templateBase = path.join(__dirname, 'templates', 'next');
|
|
354
|
+
const filesToCopy = [
|
|
355
|
+
{ template: 'package.json.example', target: 'package.json' },
|
|
356
|
+
{ template: 'tsconfig.json.example', target: 'tsconfig.json' },
|
|
357
|
+
{ template: 'next.config.js.example', target: 'next.config.js' },
|
|
358
|
+
];
|
|
359
|
+
filesToCopy.forEach(({ template, target }) => {
|
|
360
|
+
const targetPath = path.join(webDir, target);
|
|
361
|
+
if (fs.existsSync(targetPath)) {
|
|
362
|
+
console.log(`⚠️ web/${target} 파일이 이미 존재합니다. 건너뜁니다.`);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
const templatePath = path.join(templateBase, template);
|
|
366
|
+
const content = fs.readFileSync(templatePath, 'utf-8');
|
|
367
|
+
fs.writeFileSync(targetPath, content, 'utf-8');
|
|
368
|
+
console.log(`✅ web/${target} 파일이 생성되었습니다.`);
|
|
369
|
+
});
|
|
370
|
+
const appDir = path.join(webDir, 'app');
|
|
371
|
+
if (!fs.existsSync(appDir)) {
|
|
372
|
+
fs.mkdirSync(appDir, { recursive: true });
|
|
373
|
+
console.log(`✅ web/app 디렉토리가 생성되었습니다.`);
|
|
374
|
+
}
|
|
375
|
+
const appFiles = [
|
|
376
|
+
{ template: path.join('app', 'page.tsx.example'), target: path.join(appDir, 'page.tsx') },
|
|
377
|
+
{ template: path.join('app', 'layout.tsx.example'), target: path.join(appDir, 'layout.tsx') },
|
|
378
|
+
{ template: path.join('app', 'globals.css.example'), target: path.join(appDir, 'globals.css') },
|
|
379
|
+
{ template: 'next-env.d.ts.example', target: path.join(webDir, 'next-env.d.ts') },
|
|
380
|
+
];
|
|
381
|
+
appFiles.forEach(({ template, target }) => {
|
|
382
|
+
if (fs.existsSync(target)) {
|
|
383
|
+
console.log(`⚠️ ${path.relative(projectRoot, target)} 파일이 이미 존재합니다. 건너뜁니다.`);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const templatePath = path.join(templateBase, template);
|
|
387
|
+
const content = fs.readFileSync(templatePath, 'utf-8');
|
|
388
|
+
fs.writeFileSync(target, content, 'utf-8');
|
|
389
|
+
console.log(`✅ ${path.relative(projectRoot, target)} 파일이 생성되었습니다.`);
|
|
390
|
+
});
|
|
391
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import express, { Application } from 'express';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
+
import dotenv from 'dotenv';
|
|
3
4
|
import { createApp } from 'xplat-back';
|
|
4
5
|
|
|
6
|
+
dotenv.config();
|
|
7
|
+
|
|
5
8
|
const app: Application = createApp({
|
|
6
9
|
cors: {
|
|
7
10
|
origin: process.env.CORS_ALLOW_ORIGINS
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
color-scheme: light;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
* {
|
|
6
|
+
box-sizing: border-box;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
body {
|
|
10
|
+
margin: 0;
|
|
11
|
+
padding: 0;
|
|
12
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif;
|
|
13
|
+
background: #fafafa;
|
|
14
|
+
color: #111;
|
|
15
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import './globals.css';
|
|
2
|
+
import type { Metadata } from 'next';
|
|
3
|
+
import { ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
export const metadata: Metadata = {
|
|
6
|
+
title: 'xplat-web',
|
|
7
|
+
description: 'Next.js + xplat-back starter',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default function RootLayout({ children }: { children: ReactNode }) {
|
|
11
|
+
return (
|
|
12
|
+
<html lang="en">
|
|
13
|
+
<body>{children}</body>
|
|
14
|
+
</html>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export default function Home() {
|
|
2
|
+
return (
|
|
3
|
+
<main style={{ padding: '24px', fontFamily: 'sans-serif' }}>
|
|
4
|
+
<h1>Next.js + xplat-back</h1>
|
|
5
|
+
<p>프론트(Next.js)와 백엔드(xplat-back)를 한 프로젝트에서 시작합니다.</p>
|
|
6
|
+
<ul>
|
|
7
|
+
<li>백엔드: <code>npm run dev</code> (루트)</li>
|
|
8
|
+
<li>프론트: <code>cd web && npm run dev</code></li>
|
|
9
|
+
</ul>
|
|
10
|
+
</main>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "xplat-web",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev",
|
|
7
|
+
"build": "next build",
|
|
8
|
+
"start": "next start",
|
|
9
|
+
"lint": "next lint"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"next": "14.2.5",
|
|
13
|
+
"react": "18.3.1",
|
|
14
|
+
"react-dom": "18.3.1"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/node": "^20.10.0",
|
|
18
|
+
"@types/react": "^18.2.64",
|
|
19
|
+
"@types/react-dom": "^18.2.21",
|
|
20
|
+
"typescript": "^5.3.3",
|
|
21
|
+
"eslint": "^8.57.0",
|
|
22
|
+
"eslint-config-next": "14.2.5"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es5",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": false,
|
|
8
|
+
"forceConsistentCasingInFileNames": true,
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"module": "esnext",
|
|
12
|
+
"moduleResolution": "node",
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"isolatedModules": true,
|
|
15
|
+
"jsx": "preserve",
|
|
16
|
+
"incremental": true,
|
|
17
|
+
"paths": {
|
|
18
|
+
"@/*": ["./*"]
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
|
22
|
+
"exclude": ["node_modules"]
|
|
23
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { DataSource, DataSourceOptions } from 'typeorm';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import dotenv from 'dotenv';
|
|
5
|
+
|
|
6
|
+
dotenv.config();
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 프로젝트 엔티티를 동적으로 로딩합니다.
|
|
10
|
+
* 필요에 따라 경로를 조정하거나 직접 배열로 지정해 주세요.
|
|
11
|
+
*/
|
|
12
|
+
const loadEntities = (dir: string) => {
|
|
13
|
+
const entities: Function[] = [];
|
|
14
|
+
if (!fs.existsSync(dir)) return entities;
|
|
15
|
+
|
|
16
|
+
const files = fs.readdirSync(dir);
|
|
17
|
+
for (const file of files) {
|
|
18
|
+
if (file.endsWith('.ts') || file.endsWith('.js')) {
|
|
19
|
+
const entity = require(path.join(dir, file));
|
|
20
|
+
for (const key in entity) {
|
|
21
|
+
entities.push(entity[key]);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return entities;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const connectionInfo = (target: string): DataSourceOptions => {
|
|
29
|
+
const port =
|
|
30
|
+
Number(process.env[`${target}_MYSQL_PORT`]) ||
|
|
31
|
+
Number(process.env[`${target}_PORT`]) ||
|
|
32
|
+
3306;
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
type: 'mysql',
|
|
36
|
+
// 개발용 스키마 자동 동기화 (운영에서는 DB_SYNC=false 권장)
|
|
37
|
+
synchronize: process.env.DB_SYNC === 'true',
|
|
38
|
+
logging: process.env.NODE_ENV === 'production' ? ['error'] : ['query', 'error'],
|
|
39
|
+
host: process.env[`${target}_HOST`] || 'localhost',
|
|
40
|
+
port,
|
|
41
|
+
username: process.env[`${target}_USER`] || 'root',
|
|
42
|
+
password: process.env[`${target}_PASSWORD`] || '',
|
|
43
|
+
database: process.env[`${target}_DATABASE`] || 'database',
|
|
44
|
+
charset: 'utf8mb4',
|
|
45
|
+
timezone: '+09:00',
|
|
46
|
+
extra: {
|
|
47
|
+
connectionLimit: 50,
|
|
48
|
+
connectTimeout: 60000,
|
|
49
|
+
waitForConnections: true,
|
|
50
|
+
idleTimeout: 300000,
|
|
51
|
+
acquireTimeout: 60000,
|
|
52
|
+
reconnect: true,
|
|
53
|
+
dateStrings: true,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const mainDB = new DataSource({
|
|
59
|
+
...connectionInfo('DB'),
|
|
60
|
+
// 필요 시 엔티티 경로를 변경하거나 직접 배열로 설정하세요.
|
|
61
|
+
entities: loadEntities(path.join(__dirname, '..', '..', 'entities')),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 데이터베이스 연결을 초기화합니다.
|
|
66
|
+
*/
|
|
67
|
+
export const initializeDatabase = async () => {
|
|
68
|
+
if (!mainDB.isInitialized) {
|
|
69
|
+
await mainDB.initialize();
|
|
70
|
+
console.log('✅ Database connection initialized');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
await mainDB.query('SELECT 1');
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error('Connection lost. Reconnecting...', error);
|
|
78
|
+
await mainDB.destroy();
|
|
79
|
+
await mainDB.initialize();
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 엔티티 기준 스키마 동기화 (DB_SYNC=true 일 때만 실행 권장)
|
|
85
|
+
*/
|
|
86
|
+
export const syncSchema = async () => {
|
|
87
|
+
if (!mainDB.isInitialized) {
|
|
88
|
+
await mainDB.initialize();
|
|
89
|
+
}
|
|
90
|
+
await mainDB.synchronize();
|
|
91
|
+
console.log('✅ Database schema synchronized from entities');
|
|
92
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.CommerceModule = void 0;
|
|
10
|
+
const common_1 = require("@nestjs/common");
|
|
11
|
+
const admin_api_module_1 = require("./modules/admin/admin-api.module");
|
|
12
|
+
const store_api_module_1 = require("./modules/store/store-api.module");
|
|
13
|
+
/**
|
|
14
|
+
* CommerceModule
|
|
15
|
+
* - Store/Admin API 분리 구조를 제공하는 최상위 모듈
|
|
16
|
+
* - 도메인 모듈은 하위 todos에서 점진적으로 추가됩니다.
|
|
17
|
+
*/
|
|
18
|
+
let CommerceModule = class CommerceModule {
|
|
19
|
+
};
|
|
20
|
+
exports.CommerceModule = CommerceModule;
|
|
21
|
+
exports.CommerceModule = CommerceModule = __decorate([
|
|
22
|
+
(0, common_1.Module)({
|
|
23
|
+
imports: [store_api_module_1.StoreApiModule, admin_api_module_1.AdminApiModule],
|
|
24
|
+
})
|
|
25
|
+
], CommerceModule);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.AdminApiModule = void 0;
|
|
10
|
+
const common_1 = require("@nestjs/common");
|
|
11
|
+
const admin_health_controller_1 = require("./controllers/admin-health.controller");
|
|
12
|
+
/**
|
|
13
|
+
* Admin API (v1)
|
|
14
|
+
* - 운영/머천트용 엔드포인트를 제공합니다.
|
|
15
|
+
* - prefix는 generated template에서 /admin/v1 로 고정될 예정이며,
|
|
16
|
+
* 컨트롤러는 이후 도메인별로 확장합니다.
|
|
17
|
+
*/
|
|
18
|
+
let AdminApiModule = class AdminApiModule {
|
|
19
|
+
};
|
|
20
|
+
exports.AdminApiModule = AdminApiModule;
|
|
21
|
+
exports.AdminApiModule = AdminApiModule = __decorate([
|
|
22
|
+
(0, common_1.Module)({
|
|
23
|
+
controllers: [admin_health_controller_1.AdminHealthController],
|
|
24
|
+
})
|
|
25
|
+
], AdminApiModule);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.AdminHealthController = void 0;
|
|
13
|
+
const common_1 = require("@nestjs/common");
|
|
14
|
+
/**
|
|
15
|
+
* Admin Health
|
|
16
|
+
* - 스캐폴딩/연결 확인용 최소 엔드포인트
|
|
17
|
+
*/
|
|
18
|
+
let AdminHealthController = class AdminHealthController {
|
|
19
|
+
health() {
|
|
20
|
+
return {
|
|
21
|
+
ok: true,
|
|
22
|
+
scope: 'admin',
|
|
23
|
+
timestamp: new Date().toISOString(),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
exports.AdminHealthController = AdminHealthController;
|
|
28
|
+
__decorate([
|
|
29
|
+
(0, common_1.Get)('health'),
|
|
30
|
+
__metadata("design:type", Function),
|
|
31
|
+
__metadata("design:paramtypes", []),
|
|
32
|
+
__metadata("design:returntype", void 0)
|
|
33
|
+
], AdminHealthController.prototype, "health", null);
|
|
34
|
+
exports.AdminHealthController = AdminHealthController = __decorate([
|
|
35
|
+
(0, common_1.Controller)()
|
|
36
|
+
], AdminHealthController);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.StoreHealthController = void 0;
|
|
13
|
+
const common_1 = require("@nestjs/common");
|
|
14
|
+
/**
|
|
15
|
+
* Store Health
|
|
16
|
+
* - 스캐폴딩/연결 확인용 최소 엔드포인트
|
|
17
|
+
*/
|
|
18
|
+
let StoreHealthController = class StoreHealthController {
|
|
19
|
+
health() {
|
|
20
|
+
return {
|
|
21
|
+
ok: true,
|
|
22
|
+
scope: 'store',
|
|
23
|
+
timestamp: new Date().toISOString(),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
exports.StoreHealthController = StoreHealthController;
|
|
28
|
+
__decorate([
|
|
29
|
+
(0, common_1.Get)('health'),
|
|
30
|
+
__metadata("design:type", Function),
|
|
31
|
+
__metadata("design:paramtypes", []),
|
|
32
|
+
__metadata("design:returntype", void 0)
|
|
33
|
+
], StoreHealthController.prototype, "health", null);
|
|
34
|
+
exports.StoreHealthController = StoreHealthController = __decorate([
|
|
35
|
+
(0, common_1.Controller)()
|
|
36
|
+
], StoreHealthController);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.StoreApiModule = void 0;
|
|
10
|
+
const common_1 = require("@nestjs/common");
|
|
11
|
+
const store_health_controller_1 = require("./controllers/store-health.controller");
|
|
12
|
+
/**
|
|
13
|
+
* Store API (v1)
|
|
14
|
+
* - 공개/고객용 엔드포인트를 제공합니다.
|
|
15
|
+
* - prefix는 generated template에서 /store/v1 로 고정될 예정이며,
|
|
16
|
+
* 컨트롤러는 이후 도메인별로 확장합니다.
|
|
17
|
+
*/
|
|
18
|
+
let StoreApiModule = class StoreApiModule {
|
|
19
|
+
};
|
|
20
|
+
exports.StoreApiModule = StoreApiModule;
|
|
21
|
+
exports.StoreApiModule = StoreApiModule = __decorate([
|
|
22
|
+
(0, common_1.Module)({
|
|
23
|
+
controllers: [store_health_controller_1.StoreHealthController],
|
|
24
|
+
})
|
|
25
|
+
], StoreApiModule);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 커머스 플랫폼 공통 타입/계약 (Medusa 스타일 참고)
|
|
3
|
+
*/
|
|
4
|
+
export type ApiScope = 'store' | 'admin';
|
|
5
|
+
export interface ApiVersioning {
|
|
6
|
+
storePrefix: string;
|
|
7
|
+
adminPrefix: string;
|
|
8
|
+
}
|
|
9
|
+
export interface CommerceCoreConfig {
|
|
10
|
+
versioning?: Partial<ApiVersioning>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 확장 포인트: 이벤트 버스
|
|
14
|
+
* - 초기 구현은 간단한 in-memory pub/sub로 시작하고,
|
|
15
|
+
* 이후 Kafka/SQS/Redis 등으로 교체 가능하도록 포트만 정의합니다.
|
|
16
|
+
*/
|
|
17
|
+
export interface EventBus {
|
|
18
|
+
publish<TPayload>(eventName: string, payload: TPayload): Promise<void>;
|
|
19
|
+
subscribe<TPayload>(eventName: string, handler: (payload: TPayload) => Promise<void> | void): () => void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 확장 포인트: Job/Queue
|
|
23
|
+
*/
|
|
24
|
+
export interface JobQueue {
|
|
25
|
+
enqueue<TPayload>(jobName: string, payload: TPayload, opts?: Record<string, any>): Promise<string>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 확장 포인트: Hook
|
|
29
|
+
* - 특정 도메인 흐름(예: cart.updated, order.placed) 전/후 처리 훅
|
|
30
|
+
*/
|
|
31
|
+
export interface HookRunner {
|
|
32
|
+
run<TContext>(hookName: string, ctx: TContext): Promise<TContext>;
|
|
33
|
+
}
|
|
@@ -8,6 +8,7 @@ import { StorageModule } from '../modules/storage';
|
|
|
8
8
|
import { ORMModule } from '../modules/orm';
|
|
9
9
|
import { PaymentModule } from '../modules/payment';
|
|
10
10
|
import { ShippingModule } from '../modules/shipping';
|
|
11
|
+
import { CommerceModule } from '../modules/commerce';
|
|
11
12
|
/**
|
|
12
13
|
* XplatSystem - Singleton 패턴 기반 Framework 인스턴스
|
|
13
14
|
*
|
|
@@ -25,6 +26,7 @@ export declare class XplatSystem {
|
|
|
25
26
|
readonly orm: ORMModule;
|
|
26
27
|
readonly payment: PaymentModule;
|
|
27
28
|
readonly shipping: ShippingModule;
|
|
29
|
+
readonly commerce: CommerceModule;
|
|
28
30
|
/**
|
|
29
31
|
* Private constructor (Singleton 패턴)
|
|
30
32
|
*/
|
package/dist/core/XplatSystem.js
CHANGED
|
@@ -11,6 +11,7 @@ const storage_1 = require("../modules/storage");
|
|
|
11
11
|
const orm_1 = require("../modules/orm");
|
|
12
12
|
const payment_1 = require("../modules/payment");
|
|
13
13
|
const shipping_1 = require("../modules/shipping");
|
|
14
|
+
const commerce_1 = require("../modules/commerce");
|
|
14
15
|
/**
|
|
15
16
|
* XplatSystem - Singleton 패턴 기반 Framework 인스턴스
|
|
16
17
|
*
|
|
@@ -31,6 +32,7 @@ class XplatSystem {
|
|
|
31
32
|
this.orm = new orm_1.ORMModule();
|
|
32
33
|
this.payment = new payment_1.PaymentModule();
|
|
33
34
|
this.shipping = new shipping_1.ShippingModule();
|
|
35
|
+
this.commerce = new commerce_1.CommerceModule();
|
|
34
36
|
}
|
|
35
37
|
/**
|
|
36
38
|
* Singleton 인스턴스 반환
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CommerceCoreConfig } from '../../commerce/types';
|
|
2
|
+
/**
|
|
3
|
+
* CommerceModule (xplatSystem용 래퍼)
|
|
4
|
+
* - Nest 모듈(CommerceModule)을 직접 노출하기 전에,
|
|
5
|
+
* 싱글턴 접근 패턴(`xplatSystem.commerce`)을 유지하기 위한 최소 래퍼입니다.
|
|
6
|
+
* - 실제 동작 구현(이벤트/잡/도메인 서비스)은 후속 todo에서 확장합니다.
|
|
7
|
+
*/
|
|
8
|
+
export declare class CommerceModule {
|
|
9
|
+
private config;
|
|
10
|
+
constructor(config?: CommerceCoreConfig);
|
|
11
|
+
getVersioning(): {
|
|
12
|
+
storePrefix: string;
|
|
13
|
+
adminPrefix: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CommerceModule = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* CommerceModule (xplatSystem용 래퍼)
|
|
6
|
+
* - Nest 모듈(CommerceModule)을 직접 노출하기 전에,
|
|
7
|
+
* 싱글턴 접근 패턴(`xplatSystem.commerce`)을 유지하기 위한 최소 래퍼입니다.
|
|
8
|
+
* - 실제 동작 구현(이벤트/잡/도메인 서비스)은 후속 todo에서 확장합니다.
|
|
9
|
+
*/
|
|
10
|
+
class CommerceModule {
|
|
11
|
+
constructor(config = {}) {
|
|
12
|
+
this.config = config;
|
|
13
|
+
}
|
|
14
|
+
getVersioning() {
|
|
15
|
+
return {
|
|
16
|
+
storePrefix: this.config.versioning?.storePrefix ?? '/store/v1',
|
|
17
|
+
adminPrefix: this.config.versioning?.adminPrefix ?? '/admin/v1',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.CommerceModule = CommerceModule;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hamjimin/xplat-back",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Express + TypeScript 백엔드 스캐폴딩 도구 (zium-backend 기반)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
"bin": {
|
|
11
11
|
"xplat-back": "./bin/xplat-back"
|
|
12
12
|
},
|
|
13
|
-
|
|
14
13
|
"files": [
|
|
15
14
|
"dist",
|
|
16
15
|
"bin",
|
|
@@ -36,13 +35,20 @@
|
|
|
36
35
|
"author": "xplat",
|
|
37
36
|
"license": "ISC",
|
|
38
37
|
"dependencies": {
|
|
39
|
-
"
|
|
38
|
+
"@nestjs/common": "^10.4.15",
|
|
39
|
+
"@nestjs/core": "^10.4.15",
|
|
40
|
+
"@nestjs/platform-express": "^10.4.15",
|
|
41
|
+
"class-transformer": "^0.5.1",
|
|
42
|
+
"class-validator": "^0.14.1",
|
|
40
43
|
"cookie-parser": "^1.4.6",
|
|
41
44
|
"cors": "^2.8.5",
|
|
45
|
+
"express": "^4.18.2",
|
|
42
46
|
"jsonwebtoken": "^9.0.2",
|
|
43
47
|
"bcrypt": "^5.1.1",
|
|
44
48
|
"crypto-js": "^4.2.0",
|
|
45
49
|
"lodash": "^4.17.21",
|
|
50
|
+
"reflect-metadata": "^0.2.2",
|
|
51
|
+
"rxjs": "^7.8.1",
|
|
46
52
|
"uuid": "^9.0.1",
|
|
47
53
|
"@aws-sdk/client-s3": "^3.922.0",
|
|
48
54
|
"@aws-sdk/s3-request-presigner": "^3.922.0"
|