@lexho111/plainblog 0.8.6 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Blog.js +25 -1
- package/Formatter.js +33 -7
- package/dist/model/PostgresAdapter.js +230 -244
- package/dist/model/datastructures/BinarySearchTreeWithInvertedIndex.js +138 -123
- package/index.js +0 -2
- package/model/datastructures/BinarySearchTreeWithInvertedIndex.js +124 -107
- package/package.json +1 -2
- package/public/scripts.min.js +75 -9
- package/public/styles.min.css +7 -7
- package/server.js +178 -62
- package/src/fetchData.js +74 -8
- package/src/styles.css +63 -16
- package/src1/model/SqliteAdapter.ts +2 -1
- package/src1/model/datastructures/BinarySearchTreeWithInvertedIndex.ts +122 -118
- package/styles.hash +1 -1
- package/templates/article.html +1 -1
- package/templates/login.html +18 -0
- package/templates/moreButton.html +1 -1
- package/model/SequelizeAdapter.js +0 -189
package/Blog.js
CHANGED
|
@@ -273,7 +273,7 @@ export default class Blog {
|
|
|
273
273
|
this.#articles.setDatabase(this.#databaseModel);
|
|
274
274
|
const [dbTitle, dbArticles] = await Promise.all([
|
|
275
275
|
this.#databaseModel.getBlogTitle(),
|
|
276
|
-
this.#databaseModel.findAll(-1),
|
|
276
|
+
this.#databaseModel.findAll(-1), // dbArticles should be ordered by date (BST)
|
|
277
277
|
]);
|
|
278
278
|
debug("dbArticles.size(): %d", dbArticles.length);
|
|
279
279
|
//log(filename, "dbArticles.size(): " + dbArticles.length);
|
|
@@ -405,6 +405,7 @@ export default class Blog {
|
|
|
405
405
|
}
|
|
406
406
|
this.#title = data.title;
|
|
407
407
|
// Assuming data contains a title and an array of articles with title and content
|
|
408
|
+
// data.articles should be ordered by date (BST)
|
|
408
409
|
if (this.#articles && data.articles && Array.isArray(data.articles)) {
|
|
409
410
|
if (this.#articles.getStorageName().includes("BinarySearchTree")) {
|
|
410
411
|
debug("using special insert method for BST");
|
|
@@ -466,8 +467,30 @@ export default class Blog {
|
|
|
466
467
|
/** render this blog content to valid html */
|
|
467
468
|
async toHTML(loggedin, params = {}) {
|
|
468
469
|
let articles = [];
|
|
470
|
+
let singleArticle = false;
|
|
469
471
|
if (params.q) {
|
|
470
472
|
articles = this.#articles.search(params.q);
|
|
473
|
+
} else if (params.id) {
|
|
474
|
+
const id = parseInt(params.id);
|
|
475
|
+
let article = this.#articles.get(id);
|
|
476
|
+
if (!article && this.#databaseModel) {
|
|
477
|
+
const dbResults = await this.#databaseModel.findAll(1, 0, id, id);
|
|
478
|
+
if (dbResults.length > 0) {
|
|
479
|
+
const data = dbResults[0];
|
|
480
|
+
article = new Article(
|
|
481
|
+
data.id,
|
|
482
|
+
data.title,
|
|
483
|
+
data.content,
|
|
484
|
+
data.createdAt,
|
|
485
|
+
data.image,
|
|
486
|
+
);
|
|
487
|
+
this.#articles.insert(article);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
if (article) {
|
|
491
|
+
articles = [article];
|
|
492
|
+
singleArticle = true;
|
|
493
|
+
}
|
|
471
494
|
} else if (params.date) {
|
|
472
495
|
const d = new Date(params.date);
|
|
473
496
|
if (!isNaN(d.getTime())) {
|
|
@@ -491,6 +514,7 @@ export default class Blog {
|
|
|
491
514
|
articles: articles,
|
|
492
515
|
latestArticleDate: latestArticle ? latestArticle.createdAt : null,
|
|
493
516
|
loggedin,
|
|
517
|
+
singleArticle,
|
|
494
518
|
login: "",
|
|
495
519
|
};
|
|
496
520
|
|
package/Formatter.js
CHANGED
|
@@ -16,6 +16,7 @@ const articleTemplate = loadTemplate("article.html");
|
|
|
16
16
|
const articleButtonsTemplate = loadTemplate("articleButtons.html");
|
|
17
17
|
const moreButtonTemplate = loadTemplate("moreButton.html");
|
|
18
18
|
const timelineTemplate = loadTemplate("timelineSelector.html");
|
|
19
|
+
const loginTemplate = loadTemplate("login.html");
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* generates the header of the generated html file
|
|
@@ -30,7 +31,7 @@ function createNew() {
|
|
|
30
31
|
return formTemplate;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
function formatArticle(article, loggedin) {
|
|
34
|
+
function formatArticle(article, loggedin, showFull = false) {
|
|
34
35
|
if (article.content == null) return "";
|
|
35
36
|
|
|
36
37
|
let buttons = "";
|
|
@@ -38,22 +39,33 @@ function formatArticle(article, loggedin) {
|
|
|
38
39
|
buttons = articleButtonsTemplate.replaceAll("${id}", article.id);
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
const moreButton =
|
|
42
|
+
const moreButton =
|
|
43
|
+
!showFull && article.content.length > 400
|
|
44
|
+
? moreButtonTemplate.replaceAll("${id}", article.id)
|
|
45
|
+
: "";
|
|
42
46
|
|
|
43
47
|
/*const imageHtml = article.image
|
|
44
48
|
? `<img src="${article.image}" alt="${article.title}" style="max-width: 100%;">`
|
|
45
49
|
: "";*/
|
|
46
50
|
|
|
47
51
|
let html = articleTemplate;
|
|
48
|
-
html = html.
|
|
52
|
+
html = html.replaceAll("${id}", article.id);
|
|
49
53
|
html = html.replace("${createdAt}", article.createdAt);
|
|
50
54
|
html = html.replace("${buttons}", buttons);
|
|
51
55
|
html = html.replace("${title}", article.title);
|
|
52
56
|
html = html.replace("${formattedDate}", article.getFormattedDate());
|
|
53
57
|
html = html.replace("${image}", article.image);
|
|
54
|
-
html = html.replace(
|
|
58
|
+
html = html.replace(
|
|
59
|
+
"${contentShort}",
|
|
60
|
+
showFull ? "" : article.getContentShort(),
|
|
61
|
+
);
|
|
55
62
|
html = html.replace("${content}", article.content);
|
|
56
63
|
html = html.replace("${moreButton}", moreButton);
|
|
64
|
+
|
|
65
|
+
if (showFull) {
|
|
66
|
+
html = html.replace('style="display: none;"', "");
|
|
67
|
+
}
|
|
68
|
+
|
|
57
69
|
return html;
|
|
58
70
|
}
|
|
59
71
|
|
|
@@ -97,18 +109,32 @@ function formatTimeline(latestArticleDate) {
|
|
|
97
109
|
export function formatHTML(data) {
|
|
98
110
|
const form = data.loggedin ? createNew() : "";
|
|
99
111
|
const articles = data.articles
|
|
100
|
-
.map((article) => formatArticle(article, data.loggedin))
|
|
112
|
+
.map((article) => formatArticle(article, data.loggedin, data.singleArticle))
|
|
101
113
|
.join("");
|
|
102
|
-
const timeline =
|
|
114
|
+
const timeline = data.singleArticle
|
|
115
|
+
? '<a href="/" class="btn backtohome">Back to Home</a>'
|
|
116
|
+
: formatTimeline(data.latestArticleDate);
|
|
103
117
|
let html = layoutTemplate;
|
|
104
118
|
html = html.replace("${login}", data.login);
|
|
105
|
-
html = html.replace(
|
|
119
|
+
html = html.replace(
|
|
120
|
+
"${title}",
|
|
121
|
+
`<a href="/" style="text-decoration: none; color: inherit;">${data.title}</a>`,
|
|
122
|
+
);
|
|
106
123
|
html = html.replace("${form}", form);
|
|
107
124
|
html = html.replace("${timeline}", timeline);
|
|
108
125
|
html = html.replace("${articles}", articles);
|
|
109
126
|
return header(data.title) + "\n" + html;
|
|
110
127
|
}
|
|
111
128
|
|
|
129
|
+
export function formatLogin(title) {
|
|
130
|
+
let html = loginTemplate;
|
|
131
|
+
html = html.replace(
|
|
132
|
+
"${title}",
|
|
133
|
+
`<a href="/" style="text-decoration: none; color: inherit;">${title}</a>`,
|
|
134
|
+
);
|
|
135
|
+
return header(title) + "\n" + html;
|
|
136
|
+
}
|
|
137
|
+
|
|
112
138
|
/**
|
|
113
139
|
* format content like articles to markdown
|
|
114
140
|
* @param {*} data blog data like blogtitle and articles
|
|
@@ -1,262 +1,248 @@
|
|
|
1
|
-
import { createDebug } from "../../debug-loader.js";
|
|
2
|
-
|
|
1
|
+
//import { createDebug } from "../../debug-loader.js";
|
|
2
|
+
const createDebug = (namespace) => {
|
|
3
|
+
return (...args) => { };
|
|
4
|
+
};
|
|
3
5
|
const debug = createDebug("plainblog:PostgresAdapter");
|
|
4
6
|
export default class PostgresAdapter {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
this.ready = false;
|
|
26
|
-
this.pool = null;
|
|
27
|
-
}
|
|
28
|
-
async initialize() {
|
|
29
|
-
let pg;
|
|
30
|
-
try {
|
|
31
|
-
// @ts-ignore
|
|
32
|
-
pg = await import("pg");
|
|
33
|
-
} catch (error) {
|
|
34
|
-
if (
|
|
35
|
-
error.code === "ERR_MODULE_NOT_FOUND" ||
|
|
36
|
-
error.code === "MODULE_NOT_FOUND"
|
|
37
|
-
) {
|
|
38
|
-
console.error(
|
|
39
|
-
"The 'pg' package is not installed. Please install it by running: npm install pg",
|
|
40
|
-
);
|
|
41
|
-
process.exit(1);
|
|
42
|
-
} else {
|
|
43
|
-
throw error;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
const { Pool, Client } = pg.default || pg;
|
|
47
|
-
try {
|
|
48
|
-
const client = new Client({
|
|
49
|
-
user: this.username,
|
|
50
|
-
host: this.host,
|
|
51
|
-
database: "postgres",
|
|
52
|
-
password: this.password,
|
|
53
|
-
port: this.dbport,
|
|
54
|
-
});
|
|
55
|
-
await client.connect();
|
|
56
|
-
const res = await client.query(
|
|
57
|
-
"SELECT 1 FROM pg_database WHERE datname = $1",
|
|
58
|
-
[this.dbname],
|
|
59
|
-
);
|
|
60
|
-
if (res.rowCount === 0) {
|
|
61
|
-
await client.query(`CREATE DATABASE "${this.dbname}"`);
|
|
62
|
-
}
|
|
63
|
-
await client.end();
|
|
64
|
-
} catch (e) {
|
|
65
|
-
console.error("Failed to check/create database:", e);
|
|
7
|
+
dbtype = "postgres";
|
|
8
|
+
username;
|
|
9
|
+
password;
|
|
10
|
+
host;
|
|
11
|
+
dbname;
|
|
12
|
+
dbport;
|
|
13
|
+
pool;
|
|
14
|
+
ready;
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
debug(JSON.stringify(options));
|
|
17
|
+
this.username = options.username;
|
|
18
|
+
this.password = options.password;
|
|
19
|
+
this.host = options.host;
|
|
20
|
+
this.dbname = options.dbname || "blog";
|
|
21
|
+
this.dbport = options.dbport || 5432;
|
|
22
|
+
if (!this.username || !this.password || !this.host) {
|
|
23
|
+
throw new Error("PostgreSQL credentials not set. Please provide 'username', 'password', and 'host' in the options.");
|
|
24
|
+
}
|
|
25
|
+
this.ready = false;
|
|
26
|
+
this.pool = null;
|
|
66
27
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
28
|
+
async initialize() {
|
|
29
|
+
let pg;
|
|
30
|
+
try {
|
|
31
|
+
// @ts-ignore
|
|
32
|
+
pg = await import("pg");
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (error.code === "ERR_MODULE_NOT_FOUND" ||
|
|
36
|
+
error.code === "MODULE_NOT_FOUND") {
|
|
37
|
+
console.error("The 'pg' package is not installed. Please install it by running: npm install pg");
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const { Pool, Client } = pg.default || pg;
|
|
45
|
+
try {
|
|
46
|
+
const client = new Client({
|
|
47
|
+
user: this.username,
|
|
48
|
+
host: this.host,
|
|
49
|
+
database: "postgres",
|
|
50
|
+
password: this.password,
|
|
51
|
+
port: this.dbport,
|
|
52
|
+
});
|
|
53
|
+
await client.connect();
|
|
54
|
+
const res = await client.query("SELECT 1 FROM pg_database WHERE datname = $1", [this.dbname]);
|
|
55
|
+
if (res.rowCount === 0) {
|
|
56
|
+
await client.query(`CREATE DATABASE "${this.dbname}"`);
|
|
57
|
+
}
|
|
58
|
+
await client.end();
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
console.error("Failed to check/create database:", e);
|
|
62
|
+
}
|
|
63
|
+
this.pool = new Pool({
|
|
64
|
+
user: this.username,
|
|
65
|
+
host: this.host,
|
|
66
|
+
database: this.dbname,
|
|
67
|
+
password: this.password,
|
|
68
|
+
port: this.dbport,
|
|
69
|
+
});
|
|
70
|
+
try {
|
|
71
|
+
const client = await this.pool.connect();
|
|
72
|
+
try {
|
|
73
|
+
await client.query(`
|
|
74
|
+
CREATE TABLE IF NOT EXISTS "Articles" (
|
|
75
|
+
id BIGSERIAL PRIMARY KEY,
|
|
76
|
+
title VARCHAR(255),
|
|
77
|
+
content TEXT,
|
|
78
|
+
image TEXT,
|
|
79
|
+
"createdAt" TIMESTAMP,
|
|
80
|
+
"updatedAt" TIMESTAMP
|
|
81
|
+
)
|
|
86
82
|
`);
|
|
87
|
-
|
|
88
|
-
CREATE TABLE IF NOT EXISTS "BlogInfos" (
|
|
89
|
-
id SERIAL PRIMARY KEY,
|
|
90
|
-
title VARCHAR(255)
|
|
91
|
-
)
|
|
83
|
+
await client.query(`
|
|
84
|
+
CREATE TABLE IF NOT EXISTS "BlogInfos" (
|
|
85
|
+
id SERIAL PRIMARY KEY,
|
|
86
|
+
title VARCHAR(255)
|
|
87
|
+
)
|
|
92
88
|
`);
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
89
|
+
const res = await client.query('SELECT count(*) as count FROM "BlogInfos"');
|
|
90
|
+
if (parseInt(res.rows[0].count) === 0) {
|
|
91
|
+
await client.query('INSERT INTO "BlogInfos" (title) VALUES ($1)', ["My Default Blog Title"]);
|
|
92
|
+
}
|
|
93
|
+
this.ready = true;
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
client.release();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
console.error("Failed to initialize PostgresAdapter", err);
|
|
101
|
+
throw err;
|
|
100
102
|
}
|
|
101
|
-
this.ready = true;
|
|
102
|
-
} finally {
|
|
103
|
-
client.release();
|
|
104
|
-
}
|
|
105
|
-
} catch (err) {
|
|
106
|
-
console.error("Failed to initialize PostgresAdapter", err);
|
|
107
|
-
throw err;
|
|
108
103
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
104
|
+
async terminate() {
|
|
105
|
+
if (this.pool) {
|
|
106
|
+
await this.pool.end();
|
|
107
|
+
this.pool = null;
|
|
108
|
+
this.ready = false;
|
|
109
|
+
}
|
|
115
110
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
return this.dbtype;
|
|
119
|
-
}
|
|
120
|
-
getDBName() {
|
|
121
|
-
return this.dbname;
|
|
122
|
-
}
|
|
123
|
-
isReady() {
|
|
124
|
-
return this.ready;
|
|
125
|
-
}
|
|
126
|
-
async getBlogTitle() {
|
|
127
|
-
if (!this.pool) await this.initialize();
|
|
128
|
-
const res = await this.pool.query('SELECT title FROM "BlogInfos" LIMIT 1');
|
|
129
|
-
return res.rows.length > 0 ? res.rows[0].title : "Blog";
|
|
130
|
-
}
|
|
131
|
-
async updateBlogTitle(newTitle) {
|
|
132
|
-
if (!this.pool) await this.initialize();
|
|
133
|
-
await this.pool.query('UPDATE "BlogInfos" SET title = $1', [newTitle]);
|
|
134
|
-
}
|
|
135
|
-
async save(newArticle) {
|
|
136
|
-
if (!this.pool) await this.initialize();
|
|
137
|
-
const createdAt = newArticle.createdAt
|
|
138
|
-
? new Date(newArticle.createdAt)
|
|
139
|
-
: new Date();
|
|
140
|
-
const updatedAt = new Date();
|
|
141
|
-
const cols = ["title", "content", "image", '"createdAt"', '"updatedAt"'];
|
|
142
|
-
const vals = [
|
|
143
|
-
newArticle.title,
|
|
144
|
-
newArticle.content,
|
|
145
|
-
newArticle.image,
|
|
146
|
-
createdAt,
|
|
147
|
-
updatedAt,
|
|
148
|
-
];
|
|
149
|
-
if (newArticle.id) {
|
|
150
|
-
cols.unshift("id");
|
|
151
|
-
vals.unshift(newArticle.id);
|
|
111
|
+
getType() {
|
|
112
|
+
return this.dbtype;
|
|
152
113
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
INSERT INTO "Articles" (${cols.join(", ")})
|
|
156
|
-
VALUES (${finalPlaceholders.join(", ")})
|
|
157
|
-
RETURNING id
|
|
158
|
-
`;
|
|
159
|
-
const res = await this.pool.query(query, vals);
|
|
160
|
-
const id = Number(res.rows[0].id);
|
|
161
|
-
return {
|
|
162
|
-
...newArticle,
|
|
163
|
-
id,
|
|
164
|
-
createdAt,
|
|
165
|
-
updatedAt,
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
async update(id, data) {
|
|
169
|
-
if (!this.pool) await this.initialize();
|
|
170
|
-
const sets = [];
|
|
171
|
-
const values = [];
|
|
172
|
-
let paramIndex = 1;
|
|
173
|
-
if (data.title !== undefined) {
|
|
174
|
-
sets.push(`title = $${paramIndex++}`);
|
|
175
|
-
values.push(data.title);
|
|
114
|
+
getDBName() {
|
|
115
|
+
return this.dbname;
|
|
176
116
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
values.push(data.content);
|
|
117
|
+
isReady() {
|
|
118
|
+
return this.ready;
|
|
180
119
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
120
|
+
async getBlogTitle() {
|
|
121
|
+
if (!this.pool)
|
|
122
|
+
await this.initialize();
|
|
123
|
+
const res = await this.pool.query('SELECT title FROM "BlogInfos" LIMIT 1');
|
|
124
|
+
return res.rows.length > 0 ? res.rows[0].title : "Blog";
|
|
184
125
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const query = `UPDATE "Articles" SET ${sets.join(", ")} WHERE id = $${paramIndex}`;
|
|
190
|
-
await this.pool.query(query, values);
|
|
126
|
+
async updateBlogTitle(newTitle) {
|
|
127
|
+
if (!this.pool)
|
|
128
|
+
await this.initialize();
|
|
129
|
+
await this.pool.query('UPDATE "BlogInfos" SET title = $1', [newTitle]);
|
|
191
130
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
conditions.push(
|
|
226
|
-
order === "DESC"
|
|
227
|
-
? `"createdAt" <= $${paramIndex++}`
|
|
228
|
-
: `"createdAt" >= $${paramIndex++}`,
|
|
229
|
-
);
|
|
230
|
-
values.push(startId);
|
|
231
|
-
} else {
|
|
232
|
-
conditions.push(
|
|
233
|
-
order === "DESC"
|
|
234
|
-
? `id <= $${paramIndex++}`
|
|
235
|
-
: `id >= $${paramIndex++}`,
|
|
236
|
-
);
|
|
237
|
-
values.push(startId);
|
|
238
|
-
}
|
|
131
|
+
async save(newArticle) {
|
|
132
|
+
if (!this.pool)
|
|
133
|
+
await this.initialize();
|
|
134
|
+
const createdAt = newArticle.createdAt
|
|
135
|
+
? new Date(newArticle.createdAt)
|
|
136
|
+
: new Date();
|
|
137
|
+
const updatedAt = new Date();
|
|
138
|
+
const cols = ['title', 'content', 'image', '"createdAt"', '"updatedAt"'];
|
|
139
|
+
const vals = [
|
|
140
|
+
newArticle.title,
|
|
141
|
+
newArticle.content,
|
|
142
|
+
newArticle.image,
|
|
143
|
+
createdAt,
|
|
144
|
+
updatedAt,
|
|
145
|
+
];
|
|
146
|
+
if (newArticle.id) {
|
|
147
|
+
cols.unshift("id");
|
|
148
|
+
vals.unshift(newArticle.id);
|
|
149
|
+
}
|
|
150
|
+
const finalPlaceholders = vals.map((_, i) => `$${i + 1}`);
|
|
151
|
+
const query = `
|
|
152
|
+
INSERT INTO "Articles" (${cols.join(", ")})
|
|
153
|
+
VALUES (${finalPlaceholders.join(", ")})
|
|
154
|
+
RETURNING id
|
|
155
|
+
`;
|
|
156
|
+
const res = await this.pool.query(query, vals);
|
|
157
|
+
const id = Number(res.rows[0].id);
|
|
158
|
+
return {
|
|
159
|
+
...newArticle,
|
|
160
|
+
id,
|
|
161
|
+
createdAt,
|
|
162
|
+
updatedAt,
|
|
163
|
+
};
|
|
239
164
|
}
|
|
240
|
-
|
|
241
|
-
|
|
165
|
+
async update(id, data) {
|
|
166
|
+
if (!this.pool)
|
|
167
|
+
await this.initialize();
|
|
168
|
+
const sets = [];
|
|
169
|
+
const values = [];
|
|
170
|
+
let paramIndex = 1;
|
|
171
|
+
if (data.title !== undefined) {
|
|
172
|
+
sets.push(`title = $${paramIndex++}`);
|
|
173
|
+
values.push(data.title);
|
|
174
|
+
}
|
|
175
|
+
if (data.content !== undefined) {
|
|
176
|
+
sets.push(`content = $${paramIndex++}`);
|
|
177
|
+
values.push(data.content);
|
|
178
|
+
}
|
|
179
|
+
if (data.image !== undefined) {
|
|
180
|
+
sets.push(`image = $${paramIndex++}`);
|
|
181
|
+
values.push(data.image);
|
|
182
|
+
}
|
|
183
|
+
sets.push(`"updatedAt" = $${paramIndex++}`);
|
|
184
|
+
values.push(new Date());
|
|
185
|
+
if (sets.length > 0) {
|
|
186
|
+
values.push(id);
|
|
187
|
+
const query = `UPDATE "Articles" SET ${sets.join(", ")} WHERE id = $${paramIndex}`;
|
|
188
|
+
await this.pool.query(query, values);
|
|
189
|
+
}
|
|
242
190
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
191
|
+
async remove(id) {
|
|
192
|
+
if (!this.pool)
|
|
193
|
+
await this.initialize();
|
|
194
|
+
await this.pool.query('DELETE FROM "Articles" WHERE id = $1', [id]);
|
|
247
195
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
196
|
+
async findAll(limit = 4, offset = 0, startId = null, endId = null, order = "DESC") {
|
|
197
|
+
if (!this.pool)
|
|
198
|
+
await this.initialize();
|
|
199
|
+
let query = 'SELECT * FROM "Articles"';
|
|
200
|
+
const conditions = [];
|
|
201
|
+
const values = [];
|
|
202
|
+
let paramIndex = 1;
|
|
203
|
+
const isDate = typeof startId === "string" || typeof endId === "string";
|
|
204
|
+
if (startId !== null && endId !== null) {
|
|
205
|
+
if (isDate) {
|
|
206
|
+
conditions.push(`"createdAt" BETWEEN $${paramIndex++} AND $${paramIndex++}`);
|
|
207
|
+
values.push(startId, endId);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
conditions.push(`id BETWEEN $${paramIndex++} AND $${paramIndex++}`);
|
|
211
|
+
values.push(Math.min(Number(startId), Number(endId)), Math.max(Number(startId), Number(endId)));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
else if (startId !== null) {
|
|
215
|
+
if (isDate) {
|
|
216
|
+
conditions.push(order === "DESC"
|
|
217
|
+
? `"createdAt" <= $${paramIndex++}`
|
|
218
|
+
: `"createdAt" >= $${paramIndex++}`);
|
|
219
|
+
values.push(startId);
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
conditions.push(order === "DESC" ? `id <= $${paramIndex++}` : `id >= $${paramIndex++}`);
|
|
223
|
+
values.push(startId);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (conditions.length > 0) {
|
|
227
|
+
query += " WHERE " + conditions.join(" AND ");
|
|
228
|
+
}
|
|
229
|
+
query += ` ORDER BY "createdAt" ${order}, id ${order}`;
|
|
230
|
+
if (limit !== -1) {
|
|
231
|
+
query += ` LIMIT $${paramIndex++}`;
|
|
232
|
+
values.push(limit);
|
|
233
|
+
}
|
|
234
|
+
if (offset > 0) {
|
|
235
|
+
query += ` OFFSET $${paramIndex++}`;
|
|
236
|
+
values.push(offset);
|
|
237
|
+
}
|
|
238
|
+
const res = await this.pool.query(query, values);
|
|
239
|
+
return res.rows.map((row) => ({
|
|
240
|
+
id: Number(row.id),
|
|
241
|
+
title: row.title,
|
|
242
|
+
content: row.content,
|
|
243
|
+
image: row.image,
|
|
244
|
+
createdAt: new Date(row.createdAt),
|
|
245
|
+
updatedAt: new Date(row.updatedAt),
|
|
246
|
+
}));
|
|
251
247
|
}
|
|
252
|
-
const res = await this.pool.query(query, values);
|
|
253
|
-
return res.rows.map((row) => ({
|
|
254
|
-
id: Number(row.id),
|
|
255
|
-
title: row.title,
|
|
256
|
-
content: row.content,
|
|
257
|
-
image: row.image,
|
|
258
|
-
createdAt: new Date(row.createdAt),
|
|
259
|
-
updatedAt: new Date(row.updatedAt),
|
|
260
|
-
}));
|
|
261
|
-
}
|
|
262
248
|
}
|