mirror of
https://github.com/svemagie/indiekit-endpoint-blogroll.git
synced 2026-04-02 15:34:59 +02:00
fix: resolve [Object Object] bug and add sort/source API params
Rename duplicate "sync" locale key to "syncResult" to fix the sources list page showing [Object Object] instead of the Sync button label. Add sort=recent and source= query params to the blogs API for the sidebar widget tabs feature. Tag FeedLand blogs with source: "feedland" and expose source field for all blogs in API responses. Bump version to 1.0.22.
This commit is contained in:
@@ -18,16 +18,18 @@ import { handleMicrosubWebhook, isMicrosubAvailable } from "../sync/microsub.js"
|
|||||||
async function listBlogs(request, response) {
|
async function listBlogs(request, response) {
|
||||||
const { application } = request.app.locals;
|
const { application } = request.app.locals;
|
||||||
|
|
||||||
const { category, limit = 100, offset = 0 } = request.query;
|
const { category, source, sort, limit = 100, offset = 0 } = request.query;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const blogs = await getBlogs(application, {
|
const blogs = await getBlogs(application, {
|
||||||
category,
|
category,
|
||||||
|
source,
|
||||||
|
sort,
|
||||||
limit: Number(limit),
|
limit: Number(limit),
|
||||||
offset: Number(offset),
|
offset: Number(offset),
|
||||||
});
|
});
|
||||||
|
|
||||||
const total = await countBlogs(application, { category });
|
const total = await countBlogs(application, { category, source });
|
||||||
|
|
||||||
response.json({
|
response.json({
|
||||||
items: blogs.map(sanitizeBlog),
|
items: blogs.map(sanitizeBlog),
|
||||||
@@ -232,11 +234,11 @@ function sanitizeBlog(blog) {
|
|||||||
itemCount: blog.itemCount,
|
itemCount: blog.itemCount,
|
||||||
pinned: blog.pinned,
|
pinned: blog.pinned,
|
||||||
lastFetchAt: blog.lastFetchAt,
|
lastFetchAt: blog.lastFetchAt,
|
||||||
|
source: blog.source || null,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Include Microsub metadata if applicable
|
// Include Microsub metadata if applicable
|
||||||
if (blog.source === "microsub") {
|
if (blog.source === "microsub") {
|
||||||
sanitized.source = "microsub";
|
|
||||||
sanitized.microsubChannel = blog.microsubChannelName;
|
sanitized.microsubChannel = blog.microsubChannelName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -77,13 +77,13 @@ async function sync(request, response) {
|
|||||||
|
|
||||||
if (result.skipped) {
|
if (result.skipped) {
|
||||||
request.session.messages = [
|
request.session.messages = [
|
||||||
{ type: "warning", content: request.__("blogroll.sync.already_running") },
|
{ type: "warning", content: request.__("blogroll.syncResult.already_running") },
|
||||||
];
|
];
|
||||||
} else if (result.success) {
|
} else if (result.success) {
|
||||||
request.session.messages = [
|
request.session.messages = [
|
||||||
{
|
{
|
||||||
type: "success",
|
type: "success",
|
||||||
content: request.__("blogroll.sync.success", {
|
content: request.__("blogroll.syncResult.success", {
|
||||||
blogs: result.blogs.success,
|
blogs: result.blogs.success,
|
||||||
items: result.items.added,
|
items: result.items.added,
|
||||||
}),
|
}),
|
||||||
@@ -91,13 +91,13 @@ async function sync(request, response) {
|
|||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
request.session.messages = [
|
request.session.messages = [
|
||||||
{ type: "error", content: request.__("blogroll.sync.error", { error: result.error }) },
|
{ type: "error", content: request.__("blogroll.syncResult.error", { error: result.error }) },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[Blogroll] Manual sync error:", error);
|
console.error("[Blogroll] Manual sync error:", error);
|
||||||
request.session.messages = [
|
request.session.messages = [
|
||||||
{ type: "error", content: request.__("blogroll.sync.error", { error: error.message }) },
|
{ type: "error", content: request.__("blogroll.syncResult.error", { error: error.message }) },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ async function clearResync(request, response) {
|
|||||||
request.session.messages = [
|
request.session.messages = [
|
||||||
{
|
{
|
||||||
type: "success",
|
type: "success",
|
||||||
content: request.__("blogroll.sync.cleared_success", {
|
content: request.__("blogroll.syncResult.cleared_success", {
|
||||||
blogs: result.blogs.success,
|
blogs: result.blogs.success,
|
||||||
items: result.items.added,
|
items: result.items.added,
|
||||||
}),
|
}),
|
||||||
@@ -126,13 +126,13 @@ async function clearResync(request, response) {
|
|||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
request.session.messages = [
|
request.session.messages = [
|
||||||
{ type: "error", content: request.__("blogroll.sync.error", { error: result.error }) },
|
{ type: "error", content: request.__("blogroll.syncResult.error", { error: result.error }) },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[Blogroll] Clear resync error:", error);
|
console.error("[Blogroll] Clear resync error:", error);
|
||||||
request.session.messages = [
|
request.session.messages = [
|
||||||
{ type: "error", content: request.__("blogroll.sync.error", { error: error.message }) },
|
{ type: "error", content: request.__("blogroll.syncResult.error", { error: error.message }) },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,10 +29,18 @@ export async function getBlogs(application, options = {}) {
|
|||||||
if (!includeHidden) query.hidden = { $ne: true };
|
if (!includeHidden) query.hidden = { $ne: true };
|
||||||
if (category) query.category = category;
|
if (category) query.category = category;
|
||||||
if (sourceId) query.sourceId = new ObjectId(sourceId);
|
if (sourceId) query.sourceId = new ObjectId(sourceId);
|
||||||
|
if (options.source) query.source = options.source;
|
||||||
|
|
||||||
|
// Default sort: pinned first, then alphabetical
|
||||||
|
// "recent" sort: pinned first, then by last fetch time (newest first)
|
||||||
|
const sortOrder =
|
||||||
|
options.sort === "recent"
|
||||||
|
? { pinned: -1, lastFetchAt: -1, title: 1 }
|
||||||
|
: { pinned: -1, title: 1 };
|
||||||
|
|
||||||
return collection
|
return collection
|
||||||
.find(query)
|
.find(query)
|
||||||
.sort({ pinned: -1, title: 1 })
|
.sort(sortOrder)
|
||||||
.skip(offset)
|
.skip(offset)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.toArray();
|
.toArray();
|
||||||
@@ -46,11 +54,12 @@ export async function getBlogs(application, options = {}) {
|
|||||||
*/
|
*/
|
||||||
export async function countBlogs(application, options = {}) {
|
export async function countBlogs(application, options = {}) {
|
||||||
const collection = getCollection(application);
|
const collection = getCollection(application);
|
||||||
const { category, includeHidden = false } = options;
|
const { category, source, includeHidden = false } = options;
|
||||||
|
|
||||||
const query = { status: { $ne: "deleted" } };
|
const query = { status: { $ne: "deleted" } };
|
||||||
if (!includeHidden) query.hidden = { $ne: true };
|
if (!includeHidden) query.hidden = { $ne: true };
|
||||||
if (category) query.category = category;
|
if (category) query.category = category;
|
||||||
|
if (source) query.source = source;
|
||||||
|
|
||||||
return collection.countDocuments(query);
|
return collection.countDocuments(query);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ export async function syncFeedlandSource(application, source) {
|
|||||||
const result = await upsertBlog(application, {
|
const result = await upsertBlog(application, {
|
||||||
...blog,
|
...blog,
|
||||||
category,
|
category,
|
||||||
|
source: "feedland",
|
||||||
sourceId: source._id,
|
sourceId: source._id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
"clearConfirm": "Dadurch werden alle zwischengespeicherten Einträge gelöscht und alles neu abgerufen. Fortfahren?"
|
"clearConfirm": "Dadurch werden alle zwischengespeicherten Einträge gelöscht und alles neu abgerufen. Fortfahren?"
|
||||||
},
|
},
|
||||||
|
|
||||||
"sync": {
|
"syncResult": {
|
||||||
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
||||||
"error": "Sync failed: {{error}}",
|
"error": "Sync failed: {{error}}",
|
||||||
"already_running": "A sync is already in progress.",
|
"already_running": "A sync is already in progress.",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
"clearConfirm": "This will delete all cached items and re-fetch everything. Continue?"
|
"clearConfirm": "This will delete all cached items and re-fetch everything. Continue?"
|
||||||
},
|
},
|
||||||
|
|
||||||
"sync": {
|
"syncResult": {
|
||||||
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
||||||
"error": "Sync failed: {{error}}",
|
"error": "Sync failed: {{error}}",
|
||||||
"already_running": "A sync is already in progress.",
|
"already_running": "A sync is already in progress.",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
"clearConfirm": "Esto eliminará todas las entradas almacenadas en caché y volverá a descargar todo. ¿Continuar?"
|
"clearConfirm": "Esto eliminará todas las entradas almacenadas en caché y volverá a descargar todo. ¿Continuar?"
|
||||||
},
|
},
|
||||||
|
|
||||||
"sync": {
|
"syncResult": {
|
||||||
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
||||||
"error": "Sync failed: {{error}}",
|
"error": "Sync failed: {{error}}",
|
||||||
"already_running": "A sync is already in progress.",
|
"already_running": "A sync is already in progress.",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
"clearConfirm": "Esto eliminará todas las entradas almacenadas en caché y volverá a obtenerlo todo. ¿Continuar?"
|
"clearConfirm": "Esto eliminará todas las entradas almacenadas en caché y volverá a obtenerlo todo. ¿Continuar?"
|
||||||
},
|
},
|
||||||
|
|
||||||
"sync": {
|
"syncResult": {
|
||||||
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
||||||
"error": "Sync failed: {{error}}",
|
"error": "Sync failed: {{error}}",
|
||||||
"already_running": "A sync is already in progress.",
|
"already_running": "A sync is already in progress.",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
"clearConfirm": "Cela supprimera toutes les entrées mises en cache et récupérera tout à nouveau. Continuer ?"
|
"clearConfirm": "Cela supprimera toutes les entrées mises en cache et récupérera tout à nouveau. Continuer ?"
|
||||||
},
|
},
|
||||||
|
|
||||||
"sync": {
|
"syncResult": {
|
||||||
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
||||||
"error": "Sync failed: {{error}}",
|
"error": "Sync failed: {{error}}",
|
||||||
"already_running": "A sync is already in progress.",
|
"already_running": "A sync is already in progress.",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
"clearConfirm": "इससे सभी कैश किए गए आइटम हटा दिए जाएंगे और सब कुछ फिर से प्राप्त किया जाएगा। जारी रखें?"
|
"clearConfirm": "इससे सभी कैश किए गए आइटम हटा दिए जाएंगे और सब कुछ फिर से प्राप्त किया जाएगा। जारी रखें?"
|
||||||
},
|
},
|
||||||
|
|
||||||
"sync": {
|
"syncResult": {
|
||||||
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
||||||
"error": "Sync failed: {{error}}",
|
"error": "Sync failed: {{error}}",
|
||||||
"already_running": "A sync is already in progress.",
|
"already_running": "A sync is already in progress.",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
"clearConfirm": "Ini akan menghapus semua item yang di-cache dan mengambil semuanya lagi. Lanjutkan?"
|
"clearConfirm": "Ini akan menghapus semua item yang di-cache dan mengambil semuanya lagi. Lanjutkan?"
|
||||||
},
|
},
|
||||||
|
|
||||||
"sync": {
|
"syncResult": {
|
||||||
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
||||||
"error": "Sync failed: {{error}}",
|
"error": "Sync failed: {{error}}",
|
||||||
"already_running": "A sync is already in progress.",
|
"already_running": "A sync is already in progress.",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
"clearConfirm": "Questo cancellerà tutti gli elementi memorizzati e recupererà tutto nuovamente. Continuare?"
|
"clearConfirm": "Questo cancellerà tutti gli elementi memorizzati e recupererà tutto nuovamente. Continuare?"
|
||||||
},
|
},
|
||||||
|
|
||||||
"sync": {
|
"syncResult": {
|
||||||
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
||||||
"error": "Sync failed: {{error}}",
|
"error": "Sync failed: {{error}}",
|
||||||
"already_running": "A sync is already in progress.",
|
"already_running": "A sync is already in progress.",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
"clearConfirm": "Dit verwijdert alle gecachte items en haalt alles opnieuw op. Doorgaan?"
|
"clearConfirm": "Dit verwijdert alle gecachte items en haalt alles opnieuw op. Doorgaan?"
|
||||||
},
|
},
|
||||||
|
|
||||||
"sync": {
|
"syncResult": {
|
||||||
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
||||||
"error": "Sync failed: {{error}}",
|
"error": "Sync failed: {{error}}",
|
||||||
"already_running": "A sync is already in progress.",
|
"already_running": "A sync is already in progress.",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
"clearConfirm": "Spowoduje to usunięcie wszystkich elementów w pamięci podręcznej i ponowne pobranie wszystkiego. Kontynuować?"
|
"clearConfirm": "Spowoduje to usunięcie wszystkich elementów w pamięci podręcznej i ponowne pobranie wszystkiego. Kontynuować?"
|
||||||
},
|
},
|
||||||
|
|
||||||
"sync": {
|
"syncResult": {
|
||||||
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
||||||
"error": "Sync failed: {{error}}",
|
"error": "Sync failed: {{error}}",
|
||||||
"already_running": "A sync is already in progress.",
|
"already_running": "A sync is already in progress.",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
"clearConfirm": "Isso excluirá todos os itens em cache e buscará tudo novamente. Continuar?"
|
"clearConfirm": "Isso excluirá todos os itens em cache e buscará tudo novamente. Continuar?"
|
||||||
},
|
},
|
||||||
|
|
||||||
"sync": {
|
"syncResult": {
|
||||||
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
||||||
"error": "Sync failed: {{error}}",
|
"error": "Sync failed: {{error}}",
|
||||||
"already_running": "A sync is already in progress.",
|
"already_running": "A sync is already in progress.",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
"clearConfirm": "Isto eliminará todos os itens em cache e voltará a obter tudo. Continuar?"
|
"clearConfirm": "Isto eliminará todos os itens em cache e voltará a obter tudo. Continuar?"
|
||||||
},
|
},
|
||||||
|
|
||||||
"sync": {
|
"syncResult": {
|
||||||
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
||||||
"error": "Sync failed: {{error}}",
|
"error": "Sync failed: {{error}}",
|
||||||
"already_running": "A sync is already in progress.",
|
"already_running": "A sync is already in progress.",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
"clearConfirm": "Ово ће обрисати све кеширане ставке и поново преузети све. Наставити?"
|
"clearConfirm": "Ово ће обрисати све кеширане ставке и поново преузети све. Наставити?"
|
||||||
},
|
},
|
||||||
|
|
||||||
"sync": {
|
"syncResult": {
|
||||||
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
||||||
"error": "Sync failed: {{error}}",
|
"error": "Sync failed: {{error}}",
|
||||||
"already_running": "A sync is already in progress.",
|
"already_running": "A sync is already in progress.",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
"clearConfirm": "Detta kommer att ta bort alla cachade poster och hämta allt igen. Fortsätta?"
|
"clearConfirm": "Detta kommer att ta bort alla cachade poster och hämta allt igen. Fortsätta?"
|
||||||
},
|
},
|
||||||
|
|
||||||
"sync": {
|
"syncResult": {
|
||||||
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
||||||
"error": "Sync failed: {{error}}",
|
"error": "Sync failed: {{error}}",
|
||||||
"already_running": "A sync is already in progress.",
|
"already_running": "A sync is already in progress.",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
"clearConfirm": "这将删除所有缓存的条目并重新获取所有内容。继续吗?"
|
"clearConfirm": "这将删除所有缓存的条目并重新获取所有内容。继续吗?"
|
||||||
},
|
},
|
||||||
|
|
||||||
"sync": {
|
"syncResult": {
|
||||||
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
"success": "Synced {{blogs}} blogs, added {{items}} items.",
|
||||||
"error": "Sync failed: {{error}}",
|
"error": "Sync failed: {{error}}",
|
||||||
"already_running": "A sync is already in progress.",
|
"already_running": "A sync is already in progress.",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@rmdes/indiekit-endpoint-blogroll",
|
"name": "@rmdes/indiekit-endpoint-blogroll",
|
||||||
"version": "1.0.21",
|
"version": "1.0.22",
|
||||||
"description": "Blogroll endpoint for Indiekit. Aggregates blog feeds from OPML, JSON feeds, or manual entry.",
|
"description": "Blogroll endpoint for Indiekit. Aggregates blog feeds from OPML, JSON feeds, or manual entry.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"indiekit",
|
"indiekit",
|
||||||
|
|||||||
Reference in New Issue
Block a user