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
+7
View File
@@ -128,6 +128,11 @@ func (s *Store) runMigrations() error {
// Add persistent storage columns to static_sites (2026-04-12).
`ALTER TABLE static_sites ADD COLUMN storage_enabled INTEGER NOT NULL DEFAULT 0`,
`ALTER TABLE static_sites ADD COLUMN storage_limit_mb INTEGER NOT NULL DEFAULT 0`,
// Per-project + per-site webhook secrets (2026-04-23). Global
// settings.webhook_secret is deprecated; its column is retained to
// avoid a destructive migration on SQLite.
`ALTER TABLE projects ADD COLUMN webhook_secret TEXT NOT NULL DEFAULT ''`,
`ALTER TABLE static_sites ADD COLUMN webhook_secret TEXT NOT NULL DEFAULT ''`,
}
// Additive stack tables (2026-04-16). Created here rather than in the
@@ -194,6 +199,8 @@ func (s *Store) runMigrations() error {
`CREATE INDEX IF NOT EXISTS idx_static_site_secrets_site_id ON static_site_secrets(site_id)`,
`CREATE INDEX IF NOT EXISTS idx_stack_revisions_stack_id ON stack_revisions(stack_id)`,
`CREATE INDEX IF NOT EXISTS idx_stack_deploys_stack_id ON stack_deploys(stack_id)`,
`CREATE UNIQUE INDEX IF NOT EXISTS idx_projects_webhook_secret ON projects(webhook_secret) WHERE webhook_secret != ''`,
`CREATE UNIQUE INDEX IF NOT EXISTS idx_static_sites_webhook_secret ON static_sites(webhook_secret) WHERE webhook_secret != ''`,
}
for _, idx := range indexes {
if _, err := s.db.Exec(idx); err != nil {