refactor(server): split federation routes into cohesive modules (#9)

routes/federation.ts had grown to 7.6k lines, spanning HTTP route
registration, federated identity resolution, ~30 inbound relay event
processors, DM reconciliation, and rate-limiting internals — too large
to review or hold in context, and awkward to change safely.

Split the implementation into 18 focused modules under routes/federation/
(helpers, events/, handlers/) and keep routes/federation.ts as a thin
barrel that re-exports the public API and composes the HTTP registrars
into federationRoutes(). No import paths change anywhere else.

Pure move, no behavior change:
- 61/61 named functions byte-identical; only deltas are 2 dynamic-import
  paths adjusted for the new directory depth
- public export surface unchanged (barrel re-exports all 22 symbols)
- all 30 endpoints preserved (identical verb+path set)
- typecheck, build, and full server suite (790 tests) green

Docs: update federation.md source-file map; add split design doc.
This commit is contained in:
TheZwiss
2026-07-10 02:08:03 +02:00
committed by GitHub
parent 180228f2d1
commit 94fe73522d
21 changed files with 8063 additions and 7620 deletions
@@ -0,0 +1,124 @@
import { getDb } from '../../../db/index.js';
import { and } from 'drizzle-orm';
import type { FederationRelayEvent } from '@backspace/shared';
import { processDmCallAcceptEvent, processDmCallEndEvent, processDmCallRejectEvent, processDmCallStartEvent, processDmTypingStartEvent, processDmTypingStopEvent } from './calls.js';
import { processCreateEvent, processDeleteEvent, processReactionAddEvent, processReactionRemoveEvent, processUpdateEvent } from './dmMessages.js';
import { processDmCloseEvent, processDmReopenEvent, processFileRejectedEvent, processPresenceUpdateEvent, processReadStateUpdateEvent } from './dmState.js';
import { processFriendAddEvent, processFriendRemoveEvent, processFriendRequestCancelEvent, processFriendRequestCreateEvent, processFriendRequestUpdateEvent } from './friends.js';
import { processGroupMetadataUpdateEvent, processMemberAddEvent, processMemberRemoveEvent, processOwnershipTransferEvent } from './membership.js';
import { processProfileUpdateEvent } from '../profile.js';
/**
* Process an array of federation relay events. Used by the HTTP relay endpoint
* and directly by the initial-sync worker (which skips the HTTP round-trip).
*/
export async function processRelayEvents(
events: FederationRelayEvent[],
sourceInstance: string,
peerOrigin: string,
db: ReturnType<typeof getDb>,
): Promise<{
accepted: string[];
rejected: Array<{ messageId: string; reason: string }>;
undeliverable: Array<{ messageId: string; reason: string }>;
}> {
const accepted: string[] = [];
const rejected: Array<{ messageId: string; reason: string }> = [];
const undeliverable: Array<{ messageId: string; reason: string }> = [];
for (const event of events) {
try {
switch (event.eventType) {
case 'create':
await processCreateEvent(event, sourceInstance, peerOrigin, db, accepted, rejected);
break;
case 'update':
processUpdateEvent(event, sourceInstance, db, accepted, rejected);
break;
case 'delete':
processDeleteEvent(event, sourceInstance, db, accepted, rejected);
break;
case 'reaction_add':
processReactionAddEvent(event, sourceInstance, db, accepted, rejected);
break;
case 'reaction_remove':
processReactionRemoveEvent(event, sourceInstance, db, accepted, rejected);
break;
case 'member_add':
await processMemberAddEvent(event, sourceInstance, db, accepted, rejected);
break;
case 'member_remove':
processMemberRemoveEvent(event, sourceInstance, db, accepted, rejected);
break;
case 'ownership_transfer':
processOwnershipTransferEvent(event, sourceInstance, db, accepted, rejected);
break;
case 'friend_request_create':
await processFriendRequestCreateEvent(event, sourceInstance, db, accepted, rejected);
break;
case 'friend_request_update':
processFriendRequestUpdateEvent(event, sourceInstance, db, accepted, rejected);
break;
case 'friend_request_cancel':
processFriendRequestCancelEvent(event, sourceInstance, db, accepted, rejected);
break;
case 'friend_add':
await processFriendAddEvent(event, sourceInstance, db, accepted, rejected);
break;
case 'friend_remove':
processFriendRemoveEvent(event, sourceInstance, db, accepted, rejected);
break;
case 'file_rejected':
processFileRejectedEvent(event, sourceInstance, db, accepted, rejected);
break;
case 'dm_call_start':
processDmCallStartEvent(event, sourceInstance, db, accepted, rejected, undeliverable);
break;
case 'dm_call_accept':
processDmCallAcceptEvent(event, sourceInstance, db, accepted, rejected);
break;
case 'dm_call_reject':
processDmCallRejectEvent(event, sourceInstance, db, accepted, rejected);
break;
case 'dm_call_end':
processDmCallEndEvent(event, sourceInstance, db, accepted, rejected);
break;
case 'dm_typing_start':
processDmTypingStartEvent(event, sourceInstance, db, accepted, rejected);
break;
case 'dm_typing_stop':
processDmTypingStopEvent(event, sourceInstance, db, accepted, rejected);
break;
case 'profile_update':
await processProfileUpdateEvent(event, sourceInstance, db, accepted, rejected);
break;
case 'group_metadata_update':
await processGroupMetadataUpdateEvent(event, sourceInstance, db, accepted, rejected);
break;
case 'presence_update':
processPresenceUpdateEvent(event, sourceInstance, db, accepted, rejected);
break;
case 'read_state_update':
processReadStateUpdateEvent(event, sourceInstance, db, accepted, rejected);
break;
case 'dm_close':
processDmCloseEvent(event, sourceInstance, db, accepted, rejected);
break;
case 'dm_reopen':
processDmReopenEvent(event, sourceInstance, db, accepted, rejected);
break;
default:
rejected.push({ messageId: event.messageId, reason: 'unknown_event_type' });
break;
}
} catch (err) {
const errMsg = err instanceof Error ? err.message : 'unknown_error';
console.error(`[federation-relay] Error processing event ${event.messageId}:`, errMsg);
rejected.push({ messageId: event.messageId, reason: 'processing_error' });
}
}
return { accepted, rejected, undeliverable };
}
// ─── Helpers ────────────────────────────────────────────────────────────────