diff --git a/apps/hasura/metadata/databases/masterbots/tables/public_user.yaml b/apps/hasura/metadata/databases/masterbots/tables/public_user.yaml index ac96b81f4..354c518a0 100644 --- a/apps/hasura/metadata/databases/masterbots/tables/public_user.yaml +++ b/apps/hasura/metadata/databases/masterbots/tables/public_user.yaml @@ -141,23 +141,6 @@ select_permissions: - username filter: {} comment: "" - - role: moderator - permission: - columns: - - date_joined - - email - - get_free_month - - is_blocked - - is_verified - - last_login - - pro_user_subscription_id - - profile_picture - - role - - slug - - user_id - - username - filter: {} - comment: "" - role: user permission: columns: diff --git a/apps/masterbots.ai/app/(browse)/[category]/page.tsx b/apps/masterbots.ai/app/(browse)/[category]/page.tsx index 32629b690..ababbe556 100644 --- a/apps/masterbots.ai/app/(browse)/[category]/page.tsx +++ b/apps/masterbots.ai/app/(browse)/[category]/page.tsx @@ -15,10 +15,10 @@ export default async function BrowseCategoryPage({ return (
- + /> */}
diff --git a/apps/masterbots.ai/components/layout/sidebar/sidebar-link.tsx b/apps/masterbots.ai/components/layout/sidebar/sidebar-link.tsx old mode 100644 new mode 100755 index 63bbae2a3..57b5595e5 --- a/apps/masterbots.ai/components/layout/sidebar/sidebar-link.tsx +++ b/apps/masterbots.ai/components/layout/sidebar/sidebar-link.tsx @@ -3,6 +3,7 @@ import { Checkbox } from "@/components/ui/checkbox" import { IconCaretRight } from '@/components/ui/icons' import { useSidebar } from '@/lib/hooks/use-sidebar' +import { urlBuilders } from '@/lib/url' import { cn } from '@/lib/utils' import { Category, Chatbot } from 'mb-genql' import { toSlug } from 'mb-lib' @@ -20,7 +21,7 @@ interface SidebarLinkProps { export default function SidebarLink({ category, isFilterMode, page }: SidebarLinkProps) { const router = useRouter() const pathname = usePathname() - const isBrowse = !pathname.includes('/c') && !pathname.includes('/u') + const isBrowse = !/^\/(?:c|u)(?:\/|$)/.test(pathname) const { slug } = useParams() const { @@ -53,13 +54,17 @@ export default function SidebarLink({ category, isFilterMode, page }: SidebarLin navigateTo({ page, slug: typeof slug === 'string' ? slug : undefined, - categoryName: toSlug(category.name.toLowerCase()) + categoryName: toSlug(category.name.toLowerCase()), + isBrowse }) - } else { + } + else { + setActiveChatbot(null) navigateTo({ page, slug: typeof slug === 'string' ? slug : undefined, + isBrowse }) } return newCategory @@ -138,21 +143,22 @@ export default function SidebarLink({ category, isFilterMode, page }: SidebarLin return (
- { - e.preventDefault(); + e.stopPropagation(); handleClickCategory(e); }} > {categoryContent} - + {childrenContent}
) @@ -177,7 +183,7 @@ const ChatbotComponent: React.FC = React.memo(function Ch }) { const { selectedChatbots, toggleChatbotSelection, navigateTo } = useSidebar() const pathname = usePathname() - const isBrowse = !pathname.includes('/c') && !pathname.includes('/u') + const isBrowse = !/^\/(?:c|u)(?:\/|$)/.test(pathname) const { slug } = useParams() const handleChatbotClick = useCallback((e: React.MouseEvent) => { @@ -188,7 +194,8 @@ const ChatbotComponent: React.FC = React.memo(function Ch page, slug: slug as string, categoryName: toSlug(category.name.toLowerCase()), - chatbotName: chatbot.name.toLowerCase() + chatbotName: chatbot.name.toLowerCase(), + isBrowse }) } }, [chatbot, setActiveChatbot, isFilterMode]) @@ -206,7 +213,7 @@ const ChatbotComponent: React.FC = React.memo(function Ch className={cn( 'flex items-center p-2 w-full', isActive && 'bg-blue-100 dark:bg-blue-900', - 'hover:bg-gray-100 dark:hover:bg-gray-800' + 'hover:bg-gray-100 dark:hover:bg-gray-800', )} > {isFilterMode && ( @@ -228,7 +235,7 @@ const ChatbotComponent: React.FC = React.memo(function Ch ) : ( - {/* */} - {/*

Chat history

*/} - {/* */} - + w-[300px] lg:w-[250px] xl:w-[300px]"/> ) } diff --git a/apps/masterbots.ai/components/layout/sidebar/sidebar.tsx b/apps/masterbots.ai/components/layout/sidebar/sidebar.tsx index 9bd33f40e..308ff0703 100644 --- a/apps/masterbots.ai/components/layout/sidebar/sidebar.tsx +++ b/apps/masterbots.ai/components/layout/sidebar/sidebar.tsx @@ -5,10 +5,25 @@ import { SidebarHeader } from '@/components/layout/sidebar/sidebar-header' import { useSidebar } from '@/lib/hooks/use-sidebar' import { cn } from '@/lib/utils' import React from 'react' +import { usePathname } from 'next/navigation' +import { useThread } from '@/lib/hooks/use-thread' export function Sidebar({ className }: React.ComponentProps<'div'>) { const { isSidebarOpen, isLoading } = useSidebar() + const prevPathRef = React.useRef(usePathname()); + const pathname = usePathname(); + const { setActiveThread, setIsOpenPopup } = useThread() + const rootAndChatRegex = /^\/(?:c)?$/; + React.useEffect(() => { + if (rootAndChatRegex.test(pathname)) { + setActiveThread(null); + setIsOpenPopup(false); + } + prevPathRef.current = pathname; + }, [pathname]); + + if (isLoading) return null return ( diff --git a/apps/masterbots.ai/components/routes/browse/browse-list-item.tsx b/apps/masterbots.ai/components/routes/browse/browse-list-item.tsx old mode 100644 new mode 100755 index f668afb2c..fbd75ad48 --- a/apps/masterbots.ai/components/routes/browse/browse-list-item.tsx +++ b/apps/masterbots.ai/components/routes/browse/browse-list-item.tsx @@ -36,6 +36,8 @@ import Image from 'next/image' import { useRouter } from 'next/navigation' import React from 'react' import { ChatOptions } from '../chat/chat-options' +import { urlBuilders } from '@/lib/url' + let initialUrl: string | null = null @@ -139,7 +141,7 @@ export default function BrowseListItem({ e.preventDefault() e.stopPropagation() if (thread?.user?.slug) { - router.push(`/u/${thread?.user?.slug}/t`) + router.push(urlBuilders.userProfileUrl({ userSlug: thread.user.slug })) } } diff --git a/apps/masterbots.ai/components/routes/browse/browse-list.tsx b/apps/masterbots.ai/components/routes/browse/browse-list.tsx index ddd2b2859..b37c78c80 100644 --- a/apps/masterbots.ai/components/routes/browse/browse-list.tsx +++ b/apps/masterbots.ai/components/routes/browse/browse-list.tsx @@ -41,12 +41,12 @@ export default function BrowseList() { const [filteredThreads, setFilteredThreads] = React.useState([]) const [loading, setLoading] = React.useState(false) const [count, setCount] = React.useState(0) - const { selectedCategories, selectedChatbots } = useSidebar() + const { selectedCategories, selectedChatbots, activeCategory, activeChatbot } = useSidebar() const fetchThreads = async ({ categoriesId, chatbotsId, - keyword + keyword, }: { categoriesId: number[] chatbotsId: number[] @@ -55,10 +55,17 @@ export default function BrowseList() { setLoading(true) // ? Seting loading before fetch try { const threads = await getBrowseThreads({ - categoriesId, - chatbotsId, - keyword, - limit: PAGE_SIZE + ...(activeCategory !== null || activeChatbot !== null + ? { + categoryId: activeCategory, + chatbotName: activeChatbot?.name, + } + : { + categoriesId, + chatbotsId, + keyword, + }), + limit: PAGE_SIZE, }) setThreads(threads) setFilteredThreads(threads) @@ -108,12 +115,8 @@ export default function BrowseList() { categoriesId: selectedCategories, chatbotsId: selectedChatbots }) + }, [selectedCategories, selectedChatbots, activeCategory, activeChatbot]) - console.log({ - selectedCategories, - selectedChatbots - }) - }, [selectedCategories, selectedChatbots]) // biome-ignore lint/correctness/useExhaustiveDependencies: React.useEffect(() => { diff --git a/apps/masterbots.ai/components/routes/chat/chat-accordion.tsx b/apps/masterbots.ai/components/routes/chat/chat-accordion.tsx index 08ef6af8c..3ce4b94cc 100644 --- a/apps/masterbots.ai/components/routes/chat/chat-accordion.tsx +++ b/apps/masterbots.ai/components/routes/chat/chat-accordion.tsx @@ -1,8 +1,10 @@ //* Component for displaying a collapsible chat thread accordion import { useThread } from '@/lib/hooks/use-thread' +import { urlBuilders } from '@/lib/url' import { ChevronDown } from 'lucide-react' import type { Thread } from 'mb-genql' +import { useParams, usePathname, useRouter } from 'next/navigation' import React from 'react' export const ChatAccordion = ({ @@ -42,8 +44,13 @@ export const ChatAccordion = ({ isOpenPopup } = useThread() + const pathname = usePathname() + const params = useParams() + const router = useRouter(); + //* Sets the initial open state based on defaultState prop const initialState = defaultState + const profilePage = /^\/u\/[^/]+\/t(?:\/|$)/.test(pathname) const [open, setOpen] = React.useState(initialState) const isMainThread = !isOpenPopup @@ -52,12 +59,24 @@ export const ChatAccordion = ({ const handleClick = (e: React.MouseEvent) => { e.stopPropagation() - if (isMainThread && thread) { + if (isMainThread && thread && !profilePage) { //* Main thread click - open modal setActiveThread(thread) setIsOpenPopup(true) } else { //* Sub-conversation click - toggle accordion + if (profilePage) { + setIsOpenPopup(false) + setActiveThread(null) + const category = thread?.chatbot?.categories[0]?.category?.name + const chatbot = thread?.chatbot?.name + const slug = params.slug; + if (!category || !chatbot || !slug) { + console.error('Missing required navigation parameters'); + return; + } + router.push(urlBuilders.threadUrl({ slug: slug as string, category, chatbot, threadId: thread?.threadId })) + } toggle() } } diff --git a/apps/masterbots.ai/components/routes/thread/thread-component.tsx b/apps/masterbots.ai/components/routes/thread/thread-component.tsx old mode 100644 new mode 100755 index f76cc7203..a4c9596a2 --- a/apps/masterbots.ai/components/routes/thread/thread-component.tsx +++ b/apps/masterbots.ai/components/routes/thread/thread-component.tsx @@ -37,12 +37,9 @@ import { useScroll } from '@/lib/hooks/use-scroll' import { useThread } from '@/lib/hooks/use-thread' import { useThreadVisibility } from '@/lib/hooks/use-thread-visibility' import type { Thread } from 'mb-genql' -import { toSlug } from 'mb-lib' -import { redirect, useParams, usePathname } from 'next/navigation' import { useRef } from 'react' import { AdminModeApprove } from '../chat/admin-mode-approve' import { ChatOptions } from '../chat/chat-options' - export default function ThreadComponent({ thread, loadMore, @@ -60,8 +57,6 @@ export default function ThreadComponent({ const contentRef = useRef(null) const { isNewResponse } = useThread() const { isAdminMode } = useThreadVisibility() - const pathname = usePathname() - const params = useParams() const { isNearBottom, scrollToTop } = useScroll({ containerRef: contentRef, @@ -74,22 +69,8 @@ export default function ThreadComponent({ }) const threadId = thread.threadId - const handleAccordionToggle = () => { - - if (pathname.includes('u') && pathname.includes('t')) { - const category = thread?.chatbot?.categories[0]?.category?.name - const chatbot = thread?.chatbot?.name - const slug = params.slug; - - if (!category || !chatbot || !slug) { - console.error('Missing required navigation parameters'); - return scrollToTop(); - } - redirect(`/u/${slug}/t/${toSlug(category)}/${toSlug(chatbot)}/${thread.threadId}`) - } else { - scrollToTop() - } + scrollToTop() } return (
  • diff --git a/apps/masterbots.ai/components/routes/thread/user-thread-panel.tsx b/apps/masterbots.ai/components/routes/thread/user-thread-panel.tsx old mode 100644 new mode 100755 index b77b556e6..b62a14320 --- a/apps/masterbots.ai/components/routes/thread/user-thread-panel.tsx +++ b/apps/masterbots.ai/components/routes/thread/user-thread-panel.tsx @@ -35,11 +35,10 @@ import { useThreadVisibility } from '@/lib/hooks/use-thread-visibility' import { getBrowseThreads, getThreads, getUserBySlug } from '@/services/hasura' import type { Thread } from 'mb-genql' import { useSession } from 'next-auth/react' -import { useParams } from 'next/navigation' -import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useParams, usePathname } from 'next/navigation' +import { useEffect, useMemo, useRef, useState } from 'react' import { useAsync } from 'react-use' - const PAGE_SIZE = 20 export default function UserThreadPanel({ @@ -61,7 +60,7 @@ export default function UserThreadPanel({ const [loading, setLoading] = useState(false) const { threads: hookThreads } = useThreadVisibility() const [searchTerm, setSearchTerm] = useState('') - const { slug } = params; + const { slug, threadId } = params; const userWithSlug = useAsync(async () => { if (!slug) return { user: null } @@ -81,6 +80,8 @@ export default function UserThreadPanel({ const [totalThreads, setTotalThreads] = useState(0) const prevCategoryRef = useRef(activeCategory); const prevChatbotRef = useRef(activeChatbot); + const prevPathRef = useRef(usePathname()); + const pathname = usePathname(); useEffect(() => { setThreads(finalThreads) @@ -111,8 +112,9 @@ export default function UserThreadPanel({ console.log('🟡 Loading More Content') setLoading(true) let moreThreads: Thread[] = [] - - if (page === 'profile' && !session?.user) { + const userOnSlug = userWithSlug.value?.user + const isOwnProfile = session?.user?.id === userOnSlug?.userId; + if (page === 'profile' && !session?.user || !isOwnProfile) { moreThreads = await fetchBrowseThreads(); } else { moreThreads = await getThreads({ @@ -131,23 +133,20 @@ export default function UserThreadPanel({ const handleThreadsChange = async () => { let threads: Thread[] = [] - - console.log('🟡 Fetching Threads') setLoading(true) - const isOwnProfile = session?.user?.slug === slug; - if (!session?.user || !isOwnProfile) { - if (page === 'profile') { - threads = await fetchBrowseThreads(); - setThreads(_prev => threads ?? []) - setCount(_prev => threads.length ?? 0) - setTotalThreads(threads?.length ?? 0) - } + const userOnSlug = userWithSlug.value?.user + const isOwnProfile = session?.user?.id === userOnSlug?.userId; + if (!session?.user || !isOwnProfile && page === 'profile') { + threads = await fetchBrowseThreads(); + setThreads(_prev => threads ?? []) + setCount(_prev => threads.length ?? 0) + setTotalThreads(threads?.length ?? 0) setLoading(false) return; } + const currentFetchId = Date.now() // Generate a unique identifier for the current fetch fetchIdRef.current = currentFetchId - threads = await getThreads({ jwt: session!.user?.hasuraJwt, userId: session!.user.id, @@ -166,31 +165,26 @@ export default function UserThreadPanel({ setLoading(false) } - - const shouldFetchThreads = useCallback(() => { - if (isOpenPopup) return false; + useEffect(() => { + // Skip if popup is open + if (isOpenPopup) return; const shouldFetch = activeCategory || activeChatbot || (prevCategoryRef.current && !activeCategory) || - (prevChatbotRef.current && !activeChatbot); + (prevChatbotRef.current && !activeChatbot) || + pathname !== prevPathRef.current; // Add pathname check - // Update refs after checking + // Update refs prevCategoryRef.current = activeCategory; prevChatbotRef.current = activeChatbot; + prevPathRef.current = pathname; - return shouldFetch; - }, [isOpenPopup, activeCategory, activeChatbot]); - - - useEffect(() => { - if (shouldFetchThreads()) { + if (shouldFetch) { handleThreadsChange(); } - }, [activeCategory, activeChatbot, isOpenPopup, shouldFetchThreads]); - - + }, [activeCategory, activeChatbot, isOpenPopup, pathname]); useEffect(() => { if ( diff --git a/apps/masterbots.ai/lib/hooks/use-mb-chat.ts b/apps/masterbots.ai/lib/hooks/use-mb-chat.ts index 05827fa0f..983902895 100644 --- a/apps/masterbots.ai/lib/hooks/use-mb-chat.ts +++ b/apps/masterbots.ai/lib/hooks/use-mb-chat.ts @@ -2,7 +2,6 @@ import { improveMessage } from '@/app/actions' import { formatSystemPrompts } from '@/lib/actions' import { followingQuestionsPrompt, - setDefaultPrompt, setDefaultUserPreferencesPrompt } from '@/lib/constants/prompts' import { useModel } from '@/lib/hooks/use-model' @@ -16,7 +15,7 @@ import { getThread, saveNewMessage } from '@/services/hasura' -import type { AiClientType, AiToolCall, CleanPromptResult } from '@/types/types' +import type { AiClientType, AiToolCall } from '@/types/types' import type { Message as AiMessage, ChatRequestOptions, @@ -58,7 +57,7 @@ export function useMBChat(config?: MBChatHookConfig): MBChatHookCallback { messagesFromDB: [] as Message[] }) - console.log('[HOOK] webSearch', webSearch) + // console.log('[HOOK] webSearch', webSearch) const params = useParams<{ chatbot: string; threadId: string }>() const { selectedModel, clientType } = useModel() @@ -75,11 +74,11 @@ export function useMBChat(config?: MBChatHookConfig): MBChatHookCallback { // format all user prompts and chatgpt 'assistant' messages const userAndAssistantMessages: AiMessage[] = activeThread ? messagesFromDB.map(m => ({ - id: m.messageId, - role: m.role as AiMessage['role'], - content: m.content, - createdAt: m.createdAt - })) + id: m.messageId, + role: m.role as AiMessage['role'], + content: m.content, + createdAt: m.createdAt + })) : [] // concatenate all message to pass it to chat component @@ -265,12 +264,9 @@ export function useMBChat(config?: MBChatHookConfig): MBChatHookCallback { isNewChat ? { ...userMessage, content: userContentRef.current } : { - ...userMessage, - content: followingQuestionsPrompt( - userContentRef.current, - messages - ) - } + ...userMessage, + content: followingQuestionsPrompt(userContentRef.current, messages) + } // ? Provide chat attachments here... // { // experimental_attachments: [], diff --git a/apps/masterbots.ai/lib/hooks/use-profile.tsx b/apps/masterbots.ai/lib/hooks/use-profile.tsx index 3e79e0a21..69acbc311 100644 --- a/apps/masterbots.ai/lib/hooks/use-profile.tsx +++ b/apps/masterbots.ai/lib/hooks/use-profile.tsx @@ -1,15 +1,15 @@ 'use client' -import * as React from 'react' import { getUserBySlug, updateUserPersonality } from '@/services/hasura' -import { useSession } from 'next-auth/react' import { User } from 'mb-genql' +import { useSession } from 'next-auth/react' +import * as React from 'react' import toast from 'react-hot-toast' interface profileContextProps { getuserInfo: (username: string) => Promise isSameUser: (userId: string) => boolean - updateUserInfo: (bio: string | null, topic: string | null, profilePicture: string | null) => void + updateUserInfo: (bio: string | null, topic: string | null, profilePicture: string | null) => void currentUser: User | null, setCurrentUser: React.Dispatch> } @@ -42,9 +42,10 @@ export function ProfileProvider({ children }: ProfileProviderProps) { throw new Error('Slug is required') } try { + const sessionSlug = session?.user.slug ? session?.user.slug.toLowerCase() : session?.user.name?.toLowerCase() const userInfo = await getUserBySlug({ slug, - isSameUser: session?.user.slug == slug + isSameUser: sessionSlug === slug }) if (!userInfo) { throw new Error('User not found') @@ -59,12 +60,12 @@ export function ProfileProvider({ children }: ProfileProviderProps) { const isSameUser = (userId: string) => { if (!userId?.trim() || !session?.user?.id) { - return false - } + return false + } return session?.user.id === userId } - const updateUserInfo = async (bio: string | null, topic: string | null, profilePicture: string | null ) => { + const updateUserInfo = async (bio: string | null, topic: string | null, profilePicture: string | null) => { try { const jwt = session?.user?.hasuraJwt; if (!jwt || !session.user?.id) { @@ -85,7 +86,7 @@ export function ProfileProvider({ children }: ProfileProviderProps) { return ( {children} diff --git a/apps/masterbots.ai/lib/url.ts b/apps/masterbots.ai/lib/url.ts index 759d9d580..8e1e63555 100644 --- a/apps/masterbots.ai/lib/url.ts +++ b/apps/masterbots.ai/lib/url.ts @@ -1,4 +1,5 @@ import { z, ZodSchema } from 'zod' +import { toSlug } from 'mb-lib' // Zod schema for validating slug strings export const SlugSchema: ZodSchema = z @@ -16,4 +17,83 @@ export const encodeQuery = (input: string): string => { export const decodeQuery = (input: string): string => { return decodeURIComponent(input.replace(/\+/g, ' ')) -} \ No newline at end of file +} + + +interface ThreadUrlParams { + slug?: string; + category?: string; + chatbot?: string; + threadId?: string; +} + +interface ProfileUrlParams { + slug?: string; + category?: string; + chatbot?: string; +} + +interface UserProfileParams { + userSlug?: string; +} + +export const urlBuilders = { + threadUrl: (params: ThreadUrlParams): string => { + try { + const { slug, category, chatbot, threadId } = params; + + if (!slug || !category || !chatbot || !threadId) { + const missing = Object.entries(params) + .filter(([_, value]) => !value) + .map(([key]) => key) + .join(', '); + + console.error(`Missing required parameters for thread URL: ${missing}`); + return '/'; + } + + return `/u/${encodeURIComponent(slug)}/t/${toSlug(category)}/${toSlug(chatbot)}/${threadId}`; + } catch (error) { + console.error('Error constructing thread URL:', error); + return '/'; + } + }, + + userChatbotUrl: (params: ProfileUrlParams): string => { + try { + const { slug, category, chatbot } = params; + + if (!slug || !category || !chatbot) { + const missing = Object.entries(params) + .filter(([_, value]) => !value) + .map(([key]) => key) + .join(', '); + + console.error(`Missing required parameters for profile URL: ${missing}`); + return '/'; + } + + return `/u/${encodeURIComponent(slug)}/t/${toSlug(category)}/${chatbot.toLowerCase()}`; + } catch (error) { + console.error('Error constructing profile URL:', error); + return '/'; + } + }, + + userProfileUrl: (params: UserProfileParams): string => { + try { + const { userSlug } = params; + + if (!userSlug) { + console.error('Missing user slug for profile URL'); + return '/'; + } + + return `/u/${encodeURIComponent(userSlug)}/t`; + } catch (error) { + console.error('Error constructing user profile URL:', error); + return '/'; + } + }, +}; + diff --git a/apps/masterbots.ai/services/hasura/hasura.service.ts b/apps/masterbots.ai/services/hasura/hasura.service.ts old mode 100644 new mode 100755 index 5785f4816..10b51eb3e --- a/apps/masterbots.ai/services/hasura/hasura.service.ts +++ b/apps/masterbots.ai/services/hasura/hasura.service.ts @@ -680,6 +680,107 @@ export async function approveThread({ } } +export async function getUserRoleByEmail({ + email +}: { + email: string | null | undefined +}) { + try { + const client = getHasuraClient({ jwt: '' }) + const { user } = await client.query({ + user: { + __args: { + where: { email: { _eq: email } } + }, + role: true, + slug: true + } + }) + return { users: user as User[] } + } catch (error) { + console.error('Error fetching user role by email:', error) + return { users: [], error: 'Failed to fetch user role by email.' } + } +} + +export async function deleteThread({ + threadId, + jwt, + userId +}: { + threadId: string + jwt: string | undefined + userId: string | undefined +}) { + try { + if (!jwt) { + throw new Error('Authentication required for thread deletion') + } + + const client = getHasuraClient({ jwt }) + await client.mutation({ + deleteThread: { + __args: { + where: { threadId: { _eq: threadId }, userId: { _eq: userId } } + }, + returning: { + threadId: true + }, + affectedRows: true + } + }) + + return { success: true } + } catch (error) { + console.error('Error deleting thread:', error) + return { success: false, error: 'Failed to delete the thread.' } + } +} + +// get all threads that are not approved +export async function getUnapprovedThreads({ jwt }: { jwt: string }) { + if (!jwt) { + throw new Error('Authentication required to access unapproved threads') + } + const client = getHasuraClient({ jwt }) + const { thread } = await client.query({ + thread: { + __args: { + where: { isApproved: { _eq: false } }, + orderBy: [{ createdAt: 'DESC' }], + limit: 20 + }, + chatbot: { + ...everything, + categories: { + category: { + ...everything + }, + ...everything + }, + threads: { + threadId: true + }, + prompts: { + prompt: everything + } + }, + messages: { + ...everything, + __args: { + orderBy: [{ createdAt: 'ASC' }], + limit: 2 + } + }, + isApproved: true, + isPublic: true, + ...everything + } + }) + + return thread as Thread[] +} + export async function getUserBySlug({ slug, isSameUser @@ -804,7 +905,7 @@ export async function updateUserPersonality({ updateArgs._set = { ...(bio !== null && { bio }), - ...(topic !== null && { favourite_topic: topic }), + ...(topic !== null && { favouriteTopic: topic }), ...(profilePicture !== null && { profilePicture }) } @@ -824,104 +925,6 @@ export async function updateUserPersonality({ } } -export async function getUserRoleByEmail({ - email -}: { - email: string | null | undefined -}) { - try { - const client = getHasuraClient({ jwt: '' }) - const { user } = await client.query({ - user: { - __args: { - where: { email: { _eq: email } } - }, - role: true, - slug: true - } - }) - return { users: user as User[] } - } catch (error) { - console.error('Error fetching user role by email:', error) - return { users: [], error: 'Failed to fetch user role by email.' } - } -} - -export async function deleteThread({ - threadId, - jwt, - userId -}: { - threadId: string - jwt: string | undefined - userId: string | undefined -}) { - try { - if (!jwt) { - throw new Error('Authentication required for thread deletion') - } - - const client = getHasuraClient({ jwt }) - await client.mutation({ - deleteThread: { - __args: { - where: { threadId: { _eq: threadId }, userId: { _eq: userId } } - } - }, - affected_rows: true - }) - - return { success: true } - } catch (error) { - console.error('Error deleting thread:', error) - return { success: false, error: 'Failed to delete the thread.' } - } -} - -// get all threads that are not approved -export async function getUnapprovedThreads({ jwt }: { jwt: string }) { - if (!jwt) { - throw new Error('Authentication required to access unapproved threads') - } - const client = getHasuraClient({ jwt }) - const { thread } = await client.query({ - thread: { - __args: { - where: { isApproved: { _eq: false } }, - orderBy: [{ createdAt: 'DESC' }], - limit: 20 - }, - chatbot: { - ...everything, - categories: { - category: { - ...everything - }, - ...everything - }, - threads: { - threadId: true - }, - prompts: { - prompt: everything - } - }, - messages: { - ...everything, - __args: { - orderBy: [{ createdAt: 'ASC' }], - limit: 2 - } - }, - isApproved: true, - isPublic: true, - ...everything - } - }) - - return thread as Thread[] -} - export async function subtractChatbotMetadataLabels( metadataHeaders: ChatbotMetadataHeaders, userPrompt: string,