From f56616d66efc8ccff1d3da564f710b8004a3aae6 Mon Sep 17 00:00:00 2001 From: svemagie <869694+svemagie@users.noreply.github.com> Date: Sat, 4 Apr 2026 09:58:48 +0200 Subject: [PATCH] remove automatic bookmark-to-blogroll import The hook was causing duplicate blog entries. Removed the contentNegotiationRoutes middleware, importBookmarkUrl function, and lib/bookmark-import.js entirely. Co-Authored-By: Claude Sonnet 4.6 --- index.js | 67 ------------------------------- lib/bookmark-import.js | 91 ------------------------------------------ 2 files changed, 158 deletions(-) delete mode 100644 lib/bookmark-import.js diff --git a/index.js b/index.js index 2c74647..ec01fe2 100644 --- a/index.js +++ b/index.js @@ -7,71 +7,12 @@ 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). -// -// NOTE: When the Microsub plugin is installed it acts as the single source of -// truth for bookmarks — it creates the feed subscription AND notifies the -// blogroll via notifyBlogroll(). This hook therefore skips processing if -// Microsub is available, acting only as a standalone fallback. -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; - - // Microsub plugin is installed → it will handle this bookmark and notify - // the blogroll. Skip direct import to avoid duplicate entries. - if (application.collections?.has("microsub_channels")) { - return; - } - - // Extract category from any micropub body format: - // form-encoded: category=tech or category[]=tech&category[]=web - // JF2 JSON: { "category": ["tech", "web"] } - // MF2 JSON: { "properties": { "category": ["tech"] } } - const rawCategory = - request.body?.category || - request.body?.properties?.category; - const category = Array.isArray(rawCategory) - ? rawCategory[0] || "bookmarks" - : rawCategory || "bookmarks"; - - importBookmarkUrl(application, bookmarkOf, category).catch((err) => - console.warn("[Blogroll] bookmark-import failed:", err.message) - ); - }); - - next(); -}); - const defaults = { mountPath: "/blogrollapi", syncInterval: 3600000, // 1 hour @@ -113,14 +54,6 @@ 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 diff --git a/lib/bookmark-import.js b/lib/bookmark-import.js deleted file mode 100644 index 429d6cd..0000000 --- a/lib/bookmark-import.js +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Bookmark → blogroll import - * Called when a micropub bookmark post is created. - * Discovers feeds for the bookmarked site's origin and adds them to the blogroll. - * @module lib/bookmark-import - */ - -import { discoverFeeds } from "./utils/feed-discovery.js"; -import { upsertBlog } from "./storage/blogs.js"; - -/** - * Import a bookmarked URL's site into the blogroll. - * Extracts the origin URL, discovers feeds, and upserts the first feed as a blog entry. - * - * @param {object} application - Indiekit application object - * @param {string} bookmarkUrl - The URL that was bookmarked (bookmark-of value) - * @param {string} [category="bookmarks"] - Category to assign in the blogroll - * @returns {Promise} Result { added, alreadyExists, noFeeds, error } - */ -export async function importBookmarkUrl(application, bookmarkUrl, category = "bookmarks") { - // Normalise: bookmark-of may be an array in some micropub clients - const url = Array.isArray(bookmarkUrl) ? bookmarkUrl[0] : bookmarkUrl; - - let siteUrl; - try { - siteUrl = new URL(url).origin; - } catch { - return { error: `Invalid bookmark URL: ${url}` }; - } - - // Guard: blogroll DB must be available - if (typeof application.getBlogrollDb !== "function") { - console.warn("[Blogroll] bookmark-import: getBlogrollDb not available"); - return { error: "blogroll not initialised" }; - } - - const db = application.getBlogrollDb(); - - // Check if any active blog with this siteUrl is already in the blogroll - const existing = await db.collection("blogrollBlogs").findOne({ - siteUrl, - status: { $ne: "deleted" }, - }); - - if (existing) { - // If the category differs, update it (tag changed on the bookmark post) - if (existing.category !== category) { - await db.collection("blogrollBlogs").updateOne( - { _id: existing._id }, - { $set: { category, updatedAt: new Date().toISOString() } }, - ); - console.log( - `[Blogroll] bookmark-import: updated category for "${existing.title}" → "${category}"`, - ); - return { updated: true, siteUrl }; - } - console.log( - `[Blogroll] bookmark-import: ${siteUrl} already in blogroll ("${existing.title}")`, - ); - return { alreadyExists: true, siteUrl }; - } - - // Discover feeds from the origin - const discovery = await discoverFeeds(siteUrl); - - if (!discovery.success || discovery.feeds.length === 0) { - console.log(`[Blogroll] bookmark-import: no feeds found for ${siteUrl}`); - return { noFeeds: true, siteUrl }; - } - - // Add the first discovered feed - const feed = discovery.feeds[0]; - const result = await upsertBlog(application, { - title: discovery.pageTitle || siteUrl, - feedUrl: feed.url, - siteUrl, - feedType: feed.type || "rss", - category, - source: "bookmark", - sourceId: null, - status: "active", - }); - - if (result.upserted) { - console.log( - `[Blogroll] bookmark-import: added ${feed.url} ("${discovery.pageTitle || siteUrl}") → category "${category}"` - ); - } - - return { added: result.upserted ? 1 : 0, siteUrl }; -} \ No newline at end of file