/** * RustyCMS API client. Base URL from NEXT_PUBLIC_RUSTYCMS_API_URL. * Optional RUSTYCMS_API_KEY for write operations (sent as X-API-Key). */ const getBaseUrl = () => process.env.NEXT_PUBLIC_RUSTYCMS_API_URL || "http://127.0.0.1:3000"; const getHeaders = (): HeadersInit => { const headers: HeadersInit = { "Content-Type": "application/json", Accept: "application/json", }; const key = typeof window !== "undefined" ? process.env.NEXT_PUBLIC_RUSTYCMS_API_KEY ?? null : process.env.RUSTYCMS_API_KEY ?? process.env.NEXT_PUBLIC_RUSTYCMS_API_KEY ?? null; if (key) headers["X-API-Key"] = key; return headers; }; export type CollectionMeta = { name: string; description?: string; tags?: string[]; category?: string; field_count?: number; extends?: string | string[]; strict?: boolean; }; export type CollectionsResponse = { collections: CollectionMeta[]; }; export type FieldDefinition = { type: string; required?: boolean; description?: string; collection?: string; collections?: string[]; enum?: unknown[]; default?: unknown; items?: FieldDefinition; [key: string]: unknown; }; export type SchemaDefinition = { name: string; description?: string; tags?: string[]; category?: string; fields: Record; extends?: string | string[]; reusable?: boolean; [key: string]: unknown; }; export type ListResponse> = { items: T[]; total: number; page: number; per_page: number; total_pages: number; }; export async function fetchCollections(): Promise { const res = await fetch(`${getBaseUrl()}/api/collections`, { headers: getHeaders(), }); if (!res.ok) throw new Error(`Collections: ${res.status}`); return res.json(); } export async function fetchSchema( collection: string ): Promise { const res = await fetch( `${getBaseUrl()}/api/collections/${encodeURIComponent(collection)}`, { headers: getHeaders() } ); if (!res.ok) throw new Error(`Schema ${collection}: ${res.status}`); return res.json(); } export async function createSchema( schema: SchemaDefinition ): Promise { const res = await fetch(`${getBaseUrl()}/api/schemas`, { method: "POST", headers: getHeaders(), body: JSON.stringify(schema), }); if (!res.ok) { const err = await res.json().catch(() => ({})); const msg = (err as { error?: string }).error ?? (err as { errors?: string[] }).errors?.join(", ") ?? `Schema: ${res.status}`; throw new Error(msg); } return res.json(); } export type SlugCheckResponse = { valid: boolean; normalized: string; available: boolean; error?: string; }; export async function checkSlug( collection: string, slug: string, params: { exclude?: string; _locale?: string } = {} ): Promise { const sp = new URLSearchParams({ slug: slug.trim() }); if (params.exclude) sp.set("exclude", params.exclude); if (params._locale) sp.set("_locale", params._locale); const res = await fetch( `${getBaseUrl()}/api/collections/${encodeURIComponent(collection)}/slug-check?${sp}`, { headers: getHeaders() } ); if (!res.ok) throw new Error(`Slug-Check: ${res.status}`); return res.json(); } export type ContentListParams = { _page?: number; _per_page?: number; _sort?: string; _order?: "asc" | "desc"; _resolve?: string; _locale?: string; [key: string]: string | number | undefined; }; export async function fetchContentList>( collection: string, params: ContentListParams = {} ): Promise> { const sp = new URLSearchParams(); Object.entries(params).forEach(([k, v]) => { if (v !== undefined && v !== "") sp.set(k, String(v)); }); const qs = sp.toString(); const url = `${getBaseUrl()}/api/content/${encodeURIComponent(collection)}${qs ? `?${qs}` : ""}`; const res = await fetch(url, { headers: getHeaders() }); if (!res.ok) throw new Error(`List ${collection}: ${res.status}`); return res.json(); } export async function fetchEntry>( collection: string, slug: string, params: { _resolve?: string; _locale?: string } = {} ): Promise { const sp = new URLSearchParams(); if (params._resolve) sp.set("_resolve", params._resolve); if (params._locale) sp.set("_locale", params._locale); const qs = sp.toString(); const url = `${getBaseUrl()}/api/content/${encodeURIComponent(collection)}/${encodeURIComponent(slug)}${qs ? `?${qs}` : ""}`; const res = await fetch(url, { headers: getHeaders() }); if (!res.ok) throw new Error(`Entry ${collection}/${slug}: ${res.status}`); return res.json(); } export async function createEntry( collection: string, data: Record, params: { _locale?: string } = {} ): Promise> { const sp = new URLSearchParams(); if (params._locale) sp.set("_locale", params._locale); const qs = sp.toString(); const url = `${getBaseUrl()}/api/content/${encodeURIComponent(collection)}${qs ? `?${qs}` : ""}`; const res = await fetch(url, { method: "POST", headers: getHeaders(), body: JSON.stringify(data), }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error((err as { message?: string }).message || `Create: ${res.status}`); } return res.json(); } export async function updateEntry( collection: string, slug: string, data: Record, params: { _locale?: string } = {} ): Promise> { const sp = new URLSearchParams(); if (params._locale) sp.set("_locale", params._locale); const qs = sp.toString(); const url = `${getBaseUrl()}/api/content/${encodeURIComponent(collection)}/${encodeURIComponent(slug)}${qs ? `?${qs}` : ""}`; const res = await fetch(url, { method: "PUT", headers: getHeaders(), body: JSON.stringify(data), }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error((err as { message?: string }).message || `Update: ${res.status}`); } return res.json(); } export async function deleteEntry( collection: string, slug: string, params: { _locale?: string } = {} ): Promise { const sp = new URLSearchParams(); if (params._locale) sp.set("_locale", params._locale); const qs = sp.toString(); const url = `${getBaseUrl()}/api/content/${encodeURIComponent(collection)}/${encodeURIComponent(slug)}${qs ? `?${qs}` : ""}`; const res = await fetch(url, { method: "DELETE", headers: getHeaders(), }); if (!res.ok) throw new Error(`Delete: ${res.status}`); }