feat(api): add CRUD endpoints for conversations and direct messages
This commit is contained in:
parent
739c9c1bf6
commit
d94d1ec077
3 changed files with 324 additions and 0 deletions
94
app/api/dm/[conversationId]/route.ts
Normal file
94
app/api/dm/[conversationId]/route.ts
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* @file app/api/dm/[conversationId]/route.ts
|
||||
* @description API route handler for creating new direct messages within a specific conversation.
|
||||
*/
|
||||
|
||||
import { auth } from "@/auth";
|
||||
import { db } from "@/db";
|
||||
import { conversations, directMessages } from "@/db/schema";
|
||||
import { and, eq, or } from "drizzle-orm";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
/** Regular expression to validate UUID format for conversation IDs. */
|
||||
const UUID_REGEX =
|
||||
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||||
|
||||
/**
|
||||
* Handles POST requests to send a new direct message in a conversation.
|
||||
*
|
||||
* @async
|
||||
* @function POST
|
||||
* @param {Request} req - The incoming HTTP request containing the message content.
|
||||
* @param {Object} context - The route context.
|
||||
* @param {Promise<{ conversationId: string }>} context.params - The route parameters containing the conversation ID.
|
||||
* @returns {Promise<NextResponse>} The JSON response with the created message or an error status.
|
||||
*/
|
||||
export async function POST(
|
||||
req: Request,
|
||||
{ params }: { params: Promise<{ conversationId: string }> },
|
||||
) {
|
||||
try {
|
||||
const { conversationId } = await params;
|
||||
|
||||
if (!UUID_REGEX.test(conversationId)) {
|
||||
return NextResponse.json(
|
||||
{ error: "Invalid Conversation ID" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const session = await auth();
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
const { content } = await req.json();
|
||||
if (!content || !content.trim()) {
|
||||
return NextResponse.json(
|
||||
{ error: "Message content cannot be empty" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
// Sicherstellen, dass der User Teil der Unterhaltung ist
|
||||
const [conversation] = await db
|
||||
.select({ id: conversations.id })
|
||||
.from(conversations)
|
||||
.where(
|
||||
and(
|
||||
eq(conversations.id, conversationId),
|
||||
or(
|
||||
eq(conversations.userOneId, session.user.id),
|
||||
eq(conversations.userTwoId, session.user.id),
|
||||
),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
if (!conversation) {
|
||||
return NextResponse.json(
|
||||
{ error: "Conversation not found or forbidden" },
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
const [newMessage] = await db
|
||||
.insert(directMessages)
|
||||
.values({
|
||||
content: content.trim(),
|
||||
conversationId,
|
||||
senderId: session.user.id,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.returning();
|
||||
|
||||
return NextResponse.json(newMessage, { status: 201 });
|
||||
} catch (error) {
|
||||
console.error("API Direct Messages POST error:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Internal Server Error" },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
143
app/api/dm/messages/[messageId]/route.ts
Normal file
143
app/api/dm/messages/[messageId]/route.ts
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
/**
|
||||
* @file app/api/dm/messages/[messageId]/route.ts
|
||||
* @description API route handlers for updating (PATCH) and deleting (DELETE) direct messages by their unique ID, including validation and permission checks.
|
||||
*/
|
||||
|
||||
import { auth } from "@/auth";
|
||||
import { db } from "@/db";
|
||||
import { directMessages } from "@/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
/**
|
||||
* Regular expression for validating UUID version 1-5 strings.
|
||||
*
|
||||
* @constant {RegExp}
|
||||
*/
|
||||
const UUID_REGEX =
|
||||
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||||
|
||||
/**
|
||||
* Handles PATCH requests to update the content of an existing direct message.
|
||||
*
|
||||
* @async
|
||||
* @function PATCH
|
||||
* @param {Request} req - The incoming HTTP request object containing the updated message content.
|
||||
* @param {Object} context - The route context containing dynamic parameters.
|
||||
* @param {Promise<{ messageId: string }>} context.params - A promise resolving to the route parameters including the message ID.
|
||||
* @returns {Promise<NextResponse>} The updated message JSON response or an appropriate error response.
|
||||
*/
|
||||
export async function PATCH(
|
||||
req: Request,
|
||||
{ params }: { params: Promise<{ messageId: string }> },
|
||||
) {
|
||||
try {
|
||||
const { messageId } = await params;
|
||||
|
||||
if (!UUID_REGEX.test(messageId)) {
|
||||
return NextResponse.json(
|
||||
{ error: "Invalid Message ID" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const session = await auth();
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
const { content } = await req.json();
|
||||
if (!content || !content.trim()) {
|
||||
return NextResponse.json(
|
||||
{ error: "Message content cannot be empty" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const [existingMessage] = await db
|
||||
.select()
|
||||
.from(directMessages)
|
||||
.where(eq(directMessages.id, messageId))
|
||||
.limit(1);
|
||||
|
||||
if (!existingMessage) {
|
||||
return NextResponse.json({ error: "Message not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
if (existingMessage.senderId !== session.user.id) {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
}
|
||||
|
||||
const [updatedMessage] = await db
|
||||
.update(directMessages)
|
||||
.set({
|
||||
content: content.trim(),
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(directMessages.id, messageId))
|
||||
.returning();
|
||||
|
||||
return NextResponse.json(updatedMessage);
|
||||
} catch (error) {
|
||||
console.error("API Direct Message PATCH error:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Internal Server Error" },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles DELETE requests to remove a direct message by its unique ID.
|
||||
*
|
||||
* @async
|
||||
* @function DELETE
|
||||
* @param {Request} req - The incoming HTTP request object.
|
||||
* @param {Object} context - The route context containing dynamic parameters.
|
||||
* @param {Promise<{ messageId: string }>} context.params - A promise resolving to the route parameters including the message ID.
|
||||
* @returns {Promise<NextResponse>} A success JSON response or an appropriate error response.
|
||||
*/
|
||||
export async function DELETE(
|
||||
req: Request,
|
||||
{ params }: { params: Promise<{ messageId: string }> },
|
||||
) {
|
||||
try {
|
||||
const { messageId } = await params;
|
||||
|
||||
if (!UUID_REGEX.test(messageId)) {
|
||||
return NextResponse.json(
|
||||
{ error: "Invalid Message ID" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const session = await auth();
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
const [existingMessage] = await db
|
||||
.select()
|
||||
.from(directMessages)
|
||||
.where(eq(directMessages.id, messageId))
|
||||
.limit(1);
|
||||
|
||||
if (!existingMessage) {
|
||||
return NextResponse.json({ error: "Message not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
if (existingMessage.senderId !== session.user.id) {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
}
|
||||
|
||||
await db.delete(directMessages).where(eq(directMessages.id, messageId));
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error("API Direct Message DELETE error:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Internal Server Error" },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
87
app/api/dm/route.ts
Normal file
87
app/api/dm/route.ts
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
* @file app/api/dm/route.ts
|
||||
* @description API route handler for creating or retrieving direct message conversations between users.
|
||||
*/
|
||||
|
||||
import { auth } from "@/auth";
|
||||
import { db } from "@/db";
|
||||
import { conversations } from "@/db/schema";
|
||||
import { and, eq, or } from "drizzle-orm";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
/**
|
||||
* Regular expression pattern for validating UUID format.
|
||||
*
|
||||
* @type {RegExp}
|
||||
*/
|
||||
const UUID_REGEX =
|
||||
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||||
|
||||
/**
|
||||
* Handles HTTP POST requests to initiate or fetch a direct message conversation with a specified recipient.
|
||||
*
|
||||
* @async
|
||||
* @function POST
|
||||
* @param {Request} req - The incoming HTTP request containing the recipient identifier in JSON format.
|
||||
* @returns {Promise<NextResponse>} The JSON response containing the conversation object or an error message.
|
||||
*/
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const session = await auth();
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
const { recipientId } = await req.json();
|
||||
|
||||
if (
|
||||
!recipientId ||
|
||||
!UUID_REGEX.test(recipientId) ||
|
||||
recipientId === session.user.id
|
||||
) {
|
||||
return NextResponse.json(
|
||||
{ error: "Invalid recipient ID" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
// Bestehende Konversation suchen
|
||||
const [existingConversation] = await db
|
||||
.select()
|
||||
.from(conversations)
|
||||
.where(
|
||||
or(
|
||||
and(
|
||||
eq(conversations.userOneId, session.user.id),
|
||||
eq(conversations.userTwoId, recipientId),
|
||||
),
|
||||
and(
|
||||
eq(conversations.userOneId, recipientId),
|
||||
eq(conversations.userTwoId, session.user.id),
|
||||
),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
if (existingConversation) {
|
||||
return NextResponse.json(existingConversation);
|
||||
}
|
||||
|
||||
// Neue Konversation anlegen
|
||||
const [newConversation] = await db
|
||||
.insert(conversations)
|
||||
.values({
|
||||
userOneId: session.user.id,
|
||||
userTwoId: recipientId,
|
||||
})
|
||||
.returning();
|
||||
|
||||
return NextResponse.json(newConversation, { status: 201 });
|
||||
} catch (error) {
|
||||
console.error("API Conversations POST error:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Internal Server Error" },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue