Ricardo 239059deb7 fix: add missing sync and form locale keys across all languages
Added blogroll.sync.* keys (success, error, already_running, cleared_success)
and sources.form.* keys (microsubChannel, categoryPrefix with hints) that were
referenced in controllers/templates but missing from locale files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 10:19:15 +01:00
2026-02-13 18:21:59 +01:00
2026-02-13 18:21:59 +01:00

Blogroll Endpoint for Indiekit

An Indiekit plugin that provides a comprehensive blogroll management system with feed aggregation, admin UI, and public API.

Features

  • Multiple Source Types: Import blogs from OPML files/URLs, Microsub subscriptions, or add manually
  • Background Feed Fetching: Automatically syncs blogs and caches recent items
  • Microsub Integration: Mirror your Microsub subscriptions as a blogroll (zero duplication)
  • Admin UI: Manage sources, blogs, and view recent activity
  • Public JSON API: Read-only endpoints for frontend integration
  • OPML Export: Export your blogroll as OPML (all or by category)
  • Feed Discovery: Auto-discover feeds from website URLs
  • Item Retention: Automatic cleanup of old items (encourages fresh content discovery)

Installation

npm install @rmdes/indiekit-endpoint-blogroll

Configuration

Add to your indiekit.config.js:

import BlogrollEndpoint from "@rmdes/indiekit-endpoint-blogroll";

export default {
  plugins: [
    new BlogrollEndpoint({
      mountPath: "/blogrollapi",    // Admin UI and API base path
      syncInterval: 3600000,         // 1 hour (in milliseconds)
      maxItemsPerBlog: 50,           // Items to fetch per blog
      maxItemAge: 7,                 // Days - older items auto-deleted
      fetchTimeout: 15000            // 15 seconds per feed fetch
    })
  ]
};

Requirements

  • Indiekit: >=1.0.0-beta.25
  • MongoDB: Required for data storage
  • Optional: @rmdes/indiekit-endpoint-microsub for Microsub integration

Usage

Admin UI

Navigate to /blogrollapi in your Indiekit instance to access:

  • Dashboard: View sync status, blog counts, recent activity
  • Sources: Manage OPML and Microsub sources
  • Blogs: Add/edit/delete individual blogs, refresh feeds
  • Manual Sync: Trigger immediate sync or clear and resync

Source Types

  1. OPML URL: Point to a public OPML file (e.g., your feed reader's export)
  2. OPML File: Paste OPML XML directly into the form
  3. Microsub: Import subscriptions from your Microsub channels
  4. Manual: Add individual blog feeds one at a time

Public API

All API endpoints return JSON (except OPML export which returns XML).

List Blogs

GET /blogrollapi/api/blogs?category=Tech&limit=100&offset=0

Get Blog with Recent Items

GET /blogrollapi/api/blogs/:id

List Items Across All Blogs

GET /blogrollapi/api/items?blog=<id>&category=Tech&limit=50&offset=0

List Categories

GET /blogrollapi/api/categories

Sync Status

GET /blogrollapi/api/status

Export OPML

GET /blogrollapi/api/opml                  (all blogs)
GET /blogrollapi/api/opml/:category        (specific category)

Example Response

GET /blogrollapi/api/blogs

{
  "items": [
    {
      "id": "507f1f77bcf86cd799439011",
      "title": "Example Blog",
      "description": "A great blog about tech",
      "feedUrl": "https://example.com/feed",
      "siteUrl": "https://example.com",
      "feedType": "rss",
      "category": "Tech",
      "tags": ["programming", "web"],
      "photo": "https://example.com/icon.png",
      "status": "active",
      "itemCount": 25,
      "pinned": false,
      "lastFetchAt": "2026-02-13T10:30:00.000Z"
    }
  ],
  "total": 42,
  "hasMore": true
}

GET /blogrollapi/api/items

{
  "items": [
    {
      "id": "507f1f77bcf86cd799439011",
      "url": "https://example.com/post/hello",
      "title": "Hello World",
      "summary": "My first blog post...",
      "published": "2026-02-13T10:00:00.000Z",
      "isFuture": false,
      "author": { "name": "Jane Doe" },
      "photo": ["https://example.com/image.jpg"],
      "categories": ["announcement"],
      "blog": {
        "id": "507f1f77bcf86cd799439011",
        "title": "Example Blog",
        "siteUrl": "https://example.com",
        "category": "Tech",
        "photo": "https://example.com/icon.png"
      }
    }
  ],
  "hasMore": false
}

Microsub Integration

If you have @rmdes/indiekit-endpoint-microsub installed, the blogroll can mirror your subscriptions:

  1. Create a Microsub source in the admin UI
  2. Select specific channels or sync all channels
  3. Add a category prefix (optional) to distinguish Microsub blogs
  4. Blogs and items are referenced, not duplicated

Benefits:

  • Zero data duplication - items are served directly from Microsub
  • Automatic orphan cleanup when feeds are unsubscribed
  • Webhook support for real-time updates

Background Sync

The plugin automatically syncs in the background:

  1. Initial Sync: Runs 15 seconds after server startup
  2. Periodic Sync: Runs every syncInterval milliseconds (default 1 hour)
  3. What it Does:
    • Syncs enabled sources (OPML/Microsub)
    • Fetches new items from active blogs
    • Deletes items older than maxItemAge days
    • Updates sync statistics

Manual Sync:

  • Trigger from the dashboard
  • Use POST /blogrollapi/sync (protected endpoint)
  • Use POST /blogrollapi/clear-resync to clear and resync all

Feed Discovery

The plugin includes auto-discovery for finding feeds from website URLs:

// In the admin UI, when adding a blog, paste a website URL
// The plugin will:
// 1. Check <link rel="alternate"> tags in HTML
// 2. Try common feed paths (/feed, /rss, /atom.xml, etc.)
// 3. Suggest discovered feeds

Item Retention

By default, items older than 7 days are automatically deleted during sync. This encourages discovery of fresh content rather than archiving everything.

To Change Retention:

new BlogrollEndpoint({
  maxItemAge: 30  // Keep items for 30 days instead
})

Blog Status

  • active: Blog is working, fetching items normally
  • error: Last fetch failed (see lastError for details)
  • deleted: Soft-deleted, won't be recreated by sync

Navigation

The plugin adds itself to Indiekit's navigation:

  • Menu Item: "Blogroll" (requires database)
  • Shortcut: Bookmark icon in admin dashboard

Security

  • Protected Routes: Admin UI and management endpoints require authentication
  • Public Routes: Read-only API endpoints are publicly accessible
  • XSS Prevention: Feed content is sanitized with sanitize-html
  • Feed Discovery: Protected to prevent abuse (requires authentication)

Supported Feed Formats

  • RSS 2.0
  • Atom 1.0
  • JSON Feed 1.0

Contributing

Report issues at: https://github.com/rmdes/indiekit-endpoint-blogroll/issues

License

MIT

Description
No description provided
Readme 180 KiB
Languages
JavaScript 75.5%
Nunjucks 20.8%
CSS 3.7%