@rmdes/indiekit-endpoint-rss 1.0.3 → 1.0.5
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/index.js +3 -0
- package/lib/controllers/dashboard.js +53 -0
- package/lib/rss-client.js +13 -4
- package/locales/en.json +3 -1
- package/package.json +1 -1
- package/views/rss.njk +55 -7
package/index.js
CHANGED
|
@@ -61,6 +61,9 @@ export default class RssEndpoint {
|
|
|
61
61
|
// Manual sync trigger
|
|
62
62
|
protectedRouter.post("/sync", dashboardController.sync);
|
|
63
63
|
|
|
64
|
+
// Clear items and re-sync
|
|
65
|
+
protectedRouter.post("/clear-resync", dashboardController.clearResync);
|
|
66
|
+
|
|
64
67
|
// Feed management (protected - requires auth)
|
|
65
68
|
protectedRouter.post("/api/feeds", express.json(), feedsController.add);
|
|
66
69
|
protectedRouter.delete("/api/feeds/:id", feedsController.remove);
|
|
@@ -59,6 +59,59 @@ export const dashboardController = {
|
|
|
59
59
|
}
|
|
60
60
|
},
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Clear all items and re-sync
|
|
64
|
+
* POST /clear-resync
|
|
65
|
+
*/
|
|
66
|
+
async clearResync(request, response) {
|
|
67
|
+
try {
|
|
68
|
+
const { rssConfig, getRssDb } = request.app.locals.application;
|
|
69
|
+
|
|
70
|
+
if (!rssConfig) {
|
|
71
|
+
return response.status(500).json({
|
|
72
|
+
error: response.locals.__("rss.error.noConfig"),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const db = getRssDb?.();
|
|
77
|
+
if (!db) {
|
|
78
|
+
return response.status(500).json({
|
|
79
|
+
error: response.locals.__("rss.error.noDatabase"),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Drop all items
|
|
84
|
+
const itemsCollection = db.collection("rssItems");
|
|
85
|
+
const deleteResult = await itemsCollection.deleteMany({});
|
|
86
|
+
console.log(`[RSS] Cleared ${deleteResult.deletedCount} items`);
|
|
87
|
+
|
|
88
|
+
// Reset feed item counts
|
|
89
|
+
const feedsCollection = db.collection("rssFeeds");
|
|
90
|
+
await feedsCollection.updateMany({}, { $set: { itemCount: 0 } });
|
|
91
|
+
|
|
92
|
+
// Trigger sync
|
|
93
|
+
const result = await runSync(db, rssConfig);
|
|
94
|
+
|
|
95
|
+
if (result.error) {
|
|
96
|
+
return response.status(500).json({
|
|
97
|
+
success: false,
|
|
98
|
+
error: result.error,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
response.json({
|
|
103
|
+
success: true,
|
|
104
|
+
message: response.locals.__("rss.success.clearResync"),
|
|
105
|
+
itemsCleared: deleteResult.deletedCount,
|
|
106
|
+
feedsProcessed: result.feedsProcessed,
|
|
107
|
+
itemsAdded: result.itemsAdded,
|
|
108
|
+
});
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.error("[RSS] Clear & re-sync error:", error.message);
|
|
111
|
+
response.status(500).json({ error: error.message });
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
|
|
62
115
|
/**
|
|
63
116
|
* Trigger manual sync
|
|
64
117
|
* POST /sync
|
package/lib/rss-client.js
CHANGED
|
@@ -326,14 +326,23 @@ export class RssClient {
|
|
|
326
326
|
* @returns {string|null}
|
|
327
327
|
*/
|
|
328
328
|
extractFeedImage(parsed) {
|
|
329
|
+
// Helper to get first string from value (handles arrays)
|
|
330
|
+
const getString = (val) => {
|
|
331
|
+
if (!val) return null;
|
|
332
|
+
if (Array.isArray(val)) return val[0] || null;
|
|
333
|
+
if (typeof val === "object" && val.url) return val.url;
|
|
334
|
+
return typeof val === "string" ? val : null;
|
|
335
|
+
};
|
|
336
|
+
|
|
329
337
|
// RSS 2.0 image
|
|
330
|
-
if (parsed.image?.url) return parsed.image.url;
|
|
338
|
+
if (parsed.image?.url) return getString(parsed.image.url);
|
|
339
|
+
if (parsed.image) return getString(parsed.image);
|
|
331
340
|
// Atom icon
|
|
332
|
-
if (parsed.icon) return parsed.icon;
|
|
341
|
+
if (parsed.icon) return getString(parsed.icon);
|
|
333
342
|
// Atom logo
|
|
334
|
-
if (parsed.logo) return parsed.logo;
|
|
343
|
+
if (parsed.logo) return getString(parsed.logo);
|
|
335
344
|
// itunes:image
|
|
336
|
-
if (parsed.itunes?.image) return parsed.itunes.image;
|
|
345
|
+
if (parsed.itunes?.image) return getString(parsed.itunes.image);
|
|
337
346
|
return null;
|
|
338
347
|
}
|
|
339
348
|
|
package/locales/en.json
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"enableFeed": "Enable",
|
|
9
9
|
"disableFeed": "Disable",
|
|
10
10
|
"syncNow": "Sync Now",
|
|
11
|
+
"clearResync": "Clear & Re-sync",
|
|
11
12
|
"syncing": "Syncing...",
|
|
12
13
|
"lastSynced": "Last synced",
|
|
13
14
|
"noFeeds": "No feeds configured. Add a feed URL to get started.",
|
|
@@ -40,7 +41,8 @@
|
|
|
40
41
|
"feedRemoved": "Feed removed",
|
|
41
42
|
"feedEnabled": "Feed enabled",
|
|
42
43
|
"feedDisabled": "Feed disabled",
|
|
43
|
-
"syncComplete": "Sync complete"
|
|
44
|
+
"syncComplete": "Sync complete",
|
|
45
|
+
"clearResync": "Items cleared and re-synced"
|
|
44
46
|
},
|
|
45
47
|
"widget": {
|
|
46
48
|
"description": "View aggregated RSS feeds on the public page",
|
package/package.json
CHANGED
package/views/rss.njk
CHANGED
|
@@ -284,13 +284,22 @@
|
|
|
284
284
|
<span class="rss-feed-error">{{ syncState.lastError }}</span>
|
|
285
285
|
{% endif %}
|
|
286
286
|
</div>
|
|
287
|
-
<
|
|
288
|
-
{{
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
287
|
+
<div style="display: flex; gap: 0.5rem;">
|
|
288
|
+
<form action="{{ publicUrl }}/sync" method="post" style="margin: 0;">
|
|
289
|
+
{{ button({
|
|
290
|
+
type: "submit",
|
|
291
|
+
text: __("rss.syncNow"),
|
|
292
|
+
disabled: syncState.syncing
|
|
293
|
+
}) }}
|
|
294
|
+
</form>
|
|
295
|
+
<form action="{{ publicUrl }}/clear-resync" method="post" style="margin: 0;" id="clear-resync-form">
|
|
296
|
+
{{ button({
|
|
297
|
+
type: "submit",
|
|
298
|
+
text: __("rss.clearResync"),
|
|
299
|
+
disabled: syncState.syncing
|
|
300
|
+
}) }}
|
|
301
|
+
</form>
|
|
302
|
+
</div>
|
|
294
303
|
</div>
|
|
295
304
|
|
|
296
305
|
{# Stats #}
|
|
@@ -471,6 +480,45 @@
|
|
|
471
480
|
});
|
|
472
481
|
});
|
|
473
482
|
|
|
483
|
+
// Handle clear & re-sync
|
|
484
|
+
document.getElementById('clear-resync-form')?.addEventListener('submit', async (e) => {
|
|
485
|
+
e.preventDefault();
|
|
486
|
+
if (!confirm('This will delete all cached items and re-fetch them from feeds. Continue?')) return;
|
|
487
|
+
|
|
488
|
+
const form = e.target;
|
|
489
|
+
const submitBtn = form.querySelector('button[type="submit"]');
|
|
490
|
+
const originalText = submitBtn?.textContent;
|
|
491
|
+
if (submitBtn) {
|
|
492
|
+
submitBtn.disabled = true;
|
|
493
|
+
submitBtn.textContent = 'Clearing...';
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
try {
|
|
497
|
+
const response = await fetch(form.action, {
|
|
498
|
+
method: 'POST',
|
|
499
|
+
headers: { 'Content-Type': 'application/json' }
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
const data = await response.json();
|
|
503
|
+
if (response.ok) {
|
|
504
|
+
alert(`Cleared ${data.itemsCleared} items, re-fetched ${data.itemsAdded} items from ${data.feedsProcessed} feeds.`);
|
|
505
|
+
location.reload();
|
|
506
|
+
} else {
|
|
507
|
+
alert(data.error || 'Failed to clear & re-sync');
|
|
508
|
+
if (submitBtn) {
|
|
509
|
+
submitBtn.disabled = false;
|
|
510
|
+
submitBtn.textContent = originalText;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
} catch (err) {
|
|
514
|
+
alert('Failed to clear & re-sync: ' + err.message);
|
|
515
|
+
if (submitBtn) {
|
|
516
|
+
submitBtn.disabled = false;
|
|
517
|
+
submitBtn.textContent = originalText;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
|
|
474
522
|
// Handle delete feed
|
|
475
523
|
document.querySelectorAll('[data-delete-feed]').forEach(btn => {
|
|
476
524
|
btn.addEventListener('click', async (e) => {
|