Files
Synapsis/src/app/api/swarm/chat/conversations/[id]/route.ts
T

152 lines
5.3 KiB
TypeScript

/**
* Swarm Chat Conversation Management
*
* DELETE: Delete a conversation (for self or both parties)
*/
import { NextRequest, NextResponse } from 'next/server';
import { db, chatConversations, chatMessages, users } from '@/db';
import { eq, and } from 'drizzle-orm';
import { getSession } from '@/lib/auth';
import { z } from 'zod';
// Schema for conversation ID parameter
const conversationIdSchema = z.string().uuid('Invalid conversation ID format');
// Schema for delete query parameter
const deleteQuerySchema = z.object({
deleteFor: z.enum(['self', 'both']).optional(),
});
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
if (!db) {
return NextResponse.json({ error: 'Database not available' }, { status: 503 });
}
const session = await getSession();
if (!session?.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const { id } = await params;
// Validate conversation ID
const idResult = conversationIdSchema.safeParse(id);
if (!idResult.success) {
return NextResponse.json({ error: 'Invalid conversation ID', details: idResult.error.issues }, { status: 400 });
}
const { searchParams } = new URL(request.url);
const deleteForRaw = searchParams.get('deleteFor'); // 'self' or 'both'
// Validate deleteFor parameter
const deleteForResult = deleteQuerySchema.safeParse({ deleteFor: deleteForRaw || undefined });
if (!deleteForResult.success) {
return NextResponse.json({ error: 'Invalid deleteFor parameter', details: deleteForResult.error.issues }, { status: 400 });
}
const { deleteFor } = deleteForResult.data;
// Verify the conversation belongs to this user
const conversation = await db.query.chatConversations.findFirst({
where: and(
eq(chatConversations.id, id),
eq(chatConversations.participant1Id, session.user.id)
),
});
if (!conversation) {
return NextResponse.json({ error: 'Conversation not found' }, { status: 404 });
}
if (deleteFor === 'both') {
// Delete the entire conversation and all messages (cascade will handle messages)
await db.delete(chatConversations).where(eq(chatConversations.id, id));
// Send deletion request to the other party
const participant2Handle = conversation.participant2Handle;
const isRemote = participant2Handle.includes('@');
if (isRemote) {
// Extract domain from handle (format: handle@domain)
const domain = participant2Handle.split('@')[1];
const handle = participant2Handle.split('@')[0];
try {
const protocol = domain.includes('localhost') ? 'http' : 'https';
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost';
// SECURITY: Sign the deletion request
const { signPayload, getNodePrivateKey } = await import('@/lib/swarm/signature');
const privateKey = await getNodePrivateKey();
const payload = {
senderHandle: session.user.handle,
senderNodeDomain: nodeDomain,
recipientHandle: handle,
conversationId: id,
timestamp: new Date().toISOString(),
};
const signature = signPayload(payload, privateKey);
await fetch(`${protocol}://${domain}/api/swarm/chat/delete`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...payload, signature }),
});
console.log(`[Chat Delete] Sent deletion request to ${domain}`);
} catch (error) {
console.error('[Chat Delete] Failed to notify remote node:', error);
// Continue anyway - local deletion succeeded
}
} else {
// Local user - find and delete their conversation too
const recipientUser = await db.query.users.findFirst({
where: eq(users.handle, participant2Handle),
});
if (recipientUser) {
// Find their conversation with us
const recipientConversation = await db.query.chatConversations.findFirst({
where: and(
eq(chatConversations.participant1Id, recipientUser.id),
eq(chatConversations.participant2Handle, session.user.handle)
),
});
if (recipientConversation) {
await db.delete(chatConversations).where(eq(chatConversations.id, recipientConversation.id));
console.log(`[Chat Delete] Deleted conversation for local user ${participant2Handle}`);
}
}
}
return NextResponse.json({
success: true,
message: 'Conversation deleted for both parties'
});
} else {
// Delete for self only - just delete the conversation record
// The other party will still have their copy
await db.delete(chatConversations).where(eq(chatConversations.id, id));
return NextResponse.json({
success: true,
message: 'Conversation deleted for you'
});
}
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json({ error: 'Invalid input', details: error.issues }, { status: 400 });
}
console.error('Delete conversation error:', error);
return NextResponse.json({ error: 'Failed to delete conversation' }, { status: 500 });
}
}