buildmymcpserver/apps/web/lib/templates-server.ts

77 lines
2.3 KiB
TypeScript
Raw Normal View History

// Server-only fetchers for the public template marketplace. Used by the
// server-rendered template detail page (SEO metadata + JSON-LD) and the
// sitemap. Never import this into a client component.
const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:4000';
export interface TemplateTool {
name: string;
description: string;
inputSchema: Record<string, unknown>;
}
export interface TemplateSecretHint {
key: string;
description: string;
howToGetUrl?: string;
}
export interface TemplateDetail {
id: string;
slug: string;
title: string;
shortDescription: string;
longDescription: string | null;
category: string;
status: 'draft' | 'public' | 'hidden' | 'takedown';
verified: boolean;
forkCount: number;
activeDeployments: number;
toolsSchema: TemplateTool[];
generatedCode: string;
requiredSecrets: TemplateSecretHint[];
scopes: string[];
ownerName: string | null;
ownerOrgName: string | null;
sourceServerId: string | null;
createdAt: string;
}
/**
* Fetch a single public template by slug for server rendering. Returns null
* for missing / non-public templates so the page can `notFound()` we only
* want `public` templates indexed.
*/
export async function fetchTemplate(slug: string): Promise<TemplateDetail | null> {
try {
const res = await fetch(`${API_BASE}/v1/templates/${encodeURIComponent(slug)}`, {
// Cache server-side for 5 min so crawler hits don't hammer the API.
next: { revalidate: 300 },
});
if (!res.ok) return null;
const data = (await res.json()) as { template?: TemplateDetail };
const t = data.template;
if (!t || t.status !== 'public') return null;
return t;
} catch {
return null;
}
}
/** Slugs of public templates, for the sitemap. Best-effort: returns [] on error. */
export async function fetchPublicTemplateSlugs(): Promise<string[]> {
try {
const res = await fetch(`${API_BASE}/v1/templates?limit=100&sort=newest`, {
next: { revalidate: 600 },
});
if (!res.ok) return [];
const data = (await res.json()) as
| { templates?: Array<{ slug?: string }> }
| Array<{ slug?: string }>;
const list = Array.isArray(data) ? data : (data.templates ?? []);
return list.map((t) => t.slug).filter((s): s is string => typeof s === 'string');
} catch {
return [];
}
}