@lexho111/plainblog 0.0.9 → 0.0.10
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 +118 -134
- package/blog.db +0 -0
- package/blog.json +9 -0
- package/model/APIModel.js +30 -0
- package/{model.js → model/DatabaseModel.js} +2 -2
- package/model/fileModel.js +25 -0
- package/package.json +1 -1
- package/test/model.test.js +141 -0
- package/test/simpleServer.js +43 -0
package/Blog.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import http from "http";
|
|
2
2
|
import fetch from "node-fetch";
|
|
3
|
-
import { promises as fs } from "fs";
|
|
4
3
|
import { URLSearchParams } from "url";
|
|
5
4
|
import Article from "./Article.js";
|
|
6
|
-
import
|
|
5
|
+
import DatabaseModel from "./model/DatabaseModel.js";
|
|
6
|
+
import { fetchData, postData } from "./model/APIModel.js";
|
|
7
|
+
import { save as saveToFile, load as loadFromFile } from "./model/fileModel.js";
|
|
7
8
|
|
|
8
9
|
export default class Blog {
|
|
9
10
|
constructor() {
|
|
@@ -24,9 +25,9 @@ export default class Blog {
|
|
|
24
25
|
|
|
25
26
|
// Private fields
|
|
26
27
|
#server = null;
|
|
27
|
-
#
|
|
28
|
+
#databaseModel;
|
|
28
29
|
#isExternalAPI = false;
|
|
29
|
-
#
|
|
30
|
+
#apiUrl = "";
|
|
30
31
|
|
|
31
32
|
setTitle(title) {
|
|
32
33
|
this.title = title;
|
|
@@ -43,56 +44,20 @@ export default class Blog {
|
|
|
43
44
|
/** initializes database */
|
|
44
45
|
async init() {
|
|
45
46
|
if (this.#isExternalAPI) {
|
|
46
|
-
await this.loadFromAPI(
|
|
47
|
+
await this.loadFromAPI();
|
|
47
48
|
} else {
|
|
48
49
|
console.log(`initialize ${this.database.type} database`);
|
|
49
|
-
this.#
|
|
50
|
-
|
|
51
|
-
const
|
|
50
|
+
this.#databaseModel = new DatabaseModel(this.database);
|
|
51
|
+
await this.#databaseModel.initialize();
|
|
52
|
+
const dbTitle = await this.#databaseModel.getBlogTitle();
|
|
53
|
+
const dbArticles = await this.#databaseModel.findAll();
|
|
52
54
|
const responseData = { title: dbTitle, articles: dbArticles };
|
|
53
|
-
this.#
|
|
55
|
+
this.#applyBlogData(responseData);
|
|
54
56
|
}
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
// EXTERNAL DATA API
|
|
58
|
-
#APIUrl = "";
|
|
59
|
-
|
|
60
|
-
setAPI(APIUrl) {
|
|
61
|
-
this.#APIUrl = APIUrl;
|
|
62
|
-
this.#isExternalAPI = true;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** fetch data from an URL */
|
|
66
|
-
async #fetchDataFromApi(apiUrl) {
|
|
67
|
-
try {
|
|
68
|
-
const response = await fetch(apiUrl);
|
|
69
|
-
|
|
70
|
-
if (!response.ok) {
|
|
71
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const data = await response.json();
|
|
75
|
-
return data;
|
|
76
|
-
} catch (error) {
|
|
77
|
-
console.error("Failed to fetch data:", error);
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** post data to an URL */
|
|
83
|
-
async #postDataToApi(apiUrl, postData) {
|
|
84
|
-
const response = await fetch(apiUrl, {
|
|
85
|
-
method: "POST",
|
|
86
|
-
body: JSON.stringify(postData),
|
|
87
|
-
headers: { "Content-Type": "application/json" },
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
const data = await response.json();
|
|
91
|
-
return data;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
59
|
/** post a blog article */
|
|
95
|
-
async
|
|
60
|
+
async postArticle(req, res) {
|
|
96
61
|
let body = "";
|
|
97
62
|
req.on("data", (chunk) => {
|
|
98
63
|
body += chunk.toString();
|
|
@@ -107,7 +72,9 @@ export default class Blog {
|
|
|
107
72
|
const newArticleData = { title, content };
|
|
108
73
|
try {
|
|
109
74
|
// Save the new article to the database via the ApiServer
|
|
110
|
-
|
|
75
|
+
if (this.#databaseModel)
|
|
76
|
+
await this.#databaseModel.save(newArticleData);
|
|
77
|
+
if (this.#isExternalAPI) postData(this.#apiUrl, newArticleData);
|
|
111
78
|
// Add the article to the local list for immediate display
|
|
112
79
|
this.addArticle(new Article(title, content));
|
|
113
80
|
} catch (error) {
|
|
@@ -120,42 +87,10 @@ export default class Blog {
|
|
|
120
87
|
});
|
|
121
88
|
}
|
|
122
89
|
|
|
123
|
-
/** everything that happens in /api */
|
|
124
|
-
async #jsonAPI(req, res) {
|
|
125
|
-
// GET all blog data
|
|
126
|
-
if (req.method === "GET" && req.url === "/api") {
|
|
127
|
-
// controller
|
|
128
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
129
|
-
const dbArticles = await this.#apiServer.findAll();
|
|
130
|
-
const responseData = {
|
|
131
|
-
title: this.title, // Keep the title from the original constant
|
|
132
|
-
articles: dbArticles,
|
|
133
|
-
};
|
|
134
|
-
console.log(`responseData: ${responseData}`);
|
|
135
|
-
res.end(JSON.stringify(responseData));
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// POST a new article
|
|
139
|
-
else if (req.method === "POST" && req.url === "/api/articles") {
|
|
140
|
-
let body = "";
|
|
141
|
-
req.on("data", (chunk) => (body += chunk.toString()));
|
|
142
|
-
req.on("end", async () => {
|
|
143
|
-
const newArticle = JSON.parse(body);
|
|
144
|
-
|
|
145
|
-
// local
|
|
146
|
-
await this.#apiServer.save(newArticle); // --> to api server
|
|
147
|
-
// extern
|
|
148
|
-
|
|
149
|
-
res.writeHead(201, { "Content-Type": "application/json" });
|
|
150
|
-
res.end(JSON.stringify(newArticle));
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
90
|
/** start a http server with default port 8080 */
|
|
156
91
|
async startServer(port = 8080) {
|
|
157
|
-
if (this.#
|
|
158
|
-
await this.#
|
|
92
|
+
if (this.#databaseModel === undefined) await this.init(); // init blog if it didn't already happen
|
|
93
|
+
await this.#databaseModel.initialize();
|
|
159
94
|
|
|
160
95
|
const server = http.createServer(async (req, res) => {
|
|
161
96
|
// API routes
|
|
@@ -167,8 +102,8 @@ export default class Blog {
|
|
|
167
102
|
// Web Page Routes
|
|
168
103
|
// POST new article
|
|
169
104
|
if (req.method === "POST" && req.url === "/") {
|
|
170
|
-
await this.#
|
|
171
|
-
await this
|
|
105
|
+
await this.#databaseModel.updateBlogTitle(this.title);
|
|
106
|
+
await this.postArticle(req, res);
|
|
172
107
|
// GET artciles
|
|
173
108
|
} else if (req.method === "GET" && req.url === "/") {
|
|
174
109
|
// load articles
|
|
@@ -207,42 +142,8 @@ export default class Blog {
|
|
|
207
142
|
});
|
|
208
143
|
}
|
|
209
144
|
|
|
210
|
-
/** save blog content to file */
|
|
211
|
-
async save(filename = this.filename) {
|
|
212
|
-
if (this.#apiServer === undefined) this.init(); // init blog if it didn't already happen
|
|
213
|
-
//await this.#apiServer.initialize();
|
|
214
|
-
if (this.#isExternalAPI) await this.loadFromAPI(this.#APIUrl);
|
|
215
|
-
if (!filename) {
|
|
216
|
-
console.error("Error: Filename not provided and not set previously.");
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
this.filename = filename;
|
|
220
|
-
const data = {
|
|
221
|
-
title: this.title,
|
|
222
|
-
articles: this.articles,
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
try {
|
|
226
|
-
await fs.writeFile(filename, JSON.stringify(data, null, 2));
|
|
227
|
-
console.log(`Blog data saved to ${filename}`);
|
|
228
|
-
} catch (err) {
|
|
229
|
-
console.error("Error saving blog data:", err);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/** load blog content from file */
|
|
234
|
-
async load(filename) {
|
|
235
|
-
this.filename = filename;
|
|
236
|
-
const data = await fs.readFile(filename, "utf8");
|
|
237
|
-
const jsonData = JSON.parse(data);
|
|
238
|
-
this.title = jsonData.title;
|
|
239
|
-
this.articles = jsonData.articles.map(
|
|
240
|
-
(article) => new Article(article.title, article.content)
|
|
241
|
-
);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
145
|
/** Populates the blog's title and articles from a data object. */
|
|
245
|
-
#
|
|
146
|
+
#applyBlogData(data) {
|
|
246
147
|
this.articles = []; // Clear existing articles before loading new ones
|
|
247
148
|
this.setTitle(data.title);
|
|
248
149
|
// Assuming the API returns an array of objects with title and content
|
|
@@ -250,17 +151,72 @@ export default class Blog {
|
|
|
250
151
|
for (const articleData of data.articles) {
|
|
251
152
|
this.addArticle(new Article(articleData.title, articleData.content));
|
|
252
153
|
}
|
|
253
|
-
this.#apiDataLoaded = true; // Mark that we have successfully loaded data
|
|
254
154
|
}
|
|
255
155
|
}
|
|
256
156
|
|
|
257
|
-
async
|
|
258
|
-
|
|
157
|
+
async save(filename = this.filename) {
|
|
158
|
+
if (this.#databaseModel === undefined) this.init(); // init blog if it didn't already happen
|
|
159
|
+
//await this.#apiServer.initialize();
|
|
160
|
+
if (this.#isExternalAPI) await this.loadFromAPI();
|
|
161
|
+
const blogData = { title: this.title, articles: this.articles };
|
|
162
|
+
saveToFile(filename, blogData);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async load(filename) {
|
|
166
|
+
loadFromFile(filename, (title, articles) => {
|
|
167
|
+
this.title = title;
|
|
168
|
+
this.articles = articles.map(
|
|
169
|
+
(article) => new Article(article.title, article.content)
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async loadFromAPI() {
|
|
175
|
+
const data = await fetchData(this.#apiUrl);
|
|
259
176
|
if (data) {
|
|
260
|
-
this.#
|
|
177
|
+
this.#applyBlogData(data);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// controller
|
|
182
|
+
/** everything that happens in /api */
|
|
183
|
+
async #jsonAPI(req, res) {
|
|
184
|
+
// GET all blog data
|
|
185
|
+
if (req.method === "GET" && req.url === "/api") {
|
|
186
|
+
// controller
|
|
187
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
188
|
+
const dbArticles = await this.#databaseModel.findAll();
|
|
189
|
+
const responseData = {
|
|
190
|
+
title: this.title, // Keep the title from the original constant
|
|
191
|
+
articles: dbArticles,
|
|
192
|
+
};
|
|
193
|
+
console.log(`responseData: ${responseData}`);
|
|
194
|
+
res.end(JSON.stringify(responseData));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// POST a new article
|
|
198
|
+
else if (req.method === "POST" && req.url === "/api/articles") {
|
|
199
|
+
let body = "";
|
|
200
|
+
req.on("data", (chunk) => (body += chunk.toString()));
|
|
201
|
+
req.on("end", async () => {
|
|
202
|
+
const newArticle = JSON.parse(body);
|
|
203
|
+
|
|
204
|
+
// local
|
|
205
|
+
await this.#databaseModel.save(newArticle); // --> to api server
|
|
206
|
+
// extern
|
|
207
|
+
|
|
208
|
+
res.writeHead(201, { "Content-Type": "application/json" });
|
|
209
|
+
res.end(JSON.stringify(newArticle));
|
|
210
|
+
});
|
|
261
211
|
}
|
|
262
212
|
}
|
|
263
213
|
|
|
214
|
+
/** set external API */
|
|
215
|
+
setAPI(APIUrl) {
|
|
216
|
+
this.#apiUrl = APIUrl;
|
|
217
|
+
this.#isExternalAPI = true;
|
|
218
|
+
}
|
|
219
|
+
|
|
264
220
|
/** print markdown to the console */
|
|
265
221
|
async print() {
|
|
266
222
|
console.log(`# ${this.title}`);
|
|
@@ -270,22 +226,17 @@ export default class Blog {
|
|
|
270
226
|
}
|
|
271
227
|
}
|
|
272
228
|
|
|
273
|
-
/**
|
|
274
|
-
|
|
275
|
-
// If we have an API URL and haven't loaded data yet, load it now.
|
|
276
|
-
if (this.#APIUrl && !this.#apiDataLoaded) {
|
|
277
|
-
await this.loadFromAPI(this.#APIUrl);
|
|
278
|
-
}
|
|
279
|
-
|
|
229
|
+
/** format this blog content to html */
|
|
230
|
+
formatter(data) {
|
|
280
231
|
return `<!DOCTYPE html>
|
|
281
232
|
<html lang="de">
|
|
282
233
|
<head>
|
|
283
234
|
<meta charset="UTF-8">
|
|
284
|
-
<title>${
|
|
285
|
-
<style>${
|
|
235
|
+
<title>${data.title}</title>
|
|
236
|
+
<style>${data.style}</style>
|
|
286
237
|
</head>
|
|
287
238
|
<body>
|
|
288
|
-
<h1>${
|
|
239
|
+
<h1>${data.title}</h1>
|
|
289
240
|
<div style="width: 500px;">
|
|
290
241
|
<form action="/" method="POST">
|
|
291
242
|
<h3>Add a New Article</h3>
|
|
@@ -294,7 +245,7 @@ export default class Blog {
|
|
|
294
245
|
<button type="submit">Add Article</button>
|
|
295
246
|
</form>
|
|
296
247
|
<hr>
|
|
297
|
-
${
|
|
248
|
+
${data.articles
|
|
298
249
|
.map(
|
|
299
250
|
(article) => `
|
|
300
251
|
<article>
|
|
@@ -307,4 +258,37 @@ export default class Blog {
|
|
|
307
258
|
</body>
|
|
308
259
|
</html>`;
|
|
309
260
|
}
|
|
261
|
+
|
|
262
|
+
validate(html) {
|
|
263
|
+
let test = true; // all tests passed
|
|
264
|
+
if (!(html.includes("<html") && html.includes("</html"))) {
|
|
265
|
+
console.error("html not ok");
|
|
266
|
+
test = false;
|
|
267
|
+
}
|
|
268
|
+
if (!(html.includes("<head") && html.includes("</head"))) {
|
|
269
|
+
console.error("head not ok");
|
|
270
|
+
test = false;
|
|
271
|
+
}
|
|
272
|
+
if (!(html.includes("<body") && html.includes("</body"))) {
|
|
273
|
+
console.error("body not ok");
|
|
274
|
+
test = false;
|
|
275
|
+
}
|
|
276
|
+
return test;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
toHTMLFunc(data, formatter, validate) {
|
|
280
|
+
const html = formatter(data);
|
|
281
|
+
if (validate(html)) return html;
|
|
282
|
+
else throw new Error("Error. Invalid HTML!");
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** render this blog content to valid html */
|
|
286
|
+
toHTML() {
|
|
287
|
+
const data = {
|
|
288
|
+
title: this.title,
|
|
289
|
+
style: this.style,
|
|
290
|
+
articles: this.articles,
|
|
291
|
+
};
|
|
292
|
+
return this.toHTMLFunc(data, this.formatter, this.validate);
|
|
293
|
+
}
|
|
310
294
|
}
|
package/blog.db
CHANGED
|
Binary file
|
package/blog.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// EXTERNAL DATA API
|
|
2
|
+
|
|
3
|
+
/** fetch data from an URL */
|
|
4
|
+
export async function fetchData(apiUrl) {
|
|
5
|
+
try {
|
|
6
|
+
const response = await fetch(apiUrl);
|
|
7
|
+
|
|
8
|
+
if (!response.ok) {
|
|
9
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const data = await response.json();
|
|
13
|
+
return data;
|
|
14
|
+
} catch (error) {
|
|
15
|
+
console.error("Failed to fetch data:", error);
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** post data to an URL */
|
|
21
|
+
export async function postData(apiUrl, postData) {
|
|
22
|
+
const response = await fetch(apiUrl, {
|
|
23
|
+
method: "POST",
|
|
24
|
+
body: JSON.stringify(postData),
|
|
25
|
+
headers: { "Content-Type": "application/json" },
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const data = await response.json();
|
|
29
|
+
return data;
|
|
30
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Sequelize, DataTypes } from "sequelize";
|
|
2
2
|
|
|
3
|
-
export default class
|
|
3
|
+
export default class DatabaseModel {
|
|
4
4
|
#username;
|
|
5
5
|
#password;
|
|
6
6
|
#host;
|
|
@@ -16,7 +16,7 @@ export default class ApiServer {
|
|
|
16
16
|
if (databasetype === "sqlite") {
|
|
17
17
|
this.#sequelize = new Sequelize({
|
|
18
18
|
dialect: "sqlite",
|
|
19
|
-
storage:
|
|
19
|
+
storage: `./${this.#dbname}.db`,
|
|
20
20
|
logging: false,
|
|
21
21
|
});
|
|
22
22
|
} else if (databasetype === "postgres") {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { promises as fs } from "fs";
|
|
2
|
+
|
|
3
|
+
/** save blog content to file */
|
|
4
|
+
export async function save(filename, data) {
|
|
5
|
+
if (!filename) {
|
|
6
|
+
console.error("Error: Filename not provided and not set previously.");
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
await fs.writeFile(filename, JSON.stringify(data, null, 2));
|
|
12
|
+
console.log(`Blog data saved to ${filename}`);
|
|
13
|
+
} catch (err) {
|
|
14
|
+
console.error("Error saving blog data:", err);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** load blog content from file */
|
|
19
|
+
export async function load(filename, f) {
|
|
20
|
+
const data = await fs.readFile(filename, "utf8");
|
|
21
|
+
const jsonData = JSON.parse(data);
|
|
22
|
+
const title = jsonData.title;
|
|
23
|
+
const articles = jsonData.articles;
|
|
24
|
+
f(title, articles);
|
|
25
|
+
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import Blog from "../Blog.js";
|
|
2
|
+
import Article from "../Article.js";
|
|
3
|
+
import { fetchData, postData } from "../model/APIModel.js";
|
|
4
|
+
import { server } from "./simpleServer.js";
|
|
5
|
+
|
|
6
|
+
function generateRandomContent(length) {
|
|
7
|
+
const char = "a";
|
|
8
|
+
let str = "";
|
|
9
|
+
for (let i = 0; i < length; i++) {
|
|
10
|
+
const rnd = Math.random() * 10;
|
|
11
|
+
const char1 = String.fromCharCode(char.charCodeAt(0) + rnd);
|
|
12
|
+
str += char1;
|
|
13
|
+
}
|
|
14
|
+
return str;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe("File Model test", () => {
|
|
18
|
+
it("should load blog data from blog.json", async () => {
|
|
19
|
+
const blog = new Blog();
|
|
20
|
+
blog.setStyle(
|
|
21
|
+
"body { font-family: Arial, sans-serif; } h1 { color: #333; }"
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const content = generateRandomContent(200);
|
|
25
|
+
|
|
26
|
+
const article = new Article("hello", content);
|
|
27
|
+
blog.addArticle(article);
|
|
28
|
+
|
|
29
|
+
await blog.load("blog.json");
|
|
30
|
+
|
|
31
|
+
expect(await blog.toHTML()).toContain(content);
|
|
32
|
+
expect(blog).toBeDefined();
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("API Model test", () => {
|
|
37
|
+
beforeAll(() => {
|
|
38
|
+
const port = 8081;
|
|
39
|
+
server.listen(port, () => {
|
|
40
|
+
console.log(`Simple server running at http://localhost:${port}/`);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
afterAll(() => {
|
|
44
|
+
return new Promise((done) => {
|
|
45
|
+
server.close(done);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("should load blog data from API", async () => {
|
|
50
|
+
const blog = new Blog();
|
|
51
|
+
blog.setAPI("http://localhost:8081/blog");
|
|
52
|
+
blog.setStyle(
|
|
53
|
+
"body { font-family: Arial, sans-serif; } h1 { color: #333; }"
|
|
54
|
+
);
|
|
55
|
+
await blog.init();
|
|
56
|
+
|
|
57
|
+
const content = generateRandomContent(200);
|
|
58
|
+
|
|
59
|
+
await new Promise((resolve) => {
|
|
60
|
+
const req = {
|
|
61
|
+
on: (event, cb) => {
|
|
62
|
+
if (event === "data") cb(`title=hello&content=${content}`);
|
|
63
|
+
if (event === "end") cb();
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
const res = {
|
|
67
|
+
writeHead: () => {},
|
|
68
|
+
end: resolve,
|
|
69
|
+
};
|
|
70
|
+
blog.postArticle(req, res);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(await blog.toHTML()).toContain(content);
|
|
74
|
+
expect(blog).toBeDefined();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("should fetch data from API", async () => {
|
|
78
|
+
const API = "http://localhost:8081/blog";
|
|
79
|
+
const data = await fetchData(API);
|
|
80
|
+
expect(data).toBeDefined();
|
|
81
|
+
expect(data.title).toBe("Mock Blog Title");
|
|
82
|
+
expect(data.articles).toHaveLength(2);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should post blog data to API", async () => {
|
|
86
|
+
const API = "http://localhost:8081/blog";
|
|
87
|
+
const newArticle = { title: "New Post", content: "New Content" };
|
|
88
|
+
const data = await postData(API, newArticle);
|
|
89
|
+
expect(data).toEqual(newArticle);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe("Database Model test", () => {
|
|
94
|
+
it("should be empty", async () => {
|
|
95
|
+
const blog = new Blog();
|
|
96
|
+
blog.database.type = "sqlite";
|
|
97
|
+
blog.database.dbname = "test";
|
|
98
|
+
blog.setStyle(
|
|
99
|
+
"body { font-family: Arial, sans-serif; } h1 { color: #333; }"
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
//const article = new Article("hello", "hello world1!");
|
|
103
|
+
//blog.postArticle(article);
|
|
104
|
+
|
|
105
|
+
//await blog.load("blog.json");
|
|
106
|
+
|
|
107
|
+
expect(await blog.toHTML()).not.toContain("<article>");
|
|
108
|
+
expect(blog).toBeDefined();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("should load blog data from sqlite database", async () => {
|
|
112
|
+
const blog = new Blog();
|
|
113
|
+
blog.database.type = "sqlite";
|
|
114
|
+
blog.database.dbname = "test";
|
|
115
|
+
blog.setStyle(
|
|
116
|
+
"body { font-family: Arial, sans-serif; } h1 { color: #333; }"
|
|
117
|
+
);
|
|
118
|
+
await blog.init();
|
|
119
|
+
|
|
120
|
+
const content = generateRandomContent(200);
|
|
121
|
+
|
|
122
|
+
await new Promise((resolve) => {
|
|
123
|
+
const req = {
|
|
124
|
+
on: (event, cb) => {
|
|
125
|
+
if (event === "data") cb(`title=hello&content=${content}`);
|
|
126
|
+
if (event === "end") cb();
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
const res = {
|
|
130
|
+
writeHead: () => {},
|
|
131
|
+
end: resolve,
|
|
132
|
+
};
|
|
133
|
+
blog.postArticle(req, res);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
//await blog.load("blog.json");
|
|
137
|
+
|
|
138
|
+
expect(await blog.toHTML()).toContain(content);
|
|
139
|
+
expect(blog).toBeDefined();
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import http from "http";
|
|
2
|
+
|
|
3
|
+
const port = 8081;
|
|
4
|
+
|
|
5
|
+
export const server = http.createServer((req, res) => {
|
|
6
|
+
// Set CORS headers to allow requests from different origins if needed
|
|
7
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
8
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
9
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
10
|
+
|
|
11
|
+
if (req.method === "OPTIONS") {
|
|
12
|
+
res.writeHead(204);
|
|
13
|
+
res.end();
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (req.url === "/blog") {
|
|
18
|
+
if (req.method === "GET") {
|
|
19
|
+
const data = {
|
|
20
|
+
title: "Mock Blog Title",
|
|
21
|
+
articles: [
|
|
22
|
+
{ title: "Mock Article 1", content: "Content of mock article 1" },
|
|
23
|
+
{ title: "Mock Article 2", content: "Content of mock article 2" },
|
|
24
|
+
],
|
|
25
|
+
};
|
|
26
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
27
|
+
res.end(JSON.stringify(data));
|
|
28
|
+
} else if (req.method === "POST") {
|
|
29
|
+
let body = "";
|
|
30
|
+
req.on("data", (chunk) => (body += chunk.toString()));
|
|
31
|
+
req.on("end", () => {
|
|
32
|
+
res.writeHead(201, { "Content-Type": "application/json" });
|
|
33
|
+
res.end(body); // Echo the received data back
|
|
34
|
+
});
|
|
35
|
+
} else {
|
|
36
|
+
res.writeHead(405); // Method Not Allowed
|
|
37
|
+
res.end();
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
res.writeHead(404); // Not Found
|
|
41
|
+
res.end();
|
|
42
|
+
}
|
|
43
|
+
});
|