|
|
|
|
@@ -47,6 +47,7 @@ export function collapseAssetUrlsForAdmin(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const STORAGE_KEY = "rustycms_admin_api_key";
|
|
|
|
|
const ENVIRONMENT_STORAGE_KEY = "rustycms_admin_environment";
|
|
|
|
|
const PER_PAGE_KEY = "rustycms_per_page";
|
|
|
|
|
const DEFAULT_PER_PAGE = 25;
|
|
|
|
|
const PER_PAGE_OPTIONS = [10, 25, 50, 100] as const;
|
|
|
|
|
@@ -81,6 +82,35 @@ export function syncStoredApiKey(): void {
|
|
|
|
|
if (stored) clientApiKey = stored;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Current environment (space) for content/assets. When set, all content/asset API calls include _environment. */
|
|
|
|
|
let currentEnvironment: string | null = null;
|
|
|
|
|
|
|
|
|
|
export function getCurrentEnvironment(): string | null {
|
|
|
|
|
if (typeof window === "undefined") return null;
|
|
|
|
|
return currentEnvironment ?? sessionStorage.getItem(ENVIRONMENT_STORAGE_KEY);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function setCurrentEnvironment(env: string | null): void {
|
|
|
|
|
currentEnvironment = env;
|
|
|
|
|
if (typeof window !== "undefined") {
|
|
|
|
|
if (env) sessionStorage.setItem(ENVIRONMENT_STORAGE_KEY, env);
|
|
|
|
|
else sessionStorage.removeItem(ENVIRONMENT_STORAGE_KEY);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type EnvironmentsResponse = {
|
|
|
|
|
environments: string[];
|
|
|
|
|
default: string | null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export async function fetchEnvironments(): Promise<EnvironmentsResponse> {
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(`${getBaseUrl()}/api/environments`, { headers: getHeaders() });
|
|
|
|
|
if (res.ok) return res.json();
|
|
|
|
|
} catch { /* API not reachable */ }
|
|
|
|
|
return { environments: [], default: null };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Items per page for content lists (stored in localStorage). */
|
|
|
|
|
export function getPerPage(): number {
|
|
|
|
|
if (typeof window === "undefined") return DEFAULT_PER_PAGE;
|
|
|
|
|
@@ -100,9 +130,10 @@ export function setPerPage(n: number): void {
|
|
|
|
|
|
|
|
|
|
export { PER_PAGE_OPTIONS, DEFAULT_PER_PAGE };
|
|
|
|
|
|
|
|
|
|
/** Clear API key and all rustycms_* localStorage (e.g. for shared devices). */
|
|
|
|
|
/** Clear API key, current environment, and all rustycms_* storage (e.g. for shared devices). */
|
|
|
|
|
export function clearSession(): void {
|
|
|
|
|
setApiKey(null);
|
|
|
|
|
setCurrentEnvironment(null);
|
|
|
|
|
if (typeof window === "undefined") return;
|
|
|
|
|
const keys: string[] = [];
|
|
|
|
|
for (let i = 0; i < localStorage.length; i++) {
|
|
|
|
|
@@ -110,6 +141,10 @@ export function clearSession(): void {
|
|
|
|
|
if (k?.startsWith("rustycms_")) keys.push(k);
|
|
|
|
|
}
|
|
|
|
|
keys.forEach((k) => localStorage.removeItem(k));
|
|
|
|
|
for (let i = 0; i < sessionStorage.length; i++) {
|
|
|
|
|
const k = sessionStorage.key(i);
|
|
|
|
|
if (k?.startsWith("rustycms_")) sessionStorage.removeItem(k);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Check backend health (GET /health). */
|
|
|
|
|
@@ -281,11 +316,13 @@ export type SlugCheckResponse = {
|
|
|
|
|
export async function checkSlug(
|
|
|
|
|
collection: string,
|
|
|
|
|
slug: string,
|
|
|
|
|
params: { exclude?: string; _locale?: string } = {}
|
|
|
|
|
params: { exclude?: string; _locale?: string; _environment?: 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 env = params._environment ?? getCurrentEnvironment();
|
|
|
|
|
if (env) sp.set("_environment", env);
|
|
|
|
|
const res = await fetch(
|
|
|
|
|
`${getBaseUrl()}/api/collections/${encodeURIComponent(collection)}/slug-check?${sp}`,
|
|
|
|
|
{ headers: getHeaders() }
|
|
|
|
|
@@ -301,6 +338,7 @@ export type ContentListParams = {
|
|
|
|
|
_order?: "asc" | "desc";
|
|
|
|
|
_resolve?: string;
|
|
|
|
|
_locale?: string;
|
|
|
|
|
_environment?: string;
|
|
|
|
|
[key: string]: string | number | undefined;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@@ -309,7 +347,10 @@ export async function fetchContentList<T = Record<string, unknown>>(
|
|
|
|
|
params: ContentListParams = {}
|
|
|
|
|
): Promise<ListResponse<T>> {
|
|
|
|
|
const sp = new URLSearchParams();
|
|
|
|
|
const env = params._environment ?? getCurrentEnvironment();
|
|
|
|
|
if (env) sp.set("_environment", env);
|
|
|
|
|
Object.entries(params).forEach(([k, v]) => {
|
|
|
|
|
if (k === "_environment") return;
|
|
|
|
|
if (v !== undefined && v !== "") sp.set(k, String(v));
|
|
|
|
|
});
|
|
|
|
|
const qs = sp.toString();
|
|
|
|
|
@@ -322,11 +363,13 @@ export async function fetchContentList<T = Record<string, unknown>>(
|
|
|
|
|
export async function fetchEntry<T = Record<string, unknown>>(
|
|
|
|
|
collection: string,
|
|
|
|
|
slug: string,
|
|
|
|
|
params: { _resolve?: string; _locale?: string } = {}
|
|
|
|
|
params: { _resolve?: string; _locale?: string; _environment?: string } = {}
|
|
|
|
|
): Promise<T> {
|
|
|
|
|
const sp = new URLSearchParams();
|
|
|
|
|
if (params._resolve) sp.set("_resolve", params._resolve);
|
|
|
|
|
if (params._locale) sp.set("_locale", params._locale);
|
|
|
|
|
const env = params._environment ?? getCurrentEnvironment();
|
|
|
|
|
if (env) sp.set("_environment", env);
|
|
|
|
|
const qs = sp.toString();
|
|
|
|
|
const url = `${getBaseUrl()}/api/content/${encodeURIComponent(collection)}/${encodeURIComponent(slug)}${qs ? `?${qs}` : ""}`;
|
|
|
|
|
const res = await fetch(url, { headers: getHeaders() });
|
|
|
|
|
@@ -344,9 +387,12 @@ export type Referrer = {
|
|
|
|
|
|
|
|
|
|
export async function fetchReferrers(
|
|
|
|
|
collection: string,
|
|
|
|
|
slug: string
|
|
|
|
|
slug: string,
|
|
|
|
|
params: { _environment?: string } = {}
|
|
|
|
|
): Promise<Referrer[]> {
|
|
|
|
|
const url = `${getBaseUrl()}/api/content/${encodeURIComponent(collection)}/${encodeURIComponent(slug)}/referrers`;
|
|
|
|
|
const env = params._environment ?? getCurrentEnvironment();
|
|
|
|
|
const qs = env ? `?_environment=${encodeURIComponent(env)}` : "";
|
|
|
|
|
const url = `${getBaseUrl()}/api/content/${encodeURIComponent(collection)}/${encodeURIComponent(slug)}/referrers${qs}`;
|
|
|
|
|
const res = await fetch(url, { headers: getHeaders() });
|
|
|
|
|
if (!res.ok) return [];
|
|
|
|
|
const data = await res.json();
|
|
|
|
|
@@ -356,10 +402,12 @@ export async function fetchReferrers(
|
|
|
|
|
export async function createEntry(
|
|
|
|
|
collection: string,
|
|
|
|
|
data: Record<string, unknown>,
|
|
|
|
|
params: { _locale?: string } = {}
|
|
|
|
|
params: { _locale?: string; _environment?: string } = {}
|
|
|
|
|
): Promise<Record<string, unknown>> {
|
|
|
|
|
const sp = new URLSearchParams();
|
|
|
|
|
if (params._locale) sp.set("_locale", params._locale);
|
|
|
|
|
const env = params._environment ?? getCurrentEnvironment();
|
|
|
|
|
if (env) sp.set("_environment", env);
|
|
|
|
|
const qs = sp.toString();
|
|
|
|
|
const url = `${getBaseUrl()}/api/content/${encodeURIComponent(collection)}${qs ? `?${qs}` : ""}`;
|
|
|
|
|
const res = await fetch(url, {
|
|
|
|
|
@@ -381,10 +429,12 @@ export async function updateEntry(
|
|
|
|
|
collection: string,
|
|
|
|
|
slug: string,
|
|
|
|
|
data: Record<string, unknown>,
|
|
|
|
|
params: { _locale?: string } = {}
|
|
|
|
|
params: { _locale?: string; _environment?: string } = {}
|
|
|
|
|
): Promise<Record<string, unknown>> {
|
|
|
|
|
const sp = new URLSearchParams();
|
|
|
|
|
if (params._locale) sp.set("_locale", params._locale);
|
|
|
|
|
const env = params._environment ?? getCurrentEnvironment();
|
|
|
|
|
if (env) sp.set("_environment", env);
|
|
|
|
|
const qs = sp.toString();
|
|
|
|
|
const url = `${getBaseUrl()}/api/content/${encodeURIComponent(collection)}/${encodeURIComponent(slug)}${qs ? `?${qs}` : ""}`;
|
|
|
|
|
const res = await fetch(url, {
|
|
|
|
|
@@ -432,19 +482,25 @@ export type FoldersResponse = {
|
|
|
|
|
export async function fetchAssets(folder?: string): Promise<AssetsResponse> {
|
|
|
|
|
const url = new URL(`${getBaseUrl()}/api/assets`);
|
|
|
|
|
if (folder !== undefined) url.searchParams.set("folder", folder);
|
|
|
|
|
const env = getCurrentEnvironment();
|
|
|
|
|
if (env) url.searchParams.set("_environment", env);
|
|
|
|
|
const res = await fetch(url.toString(), { headers: getHeaders() });
|
|
|
|
|
if (!res.ok) throw new Error(`Assets: ${res.status}`);
|
|
|
|
|
return res.json();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function fetchFolders(): Promise<FoldersResponse> {
|
|
|
|
|
const res = await fetch(`${getBaseUrl()}/api/assets/folders`, { headers: getHeaders() });
|
|
|
|
|
const env = getCurrentEnvironment();
|
|
|
|
|
const qs = env ? `?_environment=${encodeURIComponent(env)}` : "";
|
|
|
|
|
const res = await fetch(`${getBaseUrl()}/api/assets/folders${qs}`, { headers: getHeaders() });
|
|
|
|
|
if (!res.ok) throw new Error(`Folders: ${res.status}`);
|
|
|
|
|
return res.json();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function createFolder(name: string): Promise<void> {
|
|
|
|
|
const res = await fetch(`${getBaseUrl()}/api/assets/folders`, {
|
|
|
|
|
const env = getCurrentEnvironment();
|
|
|
|
|
const qs = env ? `?_environment=${encodeURIComponent(env)}` : "";
|
|
|
|
|
const res = await fetch(`${getBaseUrl()}/api/assets/folders${qs}`, {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: getHeaders(),
|
|
|
|
|
body: JSON.stringify({ name }),
|
|
|
|
|
@@ -456,7 +512,9 @@ export async function createFolder(name: string): Promise<void> {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function deleteFolder(name: string): Promise<void> {
|
|
|
|
|
const res = await fetch(`${getBaseUrl()}/api/assets/folders/${encodeURIComponent(name)}`, {
|
|
|
|
|
const env = getCurrentEnvironment();
|
|
|
|
|
const qs = env ? `?_environment=${encodeURIComponent(env)}` : "";
|
|
|
|
|
const res = await fetch(`${getBaseUrl()}/api/assets/folders/${encodeURIComponent(name)}${qs}`, {
|
|
|
|
|
method: "DELETE",
|
|
|
|
|
headers: getHeaders(),
|
|
|
|
|
});
|
|
|
|
|
@@ -476,6 +534,8 @@ const getUploadHeaders = (): HeadersInit => {
|
|
|
|
|
export async function uploadAsset(file: File, folder?: string): Promise<Asset> {
|
|
|
|
|
const url = new URL(`${getBaseUrl()}/api/assets`);
|
|
|
|
|
if (folder) url.searchParams.set("folder", folder);
|
|
|
|
|
const env = getCurrentEnvironment();
|
|
|
|
|
if (env) url.searchParams.set("_environment", env);
|
|
|
|
|
const body = new FormData();
|
|
|
|
|
body.append("file", file);
|
|
|
|
|
const res = await fetch(url.toString(), { method: "POST", headers: getUploadHeaders(), body });
|
|
|
|
|
@@ -488,7 +548,9 @@ export async function uploadAsset(file: File, folder?: string): Promise<Asset> {
|
|
|
|
|
|
|
|
|
|
/** path = "hero.jpg" or "blog/hero.jpg" */
|
|
|
|
|
export async function deleteAsset(path: string): Promise<void> {
|
|
|
|
|
const res = await fetch(`${getBaseUrl()}/api/assets/${path}`, {
|
|
|
|
|
const env = getCurrentEnvironment();
|
|
|
|
|
const qs = env ? `?_environment=${encodeURIComponent(env)}` : "";
|
|
|
|
|
const res = await fetch(`${getBaseUrl()}/api/assets/${path}${qs}`, {
|
|
|
|
|
method: "DELETE",
|
|
|
|
|
headers: getHeaders(),
|
|
|
|
|
});
|
|
|
|
|
@@ -500,7 +562,9 @@ export async function deleteAsset(path: string): Promise<void> {
|
|
|
|
|
|
|
|
|
|
/** Rename asset; path = "hero.jpg" or "blog/hero.jpg", newFilename = "newname.jpg" */
|
|
|
|
|
export async function renameAsset(path: string, newFilename: string): Promise<Asset> {
|
|
|
|
|
const res = await fetch(`${getBaseUrl()}/api/assets/${path}`, {
|
|
|
|
|
const env = getCurrentEnvironment();
|
|
|
|
|
const qs = env ? `?_environment=${encodeURIComponent(env)}` : "";
|
|
|
|
|
const res = await fetch(`${getBaseUrl()}/api/assets/${path}${qs}`, {
|
|
|
|
|
method: "PATCH",
|
|
|
|
|
headers: getHeaders(),
|
|
|
|
|
body: JSON.stringify({ filename: newFilename }),
|
|
|
|
|
@@ -590,10 +654,12 @@ export async function fetchLocales(): Promise<LocalesResponse> {
|
|
|
|
|
export async function deleteEntry(
|
|
|
|
|
collection: string,
|
|
|
|
|
slug: string,
|
|
|
|
|
params: { _locale?: string } = {}
|
|
|
|
|
params: { _locale?: string; _environment?: string } = {}
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
const sp = new URLSearchParams();
|
|
|
|
|
if (params._locale) sp.set("_locale", params._locale);
|
|
|
|
|
const env = params._environment ?? getCurrentEnvironment();
|
|
|
|
|
if (env) sp.set("_environment", env);
|
|
|
|
|
const qs = sp.toString();
|
|
|
|
|
const url = `${getBaseUrl()}/api/content/${encodeURIComponent(collection)}/${encodeURIComponent(slug)}${qs ? `?${qs}` : ""}`;
|
|
|
|
|
const res = await fetch(url, {
|
|
|
|
|
|