diff --git a/packages/server/src/routes/federation.reset.test.ts b/packages/server/src/routes/federation.reset.test.ts index 079d887a..d1d13aaa 100644 --- a/packages/server/src/routes/federation.reset.test.ts +++ b/packages/server/src/routes/federation.reset.test.ts @@ -16,8 +16,10 @@ let currentUserId = 'admin-user'; let currentUserIsAdmin = true; // Mock getDb BEFORE importing the route module so the module reads our test DB. +// Must also re-export `schema` because federation.ts imports it from '../db/index.js'. vi.mock('../db/index.js', () => ({ getDb: () => testDb, + schema, })); // Mock the auth middleware to honour test-controlled currentUserId / currentUserIsAdmin. diff --git a/packages/server/src/routes/federation.ts b/packages/server/src/routes/federation.ts index e0a21f6b..bccb532c 100644 --- a/packages/server/src/routes/federation.ts +++ b/packages/server/src/routes/federation.ts @@ -845,6 +845,45 @@ export async function federationRoutes(app: FastifyInstance): Promise { }, ); + // ─── POST /api/federation/peers/:id/reset ────────────────────────────────── + // Admin-only: reset a peer that has transitioned to needs_attention. + // Deletes the local peer row (cascade-deletes outbox entries via FK). + // Admin must re-initiate peering out of band after reset. + app.post<{ Params: { id: string } }>( + '/api/federation/peers/:id/reset', + { preHandler: [authenticate, requireAdmin] }, + async (request, reply) => { + const { id } = request.params; + const db = getDb(); + + const peer = db + .select() + .from(schema.federationPeers) + .where(eq(schema.federationPeers.id, id)) + .get(); + + if (!peer) { + return reply.code(404).send({ error: 'Peer not found', statusCode: 404 }); + } + + if (peer.status !== 'needs_attention') { + return reply.code(400).send({ + error: 'Reset is only available for peers in the needs_attention state. Use revoke for active peers.', + statusCode: 400, + }); + } + + // Cascade-delete handles federation_outbox entries (FK onDelete: 'cascade'). + db.delete(schema.federationPeers) + .where(eq(schema.federationPeers.id, id)) + .run(); + + connectionManager.sendToAdmins({ type: 'federation_peers_changed' as const }); + + return reply.code(200).send({ success: true }); + }, + ); + // ─── PATCH /api/federation/peers/:id ──────────────────────────────────────── // Admin-only: update peer settings (e.g. auto-rotation interval). app.patch<{ Params: { id: string }; Body: { autoRotateIntervalDays?: number } }>(