Files
kandev/apps/web/lib/api/domains/marketplace-api.ts
T
2026-09-14 09:31:20 +01:00

103 lines
3.6 KiB
TypeScript

import { fetchJson, type ApiRequestOptions } from "../client";
import type { MarketplaceCatalog, MarketplaceSource } from "@/lib/types/plugins";
const BASE = "/api/plugins/marketplace";
// CatalogQuery is the filter/sort applied server-side to the merged catalog.
export type CatalogQuery = {
q?: string;
category?: string;
sort?: "stars" | "name" | "recent";
kind?: "plugin" | "canvas";
};
function toQueryString(query?: CatalogQuery): string {
if (!query) return "";
const params = new URLSearchParams();
if (query.q) params.set("q", query.q);
if (query.category) params.set("category", query.category);
if (query.sort) params.set("sort", query.sort);
if (query.kind) params.set("kind", query.kind);
const s = params.toString();
return s ? `?${s}` : "";
}
// getMarketplaceCatalog fetches the merged catalog across every enabled
// source (GET /api/plugins/marketplace), filtered/sorted by the query. Each
// entry is annotated with its install_state; degraded sources are reported in
// `sources` with healthy=false and omit their entries.
export async function getMarketplaceCatalog(
query?: CatalogQuery,
options?: ApiRequestOptions,
): Promise<MarketplaceCatalog> {
const res = await fetchJson<MarketplaceCatalog>(`${BASE}${toQueryString(query)}`, {
...options,
cache: "no-store",
});
return {
plugins: res.plugins ?? [],
...(res.canvases ? { canvases: res.canvases } : {}),
sources: res.sources ?? [],
};
}
// listMarketplaceSources returns every configured source, built-in first
// (GET /api/plugins/marketplace/sources).
export async function listMarketplaceSources(
options?: ApiRequestOptions,
): Promise<MarketplaceSource[]> {
const res = await fetchJson<{ sources?: MarketplaceSource[] }>(`${BASE}/sources`, options);
return res.sources ?? [];
}
// addMarketplaceSource registers a new operator source
// (POST /api/plugins/marketplace/sources). Throws ApiError on an invalid or
// duplicate URL (400).
export async function addMarketplaceSource(
name: string,
url: string,
options?: ApiRequestOptions,
): Promise<MarketplaceSource> {
return fetchJson<MarketplaceSource>(`${BASE}/sources`, {
...options,
init: { ...(options?.init ?? {}), method: "POST", body: JSON.stringify({ name, url }) },
});
}
// updateMarketplaceSource renames and/or enables/disables a source
// (PATCH /api/plugins/marketplace/sources/:id).
export async function updateMarketplaceSource(
id: string,
patch: { name?: string; enabled?: boolean },
options?: ApiRequestOptions,
): Promise<MarketplaceSource> {
return fetchJson<MarketplaceSource>(`${BASE}/sources/${encodeURIComponent(id)}`, {
...options,
init: { ...(options?.init ?? {}), method: "PATCH", body: JSON.stringify(patch) },
});
}
// deleteMarketplaceSource removes a non-builtin source
// (DELETE /api/plugins/marketplace/sources/:id). Throws ApiError 409 for the
// built-in official source.
export async function deleteMarketplaceSource(
id: string,
options?: ApiRequestOptions,
): Promise<{ deleted: boolean }> {
return fetchJson<{ deleted: boolean }>(`${BASE}/sources/${encodeURIComponent(id)}`, {
...options,
init: { ...(options?.init ?? {}), method: "DELETE" },
});
}
// refreshMarketplace drops the backend's cached source documents so the next
// catalog fetch re-hits every source (POST /api/plugins/marketplace/refresh).
export async function refreshMarketplace(
options?: ApiRequestOptions,
): Promise<{ refreshed: boolean }> {
return fetchJson<{ refreshed: boolean }>(`${BASE}/refresh`, {
...options,
init: { ...(options?.init ?? {}), method: "POST" },
});
}