Files
Arkie-Library-Frontend/src/favorites/api.ts

114 lines
3.0 KiB
TypeScript
Raw Normal View History

2026-06-02 00:36:11 +08:00
import { apiBase, type Resource } from "../api";
export type FavoriteSort = "favorited_at" | "published_at" | "hot";
export type FavoriteItem = {
favoritedAt: string;
resource: Resource;
};
export type FavoriteListResponse = {
items: FavoriteItem[];
page: number;
limit: number;
total: number;
};
export type FavoriteIdsResponse = {
ids: string[];
};
export type FavoriteMutationResponse = {
ok: boolean;
resourceId: string;
favorited: boolean;
favoritedAt?: string;
favoriteCount: number;
};
function authHeaders(token: string): HeadersInit {
return { Authorization: `Bearer ${token}` };
}
/** 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;
}
2026-06-02 00:36:11 +08:00
async function parseJSON<T>(res: Response): Promise<T> {
if (!res.ok) throw new FavoriteHttpError(res.status, await res.text());
2026-06-02 00:36:11 +08:00
return res.json() as Promise<T>;
}
export async function listFavorites(
token: string,
params: {
sort?: FavoriteSort;
page?: number;
limit?: number;
category?: string;
q?: string;
includeUnavailable?: boolean;
lang?: string;
} = {},
): Promise<FavoriteListResponse> {
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/me/favorites${suffix}`, {
headers: authHeaders(token),
});
return parseJSON<FavoriteListResponse>(res);
}
export async function getFavoriteIds(
token: string,
resourceIds: string[],
): Promise<string[]> {
if (resourceIds.length === 0) return [];
const uniqueIds = [...new Set(resourceIds)].slice(0, 100);
const res = await fetch(
`${apiBase}/api/me/favorites/ids?resourceIds=${encodeURIComponent(
uniqueIds.join(","),
)}`,
{ headers: authHeaders(token) },
);
const data = await parseJSON<FavoriteIdsResponse>(res);
return data.ids;
}
export async function addFavorite(
token: string,
resourceId: string,
): Promise<FavoriteMutationResponse> {
const res = await fetch(`${apiBase}/api/me/favorites/${resourceId}`, {
method: "POST",
headers: authHeaders(token),
});
return parseJSON<FavoriteMutationResponse>(res);
}
export async function removeFavorite(
token: string,
resourceId: string,
): Promise<FavoriteMutationResponse> {
const res = await fetch(`${apiBase}/api/me/favorites/${resourceId}`, {
method: "DELETE",
headers: authHeaders(token),
});
return parseJSON<FavoriteMutationResponse>(res);
}