@rmdes/indiekit-endpoint-blogroll 1.0.21 → 1.0.23
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/lib/controllers/api.js +6 -3
- package/lib/controllers/dashboard.js +7 -7
- package/lib/storage/blogs.js +15 -2
- package/lib/sync/feed.js +9 -0
- package/lib/sync/feedland.js +1 -0
- package/locales/de.json +1 -1
- package/locales/en.json +1 -1
- package/locales/es-419.json +1 -1
- package/locales/es.json +1 -1
- package/locales/fr.json +1 -1
- package/locales/hi.json +1 -1
- package/locales/id.json +1 -1
- package/locales/it.json +1 -1
- package/locales/nl.json +1 -1
- package/locales/pl.json +1 -1
- package/locales/pt-BR.json +1 -1
- package/locales/pt.json +1 -1
- package/locales/sr.json +1 -1
- package/locales/sv.json +1 -1
- package/locales/zh-Hans-CN.json +1 -1
- package/package.json +1 -1
package/lib/controllers/api.js
CHANGED
|
@@ -18,16 +18,18 @@ import { handleMicrosubWebhook, isMicrosubAvailable } from "../sync/microsub.js"
|
|
|
18
18
|
async function listBlogs(request, response) {
|
|
19
19
|
const { application } = request.app.locals;
|
|
20
20
|
|
|
21
|
-
const { category, limit = 100, offset = 0 } = request.query;
|
|
21
|
+
const { category, source, sort, limit = 100, offset = 0 } = request.query;
|
|
22
22
|
|
|
23
23
|
try {
|
|
24
24
|
const blogs = await getBlogs(application, {
|
|
25
25
|
category,
|
|
26
|
+
source,
|
|
27
|
+
sort,
|
|
26
28
|
limit: Number(limit),
|
|
27
29
|
offset: Number(offset),
|
|
28
30
|
});
|
|
29
31
|
|
|
30
|
-
const total = await countBlogs(application, { category });
|
|
32
|
+
const total = await countBlogs(application, { category, source });
|
|
31
33
|
|
|
32
34
|
response.json({
|
|
33
35
|
items: blogs.map(sanitizeBlog),
|
|
@@ -232,11 +234,12 @@ function sanitizeBlog(blog) {
|
|
|
232
234
|
itemCount: blog.itemCount,
|
|
233
235
|
pinned: blog.pinned,
|
|
234
236
|
lastFetchAt: blog.lastFetchAt,
|
|
237
|
+
lastItemAt: blog.lastItemAt || null,
|
|
238
|
+
source: blog.source || null,
|
|
235
239
|
};
|
|
236
240
|
|
|
237
241
|
// Include Microsub metadata if applicable
|
|
238
242
|
if (blog.source === "microsub") {
|
|
239
|
-
sanitized.source = "microsub";
|
|
240
243
|
sanitized.microsubChannel = blog.microsubChannelName;
|
|
241
244
|
}
|
|
242
245
|
|
|
@@ -77,13 +77,13 @@ async function sync(request, response) {
|
|
|
77
77
|
|
|
78
78
|
if (result.skipped) {
|
|
79
79
|
request.session.messages = [
|
|
80
|
-
{ type: "warning", content: request.__("blogroll.
|
|
80
|
+
{ type: "warning", content: request.__("blogroll.syncResult.already_running") },
|
|
81
81
|
];
|
|
82
82
|
} else if (result.success) {
|
|
83
83
|
request.session.messages = [
|
|
84
84
|
{
|
|
85
85
|
type: "success",
|
|
86
|
-
content: request.__("blogroll.
|
|
86
|
+
content: request.__("blogroll.syncResult.success", {
|
|
87
87
|
blogs: result.blogs.success,
|
|
88
88
|
items: result.items.added,
|
|
89
89
|
}),
|
|
@@ -91,13 +91,13 @@ async function sync(request, response) {
|
|
|
91
91
|
];
|
|
92
92
|
} else {
|
|
93
93
|
request.session.messages = [
|
|
94
|
-
{ type: "error", content: request.__("blogroll.
|
|
94
|
+
{ type: "error", content: request.__("blogroll.syncResult.error", { error: result.error }) },
|
|
95
95
|
];
|
|
96
96
|
}
|
|
97
97
|
} catch (error) {
|
|
98
98
|
console.error("[Blogroll] Manual sync error:", error);
|
|
99
99
|
request.session.messages = [
|
|
100
|
-
{ type: "error", content: request.__("blogroll.
|
|
100
|
+
{ type: "error", content: request.__("blogroll.syncResult.error", { error: error.message }) },
|
|
101
101
|
];
|
|
102
102
|
}
|
|
103
103
|
|
|
@@ -118,7 +118,7 @@ async function clearResync(request, response) {
|
|
|
118
118
|
request.session.messages = [
|
|
119
119
|
{
|
|
120
120
|
type: "success",
|
|
121
|
-
content: request.__("blogroll.
|
|
121
|
+
content: request.__("blogroll.syncResult.cleared_success", {
|
|
122
122
|
blogs: result.blogs.success,
|
|
123
123
|
items: result.items.added,
|
|
124
124
|
}),
|
|
@@ -126,13 +126,13 @@ async function clearResync(request, response) {
|
|
|
126
126
|
];
|
|
127
127
|
} else {
|
|
128
128
|
request.session.messages = [
|
|
129
|
-
{ type: "error", content: request.__("blogroll.
|
|
129
|
+
{ type: "error", content: request.__("blogroll.syncResult.error", { error: result.error }) },
|
|
130
130
|
];
|
|
131
131
|
}
|
|
132
132
|
} catch (error) {
|
|
133
133
|
console.error("[Blogroll] Clear resync error:", error);
|
|
134
134
|
request.session.messages = [
|
|
135
|
-
{ type: "error", content: request.__("blogroll.
|
|
135
|
+
{ type: "error", content: request.__("blogroll.syncResult.error", { error: error.message }) },
|
|
136
136
|
];
|
|
137
137
|
}
|
|
138
138
|
|
package/lib/storage/blogs.js
CHANGED
|
@@ -29,10 +29,18 @@ export async function getBlogs(application, options = {}) {
|
|
|
29
29
|
if (!includeHidden) query.hidden = { $ne: true };
|
|
30
30
|
if (category) query.category = category;
|
|
31
31
|
if (sourceId) query.sourceId = new ObjectId(sourceId);
|
|
32
|
+
if (options.source) query.source = options.source;
|
|
33
|
+
|
|
34
|
+
// Default sort: pinned first, then alphabetical
|
|
35
|
+
// "recent" sort: pinned first, then by last fetch time (newest first)
|
|
36
|
+
const sortOrder =
|
|
37
|
+
options.sort === "recent"
|
|
38
|
+
? { pinned: -1, lastFetchAt: -1, title: 1 }
|
|
39
|
+
: { pinned: -1, title: 1 };
|
|
32
40
|
|
|
33
41
|
return collection
|
|
34
42
|
.find(query)
|
|
35
|
-
.sort(
|
|
43
|
+
.sort(sortOrder)
|
|
36
44
|
.skip(offset)
|
|
37
45
|
.limit(limit)
|
|
38
46
|
.toArray();
|
|
@@ -46,11 +54,12 @@ export async function getBlogs(application, options = {}) {
|
|
|
46
54
|
*/
|
|
47
55
|
export async function countBlogs(application, options = {}) {
|
|
48
56
|
const collection = getCollection(application);
|
|
49
|
-
const { category, includeHidden = false } = options;
|
|
57
|
+
const { category, source, includeHidden = false } = options;
|
|
50
58
|
|
|
51
59
|
const query = { status: { $ne: "deleted" } };
|
|
52
60
|
if (!includeHidden) query.hidden = { $ne: true };
|
|
53
61
|
if (category) query.category = category;
|
|
62
|
+
if (source) query.source = source;
|
|
54
63
|
|
|
55
64
|
return collection.countDocuments(query);
|
|
56
65
|
}
|
|
@@ -101,6 +110,7 @@ export async function createBlog(application, data) {
|
|
|
101
110
|
author: data.author || null,
|
|
102
111
|
status: "active",
|
|
103
112
|
lastFetchAt: null,
|
|
113
|
+
lastItemAt: null,
|
|
104
114
|
lastError: null,
|
|
105
115
|
itemCount: 0,
|
|
106
116
|
pinned: data.pinned || false,
|
|
@@ -191,6 +201,7 @@ export async function updateBlogStatus(application, id, status) {
|
|
|
191
201
|
if (status.itemCount !== undefined) {
|
|
192
202
|
update.itemCount = status.itemCount;
|
|
193
203
|
}
|
|
204
|
+
if (status.lastItemAt) update.lastItemAt = status.lastItemAt;
|
|
194
205
|
if (status.title) update.title = status.title;
|
|
195
206
|
if (status.photo) update.photo = status.photo;
|
|
196
207
|
if (status.siteUrl) update.siteUrl = status.siteUrl;
|
|
@@ -280,6 +291,7 @@ export async function upsertBlog(application, data) {
|
|
|
280
291
|
if (data.skipItemFetch !== undefined) setFields.skipItemFetch = data.skipItemFetch;
|
|
281
292
|
if (data.photo !== undefined) setFields.photo = data.photo;
|
|
282
293
|
if (data.lastFetchAt !== undefined) setFields.lastFetchAt = data.lastFetchAt;
|
|
294
|
+
if (data.lastItemAt !== undefined) setFields.lastItemAt = data.lastItemAt;
|
|
283
295
|
if (data.status !== undefined) setFields.status = data.status;
|
|
284
296
|
|
|
285
297
|
// $setOnInsert only for fields NOT already in $set (avoids MongoDB path conflicts)
|
|
@@ -303,6 +315,7 @@ export async function upsertBlog(application, data) {
|
|
|
303
315
|
if (!("skipItemFetch" in setFields)) insertDefaults.skipItemFetch = false;
|
|
304
316
|
if (!("photo" in setFields)) insertDefaults.photo = null;
|
|
305
317
|
if (!("lastFetchAt" in setFields)) insertDefaults.lastFetchAt = null;
|
|
318
|
+
if (!("lastItemAt" in setFields)) insertDefaults.lastItemAt = null;
|
|
306
319
|
if (!("status" in setFields)) insertDefaults.status = "active";
|
|
307
320
|
|
|
308
321
|
const result = await collection.updateOne(
|
package/lib/sync/feed.js
CHANGED
|
@@ -307,10 +307,19 @@ export async function syncBlogItems(application, blog, options = {}) {
|
|
|
307
307
|
if (result.upserted) added++;
|
|
308
308
|
}
|
|
309
309
|
|
|
310
|
+
// Compute newest item publish date
|
|
311
|
+
let newestDate = null;
|
|
312
|
+
for (const item of feed.items) {
|
|
313
|
+
if (item.published && (!newestDate || item.published > newestDate)) {
|
|
314
|
+
newestDate = item.published;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
310
318
|
// Update blog metadata
|
|
311
319
|
const updateData = {
|
|
312
320
|
success: true,
|
|
313
321
|
itemCount: feed.items.length,
|
|
322
|
+
lastItemAt: newestDate,
|
|
314
323
|
};
|
|
315
324
|
|
|
316
325
|
// Update title if not manually set (still has feedUrl as title)
|
package/lib/sync/feedland.js
CHANGED
package/locales/de.json
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"clearConfirm": "Dadurch werden alle zwischengespeicherten Einträge gelöscht und alles neu abgerufen. Fortfahren?"
|
|
27
27
|
},
|
|
28
28
|
|
|
29
|
-
"
|
|
29
|
+
"syncResult": {
|
|
30
30
|
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
|
31
31
|
"error": "Sync failed: {{error}}",
|
|
32
32
|
"already_running": "A sync is already in progress.",
|
package/locales/en.json
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"clearConfirm": "This will delete all cached items and re-fetch everything. Continue?"
|
|
27
27
|
},
|
|
28
28
|
|
|
29
|
-
"
|
|
29
|
+
"syncResult": {
|
|
30
30
|
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
|
31
31
|
"error": "Sync failed: {{error}}",
|
|
32
32
|
"already_running": "A sync is already in progress.",
|
package/locales/es-419.json
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"clearConfirm": "Esto eliminará todas las entradas almacenadas en caché y volverá a descargar todo. ¿Continuar?"
|
|
27
27
|
},
|
|
28
28
|
|
|
29
|
-
"
|
|
29
|
+
"syncResult": {
|
|
30
30
|
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
|
31
31
|
"error": "Sync failed: {{error}}",
|
|
32
32
|
"already_running": "A sync is already in progress.",
|
package/locales/es.json
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"clearConfirm": "Esto eliminará todas las entradas almacenadas en caché y volverá a obtenerlo todo. ¿Continuar?"
|
|
27
27
|
},
|
|
28
28
|
|
|
29
|
-
"
|
|
29
|
+
"syncResult": {
|
|
30
30
|
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
|
31
31
|
"error": "Sync failed: {{error}}",
|
|
32
32
|
"already_running": "A sync is already in progress.",
|
package/locales/fr.json
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"clearConfirm": "Cela supprimera toutes les entrées mises en cache et récupérera tout à nouveau. Continuer ?"
|
|
27
27
|
},
|
|
28
28
|
|
|
29
|
-
"
|
|
29
|
+
"syncResult": {
|
|
30
30
|
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
|
31
31
|
"error": "Sync failed: {{error}}",
|
|
32
32
|
"already_running": "A sync is already in progress.",
|
package/locales/hi.json
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"clearConfirm": "इससे सभी कैश किए गए आइटम हटा दिए जाएंगे और सब कुछ फिर से प्राप्त किया जाएगा। जारी रखें?"
|
|
27
27
|
},
|
|
28
28
|
|
|
29
|
-
"
|
|
29
|
+
"syncResult": {
|
|
30
30
|
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
|
31
31
|
"error": "Sync failed: {{error}}",
|
|
32
32
|
"already_running": "A sync is already in progress.",
|
package/locales/id.json
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"clearConfirm": "Ini akan menghapus semua item yang di-cache dan mengambil semuanya lagi. Lanjutkan?"
|
|
27
27
|
},
|
|
28
28
|
|
|
29
|
-
"
|
|
29
|
+
"syncResult": {
|
|
30
30
|
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
|
31
31
|
"error": "Sync failed: {{error}}",
|
|
32
32
|
"already_running": "A sync is already in progress.",
|
package/locales/it.json
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"clearConfirm": "Questo cancellerà tutti gli elementi memorizzati e recupererà tutto nuovamente. Continuare?"
|
|
27
27
|
},
|
|
28
28
|
|
|
29
|
-
"
|
|
29
|
+
"syncResult": {
|
|
30
30
|
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
|
31
31
|
"error": "Sync failed: {{error}}",
|
|
32
32
|
"already_running": "A sync is already in progress.",
|
package/locales/nl.json
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"clearConfirm": "Dit verwijdert alle gecachte items en haalt alles opnieuw op. Doorgaan?"
|
|
27
27
|
},
|
|
28
28
|
|
|
29
|
-
"
|
|
29
|
+
"syncResult": {
|
|
30
30
|
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
|
31
31
|
"error": "Sync failed: {{error}}",
|
|
32
32
|
"already_running": "A sync is already in progress.",
|
package/locales/pl.json
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"clearConfirm": "Spowoduje to usunięcie wszystkich elementów w pamięci podręcznej i ponowne pobranie wszystkiego. Kontynuować?"
|
|
27
27
|
},
|
|
28
28
|
|
|
29
|
-
"
|
|
29
|
+
"syncResult": {
|
|
30
30
|
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
|
31
31
|
"error": "Sync failed: {{error}}",
|
|
32
32
|
"already_running": "A sync is already in progress.",
|
package/locales/pt-BR.json
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"clearConfirm": "Isso excluirá todos os itens em cache e buscará tudo novamente. Continuar?"
|
|
27
27
|
},
|
|
28
28
|
|
|
29
|
-
"
|
|
29
|
+
"syncResult": {
|
|
30
30
|
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
|
31
31
|
"error": "Sync failed: {{error}}",
|
|
32
32
|
"already_running": "A sync is already in progress.",
|
package/locales/pt.json
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"clearConfirm": "Isto eliminará todos os itens em cache e voltará a obter tudo. Continuar?"
|
|
27
27
|
},
|
|
28
28
|
|
|
29
|
-
"
|
|
29
|
+
"syncResult": {
|
|
30
30
|
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
|
31
31
|
"error": "Sync failed: {{error}}",
|
|
32
32
|
"already_running": "A sync is already in progress.",
|
package/locales/sr.json
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"clearConfirm": "Ово ће обрисати све кеширане ставке и поново преузети све. Наставити?"
|
|
27
27
|
},
|
|
28
28
|
|
|
29
|
-
"
|
|
29
|
+
"syncResult": {
|
|
30
30
|
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
|
31
31
|
"error": "Sync failed: {{error}}",
|
|
32
32
|
"already_running": "A sync is already in progress.",
|
package/locales/sv.json
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"clearConfirm": "Detta kommer att ta bort alla cachade poster och hämta allt igen. Fortsätta?"
|
|
27
27
|
},
|
|
28
28
|
|
|
29
|
-
"
|
|
29
|
+
"syncResult": {
|
|
30
30
|
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
|
31
31
|
"error": "Sync failed: {{error}}",
|
|
32
32
|
"already_running": "A sync is already in progress.",
|
package/locales/zh-Hans-CN.json
CHANGED