// ── Messages: Single Message ───────────────────────────────────────────────── // DELETE /api/conversations/[id]/messages/[messageId] // — Delete a specific message (soft-delete) owned by the current user. // — Handles cleanup of associated voice note audio files. // // Auth: authenticated; user must be a participant and the message sender import { NextRequest, NextResponse } from "next/server" import { getSessionUser } from "@/lib/auth" import { query } from "@/lib/db" import { unlink } from "node:fs/promises" import { join } from "node:path" import { existsSync } from "node:fs" // ── DELETE ─────────────────────────────────────────────────────────────────── export async function DELETE( _request: NextRequest, { params }: { params: Promise<{ id: string; messageId: string }> }, ) { try { const user = await getSessionUser() if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }) const { id: conversationId, messageId } = await params // Verify user is a participant in this conversation const partCheck = await query( `SELECT 1 FROM conversation_participants WHERE conversation_id = $1 AND user_id = $2`, [conversationId, user.id], ) if (partCheck.rows.length === 0) { return NextResponse.json({ error: "Not a participant" }, { status: 403 }) } // Fetch the message to verify it belongs to the sender and check if it's a voice message const msgResult = await query( `SELECT id, sender_id, content FROM messages WHERE id = $1 AND conversation_id = $2 AND deleted_at IS NULL`, [messageId, conversationId], ) if (msgResult.rows.length === 0) { return NextResponse.json({ error: "Message not found" }, { status: 404 }) } const message = msgResult.rows[0] // Only the sender can delete their own message if (message.sender_id !== user.id) { return NextResponse.json({ error: "Cannot delete another user's message" }, { status: 403 }) } // Delete the associated audio file if this is a voice message let storageDeleted = false try { const content = message.content if (content?.startsWith("{")) { const parsed = JSON.parse(content) if (parsed.v && parsed.u) { const filename = parsed.u.replace(/^\/uploads\/voice\//, "") const filePath = join(process.cwd(), "public", "uploads", "voice", filename) if (existsSync(filePath)) { await unlink(filePath) storageDeleted = true } } } } catch { // If file deletion fails, log but still proceed with DB deletion console.warn(`[voice-delete] Failed to delete audio file for message ${messageId}`) } // Soft-delete the message in the database const deleteResult = await query( `UPDATE messages SET deleted_at = NOW() WHERE id = $1 AND conversation_id = $2 RETURNING id`, [messageId, conversationId], ) const dbDeleted = deleteResult.rows.length > 0 console.log( `[voice-delete] id=${messageId} conv=${conversationId} user=${user.id} db=${dbDeleted} storage=${storageDeleted}`, ) return NextResponse.json({ success: true, messageId, dbDeleted, storageDeleted, }) } catch (error) { console.error("[voice-delete] Error:", error) return NextResponse.json({ error: "Unable to delete voice note. Please try again." }, { status: 500 }) } }