social-federation tasks 3.1–3.4, 4.1–4.8. With this, a Mastodon user can follow a public trails user: WebFinger → actor fetch → signed Follow → recorded + Accept(Follow) pushed back. Identity surface (section 3): - Actor objects now carry the user's public key (publicKey + assertionMethods via Fedify key pairs dispatcher; keys generated lazily as a fallback to the backfill) and an inbox IRI; url uses localActorIri (3.1). - Software discovery shipped as standard NodeInfo (/.well-known/nodeinfo + /nodeinfo/2.1, software.name trails-cool) instead of the originally-sketched custom AS actor field — Fedify's typed vocab can't emit arbitrary actor props and NodeInfo is what the fediverse reads. Artifacts updated accordingly (3.4). Inbox (section 4): - /users/:username/inbox resource route; HTTP Signatures verified by Fedify before any listener runs (4.1). Rate-limited 60 req/5 min per source instance (host from Signature keyId) BEFORE verification so hostile instances can't burn CPU on key fetches (4.8). - Listeners: Follow → auto-accept for public profiles + Accept pushed back (4.2); Undo(Follow) → row removed (4.3); Accept(Follow) → Pending settled + first outbox poll enqueued (4.4); Reject(Follow) → Pending dropped (4.5; UI notice deferred to 6.6). Unhandled types are acknowledged + dropped by Fedify (4.6). - Replay protection via Fedify's KvStore, now Postgres-backed (journal.federation_kv + daily sweep job) so dedupe survives restarts (4.7). Schema (discovered requirement): - follows.follower_id relaxed to nullable + follows.follower_actor_iri for inbound remote followers — the proposal's 'follows is already federation-ready' only held for outbound. Check constraint enforces exactly one follower identity; partial unique index dedupes remote follows. Notification fan-out + approve flow now filter local followers explicitly. Design/proposal updated. Tests: 7 inbox integration tests (accept/refuse/idempotence/undo/ settle/reject/check-constraint), 6 KvStore integration tests, 2 unit tests for source-host extraction. All against real Postgres, gated on FEDERATION_INTEGRATION=1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
82 lines
4.8 KiB
TypeScript
82 lines
4.8 KiB
TypeScript
import { type RouteConfig, index, route } from "@react-router/dev/routes";
|
|
|
|
export default [
|
|
index("routes/home.tsx"),
|
|
route(".well-known/trails-cool", "routes/api.well-known.trails-cool.ts"),
|
|
route(".well-known/webfinger", "routes/api.well-known.webfinger.ts"),
|
|
route(".well-known/nodeinfo", "routes/api.well-known.nodeinfo.ts"),
|
|
route("nodeinfo/2.1", "routes/api.nodeinfo.ts"),
|
|
route("users/:username/inbox", "routes/users.$username.inbox.ts"),
|
|
route("oauth/authorize", "routes/oauth.authorize.tsx"),
|
|
route("oauth/token", "routes/oauth.token.ts"),
|
|
route("auth/register", "routes/auth.register.tsx"),
|
|
route("auth/login", "routes/auth.login.tsx"),
|
|
route("auth/verify", "routes/auth.verify.tsx"),
|
|
route("auth/logout", "routes/auth.logout.tsx"),
|
|
route("auth/accept-terms", "routes/auth.accept-terms.tsx"),
|
|
route("api/auth/register", "routes/api.auth.register.ts"),
|
|
route("api/auth/login", "routes/api.auth.login.ts"),
|
|
route("routes", "routes/routes._index.tsx"),
|
|
route("routes/new", "routes/routes.new.tsx"),
|
|
route("routes/:id", "routes/routes.$id.tsx"),
|
|
route("routes/:id/edit", "routes/routes.$id.edit.tsx"),
|
|
route("api/e2e/seed", "routes/api.e2e.seed.ts"),
|
|
route("api/e2e/route/:id", "routes/api.e2e.route.$id.ts"),
|
|
route("api/e2e/komoot", "routes/api.e2e.komoot.ts"),
|
|
route("api/routes/:id/callback", "routes/api.routes.$id.callback.ts"),
|
|
route("api/routes/:id/edit-in-planner", "routes/api.routes.$id.edit-in-planner.ts"),
|
|
route("api/routes/:id/gpx", "routes/api.routes.$id.gpx.ts"),
|
|
route("activities", "routes/activities._index.tsx"),
|
|
route("activities/new", "routes/activities.new.tsx"),
|
|
route("activities/:id", "routes/activities.$id.tsx"),
|
|
route("users/:username", "routes/users.$username.tsx"),
|
|
route("users/:username/followers", "routes/users.$username.followers.tsx"),
|
|
route("users/:username/following", "routes/users.$username.following.tsx"),
|
|
route("api/users/:username/follow", "routes/api.users.$username.follow.ts"),
|
|
route("api/users/:username/unfollow", "routes/api.users.$username.unfollow.ts"),
|
|
route("follows/requests", "routes/follows.requests.tsx"),
|
|
route("api/follows/:id/approve", "routes/api.follows.$id.approve.ts"),
|
|
route("api/follows/:id/reject", "routes/api.follows.$id.reject.ts"),
|
|
route("feed", "routes/feed.tsx"),
|
|
route("explore", "routes/explore.tsx"),
|
|
route("api/events", "routes/api.events.ts"),
|
|
route("notifications", "routes/notifications.tsx"),
|
|
route("api/notifications/:id/read", "routes/api.notifications.$id.read.ts"),
|
|
route("api/notifications/read-all", "routes/api.notifications.read-all.ts"),
|
|
route("settings", "routes/settings.tsx", [
|
|
index("routes/settings._index.tsx"),
|
|
route("profile", "routes/settings.profile.tsx"),
|
|
route("account", "routes/settings.account.tsx"),
|
|
route("security", "routes/settings.security.tsx"),
|
|
route("connections", "routes/settings.connections.tsx"),
|
|
route("connections/komoot", "routes/settings.connections.komoot.tsx"),
|
|
]),
|
|
route("api/settings/profile", "routes/api.settings.profile.ts"),
|
|
route("api/settings/email", "routes/api.settings.email.ts"),
|
|
route("api/settings/passkey/delete", "routes/api.settings.passkey.delete.ts"),
|
|
route("api/settings/delete-account", "routes/api.settings.delete-account.ts"),
|
|
route("sync/import/komoot", "routes/sync.import.komoot.tsx"),
|
|
route("sync/import/:provider", "routes/sync.import.$provider.tsx"),
|
|
route("api/sync/komoot/verify", "routes/api.sync.komoot.verify.ts"),
|
|
route("api/sync/komoot/connect", "routes/api.sync.komoot.connect.ts"),
|
|
route("api/sync/komoot/import", "routes/api.sync.komoot.import.ts"),
|
|
route("api/sync/komoot/import-status", "routes/api.sync.komoot.import-status.ts"),
|
|
route("api/sync/connect/:provider", "routes/api.sync.connect.$provider.ts"),
|
|
route("api/sync/callback/:provider", "routes/api.sync.callback.$provider.ts"),
|
|
route("api/sync/disconnect/:provider", "routes/api.sync.disconnect.$provider.ts"),
|
|
route("api/sync/webhook/:provider", "routes/api.sync.webhook.$provider.ts"),
|
|
route("api/sync/push/:provider/:routeId", "routes/api.sync.push.$provider.$routeId.ts"),
|
|
route("privacy", "routes/privacy.tsx"),
|
|
route("legal/imprint", "routes/legal.imprint.tsx"),
|
|
route("legal/terms", "routes/legal.terms.tsx"),
|
|
route("legal/privacy", "routes/legal.privacy.tsx"),
|
|
// REST API v1
|
|
route("api/v1/routes", "routes/api.v1.routes._index.ts"),
|
|
route("api/v1/routes/compute", "routes/api.v1.routes.compute.ts"),
|
|
route("api/v1/routes/:id", "routes/api.v1.routes.$id.ts"),
|
|
route("api/v1/activities", "routes/api.v1.activities._index.ts"),
|
|
route("api/v1/activities/:id", "routes/api.v1.activities.$id.ts"),
|
|
route("api/v1/auth/devices", "routes/api.v1.auth.devices.ts"),
|
|
route("api/v1/auth/devices/:id", "routes/api.v1.auth.devices.$id.ts"),
|
|
route("api/v1/uploads", "routes/api.v1.uploads.ts"),
|
|
] satisfies RouteConfig;
|