@lexho111/plainblog 0.5.28 → 0.6.1

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.
Files changed (64) hide show
  1. package/Article.js +73 -4
  2. package/Blog.js +342 -140
  3. package/Formatter.js +2 -11
  4. package/README.md +1 -1
  5. package/{blog → blog_test_empty.db} +0 -0
  6. package/blog_test_load.db +0 -0
  7. package/build-scripts.js +54 -0
  8. package/coverage/clover.xml +1051 -0
  9. package/coverage/coverage-final.json +20 -0
  10. package/coverage/lcov-report/base.css +224 -0
  11. package/coverage/lcov-report/block-navigation.js +87 -0
  12. package/coverage/lcov-report/favicon.png +0 -0
  13. package/coverage/lcov-report/index.html +161 -0
  14. package/coverage/lcov-report/package/Article.js.html +406 -0
  15. package/coverage/lcov-report/package/Blog.js.html +2674 -0
  16. package/coverage/lcov-report/package/Formatter.js.html +379 -0
  17. package/coverage/lcov-report/package/build-scripts.js.html +247 -0
  18. package/coverage/lcov-report/package/build-styles.js.html +367 -0
  19. package/coverage/lcov-report/package/index.html +191 -0
  20. package/coverage/lcov-report/package/model/APIModel.js.html +190 -0
  21. package/coverage/lcov-report/package/model/ArrayList.js.html +382 -0
  22. package/coverage/lcov-report/package/model/ArrayListHashMap.js.html +379 -0
  23. package/coverage/lcov-report/package/model/BinarySearchTree.js.html +856 -0
  24. package/coverage/lcov-report/package/model/BinarySearchTreeHashMap.js.html +346 -0
  25. package/coverage/lcov-report/package/model/DataModel.js.html +322 -0
  26. package/coverage/lcov-report/package/model/DatabaseModel.js.html +232 -0
  27. package/coverage/lcov-report/package/model/FileAdapter.js.html +394 -0
  28. package/coverage/lcov-report/package/model/FileList.js.html +244 -0
  29. package/coverage/lcov-report/package/model/FileModel.js.html +358 -0
  30. package/coverage/lcov-report/package/model/SequelizeAdapter.js.html +538 -0
  31. package/coverage/lcov-report/package/model/SqliteAdapter.js.html +247 -0
  32. package/coverage/lcov-report/package/model/datastructures/ArrayList.js.html +439 -0
  33. package/coverage/lcov-report/package/model/datastructures/ArrayListHashMap.js.html +196 -0
  34. package/coverage/lcov-report/package/model/datastructures/BinarySearchTree.js.html +913 -0
  35. package/coverage/lcov-report/package/model/datastructures/BinarySearchTreeHashMap.js.html +352 -0
  36. package/coverage/lcov-report/package/model/datastructures/FileList.js.html +244 -0
  37. package/coverage/lcov-report/package/model/datastructures/index.html +176 -0
  38. package/coverage/lcov-report/package/model/index.html +206 -0
  39. package/coverage/lcov-report/package/utilities.js.html +511 -0
  40. package/coverage/lcov-report/prettify.css +1 -0
  41. package/coverage/lcov-report/prettify.js +2 -0
  42. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  43. package/coverage/lcov-report/sorter.js +210 -0
  44. package/coverage/lcov.info +2078 -0
  45. package/index.js +25 -1
  46. package/model/DataModel.js +80 -0
  47. package/model/DatabaseModel.js +21 -8
  48. package/model/FileAdapter.js +44 -4
  49. package/model/FileModel.js +48 -9
  50. package/model/SequelizeAdapter.js +11 -3
  51. package/model/datastructures/ArrayList.js +118 -0
  52. package/model/datastructures/ArrayListHashMap.js +37 -0
  53. package/model/datastructures/ArrayListHashMap.js.bk +90 -0
  54. package/model/datastructures/BinarySearchTree.js +277 -0
  55. package/model/datastructures/BinarySearchTreeHashMap.js +90 -0
  56. package/model/datastructures/BinarySearchTreeTest.js +16 -0
  57. package/model/datastructures/FileList.js +53 -0
  58. package/package.json +9 -3
  59. package/public/fetchData.js +0 -0
  60. package/public/scripts.min.js +2 -1
  61. package/public/styles.min.css +2 -1
  62. package/src/fetchData.js +150 -30
  63. package/src/styles.css +29 -0
  64. package/utilities.js +142 -0
