feat(webhook): per-project and per-site webhook URLs
Build / build (push) Successful in 10m25s

Replace the single global webhook secret with entity-scoped secrets stored
on each project and static site. Webhook-driven project autocreate is
removed — projects must exist before their URL can trigger deploys.

Also wires static-site webhooks (sync_trigger=push|tag), turning the
previously inert "push" trigger into a functional one: POST the site's
webhook URL from a Git provider and Tinyforge re-syncs on matching refs.

- Adds webhook_secret columns + unique indexes to projects and static_sites
- Per-entity GET/regenerate endpoints under /api/projects/{id}/webhook
  and /api/sites/{id}/webhook (admin-only)
- Removes /api/settings/webhook-url and the global webhook panel
- Reusable WebhookPanel Svelte component on both detail pages, i18n in en/ru
- Tests for matcher (siteRefMatches, ParseImageRef) and handler (project
  match/mismatch/404 and site push/manual/branch-skip)
This commit is contained in:
2026-04-23 15:18:19 +03:00
parent e08acf5c0e
commit 0632f512e6
21 changed files with 1119 additions and 363 deletions
+19 -4
View File
@@ -319,12 +319,27 @@ export function updateSettings(data: Partial<Settings>): Promise<Settings> {
return put<Settings>('/api/settings', data);
}
export function getWebhookUrl(): Promise<{ webhook_url: string }> {
return get<{ webhook_url: string }>('/api/settings/webhook-url');
// ── Webhooks ───────────────────────────────────────────────────────
export interface WebhookUrlResponse {
webhook_url: string;
webhook_secret: string;
}
export function regenerateWebhookUrl(): Promise<{ webhook_url: string }> {
return post<{ webhook_url: string }>('/api/settings/webhook-url/regenerate');
export function getProjectWebhook(projectId: string): Promise<WebhookUrlResponse> {
return get<WebhookUrlResponse>(`/api/projects/${projectId}/webhook`);
}
export function regenerateProjectWebhook(projectId: string): Promise<WebhookUrlResponse> {
return post<WebhookUrlResponse>(`/api/projects/${projectId}/webhook/regenerate`);
}
export function getStaticSiteWebhook(siteId: string): Promise<WebhookUrlResponse> {
return get<WebhookUrlResponse>(`/api/sites/${siteId}/webhook`);
}
export function regenerateStaticSiteWebhook(siteId: string): Promise<WebhookUrlResponse> {
return post<WebhookUrlResponse>(`/api/sites/${siteId}/webhook/regenerate`);
}
// ── Proxy Routes ───────────────────────────────────────────────────