import type {
GeneratorDefinition,
FeedGeneratorDescription,
LikeFeedState,
StoredFeedState,
FeedImportPayload,
FeedSignalRecord,
TasteProfileResult,
} from "../types";
// Convert snake_case keys to camelCase recursively
function toCamelCase(obj: unknown): unknown {
if (obj === null || typeof obj !== "object") {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(toCamelCase);
}
const result: Record<string, unknown> = {};
for (const [key, value] of Object.entries(obj)) {
const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
result[camelKey] = toCamelCase(value);
}
return result;
}
export const apiService = {
async getGenerators(): Promise<GeneratorDefinition[]> {
const response = await fetch("/api/feed-generators");
if (!response.ok) throw new Error("Failed to fetch generators");
return response.json();
},
async getFeedGeneratorInfo(): Promise<FeedGeneratorDescription> {
const response = await fetch("/api/feed-generator-info");
if (!response.ok) throw new Error("Failed to fetch feed generator info");
return response.json();
},
async getFeedState(actorDid: string, generatorId: string): Promise<StoredFeedState> {
const url = `/api/feed-state?actorDid=${encodeURIComponent(actorDid)}&generatorId=${encodeURIComponent(generatorId)}`;
const response = await fetch(url);
if (!response.ok) throw new Error("Failed to fetch feed state");
return toCamelCase(await response.json()) as StoredFeedState;
},
async getLikeFeed(actorDid: string): Promise<LikeFeedState> {
const url = `/api/like-feed?actorDid=${encodeURIComponent(actorDid)}`;
const response = await fetch(url);
if (!response.ok) throw new Error("Failed to fetch like feed");
return toCamelCase(await response.json()) as LikeFeedState;
},
async importFeed(data: FeedImportPayload): Promise<void> {
const response = await fetch("/api/feed-import", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
if (!response.ok) throw new Error("Failed to import feed");
},
async addSignal(actorDid: string, signal: FeedSignalRecord): Promise<StoredFeedState> {
const response = await fetch("/api/feed-signals", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ actorDid, signal }),
});
if (!response.ok) throw new Error("Failed to add signal");
return toCamelCase(await response.json()) as StoredFeedState;
},
async getTasteProfile(actorDid: string, generatorId: string): Promise<TasteProfileResult | null> {
// The taste profile is computed on the backend and returned via feed-state
// This is a helper to get it from the store directly if needed
const state = await this.getFeedState(actorDid, generatorId);
// The taste profile is computed client-side from the feed state
return null;
},
};