@rmdes/indiekit-endpoint-rss 1.0.14 → 1.0.16
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/README.md +2 -1
- package/index.js +10 -1
- package/lib/sync.js +45 -17
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -36,7 +36,8 @@ export default {
|
|
|
36
36
|
maxItemsPerFeed: 50, // Max items per feed to cache
|
|
37
37
|
fetchTimeout: 10_000, // 10 second timeout per feed
|
|
38
38
|
maxConcurrentFetches: 3, // Parallel feed fetches
|
|
39
|
-
retentionDays: 30
|
|
39
|
+
retentionDays: 30, // Days to keep items
|
|
40
|
+
minItemsPerFeed: 10 // Newest items always kept, whatever their age
|
|
40
41
|
})
|
|
41
42
|
],
|
|
42
43
|
// MongoDB database is REQUIRED
|
package/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { feedsController } from "./lib/controllers/feeds.js";
|
|
|
7
7
|
import { itemsController } from "./lib/controllers/items.js";
|
|
8
8
|
import { statusController } from "./lib/controllers/status.js";
|
|
9
9
|
import { startSync } from "./lib/sync.js";
|
|
10
|
+
import { waitForReady } from "@rmdes/indiekit-startup-gate";
|
|
10
11
|
|
|
11
12
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
13
|
|
|
@@ -20,6 +21,7 @@ const defaults = {
|
|
|
20
21
|
fetchTimeout: 10_000,
|
|
21
22
|
maxConcurrentFetches: 3,
|
|
22
23
|
retentionDays: 30,
|
|
24
|
+
minItemsPerFeed: 10,
|
|
23
25
|
};
|
|
24
26
|
|
|
25
27
|
export default class RssEndpoint {
|
|
@@ -110,7 +112,14 @@ export default class RssEndpoint {
|
|
|
110
112
|
|
|
111
113
|
// Start background sync if database is available
|
|
112
114
|
if (Indiekit.config.application.mongodbUrl) {
|
|
113
|
-
|
|
115
|
+
this._stopGate = waitForReady(
|
|
116
|
+
() => startSync(Indiekit, this.options),
|
|
117
|
+
{ label: "RSS" },
|
|
118
|
+
);
|
|
114
119
|
}
|
|
115
120
|
}
|
|
121
|
+
|
|
122
|
+
destroy() {
|
|
123
|
+
this._stopGate?.();
|
|
124
|
+
}
|
|
116
125
|
}
|
package/lib/sync.js
CHANGED
|
@@ -122,6 +122,7 @@ export async function runSync(dbOrIndiekit, options) {
|
|
|
122
122
|
itemsCollection,
|
|
123
123
|
feedsCollection,
|
|
124
124
|
retentionDays,
|
|
125
|
+
options.minItemsPerFeed ?? 10,
|
|
125
126
|
);
|
|
126
127
|
|
|
127
128
|
syncState.lastSync = new Date().toISOString();
|
|
@@ -258,40 +259,67 @@ async function createIndexes(feedsCollection, itemsCollection) {
|
|
|
258
259
|
}
|
|
259
260
|
|
|
260
261
|
/**
|
|
261
|
-
* Prune items older than retention period
|
|
262
|
+
* Prune items older than the retention period.
|
|
263
|
+
*
|
|
264
|
+
* The newest `minItemsPerFeed` items of every feed are kept whatever their
|
|
265
|
+
* age. Without that floor a low-traffic feed whose whole backlog predates the
|
|
266
|
+
* cutoff is emptied and refetched on every single sync, so it churns forever
|
|
267
|
+
* and always reads as empty.
|
|
262
268
|
* @param {Collection} itemsCollection - Items collection
|
|
263
269
|
* @param {Collection} feedsCollection - Feeds collection
|
|
264
270
|
* @param {number} retentionDays - Days to keep items
|
|
271
|
+
* @param {number} minItemsPerFeed - Newest items always kept per feed
|
|
265
272
|
* @returns {Promise<number>} Number of items pruned
|
|
266
273
|
*/
|
|
267
|
-
async function pruneOldItems(
|
|
274
|
+
export async function pruneOldItems(
|
|
275
|
+
itemsCollection,
|
|
276
|
+
feedsCollection,
|
|
277
|
+
retentionDays,
|
|
278
|
+
minItemsPerFeed = 10,
|
|
279
|
+
) {
|
|
268
280
|
const cutoff = new Date();
|
|
269
281
|
cutoff.setDate(cutoff.getDate() - retentionDays);
|
|
270
282
|
|
|
271
283
|
try {
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
284
|
+
const feeds = await feedsCollection.find({}).toArray();
|
|
285
|
+
let deletedCount = 0;
|
|
286
|
+
|
|
287
|
+
for (const feed of feeds) {
|
|
288
|
+
const keep = await itemsCollection
|
|
289
|
+
.find({ feedId: feed._id })
|
|
290
|
+
.sort({ pubDate: -1 })
|
|
291
|
+
.limit(minItemsPerFeed)
|
|
292
|
+
.project({ _id: 1 })
|
|
293
|
+
.toArray();
|
|
294
|
+
|
|
295
|
+
const result = await itemsCollection.deleteMany({
|
|
296
|
+
feedId: feed._id,
|
|
297
|
+
// $ne: null also excludes missing dates: in BSON null sorts before
|
|
298
|
+
// Date, so a bare $lt would delete every undated item as "too old".
|
|
299
|
+
pubDate: { $lt: cutoff, $ne: null },
|
|
300
|
+
_id: { $nin: keep.map((item) => item._id) },
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
if (result.deletedCount > 0) {
|
|
304
|
+
deletedCount += result.deletedCount;
|
|
305
|
+
|
|
306
|
+
const itemCount = await itemsCollection.countDocuments({
|
|
285
307
|
feedId: feed._id,
|
|
286
308
|
});
|
|
287
309
|
await feedsCollection.updateOne(
|
|
288
310
|
{ _id: feed._id },
|
|
289
|
-
{ $set: { itemCount
|
|
311
|
+
{ $set: { itemCount } },
|
|
290
312
|
);
|
|
291
313
|
}
|
|
292
314
|
}
|
|
293
315
|
|
|
294
|
-
|
|
316
|
+
if (deletedCount > 0) {
|
|
317
|
+
console.log(
|
|
318
|
+
`[RSS] Pruned ${deletedCount} items older than ${retentionDays} days`,
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return deletedCount;
|
|
295
323
|
} catch (err) {
|
|
296
324
|
console.error("[RSS] Prune error:", err.message);
|
|
297
325
|
return 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmdes/indiekit-endpoint-rss",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.16",
|
|
4
4
|
"description": "RSS feed reader endpoint for Indiekit. Aggregates multiple feeds, caches in MongoDB, displays on frontend.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"indiekit",
|
|
@@ -30,6 +30,9 @@
|
|
|
30
30
|
},
|
|
31
31
|
"type": "module",
|
|
32
32
|
"main": "index.js",
|
|
33
|
+
"scripts": {
|
|
34
|
+
"test": "node --test \"test/*.test.js\""
|
|
35
|
+
},
|
|
33
36
|
"exports": {
|
|
34
37
|
".": "./index.js"
|
|
35
38
|
},
|
|
@@ -41,6 +44,7 @@
|
|
|
41
44
|
"index.js"
|
|
42
45
|
],
|
|
43
46
|
"dependencies": {
|
|
47
|
+
"@rmdes/indiekit-startup-gate": "^1.0.0",
|
|
44
48
|
"@indiekit/error": "^1.0.0-beta.25",
|
|
45
49
|
"express": "^5.0.0",
|
|
46
50
|
"rss-parser": "^3.13.0",
|