refactor(services): validate UUID parameters with Zod and add error handling to prevent DB errors
This commit is contained in:
parent
4dc68e92a7
commit
b8ed4a5b66
4 changed files with 101 additions and 46 deletions
|
|
@ -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 (
|
||||
<div className="flex p-4 flex-col h-full bg-background">
|
||||
|
|
|
|||
|
|
@ -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<Object | undefined>} The channel object if found, or undefined if no matching channel exists.
|
||||
* @returns {Promise<Object | null>} 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Array<Object>>} 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<Array<Object>>} 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 [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Object | null>} The server record with nested channels array, or null if the user is not a member or the server is not found.
|
||||
* @returns {Promise<Object | null>} 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<Array<Object>>} 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 [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue