add podcast api service

This commit is contained in:
thierryverse 2025-11-18 18:35:48 +02:00
parent 5361290315
commit 20cd1951b5
5 changed files with 54 additions and 22 deletions

View file

@ -1,21 +1,13 @@
import { atom } from "jotai"; import { atom } from "jotai";
import { atomWithQuery } from "jotai-tanstack-query"; import { atomWithQuery } from "jotai-tanstack-query";
import type { ChatDetails } from "@/app/dashboard/[search_space_id]/chats/chats-client";
import { activeSearchSpaceIdAtom } from "@/atoms/seach-spaces/seach-space-queries.atom"; import { activeSearchSpaceIdAtom } from "@/atoms/seach-spaces/seach-space-queries.atom";
import type { Podcast } from "@/contracts/types/podcast.types";
import { chatsApiService } from "@/lib/apis/chats-api.service"; import { chatsApiService } from "@/lib/apis/chats-api.service";
import { podcastsApiService } from "@/lib/apis/podcasts-api.service"; import { podcastsApiService } from "@/lib/apis/podcasts-api.service";
import { cacheKeys } from "@/lib/query-client/cache-keys"; import { cacheKeys } from "@/lib/query-client/cache-keys";
type ActiveChatState = {
chatId: string | null;
chatDetails: ChatDetails | null;
podcast: Podcast | null;
};
export const activeChatIdAtom = atom<string | null>(null); export const activeChatIdAtom = atom<string | null>(null);
export const activeChatAtom = atomWithQuery<ActiveChatState>((get) => { export const activeChatAtom = atomWithQuery((get) => {
const activeChatId = get(activeChatIdAtom); const activeChatId = get(activeChatIdAtom);
const authToken = localStorage.getItem("surfsense_bearer_token"); const authToken = localStorage.getItem("surfsense_bearer_token");

View file

@ -27,8 +27,8 @@ export function ChatPanelContainer() {
await podcastsApiService.generatePodcast(request); await podcastsApiService.generatePodcast(request);
toast.success(`Podcast generation started!`); toast.success(`Podcast generation started!`);
} catch (error) { } catch (error) {
toast.error("Error generating podcast. Please log in again."); toast.error("Error generating podcast. Please try again later.");
console.error("Error generating podcast:", error); console.error("Error generating podcast:", JSON.stringify(error));
} }
}; };

View file

@ -22,6 +22,9 @@ export const getPodcastByChatIdRequest = z.object({
chat_id: z.number(), chat_id: z.number(),
}); });
export const getPodcastByChatResponse = podcast.nullish();
export type GeneratePodcastRequest = z.infer<typeof generatePodcastRequest>; export type GeneratePodcastRequest = z.infer<typeof generatePodcastRequest>;
export type GetPodcastByChatIdRequest = z.infer<typeof getPodcastByChatIdRequest>; export type GetPodcastByChatIdRequest = z.infer<typeof getPodcastByChatIdRequest>;
export type GetPodcastByChatResponse = z.infer<typeof getPodcastByChatResponse>;
export type Podcast = z.infer<typeof podcast>; export type Podcast = z.infer<typeof podcast>;

View file

