Initial frontend import
This commit is contained in:
129
src/api.ts
Normal file
129
src/api.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
export const apiBase = import.meta.env.VITE_API_URL || "";
|
||||
|
||||
/** Go JSON encodes nil slices as null — normalize before .map() */
|
||||
export function itemsOrEmpty<T>(items: T[] | null | undefined): T[] {
|
||||
return Array.isArray(items) ? items : [];
|
||||
}
|
||||
|
||||
export function assetUrl(path: string | undefined | null) {
|
||||
if (!path) return "";
|
||||
if (path.startsWith("http")) return path;
|
||||
return `${apiBase}${path}`;
|
||||
}
|
||||
|
||||
export async function getJSON<T>(path: string): Promise<T> {
|
||||
const res = await fetch(`${apiBase}${path}`);
|
||||
if (!res.ok) throw new Error(await res.text());
|
||||
return res.json() as Promise<T>;
|
||||
}
|
||||
|
||||
export async function getJSONAuth<T>(path: string, token: string): Promise<T> {
|
||||
const res = await fetch(`${apiBase}${path}`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
if (!res.ok) throw new Error(await res.text());
|
||||
return res.json() as Promise<T>;
|
||||
}
|
||||
|
||||
export async function postJSON<T>(
|
||||
path: string,
|
||||
body: unknown,
|
||||
token?: string,
|
||||
): Promise<T> {
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
if (token) headers.Authorization = `Bearer ${token}`;
|
||||
const res = await fetch(`${apiBase}${path}`, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (!res.ok) throw new Error(await res.text());
|
||||
return res.json() as Promise<T>;
|
||||
}
|
||||
|
||||
/** Best-effort favorite counter sync (anonymous; matches localStorage favorite). */
|
||||
export function postFavoriteDelta(id: string, add: boolean) {
|
||||
return postJSON(`/api/resources/${id}/favorite`, { add }).catch(() => {});
|
||||
}
|
||||
|
||||
export async function putJSON<T>(
|
||||
path: string,
|
||||
body: unknown,
|
||||
token: string,
|
||||
): Promise<T> {
|
||||
const res = await fetch(`${apiBase}${path}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (!res.ok) throw new Error(await res.text());
|
||||
return res.json() as Promise<T>;
|
||||
}
|
||||
|
||||
export async function del(path: string, token: string) {
|
||||
const res = await fetch(`${apiBase}${path}`, {
|
||||
method: "DELETE",
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
if (!res.ok) throw new Error(await res.text());
|
||||
}
|
||||
|
||||
export async function uploadFile(
|
||||
file: File,
|
||||
token: string,
|
||||
): Promise<{ url: string }> {
|
||||
const fd = new FormData();
|
||||
fd.append("file", file);
|
||||
const res = await fetch(`${apiBase}/api/admin/upload`, {
|
||||
method: "POST",
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
body: fd,
|
||||
});
|
||||
if (!res.ok) throw new Error(await res.text());
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export type Category = {
|
||||
id: number;
|
||||
slug: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
iconKey: string;
|
||||
sortOrder: number;
|
||||
};
|
||||
|
||||
export type Resource = {
|
||||
id: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
type: string;
|
||||
language: string;
|
||||
categoryId: number;
|
||||
categorySlug: string;
|
||||
categoryName: string;
|
||||
coverImage?: string;
|
||||
fileUrl?: string;
|
||||
previewUrl?: string;
|
||||
externalUrl?: string;
|
||||
bodyText?: string;
|
||||
badgeLabel?: string;
|
||||
isDownloadable: boolean;
|
||||
isRecommended: boolean;
|
||||
publishedAt?: string;
|
||||
updatedAt: string;
|
||||
tags?: string[];
|
||||
};
|
||||
|
||||
export type AdminResource = Resource & {
|
||||
isPublic: boolean;
|
||||
sortOrder: number;
|
||||
status: string;
|
||||
publishedAt?: string;
|
||||
viewCount?: number;
|
||||
downloadCount?: number;
|
||||
};
|
||||
Reference in New Issue
Block a user