feat(channels): display categories and fallback to default text channels
All checks were successful
Deploy Waveform to VPS / deploy (push) Successful in 6m56s
All checks were successful
Deploy Waveform to VPS / deploy (push) Successful in 6m56s
This commit is contained in:
parent
4b9256e570
commit
2321551518
3 changed files with 185 additions and 161 deletions
|
|
@ -1,64 +1,118 @@
|
||||||
/**
|
/**
|
||||||
* @file components/sidebar/ChannelSidebar.tsx
|
* @file components/sidebar/ChannelSidebar.tsx
|
||||||
* @description Sidebar component for navigating text channels within an active server, featuring channel creation, editing, and mobile responsiveness.
|
* @description Channel sidebar component listing categories and channels with actions for creation and editing.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Link from "next/link";
|
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import { useActiveServer } from "@/lib/context/ServerContext";
|
import { useActiveServer } from "@/lib/context/ServerContext";
|
||||||
import { useSidebarStore } from "@/lib/stores/useSidebarStore";
|
import { useSidebarStore } from "@/lib/stores/useSidebarStore";
|
||||||
import { CreateChannelModal } from "@/components/modals/CreateChannelModal";
|
import { CreateChannelModal } from "@/components/modals/CreateChannelModal";
|
||||||
import { EditChannelModal } from "@/components/modals/EditChannelModal";
|
import { EditChannelModal } from "@/components/modals/EditChannelModal";
|
||||||
import { PanelLeftClose, Plus, Settings } from "lucide-react";
|
import {
|
||||||
|
ChevronDown,
|
||||||
|
ChevronRight,
|
||||||
|
PanelLeftClose,
|
||||||
|
Plus,
|
||||||
|
Settings,
|
||||||
|
} from "lucide-react";
|
||||||
import type { Channel } from "@/db/schema";
|
import type { Channel } from "@/db/schema";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
/**
|
/** Renders the channel navigation sidebar for the active server. */
|
||||||
* Renders the channel sidebar for the active server with text channel lists, creation triggers, and settings handlers.
|
|
||||||
*
|
|
||||||
* @returns {JSX.Element | null} The rendered channel sidebar container or null if no active server exists.
|
|
||||||
*/
|
|
||||||
export function ChannelSidebar() {
|
export function ChannelSidebar() {
|
||||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||||
const [editingChannel, setEditingChannel] = useState<Channel | null>(null);
|
const [editingChannel, setEditingChannel] = useState<Channel | null>(null);
|
||||||
|
const [selectedCategoryId, setSelectedCategoryId] = useState<string | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [collapsedCategories, setCollapsedCategories] = useState<
|
||||||
|
Record<string, boolean>
|
||||||
|
>({});
|
||||||
|
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const currentChannelId = params?.channelId as string;
|
const currentChannelId = params?.channelId as string;
|
||||||
const { activeServer } = useActiveServer();
|
const { activeServer } = useActiveServer();
|
||||||
const { closeNav, toggleNav } = useSidebarStore();
|
const { closeNav, toggleNav } = useSidebarStore();
|
||||||
|
|
||||||
if (!activeServer) {
|
if (!activeServer) return null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/** Toggles the collapsed state of a channel category. */
|
||||||
* Handles channel selection clicks, automatically closing the mobile navigation drawer on smaller screens.
|
const toggleCategory = (categoryId: string) => {
|
||||||
*
|
setCollapsedCategories((prev) => ({
|
||||||
* @function handleChannelClick
|
...prev,
|
||||||
* @returns {void}
|
[categoryId]: !prev[categoryId],
|
||||||
*/
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Handles mobile navigation closure when clicking a channel link. */
|
||||||
const handleChannelClick = () => {
|
const handleChannelClick = () => {
|
||||||
if (!window.matchMedia("(min-width: 768px)").matches) {
|
if (!window.matchMedia("(min-width: 768px)").matches) {
|
||||||
closeNav();
|
closeNav();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/** Opens the edit channel modal for a specific channel. */
|
||||||
* Opens the channel settings modal for the selected channel, preventing event propagation.
|
|
||||||
*
|
|
||||||
* @function handleOpenSettings
|
|
||||||
* @param {React.MouseEvent} e - The mouse click event.
|
|
||||||
* @param {Channel} channel - The channel object to edit.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
const handleOpenSettings = (e: React.MouseEvent, channel: Channel) => {
|
const handleOpenSettings = (e: React.MouseEvent, channel: Channel) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setEditingChannel(channel);
|
setEditingChannel(channel);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Opens the create channel modal for a specific category or uncategorized. */
|
||||||
|
const handleOpenCreateModal = (categoryId: string | null = null) => {
|
||||||
|
setSelectedCategoryId(categoryId);
|
||||||
|
setIsCreateModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const uncategorizedChannels = activeServer.channels.filter(
|
||||||
|
(c) => !c.categoryId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const categoriesList = (activeServer.categories || []).map((cat) => ({
|
||||||
|
...cat,
|
||||||
|
channels: activeServer.channels.filter((c) => c.categoryId === cat.id),
|
||||||
|
}));
|
||||||
|
|
||||||
|
/** Renders an individual channel item in the list. */
|
||||||
|
const renderChannelItem = (channel: Channel) => {
|
||||||
|
const isActive = currentChannelId === channel.id;
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
key={channel.id}
|
||||||
|
href={`/servers/${activeServer.id}/channels/${channel.id}`}
|
||||||
|
onClick={handleChannelClick}
|
||||||
|
prefetch={false}
|
||||||
|
className={`flex items-center justify-between w-full px-2 py-1.5 rounded-md text-sm transition-all group min-w-0 overflow-hidden ${
|
||||||
|
isActive
|
||||||
|
? "bg-accent/50 text-white font-medium"
|
||||||
|
: "text-muted hover:bg-surface hover:text-white"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 min-w-0 flex-1 overflow-hidden">
|
||||||
|
<span className="text-muted group-hover:text-white text-base shrink-0">
|
||||||
|
#
|
||||||
|
</span>
|
||||||
|
<span className="truncate">{channel.name}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!channel.isDefault && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => handleOpenSettings(e, channel)}
|
||||||
|
className="opacity-0 group-hover:opacity-100 p-1 text-muted hover:text-white focus:outline-none transition-all cursor-pointer shrink-0 ml-2"
|
||||||
|
aria-label="Channel Settings"
|
||||||
|
>
|
||||||
|
<Settings className="w-3.5 h-3.5" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex-1 w-full bg-surface/50 border-r border-background flex flex-col h-full min-w-0 overflow-hidden">
|
<div className="flex-1 w-full bg-surface/50 border-r border-background flex flex-col h-full min-w-0 overflow-hidden">
|
||||||
|
|
@ -76,59 +130,77 @@ export function ChannelSidebar() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Channel List */}
|
{/* Channel List */}
|
||||||
<div className="flex-1 overflow-y-auto p-3 space-y-1 min-w-0">
|
<div className="flex-1 overflow-y-auto p-3 space-y-4 min-w-0">
|
||||||
<div className="flex items-center justify-between text-xs font-semibold text-muted px-2 py-1 uppercase tracking-wider">
|
<div className="space-y-1">
|
||||||
<span>Text Channels</span>
|
<div className="flex items-center justify-between text-xs font-semibold text-muted px-1 py-1 uppercase tracking-wider group">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setIsCreateModalOpen(true)}
|
onClick={() => toggleCategory("uncategorized")}
|
||||||
className="p-1 rounded text-muted hover:text-white hover:bg-surface transition-colors cursor-pointer"
|
className="flex items-center gap-1 hover:text-white transition-colors cursor-pointer min-w-0 truncate"
|
||||||
aria-label="Create channel"
|
>
|
||||||
>
|
{collapsedCategories["uncategorized"] ? (
|
||||||
<Plus className="w-4 h-4" />
|
<ChevronRight className="w-3.5 h-3.5 shrink-0" />
|
||||||
</button>
|
) : (
|
||||||
|
<ChevronDown className="w-3.5 h-3.5 shrink-0" />
|
||||||
|
)}
|
||||||
|
<span className="truncate">Text Channels</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleOpenCreateModal(null)}
|
||||||
|
className="opacity-0 group-hover:opacity-100 p-0.5 rounded text-muted hover:text-white hover:bg-surface transition-colors cursor-pointer"
|
||||||
|
aria-label="Create channel"
|
||||||
|
>
|
||||||
|
<Plus className="w-3.5 h-3.5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!collapsedCategories["uncategorized"] && (
|
||||||
|
<div className="space-y-0.5 pl-2">
|
||||||
|
{uncategorizedChannels.map(renderChannelItem)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-0.5 w-full">
|
{/* 2. Custom Kategorien */}
|
||||||
{activeServer.channels.map((channel) => {
|
{categoriesList.map((category) => {
|
||||||
const isActive = currentChannelId === channel.id;
|
const isCollapsed = collapsedCategories[category.id];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<div key={category.id} className="space-y-1">
|
||||||
key={channel.id}
|
<div className="flex items-center justify-between text-xs font-semibold text-muted px-1 py-1 uppercase tracking-wider group">
|
||||||
href={`/servers/${activeServer.id}/channels/${channel.id}`}
|
<button
|
||||||
onClick={handleChannelClick}
|
type="button"
|
||||||
prefetch={false}
|
onClick={() => toggleCategory(category.id)}
|
||||||
className={`flex items-center justify-between w-full px-2 py-1.5 rounded-md text-sm transition-all group min-w-0 overflow-hidden ${
|
className="flex items-center gap-1 hover:text-white transition-colors cursor-pointer min-w-0 truncate"
|
||||||
isActive
|
>
|
||||||
? "bg-accent/50 text-white font-medium"
|
{isCollapsed ? (
|
||||||
: "text-muted hover:bg-surface hover:text-white"
|
<ChevronRight className="w-3.5 h-3.5 shrink-0" />
|
||||||
}`}
|
) : (
|
||||||
>
|
<ChevronDown className="w-3.5 h-3.5 shrink-0" />
|
||||||
{/* Text Area */}
|
)}
|
||||||
<div className="flex items-center gap-2 min-w-0 flex-1 overflow-hidden">
|
<span className="truncate">{category.name}</span>
|
||||||
<span className="text-muted group-hover:text-white text-base shrink-0">
|
</button>
|
||||||
#
|
|
||||||
</span>
|
<button
|
||||||
<span className="truncate">{channel.name}</span>
|
type="button"
|
||||||
|
onClick={() => handleOpenCreateModal(category.id)}
|
||||||
|
className="opacity-0 group-hover:opacity-100 p-0.5 rounded text-muted hover:text-white hover:bg-surface transition-colors cursor-pointer"
|
||||||
|
aria-label="Channel in Kategorie erstellen"
|
||||||
|
>
|
||||||
|
<Plus className="w-3.5 h-3.5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!isCollapsed && (
|
||||||
|
<div className="space-y-0.5 pl-2">
|
||||||
|
{category.channels.map(renderChannelItem)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
{/* Gear Button */}
|
</div>
|
||||||
{!channel.isDefault && (
|
);
|
||||||
<button
|
})}
|
||||||
type="button"
|
|
||||||
onClick={(e) => handleOpenSettings(e, channel)}
|
|
||||||
className="opacity-0 group-hover:opacity-100 p-1 text-muted hover:text-white focus:outline-none transition-all cursor-pointer shrink-0 ml-2"
|
|
||||||
aria-label="Channel Settings"
|
|
||||||
title="Channel Settings"
|
|
||||||
>
|
|
||||||
<Settings className="w-3.5 h-3.5" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,45 +6,29 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { createContext, useContext, useState } from "react";
|
import { createContext, useContext, useState } from "react";
|
||||||
import type { Server, Channel } from "@/db/schema";
|
import type { Server, Channel, Category } from "@/db/schema";
|
||||||
|
|
||||||
/**
|
/** Type definition representing a server entity along with its associated channels array. */
|
||||||
* Type definition representing a server entity along with its associated channels array.
|
export type ServerWithChannels = Server & {
|
||||||
*/
|
channels: Channel[];
|
||||||
export type ServerWithChannels = Server & { channels: Channel[] };
|
categories: Category[];
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/** Represents a member within a server. */
|
||||||
* Represents a member within a server.
|
|
||||||
*
|
|
||||||
* @interface ServerMember
|
|
||||||
* @property {string} id - The unique identifier of the server member.
|
|
||||||
* @property {string} name - The display name of the server member.
|
|
||||||
* @property {boolean} [isOnline] - Optional flag indicating whether the member is currently online.
|
|
||||||
*/
|
|
||||||
export interface ServerMember {
|
export interface ServerMember {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
isOnline?: boolean;
|
isOnline?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Interface defining the shape of the ServerContext state and update handlers. */
|
||||||
* Interface defining the shape of the ServerContext state and update handlers.
|
|
||||||
*
|
|
||||||
* @interface ServerContextType
|
|
||||||
* @property {ServerWithChannels | null} activeServer - The currently active server instance with its associated channels.
|
|
||||||
* @property {(server: ServerWithChannels | null) => void} setActiveServer - State setter function for updating the active server.
|
|
||||||
* @property {(channel: Channel) => void} addChannel - Function to add a new channel to the active server.
|
|
||||||
* @property {(channelId: string) => void} removeChannel - Function to remove a channel by its identifier.
|
|
||||||
* @property {(channel: Channel) => void} updateChannel - Function to update an existing channel.
|
|
||||||
* @property {ServerMember[]} members - The list of members belonging to the active server.
|
|
||||||
* @property {(members: ServerMember[]) => void} setMembers - State setter function for updating the server members list.
|
|
||||||
*/
|
|
||||||
interface ServerContextType {
|
interface ServerContextType {
|
||||||
activeServer: ServerWithChannels | null;
|
activeServer: ServerWithChannels | null;
|
||||||
setActiveServer: (server: ServerWithChannels | null) => void;
|
setActiveServer: (server: ServerWithChannels | null) => void;
|
||||||
addChannel: (channel: Channel) => void;
|
addChannel: (channel: Channel) => void;
|
||||||
removeChannel: (channelId: string) => void;
|
removeChannel: (channelId: string) => void;
|
||||||
updateChannel: (channel: Channel) => void;
|
updateChannel: (channel: Channel) => void;
|
||||||
|
addCategory: (category: Category) => void;
|
||||||
members: ServerMember[];
|
members: ServerMember[];
|
||||||
setMembers: (members: ServerMember[]) => void;
|
setMembers: (members: ServerMember[]) => void;
|
||||||
}
|
}
|
||||||
|
|
@ -55,68 +39,50 @@ const ServerContext = createContext<ServerContextType>({
|
||||||
addChannel: () => {},
|
addChannel: () => {},
|
||||||
removeChannel: () => {},
|
removeChannel: () => {},
|
||||||
updateChannel: () => {},
|
updateChannel: () => {},
|
||||||
|
addCategory: () => {},
|
||||||
members: [],
|
members: [],
|
||||||
setMembers: () => {},
|
setMembers: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/** Provider component that wraps the application layout to provide global access to active server state and member listings. */
|
||||||
* Provider component that wraps the application layout to provide global access to active server state and member listings.
|
|
||||||
*
|
|
||||||
* @param {Object} props - React component properties.
|
|
||||||
* @param {React.ReactNode} props.children - The child components wrapped by the provider.
|
|
||||||
* @returns {JSX.Element} The rendered React provider wrapping the child elements.
|
|
||||||
*/
|
|
||||||
export function ServerProvider({ children }: { children: React.ReactNode }) {
|
export function ServerProvider({ children }: { children: React.ReactNode }) {
|
||||||
const [activeServer, setActiveServer] = useState<ServerWithChannels | null>(
|
const [activeServer, setActiveServer] = useState<ServerWithChannels | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
const [members, setMembers] = useState<ServerMember[]>([]);
|
const [members, setMembers] = useState<ServerMember[]>([]);
|
||||||
|
|
||||||
/**
|
/** Helper function to update channels within the active server state. */
|
||||||
* Helper function to update channels within the active server state.
|
|
||||||
*
|
|
||||||
* @function updateChannels
|
|
||||||
* @param {(channels: Channel[]) => Channel[]} fn - The updater function receiving current channels and returning new channels.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
const updateChannels = (fn: (channels: Channel[]) => Channel[]) => {
|
const updateChannels = (fn: (channels: Channel[]) => Channel[]) => {
|
||||||
setActiveServer((prev) =>
|
setActiveServer((prev) =>
|
||||||
prev ? { ...prev, channels: fn(prev.channels) } : prev,
|
prev ? { ...prev, channels: fn(prev.channels) } : prev,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/** Adds a new channel to the active server. */
|
||||||
* Adds a new channel to the active server.
|
|
||||||
*
|
|
||||||
* @function addChannel
|
|
||||||
* @param {Channel} channel - The channel object to add.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
const addChannel = (channel: Channel) =>
|
const addChannel = (channel: Channel) =>
|
||||||
updateChannels((prev) => [...prev, channel]);
|
updateChannels((prev) => [...prev, channel]);
|
||||||
|
|
||||||
/**
|
/** Removes a channel from the active server by its identifier. */
|
||||||
* Removes a channel from the active server by its identifier.
|
|
||||||
*
|
|
||||||
* @function removeChannel
|
|
||||||
* @param {string} channelId - The unique identifier of the channel to remove.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
const removeChannel = (channelId: string) =>
|
const removeChannel = (channelId: string) =>
|
||||||
updateChannels((prev) => prev.filter((c) => c.id !== channelId));
|
updateChannels((prev) => prev.filter((c) => c.id !== channelId));
|
||||||
|
|
||||||
/**
|
/** Updates an existing channel within the active server. */
|
||||||
* Updates an existing channel within the active server.
|
|
||||||
*
|
|
||||||
* @function updateChannel
|
|
||||||
* @param {Channel} updatedChannel - The updated channel object.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
const updateChannel = (updatedChannel: Channel) =>
|
const updateChannel = (updatedChannel: Channel) =>
|
||||||
updateChannels((prev) =>
|
updateChannels((prev) =>
|
||||||
prev.map((c) => (c.id === updatedChannel.id ? updatedChannel : c)),
|
prev.map((c) => (c.id === updatedChannel.id ? updatedChannel : c)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const addCategory = (category: Category) => {
|
||||||
|
setActiveServer((prev) =>
|
||||||
|
prev
|
||||||
|
? {
|
||||||
|
...prev,
|
||||||
|
categories: [...(prev.categories || []), category],
|
||||||
|
}
|
||||||
|
: prev,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ServerContext.Provider
|
<ServerContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
|
@ -125,6 +91,7 @@ export function ServerProvider({ children }: { children: React.ReactNode }) {
|
||||||
addChannel,
|
addChannel,
|
||||||
removeChannel,
|
removeChannel,
|
||||||
updateChannel,
|
updateChannel,
|
||||||
|
addCategory,
|
||||||
members,
|
members,
|
||||||
setMembers,
|
setMembers,
|
||||||
}}
|
}}
|
||||||
|
|
@ -134,13 +101,7 @@ export function ServerProvider({ children }: { children: React.ReactNode }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Custom hook to access the active server context. */
|
||||||
* Custom hook to access the active server context.
|
|
||||||
*
|
|
||||||
* @function useActiveServer
|
|
||||||
* @throws {Error} Throws an error if used outside of a ServerProvider.
|
|
||||||
* @returns {ServerContextType} The active server context value.
|
|
||||||
*/
|
|
||||||
export function useActiveServer() {
|
export function useActiveServer() {
|
||||||
const context = useContext(ServerContext);
|
const context = useContext(ServerContext);
|
||||||
if (!context) {
|
if (!context) {
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,7 @@ import { z } from "zod";
|
||||||
|
|
||||||
const uuidSchema = z.uuid();
|
const uuidSchema = z.uuid();
|
||||||
|
|
||||||
/**
|
/** Retrieves a server along with its channels and categories sorted chronologically if the specified user is a verified member. */
|
||||||
* 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, the server does not exist, or an invalid ID was provided.
|
|
||||||
*/
|
|
||||||
export async function getServerWithChannels(serverId: string, userId: string) {
|
export async function getServerWithChannels(serverId: string, userId: string) {
|
||||||
if (
|
if (
|
||||||
!uuidSchema.safeParse(serverId).success ||
|
!uuidSchema.safeParse(serverId).success ||
|
||||||
|
|
@ -39,6 +32,10 @@ export async function getServerWithChannels(serverId: string, userId: string) {
|
||||||
channels: {
|
channels: {
|
||||||
orderBy: (channels, { asc }) => [asc(channels.createdAt)],
|
orderBy: (channels, { asc }) => [asc(channels.createdAt)],
|
||||||
},
|
},
|
||||||
|
// NEU: Categories mitladen
|
||||||
|
categories: {
|
||||||
|
orderBy: (categories, { asc }) => [asc(categories.createdAt)],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -49,12 +46,7 @@ export async function getServerWithChannels(serverId: string, userId: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Fetches all servers that the specified user belongs to, including each server's sorted channels and categories list. */
|
||||||
* Fetches all servers that the specified user belongs to, including each server's sorted channels list.
|
|
||||||
*
|
|
||||||
* @param {string} userId - The unique identifier of the user whose servers are to be fetched.
|
|
||||||
* @returns {Promise<Array<Object>>} An array of server objects associated with the user.
|
|
||||||
*/
|
|
||||||
export async function getUserServers(userId: string) {
|
export async function getUserServers(userId: string) {
|
||||||
if (!uuidSchema.safeParse(userId).success) {
|
if (!uuidSchema.safeParse(userId).success) {
|
||||||
return [];
|
return [];
|
||||||
|
|
@ -69,6 +61,10 @@ export async function getUserServers(userId: string) {
|
||||||
channels: {
|
channels: {
|
||||||
orderBy: (channels, { asc }) => [asc(channels.createdAt)],
|
orderBy: (channels, { asc }) => [asc(channels.createdAt)],
|
||||||
},
|
},
|
||||||
|
// NEU: Categories mitladen
|
||||||
|
categories: {
|
||||||
|
orderBy: (categories, { asc }) => [asc(categories.createdAt)],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -83,12 +79,7 @@ export async function getUserServers(userId: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Retrieves a single server by its ID without checking membership. */
|
||||||
* Retrieves a single server by its ID without checking membership.
|
|
||||||
*
|
|
||||||
* @param {string} serverId - The unique identifier of the server to retrieve.
|
|
||||||
* @returns {Promise<Object | null>} The server record or null if not found/invalid ID.
|
|
||||||
*/
|
|
||||||
export async function getServerById(serverId: string) {
|
export async function getServerById(serverId: string) {
|
||||||
if (!uuidSchema.safeParse(serverId).success) {
|
if (!uuidSchema.safeParse(serverId).success) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue