Commit Graph

28 Commits

Author SHA1 Message Date
Ricardo
432bb7a64a fix: include federated accounts in progress bar calculation
When Accept(Follow) arrives, source transitions from refollow:sent
to federation. Without counting federated in the total, those
accounts drop out of both numerator and denominator, making the
progress bar stay flat or go backwards.
2026-02-20 08:32:51 +01:00
Ricardo
473624c709 fix: guard getInReplyTo call for partial Fedify objects
Some resolved Note objects from Create activities don't have
getInReplyTo as a function (Fedify stub/partial resolution).
Add typeof check and try-catch to prevent inbox processing crash.
2026-02-20 08:22:43 +01:00
Ricardo
84122cc470 feat: batch re-follow system for imported AP accounts
After Mastodon migration, imported accounts exist only locally — no
Follow activities were sent. This adds a gradual background processor
that sends Follow activities to all source:"import" accounts so remote
servers start delivering Create activities to our inbox.

- New batch engine (lib/batch-refollow.js) processes 10 accounts per
  batch with 3s between follows and 30s between batches
- Accept(Follow) inbox listener transitions source to "federation"
  and cleans up tracking fields
- Admin API: pause, resume, and status JSON endpoints
- Dashboard progress bar with Alpine.js polling (10s interval)
- Following list badges for refollow:sent and refollow:failed states
- Restart recovery resets stale refollow:pending back to import
- 3 retries with 1-hour cooldown before permanent failure
2026-02-20 08:10:45 +01:00
Ricardo
656b66c780 fix: add WebFinger handle mapper and Ed25519 key pair
1. mapHandle() — tells Fedify how to resolve WebFinger usernames to
   actor identifiers, suppressing the "No actor handle mapper is set"
   warning on every WebFinger lookup.

2. Ed25519 key pair — generated alongside the legacy RSA pair so Fedify
   can create Object Integrity Proofs on outbound activities. RSA is
   kept for HTTP Signatures backward compatibility.
2026-02-19 20:18:16 +01:00
Ricardo
8a03dc9c9d chore: bump version to 1.0.9 2026-02-19 19:52:27 +01:00
Ricardo
53db99400a fix: suppress LogTape context-local storage warning
Add AsyncLocalStorage to LogTape configure() to fix the repeated
"Context-local storage is not configured" warning that appeared
before every Fedify log entry. Also remove unused getLogger import.
2026-02-19 19:52:05 +01:00
Ricardo
420973e5ec fix: start delivery queue and enable authenticated shared inbox fetches
Two critical fixes for ActivityPub federation:

1. Call federation.startQueue() — without this, ctx.sendActivity() enqueues
   delivery tasks but the InProcessMessageQueue never processes them, so
   activities are never actually POSTed to follower inboxes.

2. Add setSharedKeyDispatcher on the shared inbox — enables Fedify to make
   signed/authenticated GET requests when verifying incoming HTTP Signatures.
   Servers with authorized fetch (e.g. hachyderm.io) return 401 on unsigned
   requests, which prevented Fedify from fetching sender public keys and
   caused all incoming activities to be rejected.
2026-02-19 19:34:53 +01:00
Ricardo
fb8d90232b feat: add Fedify LogTape logging for delivery visibility
Configure LogTape to route Fedify's internal logs (federation, vocab,
delivery) to console at info level. This makes activity delivery
attempts, HTTP signature issues, and queue processing visible in
container logs.
2026-02-19 19:19:15 +01:00
Ricardo
78f6d8b34a chore: bump version to 1.0.6 2026-02-19 18:12:42 +01:00
Ricardo
e40ffbf61d feat: add outbound follow/unfollow, activity logging, and Microsub timeline integration
- Add followActor() and unfollowActor() methods for sending Follow/Undo(Follow) activities
- Add shared activity-log.js utility for logging to ap_activities collection
- Log all outbound activities (syndication, follow, unfollow) with success/failure details
- Update inbox Create listener to store timeline items from followed accounts
- Add Microsub collection accessors for cross-plugin timeline integration
- Refactor inbox-listeners to use shared activity logging utility
2026-02-19 18:11:28 +01:00
Ricardo
d055408aad feat: append permalink to syndicated AP content + bump to 1.0.5
Notes and articles syndicated to ActivityPub now include a clickable
link back to the canonical post URL at the end of the content body.
This ensures fediverse clients display a visible permalink, since the
Note url property alone is not shown inline by most implementations.
2026-02-19 16:08:20 +01:00
Ricardo
b0b0605985 feat: add Reply content/targetUrl fields, fix followers card photo crash
- inbox-listeners.js: Store `targetUrl` (inReplyTo) and `content` (HTML)
  on Reply activities for the conversations plugin AP adapter
