@fuzionx/framework 0.1.42 → 0.1.44
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 +501 -501
- package/bin/fx.js +12 -12
- package/cli/db-sync.js +100 -100
- package/cli/index.js +494 -494
- package/cli/templates/make/app/controllers/HomeController.js +14 -14
- package/cli/templates/make/app/routes/api.js +7 -7
- package/cli/templates/make/app/routes/web.js +5 -5
- package/cli/templates/make/app/views/default/errors/404.html +11 -11
- package/cli/templates/make/app/views/default/errors/500.html +14 -14
- package/cli/templates/make/app/views/default/layouts/main.html +22 -22
- package/cli/templates/make/app/views/default/pages/home.html +11 -11
- package/cli/templates/make/controller.js.tpl +40 -40
- package/cli/templates/make/event.js.tpl +8 -8
- package/cli/templates/make/job.js.tpl +10 -10
- package/cli/templates/make/middleware.js.tpl +10 -10
- package/cli/templates/make/model.js.tpl +15 -15
- package/cli/templates/make/service.js.tpl +15 -15
- package/cli/templates/make/task.js.tpl +15 -15
- package/cli/templates/make/test.js.tpl +7 -7
- package/cli/templates/make/worker.js.tpl +14 -14
- package/cli/templates/make/ws.js.tpl +18 -18
- package/index.js +67 -67
- package/lib/core/AppError.js +46 -46
- package/lib/core/Application.js +1006 -1006
- package/lib/core/AutoLoader.js +227 -227
- package/lib/core/Base.js +64 -64
- package/lib/core/Config.js +331 -228
- package/lib/core/Context.js +484 -484
- package/lib/database/ConnectionManager.js +208 -208
- package/lib/database/MariaModel.js +29 -29
- package/lib/database/Model.js +247 -247
- package/lib/database/ModelRegistry.js +72 -72
- package/lib/database/MongoModel.js +232 -232
- package/lib/database/Pagination.js +37 -37
- package/lib/database/PostgreModel.js +29 -29
- package/lib/database/QueryBuilder.js +172 -172
- package/lib/database/SQLiteModel.js +27 -27
- package/lib/database/SqlModel.js +257 -257
- package/lib/database/SqlQueryBuilder.js +332 -332
- package/lib/helpers/CryptoHelper.js +48 -48
- package/lib/helpers/FileHelper.js +61 -61
- package/lib/helpers/HashHelper.js +39 -39
- package/lib/helpers/I18nHelper.js +174 -174
- package/lib/helpers/Logger.js +108 -108
- package/lib/helpers/MediaHelper.js +84 -84
- package/lib/http/Controller.js +34 -34
- package/lib/http/ErrorHandler.js +136 -136
- package/lib/http/Middleware.js +43 -43
- package/lib/http/Router.js +109 -109
- package/lib/http/Validation.js +125 -125
- package/lib/middleware/apiAuth.js +79 -79
- package/lib/middleware/auth.js +42 -42
- package/lib/middleware/bodyParser.js +19 -19
- package/lib/middleware/cors.js +47 -47
- package/lib/middleware/csrf.js +32 -32
- package/lib/middleware/index.js +13 -13
- package/lib/middleware/session.js +27 -27
- package/lib/middleware/theme.js +20 -20
- package/lib/realtime/RoomManager.js +85 -85
- package/lib/realtime/WsHandler.js +107 -107
- package/lib/schedule/Job.js +38 -38
- package/lib/schedule/Queue.js +103 -103
- package/lib/schedule/Scheduler.js +171 -171
- package/lib/schedule/Task.js +39 -39
- package/lib/schedule/WorkerPool.js +225 -225
- package/lib/services/EventBus.js +94 -94
- package/lib/services/Service.js +261 -261
- package/lib/services/Storage.js +112 -112
- package/lib/utilities/ArrUtil.js +112 -112
- package/lib/utilities/DateUtil.js +98 -98
- package/lib/utilities/FunctionUtil.js +119 -119
- package/lib/utilities/NumUtil.js +75 -75
- package/lib/utilities/ObjectUtil.js +170 -170
- package/lib/utilities/PaginationUtil.js +81 -81
- package/lib/utilities/StrUtil.js +105 -105
- package/lib/utilities/index.js +18 -18
- package/lib/view/OpenAPI.js +231 -231
- package/lib/view/View.js +83 -83
- package/package.json +2 -2
- package/testing/index.js +232 -232
package/lib/http/ErrorHandler.js
CHANGED
|
@@ -1,136 +1,136 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ErrorHandler — 기본 에러 핸들러 (JSON/HTML 분기 + 테마 에러 페이지)
|
|
3
|
-
*
|
|
4
|
-
* @see docs/framework/08-error-handling.md
|
|
5
|
-
* @see docs/framework/03-views-templates.md (에러 페이지)
|
|
6
|
-
*/
|
|
7
|
-
import AppError, { ValidationError } from '../core/AppError.js';
|
|
8
|
-
|
|
9
|
-
export default class ErrorHandler {
|
|
10
|
-
/**
|
|
11
|
-
* @param {object} opts
|
|
12
|
-
* @param {boolean} [opts.isDev=false] - dev 모드에서 스택 노출
|
|
13
|
-
* @param {Function} [opts.logger] - 에러 로깅 함수
|
|
14
|
-
* @param {import('./View.js').default} [opts.view] - 뷰 렌더러 (테마 에러 페이지용)
|
|
15
|
-
*/
|
|
16
|
-
constructor(opts = {}) {
|
|
17
|
-
this.isDev = opts.isDev ?? false;
|
|
18
|
-
this.logger = opts.logger || console.error;
|
|
19
|
-
this._view = opts.view || null;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* 에러 처리 — ctx에 응답 세팅
|
|
24
|
-
* @param {Error} err
|
|
25
|
-
* @param {import('./Context.js').default} ctx
|
|
26
|
-
*/
|
|
27
|
-
handle(err, ctx) {
|
|
28
|
-
const status = err.status || 500;
|
|
29
|
-
|
|
30
|
-
// 500 에러는 로깅
|
|
31
|
-
if (status >= 500) {
|
|
32
|
-
this.logger(err);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// JSON 응답 요청이면 JSON으로
|
|
36
|
-
const wantsJson = ctx.get('accept')?.includes('application/json')
|
|
37
|
-
|| ctx.is('json')
|
|
38
|
-
|| ctx.path?.startsWith('/api');
|
|
39
|
-
|
|
40
|
-
if (wantsJson) {
|
|
41
|
-
return this._jsonResponse(err, ctx, status);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return this._htmlResponse(err, ctx, status);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** @private */
|
|
48
|
-
_jsonResponse(err, ctx, status) {
|
|
49
|
-
const body = {
|
|
50
|
-
error: {
|
|
51
|
-
message: err.message,
|
|
52
|
-
status,
|
|
53
|
-
},
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
// ValidationError → 필드 에러 포함
|
|
57
|
-
if (err instanceof ValidationError) {
|
|
58
|
-
body.error.fields = err.fields;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// dev 모드에서 스택 노출
|
|
62
|
-
if (this.isDev && status >= 500) {
|
|
63
|
-
body.error.stack = err.stack;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// 추가 데이터
|
|
67
|
-
if (err.data && !(err instanceof ValidationError)) {
|
|
68
|
-
body.error.data = err.data;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
ctx.status(status).json(body);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* HTML 에러 응답 — 테마 에러 페이지 지원
|
|
76
|
-
*
|
|
77
|
-
* 검색 순서 (03-views-templates.md):
|
|
78
|
-
* 1. views/{theme}/errors/{code}.html
|
|
79
|
-
* 2. views/{theme}/errors/default.html
|
|
80
|
-
* 3. 프레임워크 내장 에러 페이지
|
|
81
|
-
*
|
|
82
|
-
* @private
|
|
83
|
-
*/
|
|
84
|
-
_htmlResponse(err, ctx, status) {
|
|
85
|
-
const errorData = {
|
|
86
|
-
error: {
|
|
87
|
-
code: status,
|
|
88
|
-
message: err.message,
|
|
89
|
-
stack: this.isDev ? err.stack : null,
|
|
90
|
-
},
|
|
91
|
-
request: { url: ctx.url, method: ctx.method },
|
|
92
|
-
config: { debug: this.isDev },
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
// 테마 에러 페이지 시도 (View 렌더러가 있을 때)
|
|
96
|
-
const appEntry = ctx.app?._appRegistry?.get(ctx.appName);
|
|
97
|
-
const view = this._view || appEntry?.view;
|
|
98
|
-
if (view) {
|
|
99
|
-
const theme = ctx.theme || ctx.app?.config?.get('themes.default', 'default') || 'default';
|
|
100
|
-
// 1. views/{theme}/errors/{code}.html
|
|
101
|
-
try {
|
|
102
|
-
const html = view.render(`errors/${status}`, errorData);
|
|
103
|
-
if (html && !html.startsWith('<!-- template:')) {
|
|
104
|
-
ctx.status(status).html(html);
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
} catch {}
|
|
108
|
-
// 2. views/{theme}/errors/default.html
|
|
109
|
-
try {
|
|
110
|
-
const html = view.render('errors/default', errorData);
|
|
111
|
-
if (html && !html.startsWith('<!-- template:')) {
|
|
112
|
-
ctx.status(status).html(html);
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
} catch {}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// 3. 프레임워크 내장 에러 페이지 (XSS 방지)
|
|
119
|
-
const esc = (s) => String(s)
|
|
120
|
-
.replace(/&/g, '&').replace(/</g, '<')
|
|
121
|
-
.replace(/>/g, '>').replace(/"/g, '"');
|
|
122
|
-
|
|
123
|
-
const title = status >= 500 ? 'Server Error' : esc(err.message);
|
|
124
|
-
|
|
125
|
-
ctx.status(status).html(
|
|
126
|
-
`<!DOCTYPE html><html><head><meta charset="utf-8"><title>${status}</title>` +
|
|
127
|
-
`<style>body{font-family:system-ui,-apple-system,sans-serif;max-width:600px;margin:80px auto;` +
|
|
128
|
-
`padding:0 20px;color:#333}h1{color:#e74c3c;}pre{background:#f5f5f5;padding:16px;border-radius:8px;` +
|
|
129
|
-
`overflow-x:auto;font-size:13px;}</style></head>` +
|
|
130
|
-
`<body><h1>${status} — ${title}</h1>` +
|
|
131
|
-
`<p>요청: ${esc(ctx.method)} ${esc(ctx.url)}</p>` +
|
|
132
|
-
`${this.isDev && err.stack ? `<pre>${esc(err.stack)}</pre>` : ''}` +
|
|
133
|
-
`</body></html>`
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* ErrorHandler — 기본 에러 핸들러 (JSON/HTML 분기 + 테마 에러 페이지)
|
|
3
|
+
*
|
|
4
|
+
* @see docs/framework/08-error-handling.md
|
|
5
|
+
* @see docs/framework/03-views-templates.md (에러 페이지)
|
|
6
|
+
*/
|
|
7
|
+
import AppError, { ValidationError } from '../core/AppError.js';
|
|
8
|
+
|
|
9
|
+
export default class ErrorHandler {
|
|
10
|
+
/**
|
|
11
|
+
* @param {object} opts
|
|
12
|
+
* @param {boolean} [opts.isDev=false] - dev 모드에서 스택 노출
|
|
13
|
+
* @param {Function} [opts.logger] - 에러 로깅 함수
|
|
14
|
+
* @param {import('./View.js').default} [opts.view] - 뷰 렌더러 (테마 에러 페이지용)
|
|
15
|
+
*/
|
|
16
|
+
constructor(opts = {}) {
|
|
17
|
+
this.isDev = opts.isDev ?? false;
|
|
18
|
+
this.logger = opts.logger || console.error;
|
|
19
|
+
this._view = opts.view || null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 에러 처리 — ctx에 응답 세팅
|
|
24
|
+
* @param {Error} err
|
|
25
|
+
* @param {import('./Context.js').default} ctx
|
|
26
|
+
*/
|
|
27
|
+
handle(err, ctx) {
|
|
28
|
+
const status = err.status || 500;
|
|
29
|
+
|
|
30
|
+
// 500 에러는 로깅
|
|
31
|
+
if (status >= 500) {
|
|
32
|
+
this.logger(err);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// JSON 응답 요청이면 JSON으로
|
|
36
|
+
const wantsJson = ctx.get('accept')?.includes('application/json')
|
|
37
|
+
|| ctx.is('json')
|
|
38
|
+
|| ctx.path?.startsWith('/api');
|
|
39
|
+
|
|
40
|
+
if (wantsJson) {
|
|
41
|
+
return this._jsonResponse(err, ctx, status);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return this._htmlResponse(err, ctx, status);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** @private */
|
|
48
|
+
_jsonResponse(err, ctx, status) {
|
|
49
|
+
const body = {
|
|
50
|
+
error: {
|
|
51
|
+
message: err.message,
|
|
52
|
+
status,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// ValidationError → 필드 에러 포함
|
|
57
|
+
if (err instanceof ValidationError) {
|
|
58
|
+
body.error.fields = err.fields;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// dev 모드에서 스택 노출
|
|
62
|
+
if (this.isDev && status >= 500) {
|
|
63
|
+
body.error.stack = err.stack;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 추가 데이터
|
|
67
|
+
if (err.data && !(err instanceof ValidationError)) {
|
|
68
|
+
body.error.data = err.data;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
ctx.status(status).json(body);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* HTML 에러 응답 — 테마 에러 페이지 지원
|
|
76
|
+
*
|
|
77
|
+
* 검색 순서 (03-views-templates.md):
|
|
78
|
+
* 1. views/{theme}/errors/{code}.html
|
|
79
|
+
* 2. views/{theme}/errors/default.html
|
|
80
|
+
* 3. 프레임워크 내장 에러 페이지
|
|
81
|
+
*
|
|
82
|
+
* @private
|
|
83
|
+
*/
|
|
84
|
+
_htmlResponse(err, ctx, status) {
|
|
85
|
+
const errorData = {
|
|
86
|
+
error: {
|
|
87
|
+
code: status,
|
|
88
|
+
message: err.message,
|
|
89
|
+
stack: this.isDev ? err.stack : null,
|
|
90
|
+
},
|
|
91
|
+
request: { url: ctx.url, method: ctx.method },
|
|
92
|
+
config: { debug: this.isDev },
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// 테마 에러 페이지 시도 (View 렌더러가 있을 때)
|
|
96
|
+
const appEntry = ctx.app?._appRegistry?.get(ctx.appName);
|
|
97
|
+
const view = this._view || appEntry?.view;
|
|
98
|
+
if (view) {
|
|
99
|
+
const theme = ctx.theme || ctx.app?.config?.get('themes.default', 'default') || 'default';
|
|
100
|
+
// 1. views/{theme}/errors/{code}.html
|
|
101
|
+
try {
|
|
102
|
+
const html = view.render(`errors/${status}`, errorData);
|
|
103
|
+
if (html && !html.startsWith('<!-- template:')) {
|
|
104
|
+
ctx.status(status).html(html);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
} catch {}
|
|
108
|
+
// 2. views/{theme}/errors/default.html
|
|
109
|
+
try {
|
|
110
|
+
const html = view.render('errors/default', errorData);
|
|
111
|
+
if (html && !html.startsWith('<!-- template:')) {
|
|
112
|
+
ctx.status(status).html(html);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
} catch {}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 3. 프레임워크 내장 에러 페이지 (XSS 방지)
|
|
119
|
+
const esc = (s) => String(s)
|
|
120
|
+
.replace(/&/g, '&').replace(/</g, '<')
|
|
121
|
+
.replace(/>/g, '>').replace(/"/g, '"');
|
|
122
|
+
|
|
123
|
+
const title = status >= 500 ? 'Server Error' : esc(err.message);
|
|
124
|
+
|
|
125
|
+
ctx.status(status).html(
|
|
126
|
+
`<!DOCTYPE html><html><head><meta charset="utf-8"><title>${status}</title>` +
|
|
127
|
+
`<style>body{font-family:system-ui,-apple-system,sans-serif;max-width:600px;margin:80px auto;` +
|
|
128
|
+
`padding:0 20px;color:#333}h1{color:#e74c3c;}pre{background:#f5f5f5;padding:16px;border-radius:8px;` +
|
|
129
|
+
`overflow-x:auto;font-size:13px;}</style></head>` +
|
|
130
|
+
`<body><h1>${status} — ${title}</h1>` +
|
|
131
|
+
`<p>요청: ${esc(ctx.method)} ${esc(ctx.url)}</p>` +
|
|
132
|
+
`${this.isDev && err.stack ? `<pre>${esc(err.stack)}</pre>` : ''}` +
|
|
133
|
+
`</body></html>`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
package/lib/http/Middleware.js
CHANGED
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Middleware — 기본 미들웨어 클래스
|
|
3
|
-
*
|
|
4
|
-
* @see docs/framework/12-middleware.md
|
|
5
|
-
* @see docs/framework/class-design.mm.md (Middleware)
|
|
6
|
-
*/
|
|
7
|
-
import Base from '../core/Base.js';
|
|
8
|
-
|
|
9
|
-
export default class Middleware extends Base {
|
|
10
|
-
/** @type {string} 미들웨어 등록 이름 (라우트에서 참조) */
|
|
11
|
-
static alias = '';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* 미들웨어 핸들러 (서브클래스에서 오버라이드)
|
|
15
|
-
* @param {import('./Context.js').default} ctx
|
|
16
|
-
* @param {Function} next
|
|
17
|
-
* @param {...*} params - 파라미터화 미들웨어 인자 ('name:param1:param2')
|
|
18
|
-
*/
|
|
19
|
-
async handle(ctx, next, ...params) {
|
|
20
|
-
await next();
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* 미들웨어 체인 실행기
|
|
26
|
-
*
|
|
27
|
-
* @param {Array<Function>} middlewares - [(ctx, next) => ...] 배열
|
|
28
|
-
* @param {import('./Context.js').default} ctx
|
|
29
|
-
* @returns {Promise<void>}
|
|
30
|
-
*/
|
|
31
|
-
export async function runMiddlewareChain(middlewares, ctx) {
|
|
32
|
-
let idx = 0;
|
|
33
|
-
|
|
34
|
-
async function next(err) {
|
|
35
|
-
if (err) throw err;
|
|
36
|
-
if (idx >= middlewares.length) return;
|
|
37
|
-
|
|
38
|
-
const fn = middlewares[idx++];
|
|
39
|
-
await fn(ctx, next);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
await next();
|
|
43
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Middleware — 기본 미들웨어 클래스
|
|
3
|
+
*
|
|
4
|
+
* @see docs/framework/12-middleware.md
|
|
5
|
+
* @see docs/framework/class-design.mm.md (Middleware)
|
|
6
|
+
*/
|
|
7
|
+
import Base from '../core/Base.js';
|
|
8
|
+
|
|
9
|
+
export default class Middleware extends Base {
|
|
10
|
+
/** @type {string} 미들웨어 등록 이름 (라우트에서 참조) */
|
|
11
|
+
static alias = '';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 미들웨어 핸들러 (서브클래스에서 오버라이드)
|
|
15
|
+
* @param {import('./Context.js').default} ctx
|
|
16
|
+
* @param {Function} next
|
|
17
|
+
* @param {...*} params - 파라미터화 미들웨어 인자 ('name:param1:param2')
|
|
18
|
+
*/
|
|
19
|
+
async handle(ctx, next, ...params) {
|
|
20
|
+
await next();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 미들웨어 체인 실행기
|
|
26
|
+
*
|
|
27
|
+
* @param {Array<Function>} middlewares - [(ctx, next) => ...] 배열
|
|
28
|
+
* @param {import('./Context.js').default} ctx
|
|
29
|
+
* @returns {Promise<void>}
|
|
30
|
+
*/
|
|
31
|
+
export async function runMiddlewareChain(middlewares, ctx) {
|
|
32
|
+
let idx = 0;
|
|
33
|
+
|
|
34
|
+
async function next(err) {
|
|
35
|
+
if (err) throw err;
|
|
36
|
+
if (idx >= middlewares.length) return;
|
|
37
|
+
|
|
38
|
+
const fn = middlewares[idx++];
|
|
39
|
+
await fn(ctx, next);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
await next();
|
|
43
|
+
}
|
package/lib/http/Router.js
CHANGED
|
@@ -1,109 +1,109 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Router / RouteGroup — 라우트 등록 DSL
|
|
3
|
-
*
|
|
4
|
-
* @see docs/framework/01-routing-controllers.md
|
|
5
|
-
* @see docs/framework/class-design.mm.md (Router/RouteGroup)
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export class RouteGroup {
|
|
9
|
-
/**
|
|
10
|
-
* @param {string} prefix - 그룹 접두사 ('', '/api')
|
|
11
|
-
* @param {object} [groupOpts] - 그룹 옵션 { middleware }
|
|
12
|
-
*/
|
|
13
|
-
constructor(prefix = '', groupOpts = {}) {
|
|
14
|
-
this._prefix = prefix;
|
|
15
|
-
this._groupMiddleware = groupOpts.middleware || [];
|
|
16
|
-
this._routes = [];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* @param {string} method
|
|
21
|
-
* @param {string} path
|
|
22
|
-
* @param {Function|object} handler
|
|
23
|
-
* @param {object} [opts]
|
|
24
|
-
*/
|
|
25
|
-
_addRoute(method, path, handler, opts = {}) {
|
|
26
|
-
const fullPath = this._prefix + path;
|
|
27
|
-
this._routes.push({
|
|
28
|
-
method,
|
|
29
|
-
path: fullPath,
|
|
30
|
-
handler,
|
|
31
|
-
middleware: [...this._groupMiddleware, ...(opts.middleware || [])],
|
|
32
|
-
validate: opts.validate || null,
|
|
33
|
-
upload: opts.upload || null,
|
|
34
|
-
docs: opts.docs || null,
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
get(path, handler, opts) { this._addRoute('GET', path, handler, opts); }
|
|
39
|
-
post(path, handler, opts) { this._addRoute('POST', path, handler, opts); }
|
|
40
|
-
put(path, handler, opts) { this._addRoute('PUT', path, handler, opts); }
|
|
41
|
-
patch(path, handler, opts) { this._addRoute('PATCH', path, handler, opts); }
|
|
42
|
-
delete(path, handler, opts) { this._addRoute('DELETE', path, handler, opts); }
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* 그룹 — 중첩 라우트
|
|
46
|
-
* 인자 순서: (prefix, opts, callback) 또는 (prefix, callback, opts)
|
|
47
|
-
* @param {string} prefix
|
|
48
|
-
* @param {object|Function} optsOrCallback
|
|
49
|
-
* @param {Function|object} [callbackOrOpts]
|
|
50
|
-
*/
|
|
51
|
-
group(prefix, optsOrCallback, callbackOrOpts) {
|
|
52
|
-
let opts, callback;
|
|
53
|
-
if (typeof optsOrCallback === 'function') {
|
|
54
|
-
// group('/api', (r) => {}, opts?)
|
|
55
|
-
callback = optsOrCallback;
|
|
56
|
-
opts = callbackOrOpts || {};
|
|
57
|
-
} else {
|
|
58
|
-
// group('/api', { middleware: [...] }, (r) => {})
|
|
59
|
-
opts = optsOrCallback || {};
|
|
60
|
-
callback = callbackOrOpts;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const subGroup = new RouteGroup(this._prefix + prefix, {
|
|
64
|
-
middleware: [...this._groupMiddleware, ...(opts.middleware || [])],
|
|
65
|
-
});
|
|
66
|
-
callback(subGroup);
|
|
67
|
-
this._routes.push(...subGroup._routes);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* RESTful resource 자동 등록
|
|
72
|
-
* @param {string} name - 리소스 이름 (복수형, e.g. 'users')
|
|
73
|
-
* @param {object} controller - Controller 클래스 (메서드 레퍼런스)
|
|
74
|
-
* @param {object} [opts]
|
|
75
|
-
*/
|
|
76
|
-
resource(name, controller, opts = {}) {
|
|
77
|
-
const basePath = `/${name}`;
|
|
78
|
-
const middleware = opts.middleware || [];
|
|
79
|
-
const base = { middleware };
|
|
80
|
-
|
|
81
|
-
if (controller.index) this.get(basePath, controller.index, base);
|
|
82
|
-
if (controller.store) this.post(basePath, controller.store, base);
|
|
83
|
-
if (controller.show) this.get(`${basePath}/:id`, controller.show, base);
|
|
84
|
-
if (controller.update) this.put(`${basePath}/:id`, controller.update, base);
|
|
85
|
-
if (controller.destroy) this.delete(`${basePath}/:id`, controller.destroy, base);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export default class Router extends RouteGroup {
|
|
90
|
-
constructor() {
|
|
91
|
-
super('');
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* 모든 등록된 라우트 반환
|
|
96
|
-
* @returns {Array<{method, path, handler, middleware, validate, upload, docs}>}
|
|
97
|
-
*/
|
|
98
|
-
getRoutes() {
|
|
99
|
-
return this._routes;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* 라우트 파일 로드 (routes/ callback 등록)
|
|
104
|
-
* @param {Function} routeCallback - (r: RouteGroup) => void
|
|
105
|
-
*/
|
|
106
|
-
load(routeCallback) {
|
|
107
|
-
routeCallback(this);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Router / RouteGroup — 라우트 등록 DSL
|
|
3
|
+
*
|
|
4
|
+
* @see docs/framework/01-routing-controllers.md
|
|
5
|
+
* @see docs/framework/class-design.mm.md (Router/RouteGroup)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export class RouteGroup {
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} prefix - 그룹 접두사 ('', '/api')
|
|
11
|
+
* @param {object} [groupOpts] - 그룹 옵션 { middleware }
|
|
12
|
+
*/
|
|
13
|
+
constructor(prefix = '', groupOpts = {}) {
|
|
14
|
+
this._prefix = prefix;
|
|
15
|
+
this._groupMiddleware = groupOpts.middleware || [];
|
|
16
|
+
this._routes = [];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {string} method
|
|
21
|
+
* @param {string} path
|
|
22
|
+
* @param {Function|object} handler
|
|
23
|
+
* @param {object} [opts]
|
|
24
|
+
*/
|
|
25
|
+
_addRoute(method, path, handler, opts = {}) {
|
|
26
|
+
const fullPath = this._prefix + path;
|
|
27
|
+
this._routes.push({
|
|
28
|
+
method,
|
|
29
|
+
path: fullPath,
|
|
30
|
+
handler,
|
|
31
|
+
middleware: [...this._groupMiddleware, ...(opts.middleware || [])],
|
|
32
|
+
validate: opts.validate || null,
|
|
33
|
+
upload: opts.upload || null,
|
|
34
|
+
docs: opts.docs || null,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get(path, handler, opts) { this._addRoute('GET', path, handler, opts); }
|
|
39
|
+
post(path, handler, opts) { this._addRoute('POST', path, handler, opts); }
|
|
40
|
+
put(path, handler, opts) { this._addRoute('PUT', path, handler, opts); }
|
|
41
|
+
patch(path, handler, opts) { this._addRoute('PATCH', path, handler, opts); }
|
|
42
|
+
delete(path, handler, opts) { this._addRoute('DELETE', path, handler, opts); }
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 그룹 — 중첩 라우트
|
|
46
|
+
* 인자 순서: (prefix, opts, callback) 또는 (prefix, callback, opts)
|
|
47
|
+
* @param {string} prefix
|
|
48
|
+
* @param {object|Function} optsOrCallback
|
|
49
|
+
* @param {Function|object} [callbackOrOpts]
|
|
50
|
+
*/
|
|
51
|
+
group(prefix, optsOrCallback, callbackOrOpts) {
|
|
52
|
+
let opts, callback;
|
|
53
|
+
if (typeof optsOrCallback === 'function') {
|
|
54
|
+
// group('/api', (r) => {}, opts?)
|
|
55
|
+
callback = optsOrCallback;
|
|
56
|
+
opts = callbackOrOpts || {};
|
|
57
|
+
} else {
|
|
58
|
+
// group('/api', { middleware: [...] }, (r) => {})
|
|
59
|
+
opts = optsOrCallback || {};
|
|
60
|
+
callback = callbackOrOpts;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const subGroup = new RouteGroup(this._prefix + prefix, {
|
|
64
|
+
middleware: [...this._groupMiddleware, ...(opts.middleware || [])],
|
|
65
|
+
});
|
|
66
|
+
callback(subGroup);
|
|
67
|
+
this._routes.push(...subGroup._routes);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* RESTful resource 자동 등록
|
|
72
|
+
* @param {string} name - 리소스 이름 (복수형, e.g. 'users')
|
|
73
|
+
* @param {object} controller - Controller 클래스 (메서드 레퍼런스)
|
|
74
|
+
* @param {object} [opts]
|
|
75
|
+
*/
|
|
76
|
+
resource(name, controller, opts = {}) {
|
|
77
|
+
const basePath = `/${name}`;
|
|
78
|
+
const middleware = opts.middleware || [];
|
|
79
|
+
const base = { middleware };
|
|
80
|
+
|
|
81
|
+
if (controller.index) this.get(basePath, controller.index, base);
|
|
82
|
+
if (controller.store) this.post(basePath, controller.store, base);
|
|
83
|
+
if (controller.show) this.get(`${basePath}/:id`, controller.show, base);
|
|
84
|
+
if (controller.update) this.put(`${basePath}/:id`, controller.update, base);
|
|
85
|
+
if (controller.destroy) this.delete(`${basePath}/:id`, controller.destroy, base);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export default class Router extends RouteGroup {
|
|
90
|
+
constructor() {
|
|
91
|
+
super('');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 모든 등록된 라우트 반환
|
|
96
|
+
* @returns {Array<{method, path, handler, middleware, validate, upload, docs}>}
|
|
97
|
+
*/
|
|
98
|
+
getRoutes() {
|
|
99
|
+
return this._routes;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 라우트 파일 로드 (routes/ callback 등록)
|
|
104
|
+
* @param {Function} routeCallback - (r: RouteGroup) => void
|
|
105
|
+
*/
|
|
106
|
+
load(routeCallback) {
|
|
107
|
+
routeCallback(this);
|
|
108
|
+
}
|
|
109
|
+
}
|