Add delete podcast api service

This commit is contained in:
thierryverse 2025-11-18 19:32:24 +02:00
parent bf94903459
commit 1a954bc184
7 changed files with 72 additions and 50 deletions

View file

@ -161,7 +161,7 @@ export default function ChatsPageClient({ searchSpaceId }: ChatsPageClientProps)
const handleDeleteChat = async () => {
if (!chatToDelete) return;
await deleteChat(chatToDelete.id);
await deleteChat({ id: chatToDelete.id });
setDeleteDialogOpen(false);
setChatToDelete(null);

View file

@ -1,6 +1,7 @@
"use client";
import { format } from "date-fns";
import { useAtom } from "jotai";
import {
Calendar,
MoreHorizontal,
@ -19,6 +20,7 @@ import { AnimatePresence, motion, type Variants } from "motion/react";
import Image from "next/image";
import { useEffect, useRef, useState } from "react";
import { toast } from "sonner";
import { deletePodcastMutationAtom } from "@/atoms/podcasts/podcast-mutation.atoms";
// UI Components
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
@ -88,7 +90,6 @@ export default function PodcastsPageClient({ searchSpaceId }: PodcastsPageClient
id: number;
title: string;
} | null>(null);
const [isDeleting, setIsDeleting] = useState(false);
// Audio player state
const [currentPodcast, setCurrentPodcast] = useState<Podcast | null>(null);
@ -101,6 +102,8 @@ export default function PodcastsPageClient({ searchSpaceId }: PodcastsPageClient
const [isMuted, setIsMuted] = useState(false);
const audioRef = useRef<HTMLAudioElement | null>(null);
const currentObjectUrlRef = useRef<string | null>(null);
const [{ isPending: isDeletingPodcast, mutateAsync: deletePodcast, error: deleteError }] =
useAtom(deletePodcastMutationAtom);
// Add podcast image URL constant
const PODCAST_IMAGE_URL =
@ -330,7 +333,7 @@ export default function PodcastsPageClient({ searchSpaceId }: PodcastsPageClient
try {
const response = await podcastsApiService.loadPodcast({
podcast,
request: { id: podcast.id },
controller,
});
const objectUrl = URL.createObjectURL(response);
@ -364,38 +367,13 @@ export default function PodcastsPageClient({ searchSpaceId }: PodcastsPageClient
const handleDeletePodcast = async () => {
if (!podcastToDelete) return;
setIsDeleting(true);
try {
const token = localStorage.getItem("surfsense_bearer_token");
if (!token) {
setIsDeleting(false);
return;
}
await deletePodcast({ id: podcastToDelete.id });
const response = await fetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/podcasts/${podcastToDelete.id}`,
{
method: "DELETE",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
}
);
if (!response.ok) {
throw new Error(`Failed to delete podcast: ${response.statusText}`);
}
// Close dialog and refresh podcasts
// Close dialog
setDeleteDialogOpen(false);
setPodcastToDelete(null);
// Update local state by removing the deleted podcast
setPodcasts((prevPodcasts) =>
prevPodcasts.filter((podcast) => podcast.id !== podcastToDelete.id)
);
// If the current playing podcast is deleted, stop playback
if (currentPodcast && currentPodcast.id === podcastToDelete.id) {
if (audioRef.current) {
@ -404,13 +382,9 @@ export default function PodcastsPageClient({ searchSpaceId }: PodcastsPageClient
setCurrentPodcast(null);
setIsPlaying(false);
}
toast.success("Podcast deleted successfully");
} catch (error) {
console.error("Error deleting podcast:", error);
toast.error(error instanceof Error ? error.message : "Failed to delete podcast");
} finally {
setIsDeleting(false);
}
};
@ -933,17 +907,17 @@ export default function PodcastsPageClient({ searchSpaceId }: PodcastsPageClient
<Button
variant="outline"
onClick={() => setDeleteDialogOpen(false)}
disabled={isDeleting}
disabled={isDeletingPodcast}
>
Cancel
</Button>
<Button
variant="destructive"
onClick={handleDeletePodcast}
disabled={isDeleting}
disabled={isDeletingPodcast}
className="gap-2"
>
{isDeleting ? (
{isDeletingPodcast ? (
<>
<span className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
Deleting...

View file

@ -1,6 +1,7 @@
import { atomWithMutation } from "jotai-tanstack-query";
import { toast } from "sonner";
import type { Chat } from "@/app/dashboard/[search_space_id]/chats/chats-client";
import type { DeleteChatRequest } from "@/contracts/types/chat.types";
import { chatsApiService } from "@/lib/apis/chats-api.service";
import { cacheKeys } from "@/lib/query-client/cache-keys";
import { queryClient } from "@/lib/query-client/client";
@ -13,16 +14,16 @@ export const deleteChatMutationAtom = atomWithMutation((get) => {
return {
mutationKey: cacheKeys.activeSearchSpace.chats(searchSpaceId ?? ""),
enabled: !!searchSpaceId && !!authToken,
mutationFn: async (chatId: number) => {
return chatsApiService.deleteChat({ id: chatId });
mutationFn: async (request: DeleteChatRequest) => {
return chatsApiService.deleteChat(request);
},
onSuccess: (_, chatId) => {
onSuccess: (_, request: DeleteChatRequest) => {
toast.success("Chat deleted successfully");
queryClient.setQueryData(
cacheKeys.activeSearchSpace.chats(searchSpaceId!),
(oldData: Chat[]) => {
return oldData.filter((chat) => chat.id !== chatId);
return oldData.filter((chat) => chat.id !== request.id);
}
);
},

View file

@ -68,7 +68,7 @@ export function PodcastPlayer({
try {
const response = await podcastsApiService.loadPodcast({
podcast,
request: { id: podcast.id },
controller,
});

View file

@ -22,9 +22,24 @@ export const getPodcastByChatIdRequest = z.object({
chat_id: z.number(),
});
export const getPodcastByChatResponse = podcast.nullish();
export const getPodcastByChaIdResponse = podcast.nullish();
export const deletePodcastRequest = z.object({
id: z.number(),
});
export const deletePodcastResponse = z.object({
message: z.literal("Podcast deleted successfully"),
});
export const loadPodcastRequest = z.object({
id: z.number(),
});
export type GeneratePodcastRequest = z.infer<typeof generatePodcastRequest>;
export type GetPodcastByChatIdRequest = z.infer<typeof getPodcastByChatIdRequest>;
export type GetPodcastByChatResponse = z.infer<typeof getPodcastByChatResponse>;
export type GetPodcastByChatIdResponse = z.infer<typeof getPodcastByChaIdResponse>;
export type DeletePodcastRequest = z.infer<typeof deletePodcastRequest>;
export type DeletePodcastResponse = z.infer<typeof deletePodcastResponse>;
export type LoadPodcastRequest = z.infer<typeof loadPodcastRequest>;
export type Podcast = z.infer<typeof podcast>;

View file

@ -1,10 +1,14 @@
import {
type DeletePodcastRequest,
deletePodcastRequest,
deletePodcastResponse,
type GeneratePodcastRequest,
type GetPodcastByChatIdRequest,
generatePodcastRequest,
getPodcastByChaIdResponse,
getPodcastByChatIdRequest,
getPodcastByChatResponse,
type Podcast,
type LoadPodcastRequest,
loadPodcastRequest,
} from "@/contracts/types/podcast.types";
import { ValidationError } from "../error";
import { baseApiService } from "./base-api.service";
@ -24,7 +28,7 @@ class PodcastsApiService {
return baseApiService.get(
`/api/v1/podcasts/by-chat/${request.chat_id}`,
getPodcastByChatResponse
getPodcastByChaIdResponse
);
};
@ -46,16 +50,42 @@ class PodcastsApiService {
};
loadPodcast = async ({
podcast,
request,
controller,
}: {
podcast: Podcast;
request: LoadPodcastRequest;
controller?: AbortController;
}) => {
return await baseApiService.getBlob(`/api/v1/podcasts/${podcast.id}/stream`, {
// Validate the request
const parsedRequest = loadPodcastRequest.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}`);
}
return await baseApiService.getBlob(`/api/v1/podcasts/${request.id}/stream`, {
signal: controller?.signal,
});
};
deletePodcast = async (request: DeletePodcastRequest) => {
// Validate the request
const parsedRequest = deletePodcastRequest.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}`);
}
return baseApiService.delete(`/api/v1/podcasts/${request.id}`, deletePodcastResponse);
};
}
export const podcastsApiService = new PodcastsApiService();

View file

@ -2,6 +2,8 @@ export const cacheKeys = {
activeSearchSpace: {
chats: (searchSpaceId: string) => ["active-search-space", "chats", searchSpaceId] as const,
activeChat: (chatId: string) => ["active-search-space", "active-chat", chatId] as const,
podcasts: (searchSpaceId: string) =>
["active-search-space", "podcasts", searchSpaceId] as const,
},
auth: {
user: ["auth", "user"] as const,