@ -1,3 +1,4 @@
import { th } from "date-fns/locale";
import type z from "zod"; import type z from "zod";
import { AppError, AuthenticationError, AuthorizationError, NotFoundError } from "../error"; import { AppError, AuthenticationError, AuthorizationError, NotFoundError } from "../error";
@ -50,6 +51,11 @@ class BaseApiService {
: unknown : unknown
> { > {
try { try {
/**
* ----------
* REQUEST
* ----------
*/
const defaultOptions: RequestOptions = { const defaultOptions: RequestOptions = {
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@ -68,18 +74,46 @@ class BaseApiService {
}, },
}; };
// Validate the base URL
if (!this.baseUrl) { if (!this.baseUrl) {
throw new AppError("Base URL is not set."); throw new AppError("Base URL is not set.");
} }
// Validate the bearer token
if (!this.bearerToken && !this.noAuthEndpoints.includes(url)) { if (!this.bearerToken && !this.noAuthEndpoints.includes(url)) {
throw new AuthenticationError("You are not authenticated. Please login again."); throw new AuthenticationError("You are not authenticated. Please login again.");
} }
// Construct the full URL
const fullUrl = new URL(url, this.baseUrl).toString(); const fullUrl = new URL(url, this.baseUrl).toString();
const response = await fetch(fullUrl, mergedOptions); // Prepare fetch options
const fetchOptions: RequestInit = {
method: mergedOptions.method,
headers: mergedOptions.headers,
signal: mergedOptions.signal,
};
// Automatically stringify body if Content-Type is application/json and body is an object
if (mergedOptions.body !== undefined) {
const contentType = mergedOptions.headers?.["Content-Type"];
if (contentType === "application/json" && typeof mergedOptions.body === "object") {
fetchOptions.body = JSON.stringify(mergedOptions.body);
} else {
// Pass body as-is for other content types (e.g., form data, already stringified)
fetchOptions.body = mergedOptions.body;
}
}
const response = await fetch(fullUrl, fetchOptions);
/**
* ----------
* RESPONSE
* ----------
*/
// Handle errors
if (!response.ok) { if (!response.ok) {
// biome-ignore lint/suspicious: Unknown // biome-ignore lint/suspicious: Unknown
let data; let data;
@ -87,12 +121,11 @@ class BaseApiService {
try { try {
data = await response.json(); data = await response.json();
} catch (error) { } catch (error) {
console.error("Failed to parse response as JSON:", error); console.error("Failed to parse response as JSON: ", JSON.stringify(error));
throw new AppError("Failed to parse response", response.status, response.statusText);
throw new AppError("Something went wrong", response.status, response.statusText);
} }
// for fastapi errors response // For fastapi errors response
if (typeof data === "object" && "detail" in data) { if (typeof data === "object" && "detail" in data) {
throw new AppError(data.detail, response.status, response.statusText); throw new AppError(data.detail, response.status, response.statusText);
} }
@ -138,13 +171,14 @@ class BaseApiService {
break; break;
// Add more cases as needed // Add more cases as needed
default: default:
data = await response.text(); data = await response.json();
} }
} catch (error) { } catch (error) {
console.error("Failed to parse response as JSON:", error); console.error("Failed to parse response as JSON:", error);
throw new AppError("Failed to parse response", response.status, response.statusText); throw new AppError("Failed to parse response", response.status, response.statusText);
} }
// Validate response
if (responseType === ResponseType.JSON) { if (responseType === ResponseType.JSON) {
if (!responseSchema) { if (!responseSchema) {
return data; return data;
@ -156,7 +190,7 @@ class BaseApiService {
* This is a client side error, and should be fixed by updating the responseSchema to keep things typed. * This is a client side error, and should be fixed by updating the responseSchema to keep things typed.
* This error should not be shown to the user , it is for dev only. * This error should not be shown to the user , it is for dev only.
*/ */
console.error("Invalid API response schema:", parsedData.error); console.error(`Invalid API response schema - ${url} :`, JSON.stringify(parsedData.error));
} }
return data; return data;
@ -164,7 +198,7 @@ class BaseApiService {
return data; return data;
} catch (error) { } catch (error) {
console.error("Request failed:", error); console.error("Request failed:", JSON.stringify(error));
throw error; throw error;
} }
} }

View file

@ -3,8 +3,8 @@ import {
type GetPodcastByChatIdRequest, type GetPodcastByChatIdRequest,
generatePodcastRequest, generatePodcastRequest,
getPodcastByChatIdRequest, getPodcastByChatIdRequest,
getPodcastByChatResponse,
type Podcast, type Podcast,
podcast,
} from "@/contracts/types/podcast.types"; } from "@/contracts/types/podcast.types";
import { ValidationError } from "../error"; import { ValidationError } from "../error";
import { baseApiService } from "./base-api.service"; import { baseApiService } from "./base-api.service";
@ -22,7 +22,10 @@ class PodcastsApiService {
throw new ValidationError(`Invalid request: ${errorMessage}`); throw new ValidationError(`Invalid request: ${errorMessage}`);
} }
return baseApiService.get(`/api/v1/podcasts/by-chat/${request.chat_id}`, podcast); return baseApiService.get(
`/api/v1/podcasts/by-chat/${request.chat_id}`,
getPodcastByChatResponse
);
}; };
generatePodcast = async (request: GeneratePodcastRequest) => { generatePodcast = async (request: GeneratePodcastRequest) => {
@ -38,7 +41,7 @@ class PodcastsApiService {
} }
return baseApiService.post(`/api/v1/podcasts/generate`, undefined, { return baseApiService.post(`/api/v1/podcasts/generate`, undefined, {
body: request, body: parsedRequest.data,
}); });
}; };