114 lines
4.0 KiB
TypeScript
114 lines
4.0 KiB
TypeScript
/**
|
|
* Swarm Mention Endpoint
|
|
*
|
|
* POST: Receive a mention notification from another swarm node
|
|
*
|
|
* SECURITY: All requests must be cryptographically signed by the sender.
|
|
*/
|
|
|
|
import { NextRequest, NextResponse } from 'next/server';
|
|
import { db, users, notifications } from '@/db';
|
|
import { eq } from 'drizzle-orm';
|
|
import { z } from 'zod';
|
|
import { verifySwarmRequest } from '@/lib/swarm/signature';
|
|
import { localHandleSchema, nodeDomainSchema } from '@/lib/utils/federation';
|
|
|
|
const swarmMentionSchema = z.object({
|
|
mentionedHandle: localHandleSchema,
|
|
mention: z.object({
|
|
actorHandle: localHandleSchema,
|
|
actorDisplayName: z.string().min(1).max(50),
|
|
actorAvatarUrl: z.string().url().optional(),
|
|
actorNodeDomain: nodeDomainSchema,
|
|
postId: z.string().uuid(),
|
|
postContent: z.string().max(10000),
|
|
interactionId: z.string().uuid(),
|
|
timestamp: z.string().datetime(),
|
|
}),
|
|
signature: z.string(),
|
|
});
|
|
|
|
/**
|
|
* POST /api/swarm/interactions/mention
|
|
*
|
|
* Receives a mention notification from another swarm node.
|
|
*/
|
|
export async function POST(request: NextRequest) {
|
|
try {
|
|
if (!db) {
|
|
return NextResponse.json({ error: 'Database not available' }, { status: 503 });
|
|
}
|
|
|
|
const body = await request.json();
|
|
const data = swarmMentionSchema.parse(body);
|
|
|
|
// SECURITY: Verify the signature
|
|
const { signature, ...payload } = data;
|
|
const isValid = await verifySwarmRequest(payload, signature, data.mention.actorNodeDomain);
|
|
|
|
if (!isValid) {
|
|
console.warn(`[Swarm] Invalid signature for mention from ${data.mention.actorHandle}@${data.mention.actorNodeDomain}`);
|
|
return NextResponse.json({ error: 'Invalid signature' }, { status: 403 });
|
|
}
|
|
|
|
// Find the mentioned user (local user)
|
|
const mentionedUser = await db.query.users.findFirst({
|
|
where: eq(users.handle, data.mentionedHandle.toLowerCase()),
|
|
});
|
|
|
|
if (!mentionedUser) {
|
|
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
|
}
|
|
|
|
if (mentionedUser.isSuspended) {
|
|
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
|
}
|
|
|
|
// Create notification with actor info stored directly
|
|
try {
|
|
await db.insert(notifications).values({
|
|
userId: mentionedUser.id,
|
|
actorHandle: data.mention.actorHandle,
|
|
actorDisplayName: data.mention.actorDisplayName,
|
|
actorAvatarUrl: data.mention.actorAvatarUrl || null,
|
|
actorNodeDomain: data.mention.actorNodeDomain,
|
|
postContent: data.mention.postContent.slice(0, 200),
|
|
type: 'mention',
|
|
});
|
|
console.log(`[Swarm] Created mention notification for @${data.mentionedHandle} from ${data.mention.actorHandle}@${data.mention.actorNodeDomain}`);
|
|
} catch (notifError) {
|
|
console.error(`[Swarm] Failed to create mention notification:`, notifError);
|
|
}
|
|
|
|
// Also notify bot owner if this is a bot being mentioned
|
|
if (mentionedUser.isBot && mentionedUser.botOwnerId) {
|
|
try {
|
|
await db.insert(notifications).values({
|
|
userId: mentionedUser.botOwnerId,
|
|
actorHandle: data.mention.actorHandle,
|
|
actorDisplayName: data.mention.actorDisplayName,
|
|
actorAvatarUrl: data.mention.actorAvatarUrl || null,
|
|
actorNodeDomain: data.mention.actorNodeDomain,
|
|
postContent: data.mention.postContent.slice(0, 200),
|
|
type: 'mention',
|
|
});
|
|
} catch (err) {
|
|
console.error('[Swarm] Failed to notify bot owner:', err);
|
|
}
|
|
}
|
|
|
|
console.log(`[Swarm] Received mention from ${data.mention.actorHandle}@${data.mention.actorNodeDomain} for @${data.mentionedHandle}`);
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
message: 'Mention received',
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof z.ZodError) {
|
|
return NextResponse.json({ error: 'Invalid request', details: error.issues }, { status: 400 });
|
|
}
|
|
console.error('[Swarm] Mention error:', error);
|
|
return NextResponse.json({ error: 'Failed to process mention' }, { status: 500 });
|
|
}
|
|
}
|