@lexho111/plainblog 0.5.10 → 0.5.11
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/Blog.js +119 -96
- package/package.json +1 -1
- package/public/styles.min.css +2 -2
- package/test/blog.test.js +52 -6
- package/articles.txt +0 -11
- package/blog.db +0 -0
- package/blog.json +0 -110
- package/bloginfo.json +0 -3
- package/lexho111-plainblog-0.4.1.tgz +0 -0
- package/lexho111-plainblog-0.5.7.tgz +0 -0
package/Blog.js
CHANGED
|
@@ -13,7 +13,7 @@ import { exec } from "child_process";
|
|
|
13
13
|
import { promisify } from "util";
|
|
14
14
|
import { compileStyles, mergeStyles } from "./build-styles.js";
|
|
15
15
|
|
|
16
|
-
const execPromise = promisify(exec);
|
|
16
|
+
const execPromise = promisify(exec); // x
|
|
17
17
|
|
|
18
18
|
export default class Blog {
|
|
19
19
|
constructor() {
|
|
@@ -22,31 +22,53 @@ export default class Blog {
|
|
|
22
22
|
username: "user",
|
|
23
23
|
password: "password",
|
|
24
24
|
host: "localhost",
|
|
25
|
-
dbname: "blog.json",
|
|
25
|
+
dbname: "blog.json", // x
|
|
26
26
|
};
|
|
27
27
|
this.#title = "";
|
|
28
|
-
this
|
|
29
|
-
this.filename = null;
|
|
28
|
+
this.#articles = [];
|
|
30
29
|
this.#server = null;
|
|
31
30
|
this.#password = "admin";
|
|
32
|
-
this
|
|
33
|
-
this.scripts = "";
|
|
31
|
+
this.#styles = "body { font-family: Arial; }";
|
|
32
|
+
//this.scripts = "";
|
|
34
33
|
this.compiledStyles = "";
|
|
35
|
-
this.compiledScripts = "";
|
|
34
|
+
//this.compiledScripts = "";
|
|
36
35
|
this.reloadStylesOnGET = false;
|
|
37
36
|
this.sessions = new Set();
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
console.log(`version: ${version}`);
|
|
38
|
+
this.#version = pkg.version;
|
|
39
|
+
console.log(`version: ${this.#version}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
json() {
|
|
43
|
+
const serverInfo = this.#server ? {
|
|
44
|
+
listening: this.#server.listening,
|
|
45
|
+
address: this.#server.address()
|
|
46
|
+
} : null;
|
|
47
|
+
|
|
48
|
+
const json = {
|
|
49
|
+
"version": this.#version,
|
|
50
|
+
"title": this.#title,
|
|
51
|
+
"articles": this.#articles,
|
|
52
|
+
"server": serverInfo,
|
|
53
|
+
"compiledStyles": this.compiledStyles,
|
|
54
|
+
"sessions": this.sessions,
|
|
55
|
+
"database": this.database,
|
|
56
|
+
"password": this.#password, "styles": this.#styles, "reloadStylesOnGET": this.reloadStylesOnGET
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return JSON.parse(JSON.stringify(json));
|
|
41
60
|
}
|
|
42
61
|
|
|
43
62
|
// Private fields
|
|
63
|
+
#version = null;
|
|
44
64
|
#server = null;
|
|
45
65
|
#password = null;
|
|
46
66
|
#databaseModel;
|
|
47
67
|
#isExternalAPI = false;
|
|
48
68
|
#apiUrl = "";
|
|
49
69
|
#title = "";
|
|
70
|
+
#articles = [];
|
|
71
|
+
#styles = "";
|
|
50
72
|
#stylesHash = "";
|
|
51
73
|
#scriptsHash = "";
|
|
52
74
|
#stylesheetPath = "";
|
|
@@ -60,7 +82,7 @@ export default class Blog {
|
|
|
60
82
|
}
|
|
61
83
|
|
|
62
84
|
setStyle(style) {
|
|
63
|
-
this
|
|
85
|
+
this.#styles += style;
|
|
64
86
|
}
|
|
65
87
|
|
|
66
88
|
set title(t) {
|
|
@@ -89,7 +111,7 @@ export default class Blog {
|
|
|
89
111
|
}
|
|
90
112
|
|
|
91
113
|
set style(style) {
|
|
92
|
-
this
|
|
114
|
+
this.#styles += style;
|
|
93
115
|
}
|
|
94
116
|
|
|
95
117
|
set stylesheetPath(files) {
|
|
@@ -98,68 +120,71 @@ export default class Blog {
|
|
|
98
120
|
}
|
|
99
121
|
|
|
100
122
|
addArticle(article) {
|
|
101
|
-
this
|
|
123
|
+
this.#articles.push(article);
|
|
102
124
|
}
|
|
103
125
|
|
|
104
|
-
isAuthenticated(req) {
|
|
126
|
+
#isAuthenticated(req) {
|
|
105
127
|
if (!req.headers.cookie) return false;
|
|
106
128
|
const params = new URLSearchParams(req.headers.cookie.replace(/; /g, "&"));
|
|
107
129
|
return this.sessions.has(params.get("session"));
|
|
108
|
-
|
|
130
|
+
}
|
|
109
131
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
132
|
+
async #handleLogin(req, res) {
|
|
133
|
+
const body = await new Promise((resolve, reject) => {
|
|
134
|
+
let data = "";
|
|
135
|
+
req.on("data", (chunk) => (data += chunk.toString()));
|
|
136
|
+
req.on("end", () => resolve(data));
|
|
137
|
+
req.on("error", reject);
|
|
138
|
+
});
|
|
139
|
+
const params = new URLSearchParams(body);
|
|
140
|
+
|
|
141
|
+
if (params.get("password") === this.#password) {
|
|
142
|
+
const id = crypto.randomUUID();
|
|
143
|
+
this.sessions.add(id);
|
|
144
|
+
res.writeHead(303, {
|
|
145
|
+
"Set-Cookie": `session=${id}; HttpOnly; Path=/`,
|
|
146
|
+
Location: "/",
|
|
116
147
|
});
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
this.sessions.add(id);
|
|
122
|
-
res.writeHead(303, {
|
|
123
|
-
"Set-Cookie": `session=${id}; HttpOnly; Path=/`,
|
|
124
|
-
Location: "/",
|
|
125
|
-
});
|
|
126
|
-
res.end();
|
|
127
|
-
} else {
|
|
128
|
-
res.writeHead(401, { "Content-Type": "text/html" });
|
|
129
|
-
res.end(`${header("My Blog")}
|
|
148
|
+
res.end();
|
|
149
|
+
} else {
|
|
150
|
+
res.writeHead(401, { "Content-Type": "text/html" });
|
|
151
|
+
res.end(`${header("My Blog")}
|
|
130
152
|
<body>
|
|
131
153
|
<h1>Unauthorized</h1><p>Please enter the password.<form method="POST">
|
|
132
154
|
<input type="password" name="password" placeholder="Password" />
|
|
133
155
|
<button style="margin: 2px;">Login</button></form>
|
|
134
156
|
</body></html>`);
|
|
135
157
|
}
|
|
136
|
-
|
|
158
|
+
}
|
|
137
159
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
160
|
+
#handleLogout(req, res) {
|
|
161
|
+
if (req.headers.cookie) {
|
|
162
|
+
const params = new URLSearchParams(req.headers.cookie.replace(/; /g, "&"));
|
|
163
|
+
const sessionId = params.get("session");
|
|
164
|
+
if (this.sessions.has(sessionId)) {
|
|
165
|
+
this.sessions.delete(sessionId);
|
|
145
166
|
}
|
|
146
|
-
res.writeHead(303, {
|
|
147
|
-
"Set-Cookie": "session=; HttpOnly; Path=/; Max-Age=0",
|
|
148
|
-
Location: "/",
|
|
149
|
-
});
|
|
150
|
-
res.end();
|
|
151
167
|
}
|
|
168
|
+
res.writeHead(303, {
|
|
169
|
+
"Set-Cookie": "session=; HttpOnly; Path=/; Max-Age=0",
|
|
170
|
+
Location: "/",
|
|
171
|
+
});
|
|
172
|
+
res.end();
|
|
173
|
+
}
|
|
152
174
|
|
|
153
175
|
/** initializes database */
|
|
154
176
|
async init() {
|
|
155
|
-
//await this.buildFrontend();
|
|
156
177
|
//this.loadStyles();
|
|
157
178
|
//this.loadScripts();
|
|
179
|
+
// if there is a stylesheet path provided, process it
|
|
158
180
|
if(this.#stylesheetPath != null) {
|
|
159
|
-
|
|
181
|
+
// read file from stylesheet path, compare checksums and write to public/styles.min.css
|
|
182
|
+
await this.#processStylesheets(this.#stylesheetPath);
|
|
160
183
|
}
|
|
161
184
|
if(!this.#stylesheetPath) {
|
|
162
|
-
//
|
|
185
|
+
// this.#styles
|
|
186
|
+
// src/styles.css
|
|
187
|
+
// compile and merge hardcoded styles in "this.#styles" with "src/styles.css" and write to file "styles.min.css"
|
|
163
188
|
// which will be imported by webbrowser via '<link rel="stylesheet" href="styles.min.css"...'
|
|
164
189
|
|
|
165
190
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -168,31 +193,26 @@ export default class Blog {
|
|
|
168
193
|
const publicStylePath = path.join(__dirname, "public", "styles.min.css");
|
|
169
194
|
|
|
170
195
|
let publicHash = null;
|
|
171
|
-
try {
|
|
172
|
-
const publicCSS = await fs.promises.readFile(publicStylePath, "utf8");
|
|
173
|
-
const match = publicCSS.match(/\/\* source-hash: ([a-f0-9]{64}) \*\//);
|
|
174
|
-
if (match) {
|
|
175
|
-
publicHash = match[1];
|
|
176
|
-
}
|
|
177
|
-
} catch (err) {
|
|
178
|
-
// public/styles.min.css doesn't exist, will be created.
|
|
179
|
-
}
|
|
180
|
-
|
|
181
196
|
let srcStyles = "";
|
|
182
|
-
try {
|
|
183
|
-
srcStyles = await fs.promises.readFile(srcStylePath, "utf8");
|
|
184
|
-
} catch (err) {
|
|
185
|
-
// ignore if src/styles.css doesn't exist
|
|
186
|
-
}
|
|
187
197
|
|
|
188
|
-
|
|
198
|
+
await Promise.all([
|
|
199
|
+
fs.promises.readFile(publicStylePath, "utf8").then((publicCSS) => {
|
|
200
|
+
const match = publicCSS.match(/\/\* source-hash: ([a-f0-9]{64}) \*\//);
|
|
201
|
+
if (match) publicHash = match[1];
|
|
202
|
+
}).catch((err) => console.error(err)), // public/styles.min.css doesn't exist, will be created.
|
|
203
|
+
fs.promises.readFile(srcStylePath, "utf8").then((content) => {
|
|
204
|
+
srcStyles = content;
|
|
205
|
+
}).catch((err) => console.error(err)) // ignore if src/styles.css doesn't exist
|
|
206
|
+
]);
|
|
207
|
+
|
|
208
|
+
const combinedStyles = this.#styles + srcStyles;
|
|
189
209
|
const srcHash = crypto.createHash("sha256").update(combinedStyles).digest("hex");
|
|
190
210
|
|
|
191
211
|
if (srcHash !== publicHash) {
|
|
192
212
|
console.log("Styles have changed. Recompiling...");
|
|
193
|
-
const finalStyles = await mergeStyles(this
|
|
213
|
+
const finalStyles = await mergeStyles(this.#styles, srcStyles);
|
|
194
214
|
try {
|
|
195
|
-
await fs.promises.mkdir(path.dirname(publicStylePath), { recursive: true });
|
|
215
|
+
//await fs.promises.mkdir(path.dirname(publicStylePath), { recursive: true });
|
|
196
216
|
await fs.promises.writeFile(publicStylePath, finalStyles + `\n/* source-hash: ${srcHash} */`);
|
|
197
217
|
} catch (err) {
|
|
198
218
|
console.error("Failed to write styles to public folder:", err);
|
|
@@ -201,7 +221,7 @@ export default class Blog {
|
|
|
201
221
|
}
|
|
202
222
|
if (this.#isExternalAPI) {
|
|
203
223
|
console.log("external API");
|
|
204
|
-
await this
|
|
224
|
+
await this.#loadFromAPI();
|
|
205
225
|
} else {
|
|
206
226
|
console.log(`database: ${this.database.type}`);
|
|
207
227
|
if (!this.#databaseModel) {
|
|
@@ -209,10 +229,11 @@ export default class Blog {
|
|
|
209
229
|
}
|
|
210
230
|
console.log(`connected to database`);
|
|
211
231
|
await this.#databaseModel.initialize();
|
|
212
|
-
const dbTitle = await
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
232
|
+
const [dbTitle, dbArticles] = await Promise.all([
|
|
233
|
+
this.#databaseModel.getBlogTitle(),
|
|
234
|
+
this.#databaseModel.findAll(),
|
|
235
|
+
]);
|
|
236
|
+
|
|
216
237
|
if(dbArticles.length == 0) {
|
|
217
238
|
dbArticles.push(new Article("Sample Entry #1", "Prow scuttle parrel provost Sail ho shrouds spirits boom mizzenmast yardarm. Pinnace holystone mizzenmast quarter crow's nest nipperkin grog yardarm hempen halter furl. Swab barque interloper chantey doubloon starboard grog black jack gangway rutters.", new Date()));
|
|
218
239
|
dbArticles.push(new Article("Sample Entry #2", "Deadlights jack lad schooner scallywag dance the hempen jig carouser broadside cable strike colors. Bring a spring upon her cable holystone blow the man down spanker Shiver me timbers to go on account lookout wherry doubloon chase. Belay yo-ho-ho keelhaul squiffy black spot yardarm spyglass sheet transom heave to.", new Date()));
|
|
@@ -244,12 +265,14 @@ export default class Blog {
|
|
|
244
265
|
const newArticleData = { title, content };
|
|
245
266
|
try {
|
|
246
267
|
// Save the new article to the database via the ApiServer
|
|
247
|
-
|
|
248
|
-
if (this.#
|
|
268
|
+
const promises = [];
|
|
269
|
+
if (this.#databaseModel) promises.push(this.#databaseModel.save(newArticleData));
|
|
270
|
+
if (this.#isExternalAPI) promises.push(postData(this.#apiUrl, newArticleData));
|
|
271
|
+
await Promise.all(promises);
|
|
249
272
|
// Add the article to the local list for immediate display
|
|
250
|
-
this
|
|
273
|
+
this.#articles.unshift(new Article(title, content, new Date()));
|
|
251
274
|
// remove sample entries
|
|
252
|
-
this
|
|
275
|
+
this.#articles = this.#articles.filter(
|
|
253
276
|
(art) =>
|
|
254
277
|
art.title !== "Sample Entry #1" && art.title !== "Sample Entry #2"
|
|
255
278
|
);
|
|
@@ -286,18 +309,18 @@ export default class Blog {
|
|
|
286
309
|
</body></html>`);
|
|
287
310
|
return;
|
|
288
311
|
} else if (req.method === "POST") {
|
|
289
|
-
await this
|
|
312
|
+
await this.#handleLogin(req, res);
|
|
290
313
|
return;
|
|
291
314
|
}
|
|
292
315
|
}
|
|
293
316
|
|
|
294
317
|
if (req.url === "/logout") {
|
|
295
|
-
this
|
|
318
|
+
this.#handleLogout(req, res);
|
|
296
319
|
return;
|
|
297
320
|
}
|
|
298
321
|
// POST new article
|
|
299
322
|
if (req.method === "POST" && req.url === "/") {
|
|
300
|
-
if (!this
|
|
323
|
+
if (!this.#isAuthenticated(req)) {
|
|
301
324
|
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
302
325
|
res.end("Forbidden");
|
|
303
326
|
return;
|
|
@@ -310,12 +333,12 @@ export default class Blog {
|
|
|
310
333
|
// reload styles and scripts on (every) request
|
|
311
334
|
if(this.reloadStylesOnGET) {
|
|
312
335
|
if (this.#stylesheetPath) {
|
|
313
|
-
await this
|
|
336
|
+
await this.#processStylesheets(this.#stylesheetPath);
|
|
314
337
|
}
|
|
315
338
|
}
|
|
316
339
|
|
|
317
340
|
let loggedin = false;
|
|
318
|
-
if (!this
|
|
341
|
+
if (!this.#isAuthenticated(req)) {
|
|
319
342
|
// login
|
|
320
343
|
loggedin = false;
|
|
321
344
|
} else {
|
|
@@ -374,8 +397,11 @@ export default class Blog {
|
|
|
374
397
|
|
|
375
398
|
this.#server = server;
|
|
376
399
|
|
|
377
|
-
return new Promise((resolve) => {
|
|
378
|
-
|
|
400
|
+
return new Promise((resolve, reject) => {
|
|
401
|
+
const errorHandler = (err) => reject(err);
|
|
402
|
+
this.#server.once("error", errorHandler);
|
|
403
|
+
this.#server.listen(port, '127.0.0.1', () => {
|
|
404
|
+
this.#server.removeListener("error", errorHandler);
|
|
379
405
|
console.log(`server running at http://localhost:${port}/`);
|
|
380
406
|
resolve(); // Resolve the promise when the server is listening
|
|
381
407
|
});
|
|
@@ -386,7 +412,7 @@ export default class Blog {
|
|
|
386
412
|
return new Promise((resolve, reject) => {
|
|
387
413
|
if (this.#server) {
|
|
388
414
|
this.#server.close((err) => {
|
|
389
|
-
if (err) return reject(err);
|
|
415
|
+
if (err && err.code !== "ERR_SERVER_NOT_RUNNING") return reject(err);
|
|
390
416
|
console.log("Server closed.");
|
|
391
417
|
resolve();
|
|
392
418
|
});
|
|
@@ -398,19 +424,19 @@ export default class Blog {
|
|
|
398
424
|
|
|
399
425
|
/** Populates the blog's title and articles from a data object. */
|
|
400
426
|
#applyBlogData(data) {
|
|
401
|
-
this
|
|
427
|
+
this.#articles = []; // Clear existing articles before loading new ones
|
|
402
428
|
this.#title = data.title;
|
|
403
|
-
// Assuming
|
|
429
|
+
// Assuming data contains a title and an array of articles with title and content
|
|
404
430
|
if (data.articles && Array.isArray(data.articles)) {
|
|
405
431
|
for (const articleData of data.articles) {
|
|
406
432
|
const article = new Article(articleData.title, articleData.content, articleData.createdAt);
|
|
407
|
-
article.id = articleData.id;
|
|
433
|
+
article.id = articleData.id; // TODO x
|
|
408
434
|
this.addArticle(article);
|
|
409
435
|
}
|
|
410
436
|
}
|
|
411
437
|
}
|
|
412
438
|
|
|
413
|
-
async loadFromAPI() {
|
|
439
|
+
async #loadFromAPI() {
|
|
414
440
|
const data = await fetchData(this.#apiUrl);
|
|
415
441
|
if (data) {
|
|
416
442
|
this.#applyBlogData(data);
|
|
@@ -451,7 +477,7 @@ export default class Blog {
|
|
|
451
477
|
|
|
452
478
|
// POST a new article
|
|
453
479
|
} else if (req.method === "POST" && pathname === "/api/articles") {
|
|
454
|
-
if (!this
|
|
480
|
+
if (!this.#isAuthenticated(req)) {
|
|
455
481
|
res.writeHead(403, { "Content-Type": "application/json" });
|
|
456
482
|
res.end(JSON.stringify({ error: "Forbidden" }));
|
|
457
483
|
return;
|
|
@@ -481,7 +507,7 @@ export default class Blog {
|
|
|
481
507
|
print() {
|
|
482
508
|
const data = {
|
|
483
509
|
title: this.title,
|
|
484
|
-
articles: this
|
|
510
|
+
articles: this.#articles,
|
|
485
511
|
};
|
|
486
512
|
const markdown = formatMarkdown(data);
|
|
487
513
|
console.log(markdown);
|
|
@@ -491,7 +517,7 @@ export default class Blog {
|
|
|
491
517
|
async toHTML(loggedin) {
|
|
492
518
|
const data = {
|
|
493
519
|
title: this.title,
|
|
494
|
-
articles: this
|
|
520
|
+
articles: this.#articles,
|
|
495
521
|
loggedin,
|
|
496
522
|
login: ""
|
|
497
523
|
};
|
|
@@ -505,19 +531,16 @@ export default class Blog {
|
|
|
505
531
|
}
|
|
506
532
|
|
|
507
533
|
/**
|
|
508
|
-
*
|
|
534
|
+
* read files, compare checksums, compile and write to public/styles.min.css
|
|
509
535
|
* @param {string[]} files - Array of css/scss file paths to process.
|
|
510
536
|
*/
|
|
511
|
-
async processStylesheets(files) {
|
|
537
|
+
async #processStylesheets(files) {
|
|
512
538
|
console.log("process stylesheets")
|
|
513
539
|
|
|
514
540
|
// Normalize input to array (handles string or array)
|
|
515
541
|
// "file1.css" --> ["file1.css"]
|
|
516
542
|
// ["file1.css", "file2.css",...]
|
|
517
543
|
const fileList = Array.isArray(files) ? files : [files];
|
|
518
|
-
|
|
519
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
520
|
-
const __dirname = path.dirname(__filename);
|
|
521
544
|
const styleFiles = fileList.filter(
|
|
522
545
|
(f) => typeof f === "string" && (f.endsWith(".scss") || f.endsWith(".css")) && !f.endsWith(".min.css")
|
|
523
546
|
);
|
package/package.json
CHANGED
package/public/styles.min.css
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
body{
|
|
2
|
-
/* source-hash:
|
|
1
|
+
body{background-color:#fdfdfd;font-family:Arial}nav a{color:#3b40c1;font-size:20px;text-decoration:underline}.datetime{color:#434343;font-style:normal}h2{color:#a9a9a9;margin:0 0 5px}p{margin-top:10px}span{margin:0}
|
|
2
|
+
/* source-hash: fa9deb7a7f0781f463cd3e8fd3c3ceddec518535b0a6d13af7309ef9a2f76c32 */
|
package/test/blog.test.js
CHANGED
|
@@ -6,6 +6,46 @@ import Article from "../Article.js";
|
|
|
6
6
|
import { jest } from "@jest/globals";
|
|
7
7
|
|
|
8
8
|
describe("test blog", () => {
|
|
9
|
+
test("blog bootstrap stage 1", () => {
|
|
10
|
+
const myblog = new Blog();
|
|
11
|
+
const json = myblog.json();
|
|
12
|
+
expect(json.version).toContain(".");
|
|
13
|
+
const database = json.database;
|
|
14
|
+
expect(database.type).toBe("file");
|
|
15
|
+
expect(database.username).toBe("user");
|
|
16
|
+
expect(database.password).toBe("password");
|
|
17
|
+
expect(database.host).toBe("localhost");
|
|
18
|
+
expect(database.dbname).toBe("articles.txt"); //TODO switch blog.json to articles.txt, bloginfo.json
|
|
19
|
+
expect(json.password).toBe("admin");
|
|
20
|
+
expect(json.styles).toContain("body { font-family: Arial; }");
|
|
21
|
+
expect(json.reloadStylesOnGET).not.toBeTruthy();
|
|
22
|
+
});
|
|
23
|
+
test("blog bootstrap stage 2", async () => {
|
|
24
|
+
const myblog = new Blog();
|
|
25
|
+
await myblog.init();
|
|
26
|
+
const json = myblog.json();
|
|
27
|
+
console.log(json);
|
|
28
|
+
expect(json.title).toBe("Test Blog Title");
|
|
29
|
+
expect(json.articles.length).toBeGreaterThan(2);
|
|
30
|
+
expect(json.server).toBeNull();
|
|
31
|
+
});
|
|
32
|
+
test("blog bootstrap stage 3", async () => {
|
|
33
|
+
const myblog = new Blog();
|
|
34
|
+
await myblog.init();
|
|
35
|
+
try {
|
|
36
|
+
await myblog.startServer(8080);
|
|
37
|
+
const json = myblog.json();
|
|
38
|
+
console.log(json);
|
|
39
|
+
expect(json.title).toBe("Test Blog Title"); // from bloginfo.json
|
|
40
|
+
expect(json.articles.length).toBeGreaterThan(2);
|
|
41
|
+
expect(json.server.listening).toBeTruthy();
|
|
42
|
+
expect(json.server.address.address).toBe("127.0.0.1");
|
|
43
|
+
expect(json.server.address.family).toBe("IPv4");
|
|
44
|
+
expect(json.server.address.port).toBe(8080);
|
|
45
|
+
} finally {
|
|
46
|
+
await myblog.closeServer();
|
|
47
|
+
}
|
|
48
|
+
});
|
|
9
49
|
test("is valid html", async () => {
|
|
10
50
|
const myblog = new Blog();
|
|
11
51
|
const html = await myblog.toHTML();
|
|
@@ -35,21 +75,27 @@ describe("test blog", () => {
|
|
|
35
75
|
const article = new Article("", "");
|
|
36
76
|
myblog.addArticle(article);
|
|
37
77
|
const html = await myblog.toHTML();
|
|
78
|
+
const json = myblog.json();
|
|
38
79
|
expect(html).toContain("<article");
|
|
39
|
-
expect(
|
|
80
|
+
expect(json.articles).toHaveLength(1);
|
|
40
81
|
});
|
|
41
82
|
test("add articles", async () => {
|
|
42
83
|
const myblog = new Blog();
|
|
43
|
-
|
|
84
|
+
const json = myblog.json();
|
|
85
|
+
expect(json.articles).toHaveLength(0);
|
|
44
86
|
const size = 10;
|
|
45
87
|
for (let i = 1; i <= size; i++) {
|
|
46
88
|
const article = new Article("", "");
|
|
47
89
|
myblog.addArticle(article);
|
|
48
|
-
|
|
90
|
+
const json = myblog.json();
|
|
91
|
+
expect(json.articles).toHaveLength(i);
|
|
92
|
+
}
|
|
93
|
+
{
|
|
94
|
+
const html = await myblog.toHTML();
|
|
95
|
+
expect(html).toContain("<article");
|
|
96
|
+
const json = myblog.json();
|
|
97
|
+
expect(json.articles).toHaveLength(size);
|
|
49
98
|
}
|
|
50
|
-
const html = await myblog.toHTML();
|
|
51
|
-
expect(html).toContain("<article");
|
|
52
|
-
expect(myblog.articles.length).toBe(size);
|
|
53
99
|
});
|
|
54
100
|
const __filename = fileURLToPath(import.meta.url);
|
|
55
101
|
const __dirname = path.dirname(__filename);
|
package/articles.txt
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
{"title":"Test Title from Jest","content":"This is the content of the test article.","createdAt":"2026-01-08T13:19:39.939Z"}
|
|
2
|
-
{"title":"Test Title from Jest","content":"This is the content of the test article.","createdAt":"2026-01-08T13:34:38.866Z"}
|
|
3
|
-
{"title":"Test Title from Jest","content":"This is the content of the test article.","createdAt":"2026-01-08T13:43:32.343Z"}
|
|
4
|
-
{"title":"Test Title from Jest","content":"This is the content of the test article.","createdAt":"2026-01-08T18:48:23.123Z"}
|
|
5
|
-
{"title":"Test Title from Jest","content":"This is the content of the test article.","createdAt":"2026-01-08T18:50:06.993Z"}
|
|
6
|
-
{"title":"Test Title from Jest","content":"This is the content of the test article.","createdAt":"2026-01-08T18:56:28.369Z"}
|
|
7
|
-
{"title":"Test Title from Jest","content":"This is the content of the test article.","createdAt":"2026-01-08T18:57:53.780Z"}
|
|
8
|
-
{"title":"Test Title from Jest","content":"This is the content of the test article.","createdAt":"2026-01-08T18:58:54.261Z"}
|
|
9
|
-
{"title":"Test Title from Jest","content":"This is the content of the test article.","createdAt":"2026-01-08T19:01:02.613Z"}
|
|
10
|
-
{"title":"Test Title from Jest","content":"This is the content of the test article.","createdAt":"2026-01-08T19:01:30.473Z"}
|
|
11
|
-
{"title":"Test Title from Jest","content":"This is the content of the test article.","createdAt":"2026-01-08T19:03:33.773Z"}
|
package/blog.db
DELETED
|
Binary file
|
package/blog.json
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"title": "",
|
|
3
|
-
"articles": [
|
|
4
|
-
{
|
|
5
|
-
"title": "hello",
|
|
6
|
-
"content": "hML`JfDS]ARPVAfsGdogseNVYPXTpZaJhKJkPoMTWA\\q[CkNsXYrUGyGHWTDZpByubZMf_Y\\bMELh[]afM^XMAFqvxuyWat^hqSBMIifvbTdHNOQP_rbwwlWnGvVbPi]jlTIixyAaaU_]ecJYTjr]FqLq`UY\\XoYqhmuY`rvWH]EZO`tBHZSan`mAuhnwpXgdRJtjtH[",
|
|
7
|
-
"createdAt": "2026-01-07T12:49:43.098Z"
|
|
8
|
-
},
|
|
9
|
-
{
|
|
10
|
-
"title": "hello",
|
|
11
|
-
"content": "HgcFKFWDMl]cxiCVVrgFddJaOQCSPF^KYkROxkLTmxSc^ZssZh`ENyOXZn_DtOQe^V_vbu^KRonZ]bGcqsP]FYMmY\\YLjWknhiUksXwIUsvtpfXqViiKJ`xcmScZRXfAdHshvFlEHvPXWOfZRRIDIOxAafPoMD[ZgVlFkl\\Uw]RfubZWQ]`IHIUOKea_RTO`KiZisSA[",
|
|
12
|
-
"createdAt": "2026-01-07T12:49:43.444Z"
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
"title": "Test Title from Jest",
|
|
16
|
-
"content": "This is the content of the test article.",
|
|
17
|
-
"createdAt": "2026-01-07T13:10:50.239Z"
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
"title": "hello",
|
|
21
|
-
"content": "_\\jAuGsYByS]q\\QsnlqT\\]]TEDDalywtlmwmBZRlL[vtFdGqB\\[dlVx]A^[IV`KjhWU`DW`oorGwUlBU_IMCYoymIFi[`FpXrJfUmLZTRhoisxhXIcWqmk\\SPMV^]dvCuL_Pu^EAcMvBfadWFbUuSxFkjh\\KQyLnNWWsP`agQQkmU\\TAaOvYZAO\\FGtstSbaCJPXRQTO",
|
|
22
|
-
"createdAt": "2026-01-07T13:10:57.249Z"
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
"title": "hello",
|
|
26
|
-
"content": "HRkfwNRL_r\\DF]tyVBM[AMIjfMoBbohiqUf_T^]LwiJo[duXB`mQiRmmTkbMSJxvvHccfbXsdmKMbuYmxo[JGkwfCpqXSbhKiZID[vouGhbnLZ[wjkFFgm]fOaZcxoWduI`TNvin]\\dXoGW]YtueqSYPRLmGJswvUnyPHhfLUtf`]QNfM]hcvqqZasrQZVcXYjAnJxRP",
|
|
27
|
-
"createdAt": "2026-01-07T13:10:57.357Z"
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
"title": "hello",
|
|
31
|
-
"content": "UI\\KBodT[CgUg]L^WJ`Tuafq`rS]n\\T_eMqmBQqJVQisfABj^msS]u\\OEoJ]D^CtwE`UY[UbOICHcYPjNqNQ[LlwlJGPOoppSrkaEmDh_tm]CrrPGbBSPxgQQIZ^efcmITQQaAgT_vx[GUepg]LDagmTmihHbOIqHAa_NmRsX_EjxeE]EKhnHwFm]vFUpJTPFiDNU^BE",
|
|
32
|
-
"createdAt": "2026-01-07T13:17:47.559Z"
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
"title": "hello",
|
|
36
|
-
"content": "aMDnSSZDIZjbfrMlOcjMB\\CPup[GfoqHkLWAi]V_Ctkt[ekYRqIaufps[]TOMcw\\ojbVpPNv`lTTH\\[jbOcJXU[_tBSCsZW\\akiWuRf^mZGGrFxAq^oAdZjHwmJBjt^_GUXnHdXfjSrifiqXdAAdo]EM`aBFpJvyVLnLOfqrqoPrAIhRwoPHRy^lRCLyaS^VXChowHCn",
|
|
37
|
-
"createdAt": "2026-01-07T13:17:47.681Z"
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
"title": "Test Title from Jest",
|
|
41
|
-
"content": "This is the content of the test article.",
|
|
42
|
-
"createdAt": "2026-01-07T13:18:50.764Z"
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
"title": "hello",
|
|
46
|
-
"content": "[vL^GSDHJjBq^qdTMyyMm`SGhZhvgXsyTp[kYNkiBDavKevpIkSlWQr`bJaWyH]Qa`LQMKisdHeRvTKm`dviPLkLcJCtWbGPaDnNhJtEcEQM^MmP[RYERk\\uYeXruvPmJMjdvsOxA`btQtTmtBlOvrHLANJhcYRTAXmwOjLhp_SgkhWsB_pcIUQbd^X`RIWAdqUBPVmk",
|
|
47
|
-
"createdAt": "2026-01-07T13:18:52.387Z"
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
"title": "hello",
|
|
51
|
-
"content": "jRaJYEUFnMoEcjrWFREUuIQEoCtLoEuJSxJapEGpdPvq[GyrOqdSLY`VDlqSMTh\\BgybfwxfQQ_QqKkh\\YcEHYpA^W[ttGyGjA[TdZHn]]yoPWnFyFHJfAUkxkSM[RkQVqxAg_AGoJahaoOsNnoSGx^uM\\smWlWVDGvRcWVW\\rI_[lKnZEifvdPwkCcBRLXKGw[aNedN",
|
|
52
|
-
"createdAt": "2026-01-07T13:18:52.971Z"
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
"title": "Test Title from Jest",
|
|
56
|
-
"content": "This is the content of the test article.",
|
|
57
|
-
"createdAt": "2026-01-07T13:22:54.227Z"
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
"title": "Test Title from Jest",
|
|
61
|
-
"content": "This is the content of the test article.",
|
|
62
|
-
"createdAt": "2026-01-07T13:23:08.140Z"
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
"title": "hello",
|
|
66
|
-
"content": "M_RFGy[auVjFYypv^FdPqesIlcBkg]uoeIwMNLM]TOcDWKw[CFpsPpQWxSAXAupUdVsaiGJgLgIeuJEwBMXk`VG\\OoXIxCERYs`hqGRYGTnP]ZIrOSoJk[MFAxfVjJNMyZycryoRWr\\bhCXlurxgKPFSsTYbhlqZmrqaiJyvXpFsbiLQx^LBcZi`VqZSbjc`TNF_\\Eex",
|
|
67
|
-
"createdAt": "2026-01-07T13:26:02.437Z"
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
"title": "hello",
|
|
71
|
-
"content": "\\uRyVjar]vDZKHqRqJbEJpZbdKCWmQOGToNFQAqWdJQxSwmKTAGXywMDF`D\\nBA_eJxmFV^XtTuSGNJZTFjYvYibQIlgZdV[YXZpujmuWBgKNNBjrGZLVC[QlRZOwBdY^QPom\\b^WIIkeawsGBaafELGmfmTaIikRhCnCV_SYKEwogtqdfhkMkJslllumPyeGKqe\\cca",
|
|
72
|
-
"createdAt": "2026-01-07T13:26:02.816Z"
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
"title": "Test Title from Jest",
|
|
76
|
-
"content": "This is the content of the test article.",
|
|
77
|
-
"createdAt": "2026-01-07T13:26:07.866Z"
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
"title": "hello",
|
|
81
|
-
"content": "UecpUr`PFkpfKJ^V^q[T_lTr\\uP\\WFwcFtZgrdoao[japNpDycoAgWyELuIZtQroKxXSYdI]LrDTQSGqegWjgArWk[[eYXpy_OchWDywkus`GLcdwwr[qHoLINIdUoB^C]JcLfacOEJtMZOeWjRNbGcv\\XCfSAXUKIVqkPwJbaRTjlpGBxCGXxnchUhRoePYoLE]fexb",
|
|
82
|
-
"createdAt": "2026-01-07T13:45:46.304Z"
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
"title": "hello",
|
|
86
|
-
"content": "ef[SK^DuSJrKOlieeJHPLLoGgS[ZO\\gk_dWggGZchFewEnwspxJlhCWQ\\aTEKlDj\\a\\xRj\\uJANSkNLwrnPoOxJ[h]CrLppVtoeCEWPXRpCcoNFU`YA\\RPK\\dk\\Jp^fbdmC`Hpuf[ZYExseoPRbAYAaIolQ_mkJKRfrKsthmsBtCZdeQiBhY\\XAodKo_rOeQvtfNtDQB",
|
|
87
|
-
"createdAt": "2026-01-07T13:45:46.469Z"
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
"title": "Test Title from Jest",
|
|
91
|
-
"content": "This is the content of the test article.",
|
|
92
|
-
"createdAt": "2026-01-07T13:45:47.311Z"
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
"title": "Test Title from Jest",
|
|
96
|
-
"content": "This is the content of the test article.",
|
|
97
|
-
"createdAt": "2026-01-07T18:19:21.946Z"
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
"title": "Test Title from Jest",
|
|
101
|
-
"content": "This is the content of the test article.",
|
|
102
|
-
"createdAt": "2026-01-07T18:21:43.833Z"
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
"title": "Test Title from Jest",
|
|
106
|
-
"content": "This is the content of the test article.",
|
|
107
|
-
"createdAt": "2026-01-07T18:24:28.968Z"
|
|
108
|
-
}
|
|
109
|
-
]
|
|
110
|
-
}
|
package/bloginfo.json
DELETED
|
Binary file
|
|
Binary file
|