docs: federation management design spec

This commit is contained in:
2026-05-15 03:28:01 +02:00
parent f7ac6f6476
commit 4533e35092

View File

@@ -0,0 +1,116 @@
# Federation Management Design
## Goal
Allow users to manage their ActivityPub federation: accept/reject incoming remote follow requests, remove accepted remote followers, and unfollow remote actors they're following. Surface this in three places via a shared component set.
## Architecture
Hexagonal layers respected throughout:
- **Application layer**: new use cases in `federation_management.rs` — all business routing lives here
- **Presentation layer**: handlers call use cases only, no direct port access
- **Frontend**: four components under `components/federation/`, used in three locations
---
## Backend
### New use cases — `crates/application/src/use_cases/federation_management.rs`
Six functions, each taking `&dyn FederationActionPort` and `&UserId`. Return domain types (`Vec<RemoteActor>` or `()`).
```
list_pending_requests(federation, user_id) → Result<Vec<RemoteActor>, DomainError>
accept_follow_request(federation, user_id, actor_url: &str) → Result<(), DomainError>
reject_follow_request(federation, user_id, actor_url: &str) → Result<(), DomainError>
list_remote_followers(federation, user_id) → Result<Vec<RemoteActor>, DomainError>
remove_remote_follower(federation, user_id, actor_url: &str) → Result<(), DomainError>
list_remote_following(federation, user_id) → Result<Vec<RemoteActor>, DomainError>
```
Unfollow remote reuses the existing `unfollow_actor` use case in `social.rs` (already routes `@handle` to `federation.unfollow_remote`).
### New HTTP endpoints — `crates/presentation/src/handlers/federation_management.rs`
All routes require authentication (`AuthUser` extractor). Actor URLs go in the JSON request body to avoid percent-encoding issues.
| Method | Path | Body | Action |
|--------|------|------|--------|
| `GET` | `/federation/me/followers/pending` | — | List pending follow requests |
| `POST` | `/federation/me/followers/accept` | `{ actor_url: String }` | Accept a follow request |
| `DELETE` | `/federation/me/followers` | `{ actor_url: String }` | Remove/reject a follower |
| `GET` | `/federation/me/followers` | — | List accepted remote followers |
| `GET` | `/federation/me/following` | — | List remote actors being followed |
| `DELETE` | `/federation/me/following` | `{ handle: String }` | Unfollow a remote actor (delegates to `unfollow_actor`) |
Handlers are thin: extract auth, call use case, return JSON. No logic.
---
## Frontend
### API client additions — `thoughts-frontend/lib/api.ts`
Six new functions mirroring the six endpoints. All take `token: string`.
```typescript
getPendingFollowRequests(token)
acceptFollowRequest(actorUrl: string, token)
rejectFollowRequest(actorUrl: string, token)
getRemoteFollowers(token)
removeRemoteFollower(actorUrl: string, token)
getRemoteFollowing(token)
// unfollowRemote reuses existing unfollowUser or a new call to DELETE /federation/me/following
```
Response schema: `RemoteActorSchema` (already defined — handle, display_name, avatar_url, url).
### Components — `thoughts-frontend/components/federation/`
**`pending-requests.tsx`**
- Client component
- Fetches `getPendingFollowRequests` on mount
- Renders list of remote actors with Accept and Reject buttons
- On action: optimistic removal from list, then API call
- Prop: `compact?: boolean` — when true, renders as a flat list without card chrome (for notifications embed)
**`remote-followers.tsx`**
- Client component
- Fetches `getRemoteFollowers` on mount
- Renders list of accepted remote followers with a Remove button
- On remove: optimistic removal, then API call
**`remote-following.tsx`**
- Client component
- Fetches `getRemoteFollowing` on mount
- Renders list of remote actors being followed with an Unfollow button
- On unfollow: optimistic removal, then API call
**`federation-panel.tsx`**
- Composes the three above inside a shadcn `Tabs` component
- Tabs: "Requests", "Followers", "Following"
- Shows a numeric badge on "Requests" tab when pending count > 0
- No data fetching of its own — delegates entirely to sub-components
### Usage locations
**`app/settings/federation/page.tsx`** (new)
- Server component shell, renders `<FederationPanel />`
- Add "Federation" link to the settings sidebar alongside "Profile" and "API Keys"
**`app/users/[username]/page.tsx`** (modify)
- Add a "Federation" tab to the profile tabs row
- Render `<FederationPanel />` as its content
- Tab is only visible when `isOwnProfile === true`
**Notifications page** (modify)
- Render `<PendingRequests compact />` as a card section above the notification feed
- Only shown when the user is authenticated
---
## Out of scope
- Local follow request management (local follows are auto-accepted)
- Blocking remote actors (separate feature, already partially implemented)
- Notification count badge in the nav for pending requests (can be added later)