Files
indiekit-endpoint-blogroll/lib/controllers/dashboard.js
Ricardo f02d46e76e 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.
2026-02-17 14:20:10 +01:00

179 lines
4.9 KiB
JavaScript

/**
* Dashboard controller
* @module controllers/dashboard
*/
import { getSources } from "../storage/sources.js";
import { getBlogs, countBlogs } from "../storage/blogs.js";
import { countItems } from "../storage/items.js";
import { runFullSync, clearAndResync, getSyncStatus } from "../sync/scheduler.js";
/**
* Dashboard page
* GET /
*/
async function get(request, response) {
const { application } = request.app.locals;
try {
const [rawSources, blogs, blogCount, itemCount, syncStatus] = await Promise.all([
getSources(application),
getBlogs(application, { limit: 10 }),
countBlogs(application),
countItems(application),
getSyncStatus(application),
]);
// Convert Date objects to ISO strings for template date filter compatibility
const sources = rawSources.map((source) => ({
...source,
lastSyncAt: source.lastSyncAt
? (source.lastSyncAt instanceof Date
? source.lastSyncAt.toISOString()
: source.lastSyncAt)
: null,
}));
// Get blogs with errors
const errorBlogs = await getBlogs(application, { includeHidden: true, limit: 100 });
const blogsWithErrors = errorBlogs.filter((b) => b.status === "error");
// Extract flash messages for native Indiekit notification banner
const flash = consumeFlashMessage(request);
response.render("blogroll-dashboard", {
title: request.__("blogroll.title"),
sources,
recentBlogs: blogs,
stats: {
sources: sources.length,
blogs: blogCount,
items: itemCount,
errors: blogsWithErrors.length,
},
syncStatus,
blogsWithErrors: blogsWithErrors.slice(0, 5),
baseUrl: request.baseUrl,
...flash,
});
} catch (error) {
console.error("[Blogroll] Dashboard error:", error);
response.status(500).render("error", {
title: "Error",
message: "Failed to load dashboard",
});
}
}
/**
* Manual sync trigger
* POST /sync
*/
async function sync(request, response) {
const { application } = request.app.locals;
try {
const result = await runFullSync(application, application.blogrollConfig);
if (result.skipped) {
request.session.messages = [
{ type: "warning", content: request.__("blogroll.syncResult.already_running") },
];
} else if (result.success) {
request.session.messages = [
{
type: "success",
content: request.__("blogroll.syncResult.success", {
blogs: result.blogs.success,
items: result.items.added,
}),
},
];
} else {
request.session.messages = [
{ type: "error", content: request.__("blogroll.syncResult.error", { error: result.error }) },
];
}
} catch (error) {
console.error("[Blogroll] Manual sync error:", error);
request.session.messages = [
{ type: "error", content: request.__("blogroll.syncResult.error", { error: error.message }) },
];
}
response.redirect(request.baseUrl);
}
/**
* Clear and re-sync
* POST /clear-resync
*/
async function clearResync(request, response) {
const { application } = request.app.locals;
try {
const result = await clearAndResync(application, application.blogrollConfig);
if (result.success) {
request.session.messages = [
{
type: "success",
content: request.__("blogroll.syncResult.cleared_success", {
blogs: result.blogs.success,
items: result.items.added,
}),
},
];
} else {
request.session.messages = [
{ type: "error", content: request.__("blogroll.syncResult.error", { error: result.error }) },
];
}
} catch (error) {
console.error("[Blogroll] Clear resync error:", error);
request.session.messages = [
{ type: "error", content: request.__("blogroll.syncResult.error", { error: error.message }) },
];
}
response.redirect(request.baseUrl);
}
/**
* Status API (for dashboard)
* GET /api/status (duplicated in api.js for public access)
*/
async function status(request, response) {
const { application } = request.app.locals;
try {
const syncStatus = await getSyncStatus(application);
response.json(syncStatus);
} catch (error) {
console.error("[Blogroll] Status error:", error);
response.status(500).json({ error: "Failed to fetch status" });
}
}
/**
* Extract and clear flash messages from session
* Returns { success, error } for Indiekit's native notificationBanner
*/
function consumeFlashMessage(request) {
const result = {};
if (request.session?.messages?.length) {
const msg = request.session.messages[0];
if (msg.type === "success") result.success = msg.content;
else if (msg.type === "error" || msg.type === "warning") result.error = msg.content;
request.session.messages = null;
}
return result;
}
export const dashboardController = {
get,
sync,
clearResync,
status,
};