@lexho111/plainblog 0.7.2 → 0.7.4
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/Article.js +82 -90
- package/Article.ts +97 -0
- package/AssetManager.js +63 -116
- package/Auth.js +2 -2
- package/Blog.js +103 -82
- package/cluster-server.js +36 -31
- package/debug-loader.js +16 -1
- package/index.js +2 -0
- package/model/DataModel.js +11 -9
- package/model/SqliteAdapter.js +15 -7
- package/model/datastructures/BinarySearchTree.js +1 -1
- package/modules/csscompiler/compile-style-worker.js +72 -0
- package/modules/csscompiler/csscompiler.js +135 -0
- package/modules/jscompiler/compile-js-worker.js +34 -0
- package/{build-scripts.js → modules/jscompiler/jscompiler.js} +3 -1
- package/package.json +4 -7
- package/public/styles.min.css +8 -2
- package/router.js +60 -64
- package/server.js +190 -155
- package/src/styles.css +1 -1
- package/styles.hash +1 -0
- package/tsconfig.json +12 -0
- package/.dependency-cruiser.cjs +0 -382
- package/ArrayList.cpuprofile +0 -1
- package/BinarySearchTree.cpuprofile +0 -1
- package/FlameChartProfile.cpuprofile +0 -1
- package/FlameChartProfile2.cpuprofile +0 -1
- package/FlameChartProfile3.cpuprofile +0 -1
- package/StandaloneProfile.cpuprofile +0 -1
- package/blog_test_empty +0 -0
- package/blog_test_load +0 -0
- package/build-styles.js +0 -87
- package/workers/compiler-worker.js +0 -42
package/server.js
CHANGED
|
@@ -45,15 +45,21 @@ async function readBody(req, timeout = 15000, maxSize = 1024 * 1024) {
|
|
|
45
45
|
}
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
-
req.on("end", ()
|
|
48
|
+
req.on("end", function read_body_end() {
|
|
49
49
|
if (!completed) {
|
|
50
50
|
completed = true;
|
|
51
51
|
clearTimeout(timer);
|
|
52
|
-
|
|
52
|
+
if (chunks.length === 0) {
|
|
53
|
+
resolve("");
|
|
54
|
+
} else if (chunks.length === 1) {
|
|
55
|
+
resolve(chunks[0].toString());
|
|
56
|
+
} else {
|
|
57
|
+
resolve(Buffer.concat(chunks).toString());
|
|
58
|
+
}
|
|
53
59
|
}
|
|
54
60
|
const time_end = performance.now();
|
|
55
61
|
const duration = time_end - time_start;
|
|
56
|
-
debug_perf("readBody on end
|
|
62
|
+
debug_perf("readBody on end", duration);
|
|
57
63
|
});
|
|
58
64
|
|
|
59
65
|
req.on("error", (err) => {
|
|
@@ -64,7 +70,7 @@ async function readBody(req, timeout = 15000, maxSize = 1024 * 1024) {
|
|
|
64
70
|
}
|
|
65
71
|
const time_end = performance.now();
|
|
66
72
|
const duration = time_end - time_start;
|
|
67
|
-
debug_perf("readBody on error
|
|
73
|
+
debug_perf("readBody on error", duration);
|
|
68
74
|
});
|
|
69
75
|
});
|
|
70
76
|
}
|
|
@@ -106,37 +112,55 @@ export async function createServer(
|
|
|
106
112
|
res.on("finish", clearTimeouts);
|
|
107
113
|
res.on("close", clearTimeouts);
|
|
108
114
|
//debug("query %s", req.url);
|
|
109
|
-
await api(req, res, async (req, res) => {
|
|
110
|
-
const time_start = performance.now();
|
|
111
|
-
await jsonAPI(
|
|
112
|
-
req,
|
|
113
|
-
res,
|
|
114
|
-
title,
|
|
115
|
-
articles,
|
|
116
|
-
databaseModel,
|
|
117
|
-
readBody,
|
|
118
|
-
auth.isAuthenticated.bind(auth),
|
|
119
|
-
);
|
|
120
|
-
const time_end = performance.now();
|
|
121
|
-
const duration = time_end - time_start;
|
|
122
|
-
debug_perf1("jsonAPI took " + duration + "ms", duration, 400);
|
|
123
|
-
});
|
|
124
|
-
if (res.headersSent) return;
|
|
125
115
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
res
|
|
129
|
-
|
|
130
|
-
|
|
116
|
+
// Helper: Führt eine Route aus und stoppt, wenn eine Antwort gesendet wurde
|
|
117
|
+
const tryRoute = async (routeCall) => {
|
|
118
|
+
if (res.headersSent) return true;
|
|
119
|
+
const p = routeCall();
|
|
120
|
+
if (p) {
|
|
121
|
+
await p;
|
|
122
|
+
return res.headersSent;
|
|
123
|
+
}
|
|
124
|
+
return false;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
if (
|
|
128
|
+
await tryRoute(() =>
|
|
129
|
+
api(req, res, async function handleAPIRequest(req, res) {
|
|
130
|
+
const time_start = performance.now();
|
|
131
|
+
await jsonAPI(
|
|
132
|
+
req,
|
|
133
|
+
res,
|
|
134
|
+
title,
|
|
135
|
+
articles,
|
|
136
|
+
databaseModel,
|
|
137
|
+
readBody,
|
|
138
|
+
auth.isAuthenticated.bind(auth),
|
|
139
|
+
);
|
|
140
|
+
const time_end = performance.now();
|
|
141
|
+
const duration = time_end - time_start;
|
|
142
|
+
debug_perf1("jsonAPI", duration, 400);
|
|
143
|
+
}),
|
|
144
|
+
)
|
|
145
|
+
)
|
|
146
|
+
return;
|
|
147
|
+
|
|
148
|
+
if (
|
|
149
|
+
await tryRoute(() =>
|
|
150
|
+
login(
|
|
131
151
|
req,
|
|
132
152
|
res,
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
153
|
+
async (req, res) => {
|
|
154
|
+
await auth.handleLogin(
|
|
155
|
+
req,
|
|
156
|
+
res,
|
|
157
|
+
readBody,
|
|
158
|
+
REQUEST_TIMEOUT,
|
|
159
|
+
MAX_REQUEST_SIZE,
|
|
160
|
+
);
|
|
161
|
+
},
|
|
162
|
+
async (req, res) => {
|
|
163
|
+
res.end(`${header("My Blog")}<body>
|
|
140
164
|
<form class="loginform" id="loginForm">
|
|
141
165
|
<h1>Blog</h1>
|
|
142
166
|
<!-- Message container -->
|
|
@@ -183,130 +207,144 @@ export async function createServer(
|
|
|
183
207
|
});
|
|
184
208
|
</script>
|
|
185
209
|
</body></html>`);
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
auth.handleLogout(req, res);
|
|
192
|
-
});
|
|
193
|
-
if (res.headersSent) return;
|
|
194
|
-
|
|
195
|
-
await new1(req, res, async (req, res) => {
|
|
196
|
-
if (!auth.isAuthenticated(req)) {
|
|
197
|
-
debug("not authenticated");
|
|
198
|
-
res.writeHead(403, { "Content-Type": "application/json" });
|
|
199
|
-
res.end(JSON.stringify({ error: "Forbidden" }));
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
try {
|
|
204
|
-
const body = await readBody(req);
|
|
205
|
-
const params = new URLSearchParams(body);
|
|
206
|
-
const articleData = Object.fromEntries(params.entries());
|
|
207
|
-
|
|
208
|
-
debug("New Article Data:", articleData);
|
|
209
|
-
// local
|
|
210
|
-
// Write-before-exit strategy: Create in memory, save on closeServer()
|
|
211
|
-
const savedArticle = Article.createNew(
|
|
212
|
-
articleData.title,
|
|
213
|
-
articleData.content,
|
|
214
|
-
);
|
|
210
|
+
},
|
|
211
|
+
),
|
|
212
|
+
)
|
|
213
|
+
)
|
|
214
|
+
return;
|
|
215
215
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
216
|
+
if (
|
|
217
|
+
await tryRoute(() =>
|
|
218
|
+
logout(req, res, (req, res) => {
|
|
219
|
+
auth.handleLogout(req, res);
|
|
220
|
+
}),
|
|
221
|
+
)
|
|
222
|
+
)
|
|
223
|
+
return;
|
|
219
224
|
|
|
220
|
-
|
|
221
|
-
|
|
225
|
+
if (
|
|
226
|
+
await tryRoute(() =>
|
|
227
|
+
new1(req, res, async (req, res) => {
|
|
228
|
+
if (!auth.isAuthenticated(req)) {
|
|
229
|
+
debug("not authenticated");
|
|
230
|
+
res.writeHead(403, { "Content-Type": "application/json" });
|
|
231
|
+
res.end(JSON.stringify({ error: "Forbidden" }));
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
222
234
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
if (res.headersSent) return;
|
|
235
|
-
|
|
236
|
-
await getPages(
|
|
237
|
-
req,
|
|
238
|
-
res,
|
|
239
|
-
async (req, res) => {
|
|
240
|
-
// reload styles and scripts on (every) request
|
|
241
|
-
if (assetManager.reloadStylesOnGET) {
|
|
242
|
-
// This is a development-only feature and a major performance risk.
|
|
243
|
-
if (process.env.NODE_ENV === "production") {
|
|
244
|
-
console.warn(
|
|
245
|
-
"Warning: 'reloadStylesOnGET' is enabled in a production-like environment. This is a major performance risk and should be disabled.",
|
|
235
|
+
try {
|
|
236
|
+
const body = await readBody(req);
|
|
237
|
+
const params = new URLSearchParams(body);
|
|
238
|
+
const articleData = Object.fromEntries(params.entries());
|
|
239
|
+
|
|
240
|
+
debug("New Article Data:", articleData);
|
|
241
|
+
// local
|
|
242
|
+
// Write-before-exit strategy: Create in memory, save on closeServer()
|
|
243
|
+
const savedArticle = Article.createNew(
|
|
244
|
+
articleData.title,
|
|
245
|
+
articleData.content,
|
|
246
246
|
);
|
|
247
|
-
assetManager.reloadStylesOnGET = false; // Disable it for subsequent requests
|
|
248
|
-
}
|
|
249
|
-
await assetManager.reload();
|
|
250
|
-
}
|
|
251
247
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
loggedin = false;
|
|
256
|
-
} else {
|
|
257
|
-
// logout
|
|
258
|
-
loggedin = true;
|
|
259
|
-
}
|
|
248
|
+
databaseModel
|
|
249
|
+
.save(savedArticle)
|
|
250
|
+
.catch((err) => console.error("Async save failed:", err));
|
|
260
251
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const filePath = path.join(publicDir, "index.html");
|
|
252
|
+
await postArticle(savedArticle);
|
|
253
|
+
// external
|
|
264
254
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const mimeTypes = {
|
|
271
|
-
".html": "text/html",
|
|
272
|
-
".css": "text/css",
|
|
273
|
-
".js": "text/javascript",
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
res.writeHead(200, {
|
|
277
|
-
"Content-Type": mimeTypes[ext] || "application/octet-stream",
|
|
278
|
-
});
|
|
279
|
-
res.end(data);
|
|
280
|
-
} catch (fileErr) {
|
|
281
|
-
debug("Error reading index file: %s", fileErr.message);
|
|
255
|
+
// Success response
|
|
256
|
+
res.writeHead(302, { Location: "/" });
|
|
257
|
+
res.end();
|
|
258
|
+
} catch (formErr) {
|
|
259
|
+
debug("Error handling form submission: %s", formErr.message);
|
|
282
260
|
if (!res.headersSent) {
|
|
283
|
-
|
|
284
|
-
res.
|
|
285
|
-
return res.end("Index-File Not Found");
|
|
261
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
262
|
+
res.end(JSON.stringify({ error: "Failed to parse form data" }));
|
|
286
263
|
}
|
|
287
264
|
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
265
|
+
}),
|
|
266
|
+
)
|
|
267
|
+
)
|
|
268
|
+
return;
|
|
269
|
+
|
|
270
|
+
if (
|
|
271
|
+
await tryRoute(() =>
|
|
272
|
+
getPages(
|
|
273
|
+
req,
|
|
274
|
+
res,
|
|
275
|
+
async (req, res) => {
|
|
276
|
+
// reload styles and scripts on (every) request
|
|
277
|
+
if (assetManager.reloadStylesOnGET) {
|
|
278
|
+
// This is a development-only feature and a major performance risk.
|
|
279
|
+
if (process.env.NODE_ENV === "production") {
|
|
280
|
+
console.warn(
|
|
281
|
+
"Warning: 'reloadStylesOnGET' is enabled in a production-like environment. This is a major performance risk and should be disabled.",
|
|
282
|
+
);
|
|
283
|
+
assetManager.reloadStylesOnGET = false; // Disable it for subsequent requests
|
|
284
|
+
}
|
|
285
|
+
await assetManager.reload();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
let loggedin = false;
|
|
289
|
+
if (!auth.isAuthenticated(req)) {
|
|
290
|
+
// login
|
|
291
|
+
loggedin = false;
|
|
292
|
+
} else {
|
|
293
|
+
// logout
|
|
294
|
+
loggedin = true;
|
|
302
295
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
296
|
+
|
|
297
|
+
if (angular) {
|
|
298
|
+
// use angular frontend
|
|
299
|
+
const filePath = path.join(publicDir, "index.html");
|
|
300
|
+
|
|
301
|
+
debug("%s", filePath);
|
|
302
|
+
try {
|
|
303
|
+
const data = await readFile(filePath);
|
|
304
|
+
// Manual MIME type detection (simplified)
|
|
305
|
+
const ext = path.extname(filePath);
|
|
306
|
+
const mimeTypes = {
|
|
307
|
+
".html": "text/html",
|
|
308
|
+
".css": "text/css",
|
|
309
|
+
".js": "text/javascript",
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
res.writeHead(200, {
|
|
313
|
+
"Content-Type": mimeTypes[ext] || "application/octet-stream",
|
|
314
|
+
});
|
|
315
|
+
res.end(data);
|
|
316
|
+
} catch (fileErr) {
|
|
317
|
+
debug("Error reading index file: %s", fileErr.message);
|
|
318
|
+
if (!res.headersSent) {
|
|
319
|
+
debug404("unmatched route: %s %s", req.method, req.url);
|
|
320
|
+
res.writeHead(404);
|
|
321
|
+
return res.end("Index-File Not Found");
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
} else {
|
|
325
|
+
// use built in view engine
|
|
326
|
+
try {
|
|
327
|
+
const html = await html_content(loggedin); // render this blog to HTML
|
|
328
|
+
res.writeHead(200, {
|
|
329
|
+
"Content-Type": "text/html; charset=UTF-8",
|
|
330
|
+
});
|
|
331
|
+
res.end(html);
|
|
332
|
+
return;
|
|
333
|
+
} catch (renderErr) {
|
|
334
|
+
console.error("Error rendering HTML:", renderErr);
|
|
335
|
+
if (!res.headersSent) {
|
|
336
|
+
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
337
|
+
res.end("Internal Server Error");
|
|
338
|
+
}
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
publicDir,
|
|
344
|
+
),
|
|
345
|
+
)
|
|
346
|
+
)
|
|
347
|
+
return;
|
|
310
348
|
|
|
311
349
|
// If no route was matched, send a 404
|
|
312
350
|
if (!res.headersSent) {
|
|
@@ -472,7 +510,7 @@ async function jsonAPI(
|
|
|
472
510
|
const dbArticles = await articles.findAll(start, end, limit);
|
|
473
511
|
const time_end = performance.now();
|
|
474
512
|
const duration = time_end - time_start;
|
|
475
|
-
debug_perf('GET "/api/articles"
|
|
513
|
+
debug_perf('GET "/api/articles"', duration);
|
|
476
514
|
if (!res.headersSent) {
|
|
477
515
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
478
516
|
const responseData = {
|
|
@@ -502,13 +540,10 @@ async function jsonAPI(
|
|
|
502
540
|
body = await readBody(req, REQUEST_TIMEOUT, MAX_REQUEST_SIZE);
|
|
503
541
|
const time_end1 = performance.now();
|
|
504
542
|
const duration1 = time_end1 - time_start1;
|
|
505
|
-
debug_perf(
|
|
506
|
-
'POST "/api/articles" readBody took ' + duration1 + "ms",
|
|
507
|
-
duration1,
|
|
508
|
-
);
|
|
543
|
+
debug_perf('POST "/api/articles" readBody', duration1);
|
|
509
544
|
const time_end = performance.now();
|
|
510
545
|
const duration = time_end - time_start;
|
|
511
|
-
debug_perf('POST "/api/articles"
|
|
546
|
+
debug_perf('POST "/api/articles"', duration);
|
|
512
547
|
} catch (err) {
|
|
513
548
|
debug("Error reading request body: %s", err.message);
|
|
514
549
|
if (!res.headersSent) {
|
|
@@ -541,7 +576,7 @@ async function jsonAPI(
|
|
|
541
576
|
debug("new article: %s", newArticle.title);
|
|
542
577
|
const time_end1 = performance.now();
|
|
543
578
|
const duration1 = time_end1 - time_start;
|
|
544
|
-
debug_perf("new article took "
|
|
579
|
+
debug_perf("new article took ", duration1);
|
|
545
580
|
|
|
546
581
|
// Create article with a temporary ID for immediate use
|
|
547
582
|
const articleWithTempId = Article.createNew(
|
|
@@ -563,7 +598,7 @@ async function jsonAPI(
|
|
|
563
598
|
|
|
564
599
|
const time_end1 = performance.now();
|
|
565
600
|
const duration = time_end1 - time_start;
|
|
566
|
-
debug_perf("databaseModel
|
|
601
|
+
debug_perf("databaseModel", duration);
|
|
567
602
|
} catch (dbErr) {
|
|
568
603
|
console.error("Database save error:", dbErr.message);
|
|
569
604
|
console.error("Stack:", dbErr.stack);
|
|
@@ -636,7 +671,7 @@ async function jsonAPI(
|
|
|
636
671
|
}
|
|
637
672
|
const time_end = performance.now();
|
|
638
673
|
const duration = time_end - time_start;
|
|
639
|
-
debug_perf('DELETE "/api/articles"
|
|
674
|
+
debug_perf('DELETE "/api/articles"', duration);
|
|
640
675
|
} else if (req.method === "PUT") {
|
|
641
676
|
debug("PUT an article");
|
|
642
677
|
if (!isAuthenticated(req)) {
|
|
@@ -698,7 +733,7 @@ async function jsonAPI(
|
|
|
698
733
|
}
|
|
699
734
|
const time_end = performance.now();
|
|
700
735
|
const duration = time_end - time_start;
|
|
701
|
-
debug_perf('POST "/api/articles"
|
|
736
|
+
debug_perf('POST "/api/articles"', duration);
|
|
702
737
|
} else {
|
|
703
738
|
if (!res.headersSent) {
|
|
704
739
|
debug404("unmatched route: %s %s", req.method, req.url);
|
package/src/styles.css
CHANGED
package/styles.hash
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
d7f416eb85f42f3fe51ac52dc717c448
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Visit https://aka.ms/tsconfig to read more about this file
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"target": "ES2021",
|
|
5
|
+
"module": "es2020",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true
|
|
10
|
+
},
|
|
11
|
+
"include": ["Article.ts"]
|
|
12
|
+
}
|