- activitypub-followers.njk: Fix photo property name (`src` → `url`)
  to match the card component's expected interface, fixing TypeError
  crash on followers page when avatars are present
- Bump to v1.0.4
2026-02-19 15:10:34 +01:00
Ricardo
06e521cfa7 fix: skip Fedify middleware for non-GET requests in contentNegotiationRoutes
The contentNegotiationRoutes getter is mounted at root / and was passing
ALL requests through Fedify, including POST requests to admin routes.
fromExpressRequest() calls Readable.toWeb(req) which consumes the body
stream, causing "response body object should not be distributed or locked"
errors when admin controllers try to read req.body.

The v1.0.2 fix only protected routesPublic (mounted at /activitypub).
This fixes the actual culprit by skipping non-GET/HEAD methods in
contentNegotiationRoutes, since content negotiation and NodeInfo are
both GET-only concerns.
2026-02-19 12:52:02 +01:00
Ricardo
e461291178 fix: skip Fedify middleware for admin UI routes
POST to /admin/migrate was going through Fedify's federation.fetch()
which consumed the already-parsed request body stream, causing
"response body object should not be distributed or locked" errors.

Admin routes (/admin/*) are UI routes handled by authenticated
Express handlers, not federation endpoints.
2026-02-19 12:35:01 +01:00
Ricardo
599f15e8b4 fix: handle inbox GET, add missing activity type handlers
- Return 405 for GET on inbox endpoints instead of falling through
  to Indiekit's auth middleware (which redirects to login)
- Add handlers for Update (refresh follower data), Block (remove
  follower), Add and Remove (Mastodon pin/unpin — ignored)
- Bump to 1.0.1
2026-02-19 12:27:47 +01:00
Ricardo
f81b9212f7 chore: bump version to 1.0.0 for Fedify migration 2026-02-19 12:00:07 +01:00
Ricardo
eaf0f1d126 feat: migrate to Fedify for ActivityPub federation (v0.2.0)
Replace hand-rolled federation code with Fedify's battle-tested
implementation. This gives us proper HTTP Signatures, WebFinger,
NodeInfo, typed inbox listeners, and collection dispatchers out
of the box.

New modules:
- lib/federation-setup.js — Fedify Federation configuration
- lib/federation-bridge.js — Express↔Fedify middleware bridge
- lib/inbox-listeners.js — typed inbox handlers (Follow, Undo, etc.)
- lib/kv-store.js — MongoDB-backed KvStore adapter
- lib/controllers/profile.js — admin profile management
- views/activitypub-profile.njk — profile editing form

Removed hand-rolled modules:
- lib/actor.js, lib/federation.js, lib/inbox.js
- lib/keys.js, lib/webfinger.js

Key changes:
- Actor, inbox, outbox, followers, following all delegate to Fedify
- Syndication uses ctx.sendActivity() instead of manual delivery
- Profile managed via admin UI, stored in ap_profile collection
- Legacy PKCS#8 keys imported via Web Crypto API
- Custom bridge preserves Express mount path (req.originalUrl)
2026-02-19 11:59:23 +01:00
Ricardo
c522989d38 fix: parse CSV client-side to avoid 413 payload too large
Express's app-level body parser has a 100KB default limit that
runs before any route-level overrides. A 3K-line CSV at 113KB
exceeds this. Instead of sending raw CSV, the client now extracts
handles (first column only) and sends just the array — typically
under 90KB for 3000 accounts.
2026-02-19 10:41:20 +01:00
Ricardo
d159e79998 fix: switch CSV import to JSON fetch to bypass body size limit
The app-level Express urlencoded parser (100KB limit) runs before
route-level middleware, so overriding the limit on the route doesn't
help. Solution: POST the CSV as JSON via fetch() to a dedicated
/admin/migrate/import endpoint with its own express.json({ limit: '5mb' }).

- Import button now shows "Importing..." while working
- Results appear inline without page reload
- Failed handles shown in a collapsible details element
- Import button disabled until a file is selected
- Alias form remains a regular POST (small payload, no issue)
2026-02-19 10:22:34 +01:00
Ricardo
de00d3a16c fix: payload too large error and add migration logging
- Add express.urlencoded({ limit: '5mb' }) to migration POST route
  to handle large CSV files (default 100KB was too small)
- Add per-handle progress logging to console for monitoring imports
- Log failed handles with reasons (WebFinger failure, no AP link, etc.)
- Show failed handles in the UI result notification
- Use error notification type when all imports fail
2026-02-19 10:14:23 +01:00
Ricardo
fec4b1f242 fix: use client-side FileReader for CSV upload
Multipart form uploads fail because Indiekit has no multipart parsing
middleware. Instead, read the CSV file client-side with FileReader and
submit the text content as a hidden form field. Shows file name and
line count after selection for user confidence.
2026-02-19 09:50:43 +01:00
Ricardo
334f71d601 fix: improve migration page UX
- Show current alias value on the page (persists across GET/POST)
- Pre-fill alias input with current value
- Add fieldset legend and per-item hints to import checkboxes
- Add intro paragraph explaining the migration flow
- Rewrite copy to be clearer and more reassuring
- Note irreversibility of step 3 explicitly
2026-02-19 09:27:35 +01:00
Ricardo
cd587282f2 fix: add options property to syndicator for Indiekit compatibility
Indiekit's endpoint-posts accesses target.options.checked directly on
syndicator objects. Upstream syndicators are class instances with
this.options from the constructor. Our plain-object syndicator lacked
this property, causing a 500 TypeError on post creation.
2026-02-19 00:27:53 +01:00
Ricardo
43549c6334 fix: restructure locale keys to use proper nesting for migrate section
The i18n system resolves dots as nested path separators, but migrate
keys were flat strings with dots in the key name. Restructure migrate
as a nested object with a title sub-key.
2026-02-18 23:50:02 +01:00
Ricardo
deb9cb54a3 fix: prefix view templates to avoid Nunjucks name collisions
Rename all views to activitypub-*.njk to prevent collisions with other
plugins that have dashboard.njk (podroll). Fix all new Date() calls to
use .toISOString() per Indiekit convention. Add try-catch in syndicator
to prevent delivery failures from crashing post creation.
2026-02-18 23:10:35 +01:00
Ricardo
b551d82a21 fix: use Express 5 wildcard syntax for content negotiation route
Express 5 uses path-to-regexp v8 which requires named wildcards.
Bare "*" is no longer valid — use "{*path}" instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 22:37:32 +01:00
Ricardo
4350010d5d fix: remove filePath getter that conflicts with Indiekit plugin loader
Indiekit's getInstalledPlugins() assigns plugin.filePath via require.resolve().
Our getter made the property read-only, causing:
  TypeError: Cannot set property filePath which has only a getter

Let Indiekit set it instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 22:29:26 +01:00
Ricardo
da625592fd feat: ActivityPub federation endpoint for Indiekit
Implements full ActivityPub federation as an Indiekit plugin:
- Actor document (Person) with RSA key pair for HTTP Signatures
- WebFinger discovery (acct:rick@rmendes.net)
- Inbox: handles Follow, Undo, Like, Announce, Create, Delete, Move
- Outbox: serves published posts as ActivityStreams 2.0
- Content negotiation: AS2 JSON for AP clients, passthrough for browsers
- JF2-to-AS2 converter for all Indiekit post types
- Syndicator integration (pre-ticked checkbox for delivery to followers)
- Mastodon migration: alias config, CSV import for followers/following
- Admin UI: dashboard, followers, following, activity log, migration page
- Data retention: configurable TTL on activities, optional raw JSON storage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 22:13:51 +01:00