263 Commits

Author SHA1 Message Date
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