@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 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.articles = [];
29
- this.filename = null;
28
+ this.#articles = [];
30
29
  this.#server = null;
31
30
  this.#password = "admin";
32
- this.styles = "body { font-family: Arial; }";
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
- const version = pkg.version;
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.styles += style;
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.styles += style;
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.articles.push(article);
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
- async handleLogin(req, res) {
111
- const body = await new Promise((resolve, reject) => {
112
- let data = "";
113
- req.on("data", (chunk) => (data += chunk.toString()));
114
- req.on("end", () => resolve(data));
115
- req.on("error", reject);
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
- const params = new URLSearchParams(body);
118
-
119
- if (params.get("password") === this.#password) {
120
- const id = crypto.randomUUID();
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
- handleLogout(req, res) {
139
- if (req.headers.cookie) {
140
- const params = new URLSearchParams(req.headers.cookie.replace(/; /g, "&"));
141
- const sessionId = params.get("session");
142
- if (this.sessions.has(sessionId)) {
143
- this.sessions.delete(sessionId);
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
- await this.processStylesheets(this.#stylesheetPath);
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
- // compile and merge hardcoded styles with src/styles.css and write to file "styles.min.css"
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
- const combinedStyles = this.styles + srcStyles;
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.styles, srcStyles);
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.loadFromAPI();
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 this.#databaseModel.getBlogTitle();
213
- const dbArticles = await this.#databaseModel.findAll();
214
- //console.log(`articles: ${JSON.stringify(dbarticles)}`)
215
- console.log(`dbArticles.length: ${dbArticles.length}`)
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
- if (this.#databaseModel) await this.#databaseModel.save(newArticleData);
248
- if (this.#isExternalAPI) await postData(this.#apiUrl, newArticleData);
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.articles.unshift(new Article(title, content, new Date()));
273
+ this.#articles.unshift(new Article(title, content, new Date()));
251
274
  // remove sample entries
252
- this.articles = this.articles.filter(
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.handleLogin(req, res);
312
+ await this.#handleLogin(req, res);
290
313
  return;
291
314
  }
292
315
  }
293
316
 
294
317
  if (req.url === "/logout") {
295
- this.handleLogout(req, res);
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.isAuthenticated(req)) {
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.processStylesheets(this.#stylesheetPath);
336
+ await this.#processStylesheets(this.#stylesheetPath);
314
337
  }
315
338
  }
316
339
 
317
340
  let loggedin = false;
318
- if (!this.isAuthenticated(req)) {
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
- this.#server.listen(port, () => {
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.articles = []; // Clear existing articles before loading new ones
427
+ this.#articles = []; // Clear existing articles before loading new ones
402
428
  this.#title = data.title;
403
- // Assuming the API returns an array of objects with title and content
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.isAuthenticated(req)) {
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.articles,
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.articles,
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
- * Loads files, generates checksums and compiles sass files to css if anything changed
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lexho111/plainblog",
3
- "version": "0.5.10",
3
+ "version": "0.5.11",
4
4
  "description": "A tool for creating and serving a minimalist, single-page blog.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -1,2 +1,2 @@
1
- body{font-family:Arial;font-family:Arial,sans-serif}h1{color:#333}.grid{border:0 solid #000;display:grid;gap:.25rem;grid-template-columns:1fr}.grid article{border:0 solid #ccc;border-radius:4px;min-width:0;overflow-wrap:break-word;padding:.25rem}.grid article h2{color:#353535;margin-bottom:5px}.grid article .datetime{color:#757575;margin:0}.grid article p{margin-bottom:0;margin-top:10px}article a,article a:visited,h1{color:#696969}nav a{color:#3b40c1;font-size:20px;text-decoration:underline}nav a:visited{color:#3b40c1;text-decoration-color:#3b40c1}#wrapper{max-width:500px;width:100%}@media screen and (max-width:1000px){*{font-size:4vw}#wrapper{box-sizing:border-box;max-width:100%;padding:0 10px;width:100%}}
2
- /* source-hash: a07f631befba4b6bc703f8709f5ef455faafeff4e5f00b62f835576eea7fb529 */
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(myblog.articles.length).toBe(1);
80
+ expect(json.articles).toHaveLength(1);
40
81
  });
41
82
  test("add articles", async () => {
42
83
  const myblog = new Blog();
43
- expect(myblog.articles.length).toBe(0);
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
- expect(myblog.articles.length).toBe(i);
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
@@ -1,3 +0,0 @@
1
- {
2
- "title": "Test Blog Title"
3
- }
Binary file
Binary file