Files
rustycms/admin-ui/src/lib/api.ts

223 lines
6.5 KiB
TypeScript

/**
* 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<string, FieldDefinition>;
extends?: string | string[];
reusable?: boolean;
[key: string]: unknown;
};
export type ListResponse<T = Record<string, unknown>> = {
items: T[];
total: number;
page: number;
per_page: number;
total_pages: number;
};
export async function fetchCollections(): Promise<CollectionsResponse> {
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<SchemaDefinition> {
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<SchemaDefinition> {
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<SlugCheckResponse> {
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<T = Record<string, unknown>>(
collection: string,
params: ContentListParams = {}
): Promise<ListResponse<T>> {
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<T = Record<string, unknown>>(
collection: string,
slug: string,
params: { _resolve?: string; _locale?: string } = {}
): Promise<T> {
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<string, unknown>,
params: { _locale?: string } = {}
): Promise<Record<string, unknown>> {
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<string, unknown>,
params: { _locale?: string } = {}
): Promise<Record<string, unknown>> {
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<void> {
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}`);
}