Files
Synapsis/src/app/api/federation/gossip/route.ts
T
2026-01-26 08:34:48 +01:00

61 lines
2.1 KiB
TypeScript

import { NextResponse } from 'next/server';
import { z } from 'zod';
import { requireAdmin } from '@/lib/auth/admin';
import { upsertHandleEntries } from '@/lib/federation/handles';
const gossipSchema = z.object({
nodes: z.array(z.string().min(1)).min(1),
since: z.string().optional(),
});
export async function POST(request: Request) {
try {
await requireAdmin();
const body = await request.json();
const data = gossipSchema.parse(body);
const results = [];
for (const node of data.nodes) {
const baseUrl = node.startsWith('http') ? node : `https://${node}`;
const url = new URL('/.well-known/synapsis-handles', baseUrl);
if (data.since) {
url.searchParams.set('since', data.since);
}
try {
const res = await fetch(url.toString(), { method: 'GET' });
if (!res.ok) {
results.push({ node, success: false, error: `HTTP ${res.status}` });
continue;
}
const payload = await res.json();
const handles = Array.isArray(payload?.handles) ? payload.handles : [];
const merged = await upsertHandleEntries(handles);
results.push({
node,
success: true,
added: merged.added,
updated: merged.updated,
});
} catch (error) {
results.push({ node, success: false, error: 'Fetch failed' });
}
}
return NextResponse.json({ results });
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json({ error: 'Invalid payload', details: error.issues }, { status: 400 });
}
if (error instanceof Error && error.message === 'Admin required') {
return NextResponse.json({ error: 'Admin required' }, { status: 403 });
}
console.error('Gossip error:', error);
return NextResponse.json({ error: 'Failed to gossip handles' }, { status: 500 });
}
}