import { apiBase, itemsOrEmpty } from "../api"; import type { Post } from "../types/post"; export type FavoriteSort = "favorited_at" | "published_at" | "hot"; export type FavoriteListResponse = { items: Post[]; page?: number; limit?: number; total?: number; }; export type FavoriteIdsResponse = { ids: string[]; }; export type FavoriteMutationResponse = { ok: boolean; changed?: boolean; resourceId?: string; favorited?: boolean; favoritedAt?: string; favoriteCount?: number; }; function authHeaders(token: string): HeadersInit { return { Authorization: `Bearer ${token}` }; } function authJSONHeaders(token: string): HeadersInit { return { ...authHeaders(token), "Content-Type": "application/json", }; } /** HTTP error that preserves the status code so callers can react to 401s. */ export class FavoriteHttpError extends Error { readonly status: number; constructor(status: number, message: string) { super(message || `Request failed (${status})`); this.name = "FavoriteHttpError"; this.status = status; } } /** True when an error means the wallet session is no longer authorized. */ export function isFavoritesAuthError(error: unknown): boolean { return error instanceof FavoriteHttpError && error.status === 401; } async function parseJSON(res: Response): Promise { if (!res.ok) throw new FavoriteHttpError(res.status, await res.text()); return res.json() as Promise; } export async function listFavorites( token: string, params: { sort?: FavoriteSort; page?: number; limit?: number; category?: string; q?: string; includeUnavailable?: boolean; lang?: string; } = {}, ): Promise { const sp = new URLSearchParams(); Object.entries(params).forEach(([key, value]) => { if (value === undefined || value === "") return; sp.set(key, String(value)); }); const suffix = sp.toString() ? `?${sp}` : ""; const res = await fetch(`${apiBase}/api/favorites${suffix}`, { headers: authHeaders(token), }); return parseJSON(res); } export async function getFavoriteIds( token: string, resourceIds: string[], ): Promise { if (resourceIds.length === 0) return []; const uniqueIds = [...new Set(resourceIds)].slice(0, 100); const res = await fetch( `${apiBase}/api/favorites?ids=${encodeURIComponent(uniqueIds.join(","))}`, { headers: authHeaders(token) }, ); const data = await parseJSON(res); if ("ids" in data && Array.isArray(data.ids)) return data.ids; if ("items" in data) return itemsOrEmpty(data.items).map((item) => item.id); return []; } export async function addFavorite( token: string, resourceId: string, ): Promise { const res = await fetch(`${apiBase}/api/posts/${resourceId}/favorite`, { method: "POST", headers: authJSONHeaders(token), body: JSON.stringify({ add: true }), }); return parseJSON(res); } export async function removeFavorite( token: string, resourceId: string, ): Promise { const res = await fetch(`${apiBase}/api/posts/${resourceId}/favorite`, { method: "POST", headers: authJSONHeaders(token), body: JSON.stringify({ add: false }), }); return parseJSON(res); }