package/index.js CHANGED
@@ -3,6 +3,30 @@ import Article from "./Article.js";
3
3
  import FileAdapter from "./model/FileAdapter.js";
4
4
  import SqliteAdapter from "./model/SqliteAdapter.js";
5
5
  import PostgresAdapter from "./model/PostgresAdapter.js";
6
+ import DataModel from "./model/DataModel.js";
7
+ import ArrayList from "./model/datastructures/ArrayList.js";
8
+ import FileList from "./model/datastructures/FileList.js";
9
+ import { BinarySearchTree } from "./model/datastructures/BinarySearchTree.js";
10
+ import {
11
+ generateTitle,
12
+ generateContent,
13
+ generateDateList,
14
+ } from "./utilities.js";
15
+ import { appendArticle } from "./model/FileModel.js";
6
16
 
7
- export { Blog, Article, FileAdapter, SqliteAdapter, PostgresAdapter };
17
+ export {
18
+ Blog,
19
+ Article,
20
+ FileAdapter,
21
+ SqliteAdapter,
22
+ PostgresAdapter,
23
+ DataModel,
24
+ ArrayList,
25
+ FileList,
26
+ BinarySearchTree,
27
+ generateTitle,
28
+ generateContent,
29
+ generateDateList,
30
+ appendArticle,
31
+ };
8
32
  export default Blog;
@@ -0,0 +1,80 @@
1
+ //import createDebug from "debug";
2
+ import { debuglog as createDebug } from "node:util";
3
+ import Article from "../Article.js";
4
+
5
+ const debug = createDebug("plainblog:DataModel");
6
+
7
+ export default class DataModel {
8
+ constructor(storage) {
9
+ this.storage = storage;
10
+ this.db = null;
11
+ }
12
+
13
+ setDataModel(storage) {
14
+ this.storage = storage;
15
+ }
16
+
17
+ setDatabase(db) {
18
+ this.db = db;
19
+ }
20
+
21
+ getStorageName() {
22
+ // Check if storage exists and has a constructor with a name
23
+ return this.storage?.constructor?.name || "UnknownStorage";
24
+ }
25
+
26
+ insert(article) {
27
+ //console.log("DataModel: insert article");
28
+ this.storage.insert(article);
29
+ }
30
+
31
+ update(id, title, content) {
32
+ debug("update");
33
+ if (this.storage.update) {
34
+ return this.storage.update(id, title, content);
35
+ }
36
+ }
37
+
38
+ search(query) {
39
+ debug("search");
40
+ if (this.storage.search) {
41
+ return this.storage.search(query);
42
+ }
43
+ }
44
+
45
+ remove(article) {
46
+ debug("remove");
47
+ this.storage.remove(article);
48
+ }
49
+
50
+ getAllArticles() {
51
+ debug("get all articles");
52
+ const order = "newest";
53
+ return this.storage.getAllArticles(order);
54
+ }
55
+
56
+ async findAll(start, end, limit) {
57
+ debug("find all %d %d %d", start, end, limit);
58
+ if (start == null && end == null && limit == null)
59
+ return this.getAllArticles();
60
+
61
+ let articles = this.storage.getRange(start, end, limit);
62
+
63
+ if (articles.length < limit && this.db) {
64
+ const dbArticles = await this.db.findAll(limit, 0, start, end);
65
+ for (const data of dbArticles) {
66
+ if (!this.storage.contains(data.id)) {
67
+ this.storage.insert(
68
+ new Article(data.id, data.title, data.content, data.createdAt),
69
+ );
70
+ }
71
+ }
72
+ articles = this.storage.getRange(start, end, limit);
73
+ }
74
+ return articles;
75
+ }
76
+
77
+ size() {
78
+ return this.storage.size();
79
+ }
80
+ }
@@ -1,13 +1,11 @@
1
- import FileAdapter from "./FileAdapter.js";
1
+ //import createDebug from "debug";
2
+ import { debuglog as createDebug } from "node:util";
3
+
4
+ const debug = createDebug("plainblog:DatabaseModel");
2
5
 
