@awesomeness-js/server 1.0.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/.editorconfig +4 -0
- package/.gitattributes +2 -0
- package/.jshintrc +3 -0
- package/.vscode/settings.json +17 -0
- package/CLA.md +9 -0
- package/CONTRIBUTING.md +18 -0
- package/LICENSE +13 -0
- package/NOTICE +15 -0
- package/README.md +15 -0
- package/SECURITY.md +7 -0
- package/config.js +29 -0
- package/eslint.config.js +101 -0
- package/example-.awesomeness/applicationMap.js +4 -0
- package/example-.awesomeness/beforeRouteMiddleware.js +15 -0
- package/example-.awesomeness/checkSession.js +15 -0
- package/example-.awesomeness/config.js +40 -0
- package/example-.awesomeness/hostMap.js +44 -0
- package/example-.awesomeness/initDB.js +65 -0
- package/example-.awesomeness/setupDB/applications.js +47 -0
- package/example-.awesomeness/setupDB/users.js +65 -0
- package/example-.awesomeness/setupDB/websites.js +49 -0
- package/example-.awesomeness/specialRoutes.js +7 -0
- package/example-.awesomeness/wsHandler.js +13 -0
- package/index.js +22 -0
- package/package.json +34 -0
- package/server/applicationMap.js +33 -0
- package/server/awesomenessNormalizeRequest.js +131 -0
- package/server/brotliJsonResponse.js +28 -0
- package/server/checkAccess.js +34 -0
- package/server/componentDependencies.js +301 -0
- package/server/errors.js +11 -0
- package/server/fetchPage.js +269 -0
- package/server/getMD.js +22 -0
- package/server/koa/attachAwesomenessRequest.js +24 -0
- package/server/koa/cors.js +22 -0
- package/server/koa/errorHandler.js +32 -0
- package/server/koa/finalFormat.js +34 -0
- package/server/koa/jsonBodyParser.js +172 -0
- package/server/koa/routeRequest.js +288 -0
- package/server/koa/serverUp.js +7 -0
- package/server/koa/staticFiles.js +97 -0
- package/server/koa/timeout.js +42 -0
- package/server/pageInfo.js +121 -0
- package/server/reRoute.js +54 -0
- package/server/resolveRealCasePath.js +56 -0
- package/server/specialPaths.js +107 -0
- package/server/validateRequest.js +127 -0
- package/server/ws/handlers.js +67 -0
- package/server/ws/index.js +50 -0
- package/start.js +122 -0
- package/vitest.config.js +15 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
|
|
4
|
+
import { componentDependencies } from "./componentDependencies.js";
|
|
5
|
+
import pageInfo from "./pageInfo.js";
|
|
6
|
+
import { each, md5, combineFiles } from "@awesomeness-js/utils";
|
|
7
|
+
import { getConfig } from "../config.js";
|
|
8
|
+
|
|
9
|
+
const componentNamespace = "ui";
|
|
10
|
+
const pageNamespaceBase = `app.pages`;
|
|
11
|
+
|
|
12
|
+
export default async function fetchPage(
|
|
13
|
+
awesomenessRequest,
|
|
14
|
+
{
|
|
15
|
+
about,
|
|
16
|
+
cssPath,
|
|
17
|
+
jsPath,
|
|
18
|
+
getData,
|
|
19
|
+
showDetails = false,
|
|
20
|
+
page = null
|
|
21
|
+
} = {}
|
|
22
|
+
) {
|
|
23
|
+
|
|
24
|
+
const awesomenessConfig = getConfig();
|
|
25
|
+
|
|
26
|
+
// normalize siteURL (expected to point at the /sites/ directory)
|
|
27
|
+
const sitesRootPath =
|
|
28
|
+
awesomenessConfig.siteURL instanceof URL
|
|
29
|
+
? fileURLToPath(awesomenessConfig.siteURL)
|
|
30
|
+
: awesomenessConfig.siteURL;
|
|
31
|
+
|
|
32
|
+
const sitePagesRoot = path.join(sitesRootPath, awesomenessRequest.site, "pages");
|
|
33
|
+
|
|
34
|
+
function pageFn(filePath){
|
|
35
|
+
|
|
36
|
+
// make file path relative to ".../sites/<site>/pages/"
|
|
37
|
+
let rel = path.relative(sitePagesRoot, filePath);
|
|
38
|
+
|
|
39
|
+
// normalize to posix-style so splitting works on Windows + *nix
|
|
40
|
+
rel = rel.split(path.sep).join("/");
|
|
41
|
+
|
|
42
|
+
// remove extension and split on /js/
|
|
43
|
+
const parts = rel.replace(/\.js$/i, "").split("/js/");
|
|
44
|
+
|
|
45
|
+
const pagePath = parts[0] || "";
|
|
46
|
+
const fnPath = (parts[1] || "").split("/").filter(Boolean).join(".");
|
|
47
|
+
|
|
48
|
+
const pageNamespace = `${pageNamespaceBase}.${pagePath.split("/").filter(Boolean).join(".")}`;
|
|
49
|
+
const pageFnName = `${pageNamespace}.${fnPath}`;
|
|
50
|
+
|
|
51
|
+
return pageFnName;
|
|
52
|
+
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
// initialize if not already available
|
|
57
|
+
if (!awesomenessRequest.updatedMeta) {
|
|
58
|
+
|
|
59
|
+
awesomenessRequest.updatedMeta = {};
|
|
60
|
+
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let forceRefresh = false;
|
|
64
|
+
|
|
65
|
+
if (!page) {
|
|
66
|
+
|
|
67
|
+
page = awesomenessRequest.pageRoute;
|
|
68
|
+
|
|
69
|
+
} else {
|
|
70
|
+
|
|
71
|
+
forceRefresh = true;
|
|
72
|
+
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!about || !cssPath || !jsPath || !getData) {
|
|
76
|
+
|
|
77
|
+
let info = await pageInfo(awesomenessRequest, { page });
|
|
78
|
+
|
|
79
|
+
cssPath = info.cssPath;
|
|
80
|
+
jsPath = info.jsPath;
|
|
81
|
+
about = info.about;
|
|
82
|
+
getData = info.getData;
|
|
83
|
+
|
|
84
|
+
if (info.mdContent) {
|
|
85
|
+
|
|
86
|
+
page = "_md";
|
|
87
|
+
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!about || !cssPath || !jsPath) {
|
|
93
|
+
|
|
94
|
+
throw {
|
|
95
|
+
reason: "page not found",
|
|
96
|
+
aboutPath,
|
|
97
|
+
cssPath,
|
|
98
|
+
jsPath,
|
|
99
|
+
awesomenessRequest,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const meta = {
|
|
105
|
+
about,
|
|
106
|
+
pages: {},
|
|
107
|
+
components: {},
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// CHECK PERMISSIONS
|
|
111
|
+
if (about?.permissions.length) {
|
|
112
|
+
|
|
113
|
+
const userPermissions = awesomenessRequest.user?.permissions || [];
|
|
114
|
+
const hasPermission = about.permissions.some((rp) => userPermissions.includes(rp) || rp === "*");
|
|
115
|
+
|
|
116
|
+
if (!hasPermission) {
|
|
117
|
+
|
|
118
|
+
if (process.env.NODE_ENV === "development") {
|
|
119
|
+
|
|
120
|
+
console.log("by page passing access requirement.");
|
|
121
|
+
|
|
122
|
+
} else {
|
|
123
|
+
|
|
124
|
+
throw {
|
|
125
|
+
message: "user does not have permission to view this page",
|
|
126
|
+
permissions: about.permissions,
|
|
127
|
+
userPermissions,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// GET PAGE
|
|
137
|
+
if (
|
|
138
|
+
forceRefresh === true ||
|
|
139
|
+
awesomenessRequest.testing === true ||
|
|
140
|
+
about.version != awesomenessRequest.meta?.pages[page]
|
|
141
|
+
) {
|
|
142
|
+
|
|
143
|
+
meta.pages[page] = meta.pages[page] || {};
|
|
144
|
+
|
|
145
|
+
meta.pages[page].version = about.version;
|
|
146
|
+
meta.pages[page].about = about;
|
|
147
|
+
|
|
148
|
+
// GET ALL PAGE SCRIPTS
|
|
149
|
+
try {
|
|
150
|
+
|
|
151
|
+
let js = "";
|
|
152
|
+
|
|
153
|
+
js += combineFiles(jsPath, "js", {
|
|
154
|
+
processContent: ({
|
|
155
|
+
content, path
|
|
156
|
+
}) => {
|
|
157
|
+
|
|
158
|
+
const fnName = pageFn(path, awesomenessRequest);
|
|
159
|
+
|
|
160
|
+
content = content.replaceAll(`import ui from '#ui';`, "");
|
|
161
|
+
content = content.replaceAll(`import ui from "#ui";`, "");
|
|
162
|
+
|
|
163
|
+
content = content.replaceAll("export default function", `${fnName} = function`);
|
|
164
|
+
content = content.replaceAll("export default async function", `${fnName} = async function`);
|
|
165
|
+
content = content.replaceAll("export default async", `${fnName} = async`);
|
|
166
|
+
content = content.replaceAll("export default", `${fnName} =`);
|
|
167
|
+
|
|
168
|
+
return content;
|
|
169
|
+
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
meta.pages[page].js = js;
|
|
174
|
+
|
|
175
|
+
} catch (err) {
|
|
176
|
+
|
|
177
|
+
console.log("failed to get page js", { err });
|
|
178
|
+
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// GET ALL PAGE CSS
|
|
182
|
+
try {
|
|
183
|
+
|
|
184
|
+
let css = "";
|
|
185
|
+
|
|
186
|
+
css += combineFiles(cssPath, "css");
|
|
187
|
+
meta.pages[page].css = css;
|
|
188
|
+
|
|
189
|
+
} catch (err) {
|
|
190
|
+
|
|
191
|
+
meta.pages[page].css = "/* no css found */";
|
|
192
|
+
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (about?.components?.length) {
|
|
198
|
+
|
|
199
|
+
const allDependencies = componentDependencies(about.components, {
|
|
200
|
+
componentLocations: awesomenessConfig.componentLocations(awesomenessRequest),
|
|
201
|
+
namespace: componentNamespace,
|
|
202
|
+
showDetails,
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
each(allDependencies, (data, component) => {
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
|
|
209
|
+
const css = data.css;
|
|
210
|
+
const js = data.js;
|
|
211
|
+
let hash = md5(css + js);
|
|
212
|
+
|
|
213
|
+
if (hash !== awesomenessRequest.meta.components[component]) {
|
|
214
|
+
|
|
215
|
+
meta.components[component] = {
|
|
216
|
+
css,
|
|
217
|
+
js,
|
|
218
|
+
hash
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
if (showDetails) {
|
|
222
|
+
|
|
223
|
+
meta.components[component].css_details = data.css_details;
|
|
224
|
+
meta.components[component].js_details = data.js_details;
|
|
225
|
+
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
} catch (err) {
|
|
231
|
+
|
|
232
|
+
console.log(`failed to get component: ${component}`, {
|
|
233
|
+
component,
|
|
234
|
+
err
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const pages = awesomenessRequest.updatedMeta.pages || {};
|
|
244
|
+
const components = awesomenessRequest.updatedMeta.components || {};
|
|
245
|
+
|
|
246
|
+
if (Object.keys(meta.pages).length) {
|
|
247
|
+
|
|
248
|
+
awesomenessRequest.updatedMeta.pages = {
|
|
249
|
+
...pages,
|
|
250
|
+
...meta.pages
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (Object.keys(meta.components).length) {
|
|
256
|
+
|
|
257
|
+
awesomenessRequest.updatedMeta.components = {
|
|
258
|
+
...components,
|
|
259
|
+
...meta.components
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const pageData = await getData(awesomenessRequest);
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
return pageData;
|
|
268
|
+
|
|
269
|
+
}
|
package/server/getMD.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
export default async function getMD(relativePath, callerUrl) {
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
|
|
9
|
+
const callerDir = path.dirname(fileURLToPath(callerUrl));
|
|
10
|
+
const fullPath = path.resolve(callerDir, relativePath);
|
|
11
|
+
|
|
12
|
+
console.log(`Loading MD file from: ${fullPath}`);
|
|
13
|
+
|
|
14
|
+
return await readFile(fullPath, 'utf8');
|
|
15
|
+
|
|
16
|
+
} catch {
|
|
17
|
+
|
|
18
|
+
return null;
|
|
19
|
+
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import awesomenessNormalizeRequest from '../awesomenessNormalizeRequest.js';
|
|
2
|
+
import specialPaths from '../specialPaths.js';
|
|
3
|
+
import { getConfig } from "../../config.js";
|
|
4
|
+
|
|
5
|
+
async function attachAwesomenessRequest(ctx, next) {
|
|
6
|
+
|
|
7
|
+
const awesomenessConfig = getConfig();
|
|
8
|
+
|
|
9
|
+
ctx.awesomenessRequest = await awesomenessNormalizeRequest({ req: ctx });
|
|
10
|
+
|
|
11
|
+
const routes = awesomenessConfig.specialRoutes[ctx.awesomenessRequest.site] || [];
|
|
12
|
+
|
|
13
|
+
if(ctx.awesomenessRequest.awesomenessType === 'page'){
|
|
14
|
+
|
|
15
|
+
await specialPaths(ctx.awesomenessRequest, routes);
|
|
16
|
+
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
await next();
|
|
20
|
+
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { attachAwesomenessRequest };
|
|
24
|
+
export default attachAwesomenessRequest;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const cors = async (ctx, next) => {
|
|
2
|
+
|
|
3
|
+
// Set CORS headers
|
|
4
|
+
ctx.set('Access-Control-Allow-Origin', '*');
|
|
5
|
+
ctx.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
6
|
+
ctx.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
7
|
+
|
|
8
|
+
// Handle OPTIONS requests
|
|
9
|
+
if (ctx.method === 'OPTIONS') {
|
|
10
|
+
|
|
11
|
+
ctx.status = 204; // No Content
|
|
12
|
+
|
|
13
|
+
return;
|
|
14
|
+
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Proceed to the next middleware
|
|
18
|
+
await next();
|
|
19
|
+
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export { cors };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const errorHandler = async (ctx, next) => {
|
|
2
|
+
|
|
3
|
+
try {
|
|
4
|
+
|
|
5
|
+
await next();
|
|
6
|
+
|
|
7
|
+
} catch (err) {
|
|
8
|
+
|
|
9
|
+
if (err.code === 'EPIPE') {
|
|
10
|
+
|
|
11
|
+
console.error('EPIPE error: Client closed connection');
|
|
12
|
+
ctx.status = 499; // Client Closed Request
|
|
13
|
+
ctx.body = 'Client closed connection unexpectedly';
|
|
14
|
+
|
|
15
|
+
return;
|
|
16
|
+
|
|
17
|
+
} else {
|
|
18
|
+
|
|
19
|
+
// Handle other errors normally
|
|
20
|
+
ctx.status = err.status || 500;
|
|
21
|
+
ctx.body = err.message || 'Internal Server Error';
|
|
22
|
+
ctx.app.emit('error', err, ctx);
|
|
23
|
+
|
|
24
|
+
return;
|
|
25
|
+
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export { errorHandler };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
function finalFormat(awesomenessRequest, ctx){
|
|
2
|
+
|
|
3
|
+
if(!ctx.body.meta){
|
|
4
|
+
|
|
5
|
+
ctx.body.meta = {};
|
|
6
|
+
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if(awesomenessRequest.pageInit){
|
|
10
|
+
|
|
11
|
+
ctx.body.meta.pageInit = awesomenessRequest.pageInit;
|
|
12
|
+
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// just a test
|
|
16
|
+
if(process.env.TESTING){
|
|
17
|
+
|
|
18
|
+
ctx.body.awesomenessRequest = awesomenessRequest;
|
|
19
|
+
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// meta update user
|
|
23
|
+
if(awesomenessRequest.user) {
|
|
24
|
+
|
|
25
|
+
ctx.body.meta.user = awesomenessRequest.user;
|
|
26
|
+
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return;
|
|
30
|
+
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { finalFormat };
|
|
34
|
+
export default finalFormat;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import Busboy from 'busboy';
|
|
2
|
+
|
|
3
|
+
// ---------- helper for JSON ----------
|
|
4
|
+
const parseBody = (req, limit) => {
|
|
5
|
+
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
|
|
8
|
+
let body = '';
|
|
9
|
+
let receivedLength = 0;
|
|
10
|
+
|
|
11
|
+
req.on('data', (chunk) => {
|
|
12
|
+
|
|
13
|
+
receivedLength += chunk.length;
|
|
14
|
+
|
|
15
|
+
if (receivedLength > limit) {
|
|
16
|
+
|
|
17
|
+
reject(new Error('Payload too large'));
|
|
18
|
+
req.destroy();
|
|
19
|
+
|
|
20
|
+
return;
|
|
21
|
+
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
body += chunk;
|
|
25
|
+
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
req.on('end', () => resolve(body));
|
|
29
|
+
req.on('error', (err) => reject(err));
|
|
30
|
+
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// ---------- multipart with Busboy (in memory, preserves field structure) ----------
|
|
36
|
+
const parseMultipart = (req, limit = 10 * 1024 * 1024) => {
|
|
37
|
+
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
|
|
40
|
+
const busboy = Busboy({
|
|
41
|
+
headers: req.headers,
|
|
42
|
+
limits: { fileSize: limit }
|
|
43
|
+
});
|
|
44
|
+
const body = {};
|
|
45
|
+
|
|
46
|
+
busboy.on('field', (name, value) => {
|
|
47
|
+
|
|
48
|
+
// handle "name[]" style fields as arrays
|
|
49
|
+
if (name.endsWith('[]')) {
|
|
50
|
+
|
|
51
|
+
const clean = name.slice(0, -2);
|
|
52
|
+
|
|
53
|
+
body[clean] ??= [];
|
|
54
|
+
body[clean].push(value);
|
|
55
|
+
|
|
56
|
+
} else if (body[name] !== undefined) {
|
|
57
|
+
|
|
58
|
+
// already exists → turn into array
|
|
59
|
+
if (!Array.isArray(body[name])) body[name] = [ body[name] ];
|
|
60
|
+
body[name].push(value);
|
|
61
|
+
|
|
62
|
+
} else {
|
|
63
|
+
|
|
64
|
+
body[name] = value;
|
|
65
|
+
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
busboy.on('file', (name, file, info) => {
|
|
71
|
+
|
|
72
|
+
const {
|
|
73
|
+
filename, encoding, mimeType
|
|
74
|
+
} = info;
|
|
75
|
+
const chunks = [];
|
|
76
|
+
let totalSize = 0;
|
|
77
|
+
|
|
78
|
+
file.on('data', (chunk) => {
|
|
79
|
+
|
|
80
|
+
totalSize += chunk.length;
|
|
81
|
+
|
|
82
|
+
if (totalSize > limit) {
|
|
83
|
+
|
|
84
|
+
reject(new Error('File too large'));
|
|
85
|
+
req.destroy();
|
|
86
|
+
|
|
87
|
+
return;
|
|
88
|
+
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
chunks.push(chunk);
|
|
92
|
+
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
file.on('end', () => {
|
|
96
|
+
|
|
97
|
+
const fileObj = {
|
|
98
|
+
filename,
|
|
99
|
+
mimeType,
|
|
100
|
+
encoding,
|
|
101
|
+
buffer: Buffer.concat(chunks),
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// name[] → array
|
|
105
|
+
if (name.endsWith('[]')) {
|
|
106
|
+
|
|
107
|
+
const clean = name.slice(0, -2);
|
|
108
|
+
|
|
109
|
+
body[clean] ??= [];
|
|
110
|
+
body[clean].push(fileObj);
|
|
111
|
+
|
|
112
|
+
} else if (body[name] !== undefined) {
|
|
113
|
+
|
|
114
|
+
if (!Array.isArray(body[name])) body[name] = [ body[name] ];
|
|
115
|
+
body[name].push(fileObj);
|
|
116
|
+
|
|
117
|
+
} else {
|
|
118
|
+
|
|
119
|
+
body[name] = fileObj;
|
|
120
|
+
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
busboy.on('finish', () => resolve(body));
|
|
128
|
+
busboy.on('error', (err) => reject(err));
|
|
129
|
+
|
|
130
|
+
req.pipe(busboy);
|
|
131
|
+
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
// ---------- middleware ----------
|
|
139
|
+
const jsonBodyParser = async (ctx, next) => {
|
|
140
|
+
|
|
141
|
+
const method = ctx.method;
|
|
142
|
+
const isJson =
|
|
143
|
+
[ 'POST', 'PUT', 'PATCH' ].includes(method) && ctx.is('application/json');
|
|
144
|
+
const isMultipart =
|
|
145
|
+
[ 'POST', 'PUT', 'PATCH' ].includes(method) && ctx.is('multipart/form-data');
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
|
|
150
|
+
if (isJson) {
|
|
151
|
+
|
|
152
|
+
const body = await parseBody(ctx.req, 10 * 1024 * 1024);
|
|
153
|
+
|
|
154
|
+
ctx.request.body = JSON.parse(body);
|
|
155
|
+
|
|
156
|
+
} else if (isMultipart) {
|
|
157
|
+
|
|
158
|
+
ctx.request.body = await parseMultipart(ctx.req);
|
|
159
|
+
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
} catch (err) {
|
|
163
|
+
|
|
164
|
+
ctx.throw(413, new Error('Payload too large'));
|
|
165
|
+
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
await next();
|
|
169
|
+
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export { jsonBodyParser };
|