diff --git a/app/(app)/servers/[serverId]/channels/[channelId]/page.tsx b/app/(app)/servers/[serverId]/channels/[channelId]/page.tsx index 6ba4be9..83c49a4 100644 --- a/app/(app)/servers/[serverId]/channels/[channelId]/page.tsx +++ b/app/(app)/servers/[serverId]/channels/[channelId]/page.tsx @@ -3,7 +3,7 @@ * @description Page component for viewing a specific channel and its messages. */ -import { notFound } from "next/navigation"; +import { redirect } from "next/navigation"; import { AppHeader } from "@/components/layout/AppHeader"; import { ChatInput } from "@/components/chat/ChatInput"; import { ChatMessages } from "@/components/chat/ChatMessages"; @@ -24,13 +24,13 @@ export default async function ChannelPage({ }) { const { channelId } = await params; - // Paralleles Laden von Kanal-Daten und Nachrichten + // Parallel loading of channel data and messages const [channel, channelMessages] = await Promise.all([ getChannelById(channelId), getChannelMessages(channelId), ]); - if (!channel) return notFound(); + if (!channel) redirect("/"); return (
diff --git a/lib/services/channel.service.ts b/lib/services/channel.service.ts index 9ca7bfd..7a51bac 100644 --- a/lib/services/channel.service.ts +++ b/lib/services/channel.service.ts @@ -1,20 +1,34 @@ /** * @file lib/services/channel.service.ts - * @description Service module providing database access methods for channel management. + * @description Service module providing database access methods for channel management and validation. */ import { db } from "@/db"; import { channels } from "@/db/schema"; import { eq } from "drizzle-orm"; +import { z } from "zod"; + +const uuidSchema = z.uuid(); /** * Retrieves a single channel record from the database by its unique identifier. + * Validates the UUID format before executing the database query. * * @param {string} channelId - The unique identifier of the channel to fetch. - * @returns {Promise} The channel object if found, or undefined if no matching channel exists. + * @returns {Promise} The channel object if found, or null if no matching channel exists or an invalid ID was provided. */ export async function getChannelById(channelId: string) { - return await db.query.channels.findFirst({ - where: eq(channels.id, channelId), - }); + if (!uuidSchema.safeParse(channelId).success) { + return null; + } + + try { + const channel = await db.query.channels.findFirst({ + where: eq(channels.id, channelId), + }); + return channel ?? null; + } catch (error) { + console.error(`Error fetching channel ${channelId}:`, error); + return null; + } } diff --git a/lib/services/message.service.ts b/lib/services/message.service.ts index 0812512..bda0e7a 100644 --- a/lib/services/message.service.ts +++ b/lib/services/message.service.ts @@ -1,29 +1,43 @@ /** * @file lib/services/message.service.ts - * @description Data access service for retrieving and managing message entities from the database. + * @description Service module providing database access methods for fetching and managing channel messages and associated member profiles. */ import { db } from "@/db"; import { messages } from "@/db/schema"; -import { eq } from "drizzle-orm"; +import { eq, asc } from "drizzle-orm"; +import { z } from "zod"; + +const uuidSchema = z.uuid(); /** - * Retrieves all messages belonging to a specific channel, ordered chronologically ascending, - * including associated member and user details. + * Retrieves all messages for a specific channel sorted chronologically, including member and user details. + * Validates the UUID format before querying the database and handles potential runtime errors gracefully. * - * @param {string} channelId - The unique identifier of the target channel. - * @returns {Promise>} A promise resolving to a list of message objects populated with nested member and user data. + * @param {string} channelId - The unique identifier of the channel whose messages are to be fetched. + * @returns {Promise>} An array of message objects with nested member and user data, or an empty array if invalid or failed. */ export async function getChannelMessages(channelId: string) { - return await db.query.messages.findMany({ - where: eq(messages.channelId, channelId), - with: { - member: { - with: { - user: true, + if (!uuidSchema.safeParse(channelId).success) { + return []; + } + + try { + const channelMessages = await db.query.messages.findMany({ + where: eq(messages.channelId, channelId), + orderBy: [asc(messages.createdAt)], + with: { + member: { + with: { + user: true, + }, }, }, - }, - orderBy: (messages, { asc }) => [asc(messages.createdAt)], - }); + }); + + return channelMessages; + } catch (error) { + console.error(`Error fetching messages for channel ${channelId}:`, error); + return []; + } } diff --git a/lib/services/server.service.ts b/lib/services/server.service.ts index 37c75c5..16ab768 100644 --- a/lib/services/server.service.ts +++ b/lib/services/server.service.ts @@ -6,31 +6,47 @@ import { db } from "@/db"; import { members, servers } from "@/db/schema"; import { eq, and } from "drizzle-orm"; +import { z } from "zod"; + +const uuidSchema = z.uuid(); /** * Retrieves a server along with its channels sorted chronologically if the specified user is a verified member. + * Validates UUID formats prior to database execution to prevent database errors. * * @param {string} serverId - The unique identifier of the server to retrieve. * @param {string} userId - The unique identifier of the requesting user. - * @returns {Promise} The server record with nested channels array, or null if the user is not a member or the server is not found. + * @returns {Promise} The server record with nested channels array, or null if the user is not a member, the server does not exist, or an invalid ID was provided. */ export async function getServerWithChannels(serverId: string, userId: string) { - const isMember = await db.query.members.findFirst({ - where: and(eq(members.serverId, serverId), eq(members.userId, userId)), - }); + if ( + !uuidSchema.safeParse(serverId).success || + !uuidSchema.safeParse(userId).success + ) { + return null; + } - if (!isMember) return null; + try { + const isMember = await db.query.members.findFirst({ + where: and(eq(members.serverId, serverId), eq(members.userId, userId)), + }); - const server = await db.query.servers.findFirst({ - where: eq(servers.id, serverId), - with: { - channels: { - orderBy: (channels, { asc }) => [asc(channels.createdAt)], + if (!isMember) return null; + + const server = await db.query.servers.findFirst({ + where: eq(servers.id, serverId), + with: { + channels: { + orderBy: (channels, { asc }) => [asc(channels.createdAt)], + }, }, - }, - }); + }); - return server; + return server ?? null; + } catch (error) { + console.error(`Error fetching server ${serverId}:`, error); + return null; + } } /** @@ -40,18 +56,29 @@ export async function getServerWithChannels(serverId: string, userId: string) { * @returns {Promise>} An array of server objects associated with the user. */ export async function getUserServers(userId: string) { - const userMemberships = await db.query.members.findMany({ - where: eq(members.userId, userId), - with: { - server: { - with: { - channels: { - orderBy: (channels, { asc }) => [asc(channels.createdAt)], + if (!uuidSchema.safeParse(userId).success) { + return []; + } + + try { + const userMemberships = await db.query.members.findMany({ + where: eq(members.userId, userId), + with: { + server: { + with: { + channels: { + orderBy: (channels, { asc }) => [asc(channels.createdAt)], + }, }, }, }, - }, - }); + }); - return userMemberships.map((membership) => membership.server).filter(Boolean); + return userMemberships + .map((membership) => membership.server) + .filter(Boolean); + } catch (error) { + console.error(`Error fetching servers for user ${userId}:`, error); + return []; + } }