152 lines
5.3 KiB
TypeScript
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 });
|
|
}
|
|
}
|