const WP_API_BASE = ""; 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; }; } 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; /** ترتیب بر اساس: date, modified, title, ... در وردپرس. برای اولویت دستی (menu_order) پلاگین/فیلتر در وردپرس لازم است. */ orderby?: string; order?: "asc" | "desc"; } async function fetchWithTimeout( url: string, options?: RequestInit ): Promise { 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 { const { page = 1, perPage = 9, search, categoryId, orderby, order } = 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)); if (orderby) params.set("orderby", orderby); if (order) params.set("order", order); 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 { 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 getRelatedPosts( postId: number, categoryIds: number[], limit = 3 ): Promise { const params = new URLSearchParams(); params.set("exclude", String(postId)); params.set("per_page", String(limit)); params.set("_embed", "1"); if (categoryIds.length > 0) { params.set("categories", String(categoryIds[0])); } 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; } export async function getCategories(): Promise { 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); }