feat: public template catalog + Vercel git integration

Backend: service_templates.is_public lets owners publish user templates to
the anonymous catalog (GET /api/v1/templates), mirroring Railway's
marketplace publish flow. Migration, sqlc queries, visibility rules, and
openapi.yaml updated; types regenerated.

App: template editor gains a "List in the public catalog" checkbox;
Public badge on catalog cards and detail view.

Site: new templates.html catalog page - search, category filters, Official
vs Community badges, detail modal with variables and copyable import JSON.
Fetches the live catalog from a reachable instance (?api= or localStorage
override) and falls back to templates.json, generated from SeedTemplates
via cmd/dumptemplates. Shared styles extracted to style.css.

Vercel: project connected to GitHub repo with rootDirectory=site;
.vercelignore limits CLI uploads to site/ so `vercel deploy --prod` from
repo root matches git builds.

Authored By: TDvorak <[email protected]>

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-23 16:40:58 +02:00
1 parent 2b38406a63
commit 117b469fae
18 files changed
+1589 -492

No files matched your search

+4
View File
@@ -124,6 +124,7 @@ export type TemplateEntity = {
variablesRaw: string;
isOfficial: boolean;
ownerId: string | null;
isPublic: boolean;
createdAt?: string;
updatedAt?: string;
};
@@ -615,6 +616,7 @@ function normalizeTemplate(template: RawServiceTemplate): TemplateEntity | null
variablesRaw: template.variables ?? '',
isOfficial: Boolean(template.is_official),
ownerId: template.owner_id ?? null,
isPublic: Boolean(template.is_public),
createdAt: template.created_at,
updatedAt: template.updated_at,
};
@@ -1358,6 +1360,7 @@ export type TemplateWriteInput = {
logo?: string;
config: Record<string, unknown>;
variables?: TemplateVariableEntity[];
isPublic?: boolean;
};
function templateWriteBody(input: TemplateWriteInput): string {
@@ -1368,6 +1371,7 @@ function templateWriteBody(input: TemplateWriteInput): string {
logo: input.logo ?? '',
config: input.config,
variables: input.variables ?? [],
is_public: input.isPublic ?? false,
});
}