3
6
  export default class DatabaseModel {
4
- dbtype;
5
- constructor(options = {}) {
6
- console.log(JSON.stringify(options));
7
- this.dbtype = options.type;
8
- if (options.type === "file") {
9
- this.adapter = new FileAdapter(options);
10
- }
7
+ constructor(adapter) {
8
+ this.adapter = adapter;
11
9
  }
12
10
 
13
11
  async setDatabaseAdapter(adapter) {
@@ -24,14 +22,29 @@ export default class DatabaseModel {
24
22
  }
25
23
 
26
24
  async save(newArticle) {
25
+ console.log("save article to database");
27
26
  return this.adapter.save(newArticle);
28
27
  }
29
28
 
29
+ async update(id, data) {
30
+ if (this.adapter.update) {
31
+ return this.adapter.update(id, data);
32
+ }
33
+ }
34
+
35
+ async remove(id) {
36
+ debug("remove by id");
37
+ if (this.adapter.remove) {
38
+ return this.adapter.remove(id);
39
+ }
40
+ }
41
+
30
42
  async updateBlogTitle(newTitle) {
31
43
  return this.adapter.updateBlogTitle(newTitle);
32
44
  }
33
45
 
34
46
  async findAll(limit, offset, startId, endId, order) {
47
+ debug("get all articles from database");
35
48
  return this.adapter.findAll(limit, offset, startId, endId, order);
36
49
  }
37
50
  }
@@ -4,7 +4,12 @@ import {
4
4
  appendArticle,
5
5
  loadArticles,
6
6
  initFiles,
7
+ saveArticles,
7
8
  } from "./FileModel.js";
9
+ //import createDebug from "debug";
10
+ import { debuglog as createDebug } from "node:util";
11
+
12
+ const debug = createDebug("plainblog:FileAdapter");
8
13
 
9
14
  export default class FileAdapter {
10
15
  dbtype = "file";
@@ -14,7 +19,7 @@ export default class FileAdapter {
14
19
  }
15
20
 
16
21
  async initialize() {
17
- console.log("file adapter init");
22
+ debug("init");
18
23
  await initFiles(this.infoFile, this.articlesFile);
19
24
  }
20
25
 
@@ -31,6 +36,9 @@ export default class FileAdapter {
31
36
  if (!newArticle.createdAt) {
32
37
  newArticle.createdAt = new Date().toISOString();
33
38
  }
39
+ if (!newArticle.id) {
40
+ newArticle.id = Date.now();
41
+ }
34
42
  await appendArticle(this.articlesFile, newArticle);
35
43
  }
36
44
 
@@ -38,23 +46,55 @@ export default class FileAdapter {
38
46
  await saveInfo(this.infoFile, { title: newTitle });
39
47
  }
40
48
 
49
+ async update(id, data) {
50
+ const articles = await loadArticles(this.articlesFile);
51
+ const article = articles.find((a) => a.id == id);
52
+ if (article) {
53
+ if (data.title) article.title = data.title;
54
+ if (data.content) article.content = data.content;
55
+ await saveArticles(this.articlesFile, articles);
56
+ }
57
+ }
58
+
59
+ async remove(id) {
60
+ debug("remove from '%s'", this.articlesFile);
61
+ const articles = await loadArticles(this.articlesFile);
62
+ const newArticles = articles.filter((a) => a.id != id);
63
+ // Only write if something was actually removed
64
+ if (newArticles.length < articles.length) {
65
+ await saveArticles(this.articlesFile, newArticles);
66
+ }
67
+ }
68
+
69
+ // findAll()
70
+ // findAll(limit, offset, order)
41
71
  async findAll(
42
- limit = 4,
72
+ limit = -1,
43
73
  offset = 0,
44
74
  //startId = null,
45
75
  //endId = null,
46
- order = "DESC"
76
+ order = "DESC",
47
77
  ) {
48
78
  let dbArticles = [];
49
79
  try {
50
80
  const articles = await loadArticles(this.articlesFile);
51
81
  if (Array.isArray(articles)) {
82
+ // Ensure every article has an ID (backward compatibility)
83
+ articles.forEach((article, index) => {
84
+ if (!article.id) {
85
+ const ts = article.createdAt
86
+ ? new Date(article.createdAt).getTime()
87
+ : Date.now();
88
+ article.id = (isNaN(ts) ? Date.now() : ts) + index; // Add index to ensure uniqueness
89
+ }
90
+ });
52
91
  articles.sort((a, b) => {
53
92
  const dateA = new Date(a.createdAt || 0);
54
93
  const dateB = new Date(b.createdAt || 0);
55
94
  return order === "DESC" ? dateB - dateA : dateA - dateB;
56
95
  });
57
- dbArticles = articles.slice(offset, offset + limit);
96
+ if (limit == -1) dbArticles = articles;
97
+ else dbArticles = articles.slice(offset, offset + limit);
58
98
  }
59
99
  } catch (err) {
60
100
  console.error(err);
@@ -1,14 +1,20 @@
1
- import { promises as fs } from "fs";
1
+ import { promises as promise } from "fs";
2
+ import { appendFile, appendFileSync, readFile, readFileSync } from "node:fs";
3
+ //import createDebug from "debug";
4
+ import { debuglog as createDebug } from "node:util";
5
+ import Article from "../Article.js";
6
+
7
+ const debug = createDebug("plainblog:FileModel");
2
8
 
3
9
  /** save blog info (title, etc) to a standard JSON file */
4
10
  export async function saveInfo(filename, data) {
5
- await fs.writeFile(filename, JSON.stringify(data, null, 2));
11
+ await promise.writeFile(filename, JSON.stringify(data, null, 2));
6
12
  }
7
13
 
8
14
  /** load blog info */
9
15
  export async function loadInfo(filename) {
10
16
  try {
11
- const data = await fs.readFile(filename, "utf8");
17
+ const data = await promise.readFile(filename, "utf8");
12
18
  return JSON.parse(data);
13
19
  } catch (err) {
14
20
  console.error(err);
@@ -18,17 +24,50 @@ export async function loadInfo(filename) {
18
24
 
19
25
  /** append an article as a new line to the file */
20
26
  export async function appendArticle(filename, article) {
21
- await fs.appendFile(filename, JSON.stringify(article) + "\n");
27
+ await promise.appendFile(filename, JSON.stringify(article) + "\n");
28
+ }
29
+
30
+ /** overwrite all articles in the file */
31
+ export async function saveArticles(filename, articles) {
32
+ const data = articles.map((article) => JSON.stringify(article)).join("\n");
33
+ await promise.writeFile(filename, data + "\n");
34
+ }
35
+
36
+ /** append an article as a new line to the file */
37
+ export function appendArticleSync(filename, article) {
38
+ appendFileSync(filename, JSON.stringify(article) + "\n");
22
39
  }
23
40
 
24
41
  /** load all articles by reading line by line */
25
42
  export async function loadArticles(filename) {
43
+ debug("loadArticles from file");
44
+ try {
45
+ const data = await promise.readFile(filename, "utf8");
46
+ return data
47
+ .split("\n")
48
+ .filter((line) => line.trim() !== "")
49
+ .map((line) => {
50
+ const json = JSON.parse(line);
51
+ return new Article(json.id, json.title, json.content, json.createdAt);
52
+ });
53
+ } catch (err) {
54
+ console.error(err);
55
+ return [];
56
+ }
57
+ }
58
+
59
+ /** load all articles by reading line by line */
60
+ export function loadArticlesSync(filename) {
61
+ console.log("FileModel: loadArticles from '%s'", filename);
26
62
  try {
27
- const data = await fs.readFile(filename, "utf8");
63
+ const data = readFileSync(filename, "utf8");
28
64
  return data
29
65
  .split("\n")
30
66
  .filter((line) => line.trim() !== "")
31
- .map((line) => JSON.parse(line));
67
+ .map((line) => {
68
+ const json = JSON.parse(line);
69
+ return new Article(json.id, json.title, json.content, json.createdAt);
70
+ });
32
71
  } catch (err) {
33
72
  console.error(err);
34
73
  return [];
@@ -38,16 +77,16 @@ export async function loadArticles(filename) {
38
77
  /** ensure files exist */
39
78
  export async function initFiles(infoFilename, articlesFilename) {
40
79
  try {
41
- await fs.access(infoFilename);
80
+ await promise.access(infoFilename);
42
81
  } catch (err) {
43
82
  console.error(err);
44
83
  await saveInfo(infoFilename, { title: "Blog" });
45
84
  }
46
85
 
47
86
  try {
48
- await fs.access(articlesFilename);
87
+ await promise.access(articlesFilename);
49
88
  } catch (err) {
50
89
  console.error(err);
51
- await fs.writeFile(articlesFilename, "");
90
+ await promise.writeFile(articlesFilename, "");
52
91
  }
53
92
  }
@@ -68,7 +68,7 @@ export default class SequelizeAdapter {
68
68
  },
69
69
  {
70
70
  timestamps: true,
71
- }
71
+ },
72
72
  );
73
73
 
74
74
  this.BlogInfo = this.sequelize.define(
@@ -78,7 +78,7 @@ export default class SequelizeAdapter {
78
78
  },
79
79
  {
80
80
  timestamps: false,
81
- }
81
+ },
82
82
  );
83
83
 
84
84
  // This creates the tables if they don't exist.
@@ -99,7 +99,7 @@ export default class SequelizeAdapter {
99
99
  offset = 0,
100
100
  startId = null,
101
101
  endId = null,
102
- order = "DESC"
102
+ order = "DESC",
103
103
  ) {
104
104
  await this.loadSequelize();
105
105
  const where = {};
@@ -128,6 +128,14 @@ export default class SequelizeAdapter {
128
128
  console.log("Added new article:", newArticle);
129
129
  }
130
130
 
131
+ async update(id, data) {
132
+ await this.Article.update(data, { where: { id } });
133
+ }
134
+
135
+ async remove(id) {
136
+ await this.Article.destroy({ where: { id } });
137
+ }
138
+
131
139
  async getBlogTitle() {
132
140
  // Find the first (and only) entry in the BlogInfo table.
133
141
  const blogInfo = await this.BlogInfo.findOne();
@@ -0,0 +1,118 @@
1
+ export default class ArrayList {
2
+ articles = [];
3
+ constructor() {
4
+ ////this.ids = new Map();
5
+ }
6
+
7
+ clear() {
8
+ this.articles = [];
9
+ //this.ids.clear();
10
+ }
11
+
12
+ insert(article) {
13
+ this.articles.push(article);
14
+ //this.ids.set(article.id, article);
15
+ }
16
+
17
+ update(id, title, content) {
18
+ const article = this.find(id);
19
+ article.title = title;
20
+ article.content = content;
21
+ }
22
+
23
+ search(query) {
24
+ const lower = query.toLowerCase();
25
+ return this.getAllArticles().filter(
26
+ (a) =>
27
+ a.title.toLowerCase().includes(lower) ||
28
+ a.content.toLowerCase().includes(lower),
29
+ );
30
+ }
31
+
32
+ remove(article) {
33
+ const id = article && article.id ? article.id : article;
34
+ this.articles = this.articles.filter((article) => article.id != id);
35
+ //this.ids.delete(id);
36
+ }
37
+
38
+ /*contains(id) {
39
+ return this.articles.includes(id);
40
+ }*/
41
+
42
+ contains(article) {
43
+ const id = article.id;
44
+ return this.articles.some((a) => a.id === id);
45
+ }
46
+
47
+ find(id) {
48
+ return this.articles.find((u) => u.id === id);
49
+ }
50
+
51
+ formatter(tstamp) {
52
+ const date = new Date(tstamp);
53
+ /*const formatted = date.toLocaleString("de-DE", {
54
+ year: "numeric",
55
+ month: "2-digit",
56
+ day: "2-digit",
57
+ hour: "2-digit",
58
+ minute: "2-digit",
59
+ hour12: false, // Ensures 24-hour format
60
+ });*/
61
+ const formatted = date;
62
+ return formatted;
63
+ }
64
+
65
+ async getRange(start, end, limit) {
66
+ let result = [];
67
+
68
+ const startTime = start
69
+ ? new Date(
70
+ start instanceof Date
71
+ ? start
72
+ : isNaN(start)
73
+ ? start
74
+ : parseInt(start),
75
+ ).getTime()
76
+ : 0;
77
+ const endTime = end
78
+ ? new Date(
79
+ end instanceof Date ? end : isNaN(end) ? end : parseInt(end),
80
+ ).getTime()
81
+ : Infinity;
82
+
83
+ for (const article of this.articles) {
84
+ // Optimization: article.createdAt is already a timestamp. No need for new Date().
85
+ const articleTime = article.createdAt;
86
+ if (articleTime >= startTime && articleTime <= endTime) {
87
+ result.push(article);
88
+ }
89
+ }
90
+
91
+ // Sort: Newest first (Descending)
92
+ result.sort((a, b) => b.createdAt - a.createdAt);
93
+ // Sort: Newest first (Descending), then by ID Descending for stability
94
+ /*result.sort((a, b) => {
95
+ const timeDiff = b.createdAt - a.createdAt;
96
+ if (timeDiff !== 0) return timeDiff;
97
+ return b.id - a.id;
98
+ });*/
99
+
100
+ if (limit && result.length > limit) {
101
+ result = result.slice(0, limit);
102
+ }
103
+
104
+ return result;
105
+ }
106
+
107
+ getAllArticles() {
108
+ return this.articles;
109
+ }
110
+
111
+ size() {
112
+ return this.articles.length;
113
+ }
114
+
115
+ get length() {
116
+ return this.size();
117
+ }
118
+ }
@@ -0,0 +1,37 @@
1
+ import ArrayList from "./ArrayList.js";
2
+
3
+ export default class ArrayListHashMap extends ArrayList {
4
+ constructor() {
5
+ super();
6
+ this.ids = new Map();
7
+ }
8
+
9
+ clear() {
10
+ super.clear();
11
+ this.ids.clear();
12
+ }
13
+
14
+ insert(article) {
15
+ super.insert(article);
16
+ this.ids.set(article.id, article);
17
+ }
18
+
19
+ remove(article) {
20
+ const id = article && article.id ? article.id : article;
21
+ super.remove(article);
22
+ this.ids.delete(id);
23
+ }
24
+
25
+ /*contains(id) {
26
+ return this.articles.includes(id);
27
+ }*/
28
+
29
+ contains(article) {
30
+ const id = article.id;
31
+ return this.ids.has(id);
32
+ }
33
+
34
+ find(id) {
35
+ return this.ids.get(id);
36
+ }
37
+ }
@@ -0,0 +1,90 @@
1
+ export default class ArrayListHashMap {
2
+ articles = [];
3
+ constructor() {
4
+ this.ids = new Map();
5
+ }
6
+
7
+ clear() {
8
+ this.articles = [];
9
+ this.ids.clear();
10
+ }
11
+
12
+ insert(article) {
13
+ this.articles.push(article);
14
+ this.ids.set(article.id, article);
15
+ }
16
+
17
+ remove(article) {
18
+ const id = article && article.id ? article.id : article;
19
+ this.articles = this.articles.filter((article) => article.id != id);
20
+ this.ids.delete(id);
21
+ }
22
+
23
+ /*contains(id) {
24
+ return this.articles.includes(id);
25
+ }*/
26
+
27
+ contains(article) {
28
+ const id = article.id;
29
+ return this.ids.has(id);
30
+ }
31
+
32
+ find(id) {
33
+ return this.ids.get(id);
34
+ }
35
+
36
+ formatter(tstamp) {
37
+ const date = new Date(tstamp);
38
+ /*const formatted = date.toLocaleString("de-DE", {
39
+ year: "numeric",
40
+ month: "2-digit",
41
+ day: "2-digit",
42
+ hour: "2-digit",
43
+ minute: "2-digit",
44
+ hour12: false, // Ensures 24-hour format
45
+ });*/
46
+ const formatted = date;
47
+ return formatted;
48
+ }
49
+
50
+ async getRange(start, end, limit) {
51
+ let result = [];
52
+
53
+ const startTime = start ? new Date(start).getTime() : 0;
54
+ const endTime = end ? new Date(end).getTime() : Infinity;
55
+
56
+ for (const article of this.articles) {
57
+ const articleTime = new Date(article.createdAt).getTime();
58
+ if (articleTime >= startTime && articleTime <= endTime) {
59
+ result.push(article);
60
+ }
61
+ }
62
+
63
+ // Sort: Newest first (Descending)
64
+ result.sort((a, b) => b.createdAt - a.createdAt);
65
+ // Sort: Newest first (Descending), then by ID Descending for stability
66
+ /*result.sort((a, b) => {
67
+ const timeDiff = b.createdAt - a.createdAt;
68
+ if (timeDiff !== 0) return timeDiff;
69
+ return b.id - a.id;
70
+ });*/
71
+
72
+ if (limit && result.length > limit) {
73
+ result = result.slice(0, limit);
74
+ }
75
+
76
+ return result;
77
+ }
78
+
79
+ getAllArticles() {
80
+ return this.articles;
81
+ }
82
+
83
+ size() {
84
+ return this.articles.length;
85
+ }
86
+
87
+ get length() {
88
+ return this.size();
89
+ }
90
+ }