feat: connect wallet favorites to backend

This commit is contained in:
TerryM
2026-06-04 17:06:29 +08:00
parent fd19ed438e
commit 01eab88c0f
14 changed files with 479 additions and 172 deletions

View File

@@ -1,17 +1,12 @@
import { apiBase, type Resource } from "../api";
import { apiBase, itemsOrEmpty, 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;
items: Resource[];
page?: number;
limit?: number;
total?: number;
};
export type FavoriteIdsResponse = {
@@ -20,16 +15,24 @@ export type FavoriteIdsResponse = {
export type FavoriteMutationResponse = {
ok: boolean;
resourceId: string;
favorited: boolean;
changed?: boolean;
resourceId?: string;
favorited?: boolean;
favoritedAt?: string;
favoriteCount: number;
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;
@@ -68,7 +71,7 @@ export async function listFavorites(
sp.set(key, String(value));
});
const suffix = sp.toString() ? `?${sp}` : "";
const res = await fetch(`${apiBase}/api/me/favorites${suffix}`, {
const res = await fetch(`${apiBase}/api/favorites${suffix}`, {
headers: authHeaders(token),
});
return parseJSON<FavoriteListResponse>(res);
@@ -81,22 +84,23 @@ export async function getFavoriteIds(
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(","),
)}`,
`${apiBase}/api/favorites?ids=${encodeURIComponent(uniqueIds.join(","))}`,
{ headers: authHeaders(token) },
);
const data = await parseJSON<FavoriteIdsResponse>(res);
return data.ids;
const data = await parseJSON<FavoriteIdsResponse | FavoriteListResponse>(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<FavoriteMutationResponse> {
const res = await fetch(`${apiBase}/api/me/favorites/${resourceId}`, {
const res = await fetch(`${apiBase}/api/posts/${resourceId}/favorite`, {
method: "POST",
headers: authHeaders(token),
headers: authJSONHeaders(token),
body: JSON.stringify({ add: true }),
});
return parseJSON<FavoriteMutationResponse>(res);
}
@@ -105,9 +109,10 @@ export async function removeFavorite(
token: string,
resourceId: string,
): Promise<FavoriteMutationResponse> {
const res = await fetch(`${apiBase}/api/me/favorites/${resourceId}`, {
method: "DELETE",
headers: authHeaders(token),
const res = await fetch(`${apiBase}/api/posts/${resourceId}/favorite`, {
method: "POST",
headers: authJSONHeaders(token),
body: JSON.stringify({ add: false }),
});
return parseJSON<FavoriteMutationResponse>(res);
}