54 lines
1.6 KiB
TypeScript
54 lines
1.6 KiB
TypeScript
/**
|
|
* Server-side listing data fetching for Next.js Server Components.
|
|
*
|
|
* This module uses `fetch` directly (no browser-only helpers) so it can run
|
|
* inside `generateMetadata`, `generateStaticParams`, `sitemap()`, etc.
|
|
*/
|
|
|
|
import type { ListingDetail, PaginatedResult } from './listings-api';
|
|
|
|
const API_BASE_URL = process.env['NEXT_PUBLIC_API_URL'] || 'http://localhost:3001/api/v1';
|
|
|
|
/**
|
|
* Fetch a single listing by ID — server-only.
|
|
* Returns `null` when the listing is not found (404) so callers can `notFound()`.
|
|
*/
|
|
export async function fetchListingById(id: string): Promise<ListingDetail | null> {
|
|
try {
|
|
const res = await fetch(`${API_BASE_URL}/listings/${id}`, {
|
|
// Listing detail includes mutable status, price, legal and moderation data.
|
|
// Avoid serving stale details after admin/user actions.
|
|
cache: 'no-store',
|
|
});
|
|
|
|
if (!res.ok) return null;
|
|
return (await res.json()) as ListingDetail;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch active listings — server-only, used by the dynamic sitemap.
|
|
*/
|
|
export async function fetchActiveListings(params: {
|
|
page?: number;
|
|
limit?: number;
|
|
}): Promise<PaginatedResult<ListingDetail>> {
|
|
const query = new URLSearchParams({
|
|
status: 'ACTIVE',
|
|
page: String(params.page ?? 1),
|
|
limit: String(params.limit ?? 100),
|
|
});
|
|
|
|
const res = await fetch(`${API_BASE_URL}/listings?${query}`, {
|
|
next: { revalidate: 3600 }, // re-validate every hour for sitemap
|
|
});
|
|
|
|
if (!res.ok) {
|
|
return { data: [], total: 0, page: 1, limit: 100, totalPages: 0 };
|
|
}
|
|
|
|
return (await res.json()) as PaginatedResult<ListingDetail>;
|
|
}
|