diff --git a/surfsense_web/app/dashboard/[search_space_id]/chats/chats-client.tsx b/surfsense_web/app/dashboard/[search_space_id]/chats/chats-client.tsx index b007892c7..1d00ef01a 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/chats/chats-client.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/chats/chats-client.tsx @@ -15,7 +15,7 @@ import { AnimatePresence, motion, type Variants } from "motion/react"; import { useRouter, useSearchParams } from "next/navigation"; import { useEffect, useState } from "react"; import { deleteChatMutationAtom } from "@/atoms/chats/chat-mutation.atoms"; -import { chatsAtom } from "@/atoms/chats/chat-querie.atoms"; +import { chatsAtom } from "@/atoms/chats/chat-query.atoms"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; diff --git a/surfsense_web/app/dashboard/[search_space_id]/researcher/[[...chat_id]]/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/researcher/[[...chat_id]]/page.tsx index 4703d5858..78bc0609a 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/researcher/[[...chat_id]]/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/researcher/[[...chat_id]]/page.tsx @@ -4,7 +4,7 @@ import { type CreateMessage, type Message, useChat } from "@ai-sdk/react"; import { useAtomValue } from "jotai"; import { useParams, useRouter } from "next/navigation"; import { useEffect, useMemo, useRef } from "react"; -import { activeChatAtom } from "@/atoms/chats/chat-querie.atoms"; +import { activeChatAtom } from "@/atoms/chats/chat-query.atoms"; import { activeChatIdAtom } from "@/atoms/chats/ui.atoms"; import ChatInterface from "@/components/chat/ChatInterface"; import { useChatAPI, useChatState } from "@/hooks/use-chat"; diff --git a/surfsense_web/atoms/chats/chat-querie.atoms.ts b/surfsense_web/atoms/chats/chat-query.atoms.ts similarity index 100% rename from surfsense_web/atoms/chats/chat-querie.atoms.ts rename to surfsense_web/atoms/chats/chat-query.atoms.ts diff --git a/surfsense_web/atoms/podcasts/podcast-mutation.atoms.ts b/surfsense_web/atoms/podcasts/podcast-mutation.atoms.ts index c86c38391..a44a15558 100644 --- a/surfsense_web/atoms/podcasts/podcast-mutation.atoms.ts +++ b/surfsense_web/atoms/podcasts/podcast-mutation.atoms.ts @@ -5,13 +5,15 @@ import type { DeletePodcastRequest, Podcast } from "@/contracts/types/podcast.ty import { podcastsApiService } from "@/lib/apis/podcasts-api.service"; import { cacheKeys } from "@/lib/query-client/cache-keys"; import { queryClient } from "@/lib/query-client/client"; +import { globalPodcastsQueryParamsAtom } from "./ui.atoms"; export const deletePodcastMutationAtom = atomWithMutation((get) => { const searchSpaceId = get(activeSearchSpaceIdAtom); const authToken = localStorage.getItem("surfsense_bearer_token"); + const podcastsQueryParams = get(globalPodcastsQueryParamsAtom); return { - mutationKey: cacheKeys.podcasts(), + mutationKey: cacheKeys.podcasts.globalQueryParams(podcastsQueryParams), enabled: !!searchSpaceId && !!authToken, mutationFn: async (request: DeletePodcastRequest) => { return podcastsApiService.deletePodcast(request); @@ -19,9 +21,12 @@ export const deletePodcastMutationAtom = atomWithMutation((get) => { onSuccess: (_, request: DeletePodcastRequest) => { toast.success("Podcast deleted successfully"); - queryClient.setQueryData(cacheKeys.podcasts(), (oldData: Podcast[]) => { - return oldData.filter((podcast) => podcast.id !== request.id); - }); + queryClient.setQueryData( + cacheKeys.podcasts.globalQueryParams(podcastsQueryParams), + (oldData: Podcast[]) => { + return oldData.filter((podcast) => podcast.id !== request.id); + } + ); }, }; }); diff --git a/surfsense_web/atoms/podcasts/podcast-query.atoms.ts b/surfsense_web/atoms/podcasts/podcast-query.atoms.ts new file mode 100644 index 000000000..ea5c1c104 --- /dev/null +++ b/surfsense_web/atoms/podcasts/podcast-query.atoms.ts @@ -0,0 +1,17 @@ +import { atomWithQuery } from "jotai-tanstack-query"; +import { podcastsApiService } from "@/lib/apis/podcasts-api.service"; +import { cacheKeys } from "@/lib/query-client/cache-keys"; +import { globalPodcastsQueryParamsAtom } from "./ui.atoms"; + +export const podcastsAtom = atomWithQuery((get) => { + const queryParams = get(globalPodcastsQueryParamsAtom); + + return { + queryKey: cacheKeys.podcasts.globalQueryParams(queryParams), + queryFn: async () => { + return podcastsApiService.getPodcasts({ + queryParams: queryParams, + }); + }, + }; +}); diff --git a/surfsense_web/atoms/podcasts/ui.atoms.ts b/surfsense_web/atoms/podcasts/ui.atoms.ts new file mode 100644 index 000000000..0f2701375 --- /dev/null +++ b/surfsense_web/atoms/podcasts/ui.atoms.ts @@ -0,0 +1,7 @@ +import { atom } from "jotai"; +import type { GetPodcastsRequest } from "@/contracts/types/podcast.types"; + +export const globalPodcastsQueryParamsAtom = atom({ + limit: 5, + skip: 0, +}); diff --git a/surfsense_web/components/chat/ChatPanel/ChatPanelContainer.tsx b/surfsense_web/components/chat/ChatPanel/ChatPanelContainer.tsx index 045eb873d..aebb1068b 100644 --- a/surfsense_web/components/chat/ChatPanel/ChatPanelContainer.tsx +++ b/surfsense_web/components/chat/ChatPanel/ChatPanelContainer.tsx @@ -2,7 +2,7 @@ import { useAtom, useAtomValue } from "jotai"; import { LoaderIcon, PanelRight, TriangleAlert } from "lucide-react"; import { toast } from "sonner"; -import { activeChatAtom } from "@/atoms/chats/chat-querie.atoms"; +import { activeChatAtom } from "@/atoms/chats/chat-query.atoms"; import { activeChathatUIAtom, activeChatIdAtom } from "@/atoms/chats/ui.atoms"; import type { GeneratePodcastRequest } from "@/contracts/types/podcast.types"; import { podcastsApiService } from "@/lib/apis/podcasts-api.service"; diff --git a/surfsense_web/components/chat/ChatPanel/ChatPanelView.tsx b/surfsense_web/components/chat/ChatPanel/ChatPanelView.tsx index d0e7c47e8..5eb428946 100644 --- a/surfsense_web/components/chat/ChatPanel/ChatPanelView.tsx +++ b/surfsense_web/components/chat/ChatPanel/ChatPanelView.tsx @@ -4,7 +4,7 @@ import { useAtom, useAtomValue } from "jotai"; import { AlertCircle, Play, RefreshCw, Sparkles } from "lucide-react"; import { motion } from "motion/react"; import { useCallback } from "react"; -import { activeChatAtom } from "@/atoms/chats/chat-querie.atoms"; +import { activeChatAtom } from "@/atoms/chats/chat-query.atoms"; import { activeChathatUIAtom } from "@/atoms/chats/ui.atoms"; import { cn } from "@/lib/utils"; import { getPodcastStalenessMessage, isPodcastStale } from "../PodcastUtils"; diff --git a/surfsense_web/components/chat/ChatPanel/ConfigModal.tsx b/surfsense_web/components/chat/ChatPanel/ConfigModal.tsx index 09a49a2b1..7c1497df9 100644 --- a/surfsense_web/components/chat/ChatPanel/ConfigModal.tsx +++ b/surfsense_web/components/chat/ChatPanel/ConfigModal.tsx @@ -3,7 +3,7 @@ import { useAtomValue } from "jotai"; import { Pencil } from "lucide-react"; import { useCallback, useContext, useState } from "react"; -import { activeChatAtom } from "@/atoms/chats/chat-querie.atoms"; +import { activeChatAtom } from "@/atoms/chats/chat-query.atoms"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import type { GeneratePodcastRequest } from "./ChatPanelContainer"; diff --git a/surfsense_web/components/dashboard-breadcrumb.tsx b/surfsense_web/components/dashboard-breadcrumb.tsx index d29b54685..0324ee1b6 100644 --- a/surfsense_web/components/dashboard-breadcrumb.tsx +++ b/surfsense_web/components/dashboard-breadcrumb.tsx @@ -4,7 +4,7 @@ import { useAtomValue } from "jotai"; import { usePathname } from "next/navigation"; import { useTranslations } from "next-intl"; import React, { useEffect } from "react"; -import { activeChatAtom } from "@/atoms/chats/chat-querie.atoms"; +import { activeChatAtom } from "@/atoms/chats/chat-query.atoms"; import { Breadcrumb, BreadcrumbItem, diff --git a/surfsense_web/components/sidebar/AppSidebarProvider.tsx b/surfsense_web/components/sidebar/AppSidebarProvider.tsx index e413f3c92..e100a4c33 100644 --- a/surfsense_web/components/sidebar/AppSidebarProvider.tsx +++ b/surfsense_web/components/sidebar/AppSidebarProvider.tsx @@ -5,7 +5,7 @@ import { Trash2 } from "lucide-react"; import { useTranslations } from "next-intl"; import { useCallback, useEffect, useMemo, useState } from "react"; import { deleteChatMutationAtom } from "@/atoms/chats/chat-mutation.atoms"; -import { chatsAtom } from "@/atoms/chats/chat-querie.atoms"; +import { chatsAtom } from "@/atoms/chats/chat-query.atoms"; import { globalChatsQueryParamsAtom } from "@/atoms/chats/ui.atoms"; import { AppSidebar } from "@/components/sidebar/app-sidebar"; import { Button } from "@/components/ui/button"; diff --git a/surfsense_web/contracts/types/podcast.types.ts b/surfsense_web/contracts/types/podcast.types.ts index 4c646a2b6..7bc5faece 100644 --- a/surfsense_web/contracts/types/podcast.types.ts +++ b/surfsense_web/contracts/types/podcast.types.ts @@ -1,4 +1,5 @@ import { z } from "zod"; +import { paginationQueryParams } from "."; export const podcast = z.object({ id: z.number(), @@ -36,6 +37,10 @@ export const loadPodcastRequest = z.object({ id: z.number(), }); +export const getPodcastsRequest = z.object({ + queryParams: paginationQueryParams.nullish(), +}); + export type GeneratePodcastRequest = z.infer; export type GetPodcastByChatIdRequest = z.infer; export type GetPodcastByChatIdResponse = z.infer; @@ -43,3 +48,4 @@ export type DeletePodcastRequest = z.infer; export type DeletePodcastResponse = z.infer; export type LoadPodcastRequest = z.infer; export type Podcast = z.infer; +export type GetPodcastsRequest = z.infer; diff --git a/surfsense_web/lib/apis/podcasts-api.service.ts b/surfsense_web/lib/apis/podcasts-api.service.ts index 03c32e11e..733499a40 100644 --- a/surfsense_web/lib/apis/podcasts-api.service.ts +++ b/surfsense_web/lib/apis/podcasts-api.service.ts @@ -5,9 +5,11 @@ import { deletePodcastResponse, type GeneratePodcastRequest, type GetPodcastByChatIdRequest, + type GetPodcastsRequest, generatePodcastRequest, getPodcastByChaIdResponse, getPodcastByChatIdRequest, + getPodcastsRequest, type LoadPodcastRequest, loadPodcastRequest, podcast, @@ -16,8 +18,30 @@ import { ValidationError } from "../error"; import { baseApiService } from "./base-api.service"; class PodcastsApiService { - getPodcasts = async () => { - return baseApiService.get("/api/v1/podcasts", z.array(podcast)); + getPodcasts = async (request: GetPodcastsRequest) => { + // Validate the request + const parsedRequest = getPodcastsRequest.safeParse(request); + + if (!parsedRequest.success) { + console.error("Invalid request:", parsedRequest.error); + + // Format a user frendly error message + const errorMessage = parsedRequest.error.errors.map((err) => err.message).join(", "); + throw new ValidationError(`Invalid request: ${errorMessage}`); + } + + // Transform queries params to be string values + const transformedQueryParams = parsedRequest.data.queryParams + ? Object.fromEntries( + Object.entries(parsedRequest.data.queryParams).map(([k, v]) => [k, String(v)]) + ) + : undefined; + + const queryParams = transformedQueryParams + ? new URLSearchParams(transformedQueryParams).toString() + : undefined; + + return baseApiService.get(`/api/v1/podcasts?${queryParams}`, z.array(podcast)); }; getPodcastByChatId = async (request: GetPodcastByChatIdRequest) => { diff --git a/surfsense_web/lib/query-client/cache-keys.ts b/surfsense_web/lib/query-client/cache-keys.ts index aae0f09d1..12b73bc35 100644 --- a/surfsense_web/lib/query-client/cache-keys.ts +++ b/surfsense_web/lib/query-client/cache-keys.ts @@ -1,4 +1,5 @@ import type { GetChatsRequest } from "@/contracts/types/chat.types"; +import type { GetPodcastsRequest } from "@/contracts/types/podcast.types"; export const cacheKeys = { chats: { @@ -6,7 +7,10 @@ export const cacheKeys = { globalQueryParams: (queries: GetChatsRequest["queryParams"]) => ["chats", ...(queries ? Object.values(queries) : [])] as const, }, - podcasts: () => ["podcasts"] as const, + podcasts: { + globalQueryParams: (queries: GetPodcastsRequest["queryParams"]) => + ["podcasts", ...(queries ? Object.values(queries) : [])] as const, + }, auth: { user: ["auth", "user"] as const, },