feat: integrate bookmark→blogroll hook via contentNegotiationRoutes

Uses res.on('finish') middleware mounted at '/' to detect successful
micropub bookmark creations and auto-import the bookmarked site's feed
into the blogroll. Self-contained within the plugin — no external patch
scripts required.
This commit is contained in:
svemagie
2026-03-10 19:23:44 +01:00
parent 93de24c593
commit 34739735e7

View File

@@ -7,12 +7,48 @@ import { blogsController } from "./lib/controllers/blogs.js";
import { sourcesController } from "./lib/controllers/sources.js";
import { apiController } from "./lib/controllers/api.js";
import { startSync, stopSync } from "./lib/sync/scheduler.js";
import { importBookmarkUrl } from "./lib/bookmark-import.js";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const protectedRouter = express.Router();
const publicRouter = express.Router();
// Global hook router: intercepts POST requests site-wide to detect micropub
// bookmark creations and auto-import the bookmarked site into the blogroll.
// Mounted at "/" via contentNegotiationRoutes (runs before auth middleware).
const bookmarkHookRouter = express.Router();
bookmarkHookRouter.use((request, response, next) => {
response.on("finish", () => {
// Only act on successful POST creates (201 Created / 202 Accepted)
if (
request.method !== "POST" ||
(response.statusCode !== 201 && response.statusCode !== 202)
) {
return;
}
// Ignore non-create actions (update, delete, undelete)
const action =
request.query?.action || request.body?.action || "create";
if (action !== "create") return;
// bookmark-of may be a top-level field (form-encoded / JF2 JSON)
// or nested inside properties (MF2 JSON format)
const bookmarkOf =
request.body?.["bookmark-of"] ||
request.body?.properties?.["bookmark-of"]?.[0];
if (!bookmarkOf) return;
const { application } = request.app.locals;
importBookmarkUrl(application, bookmarkOf).catch((err) =>
console.warn("[Blogroll] bookmark-import failed:", err.message)
);
});
next();
});
const defaults = {
mountPath: "/blogrollapi",
syncInterval: 3600000, // 1 hour
@@ -54,6 +90,14 @@ export default class BlogrollEndpoint {
};
}
/**
* Global middleware (mounted at "/") — intercepts micropub bookmark creations.
* Uses res.on("finish") so it never interferes with the request lifecycle.
*/
get contentNegotiationRoutes() {
return bookmarkHookRouter;
}
/**
* Protected routes (require authentication)
* Admin dashboard and management
@@ -149,4 +193,4 @@ export default class BlogrollEndpoint {
destroy() {
stopSync();
}
}
}