feat(federation): add POST /api/federation/peers/:id/reset
Admin-only endpoint for recovering from needs_attention. Deletes the local peer row; FK cascade removes queued outbox entries. Gated to peers in needs_attention to prevent accidental resets of healthy peerings (use /peers/:id for revoke on active peers). Also extends the Task 8.5 test mock of '../db/index.js' to re-export `schema`. federation.ts imports `schema` from the re-export alongside `getDb`; the previous mock only exposed `getDb`, causing the route handler to blow up with 500s before reaching any assertion. This is a scaffolding fix — no test assertions were changed.
This commit is contained in:
@@ -16,8 +16,10 @@ let currentUserId = 'admin-user';
|
|||||||
let currentUserIsAdmin = true;
|
let currentUserIsAdmin = true;
|
||||||
|
|
||||||
// Mock getDb BEFORE importing the route module so the module reads our test DB.
|
// 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', () => ({
|
vi.mock('../db/index.js', () => ({
|
||||||
getDb: () => testDb,
|
getDb: () => testDb,
|
||||||
|
schema,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Mock the auth middleware to honour test-controlled currentUserId / currentUserIsAdmin.
|
// Mock the auth middleware to honour test-controlled currentUserId / currentUserIsAdmin.
|
||||||
|
|||||||
@@ -845,6 +845,45 @@ export async function federationRoutes(app: FastifyInstance): Promise<void> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ─── 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 ────────────────────────────────────────
|
// ─── PATCH /api/federation/peers/:id ────────────────────────────────────────
|
||||||
// Admin-only: update peer settings (e.g. auto-rotation interval).
|
// Admin-only: update peer settings (e.g. auto-rotation interval).
|
||||||
app.patch<{ Params: { id: string }; Body: { autoRotateIntervalDays?: number } }>(
|
app.patch<{ Params: { id: string }; Body: { autoRotateIntervalDays?: number } }>(
|
||||||
|
|||||||
Reference in New Issue
Block a user