146 lines
3.7 KiB
TypeScript
146 lines
3.7 KiB
TypeScript
const WP_API_BASE = "https://wpa.feep.ir/wp-json/wp/v2";
|
|
const FETCH_TIMEOUT_MS = 15_000;
|
|
|
|
export interface WpMediaSize {
|
|
source_url: string;
|
|
width?: number;
|
|
height?: number;
|
|
}
|
|
|
|
export interface WpFeaturedMedia {
|
|
source_url: string;
|
|
alt_text?: string;
|
|
media_details?: {
|
|
width?: number;
|
|
height?: number;
|
|
sizes?: Record<string, WpMediaSize>;
|
|
};
|
|
}
|
|
|
|
export interface WpAuthor {
|
|
id: number;
|
|
name: string;
|
|
slug?: string;
|
|
}
|
|
|
|
export interface WpPost {
|
|
id: number;
|
|
date: string;
|
|
modified?: string;
|
|
slug: string;
|
|
title: { rendered: string };
|
|
content: { rendered: string };
|
|
excerpt: { rendered: string };
|
|
featured_media: number;
|
|
categories: number[];
|
|
author?: number;
|
|
_embedded?: {
|
|
"wp:featuredmedia"?: WpFeaturedMedia[];
|
|
author?: WpAuthor[];
|
|
};
|
|
}
|
|
|
|
/** Returns thumbnail URL for list/card (medium) or full for single post. */
|
|
export function getFeaturedImageUrl(
|
|
post: WpPost,
|
|
size: "medium" | "medium_large" | "full" = "medium"
|
|
): string | null {
|
|
const media = post._embedded?.["wp:featuredmedia"]?.[0];
|
|
if (!media?.source_url) return null;
|
|
const sizes = media.media_details?.sizes;
|
|
if (sizes?.[size]?.source_url) return sizes[size].source_url;
|
|
if (size !== "full" && sizes?.medium?.source_url) return sizes.medium.source_url;
|
|
return media.source_url;
|
|
}
|
|
|
|
export interface WpCategory {
|
|
id: number;
|
|
name: string;
|
|
slug: string;
|
|
count: number;
|
|
}
|
|
|
|
export interface GetPostsResult {
|
|
posts: WpPost[];
|
|
total: number;
|
|
totalPages: number;
|
|
}
|
|
|
|
export interface GetPostsOptions {
|
|
page?: number;
|
|
perPage?: number;
|
|
search?: string;
|
|
categoryId?: number;
|
|
}
|
|
|
|
async function fetchWithTimeout(
|
|
url: string,
|
|
options?: RequestInit
|
|
): Promise<Response> {
|
|
const controller = new AbortController();
|
|
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
try {
|
|
const res = await fetch(url, {
|
|
...options,
|
|
signal: controller.signal,
|
|
next: { revalidate: 0 },
|
|
});
|
|
clearTimeout(timeout);
|
|
return res;
|
|
} catch (e) {
|
|
clearTimeout(timeout);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
export async function getPosts(
|
|
options: GetPostsOptions = {}
|
|
): Promise<GetPostsResult> {
|
|
const { page = 1, perPage = 9, search, categoryId } = options;
|
|
const params = new URLSearchParams();
|
|
params.set("page", String(page));
|
|
params.set("per_page", String(perPage));
|
|
if (search?.trim()) params.set("search", search.trim());
|
|
if (categoryId) params.set("categories", String(categoryId));
|
|
params.set("_embed", "1");
|
|
|
|
const url = `${WP_API_BASE}/posts?${params.toString()}`;
|
|
const res = await fetchWithTimeout(url);
|
|
|
|
if (!res.ok) {
|
|
throw new Error(`WP API error: ${res.status} ${res.statusText}`);
|
|
}
|
|
|
|
const total = parseInt(res.headers.get("x-wp-total") ?? "0", 10);
|
|
const totalPages = parseInt(res.headers.get("x-wp-totalpages") ?? "1", 10);
|
|
const posts: WpPost[] = await res.json();
|
|
|
|
return { posts, total, totalPages };
|
|
}
|
|
|
|
export async function getPostBySlug(slug: string): Promise<WpPost | null> {
|
|
const decodedSlug = decodeURIComponent(slug);
|
|
const params = new URLSearchParams({ slug: decodedSlug, _embed: "1" });
|
|
const url = `${WP_API_BASE}/posts?${params.toString()}`;
|
|
const res = await fetchWithTimeout(url);
|
|
|
|
if (!res.ok) {
|
|
throw new Error(`WP API error: ${res.status} ${res.statusText}`);
|
|
}
|
|
|
|
const posts: WpPost[] = await res.json();
|
|
return posts.length > 0 ? posts[0] : null;
|
|
}
|
|
|
|
export async function getCategories(): Promise<WpCategory[]> {
|
|
const url = `${WP_API_BASE}/categories?per_page=100`;
|
|
const res = await fetchWithTimeout(url);
|
|
|
|
if (!res.ok) {
|
|
throw new Error(`WP API error: ${res.status} ${res.statusText}`);
|
|
}
|
|
|
|
const data: WpCategory[] = await res.json();
|
|
return data.filter((c) => c.count > 0);
|
|
}
|