feat(site): implement site registration API #141
No reviewers
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
thwap/thwap-pagesd!141
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/site-registration-api"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Implement the
POST /api/v1/sitesregistration endpoint end to end:SiteService.Registerorchestrates the flow:Visibility, optional custom domain hostnameSiteRepository.GetForgejoService.CreateRepositoryForgejoService.CreateWebhooktargetingwebhook.public_url+/webhookKubernetesService.CreateSiteIngressSiteRepository.CreateSiteRepository(internal/site/memory.go) — map + mutex,ErrNotFound/ErrExistssemantics. ConfigMap-backed persistence arrives in a later phase.internal/httpapi/sites.go) —POST /api/v1/sites:201+ Site on success,400invalid body/validation,500downstream failure.webhook.public_urlconfig key drives the webhook target.Import cycle resolution:
SiteServicedepends on narrow structural interfaces (site.ForgejoClient,site.KubernetesClient) instead of importing the integration packages, which already importsite. Compile-time assertions inforgejo/kubernetesguarantee the concrete clients satisfy them.Auth (#62): deferred to the Phase 9 authentication middleware task, per the roadmap split (no auth on the endpoint yet).
Why
Phase 5 task #69 — the first end-to-end wiring of the domain, Forgejo, and Kubernetes layers.
Testing
go build ./...go vet ./...go test -race ./...— site (6 tests) + httpapi (7 tests) passgofmt -l ./cmd ./internal ./pkgcleangolangci-lint run— 0 issuesBreaking Changes
None.
Notes
Publish/RollbackonSiteServiceremain not-implemented (webhook/rollback phases).Closes #62
Closes #63
Closes #64
Closes #65
Closes #66
Closes #67
Closes #68
Closes #69
Review Summary
Verified locally on the PR head (
93eb0ff):go build,go vet,go mod verify,gofmt,golangci-lint runall clean; all 13 new site/httpapi tests plus the full suite pass with-race.The architecture is well done — the narrow structural interfaces (
site.ForgejoClient,site.KubernetesClient) cleanly break the import cycle, the in-memory repository has correctErrExists/ErrNotFoundsemantics, the validation covers name/owner/visibility/domain, and the handler error mapping is sensible. Tests are solid (happy path, duplicate, validation table, forgejo/k8s error paths, handler status codes).Blocking Issue
internal/site/service.go:81— the installed webhook targetswebhookURLdirectly, butNewServiceis fedcfg.Webhook.PublicURL(cmd/thwap-pagesd/main.go:97) with no/webhooksuffix appended. The PR description says the hook targetspublic_url + /webhook, and the webhook receiver (task #70) is registered atPOST /webhook. With the natural config (public_url: https://pages.example.com), Forgejo will POST push events to the site root — which has no route — so webhooks never deliver.Empirically confirmed: with
webhookURL: "https://pages.example.com",CreateWebhookreceives target"https://pages.example.com", not"https://pages.example.com/webhook". The fake inservice_test.godoesn't assert the target, so tests don't catch it.Fix: append the path in the service — e.g.
s.forgejo.CreateWebhook(ctx, req.Owner, req.Name, s.webhookURL+"/webhook")— or document thatpublic_urlmust include the full path. Add a test asserting the webhook target ends in/webhook.Non-blocking Suggestions
internal/httpapi/sites.go—isValidationErrorsniffs error message text ("invalid ", "required", "already exists"); a typed sentinel (e.g.ErrValidation/ErrExistsmapped to distinct statuses) would be more robust. Also, "already exists" returning 400 is arguably 409 Conflict.Registercreates the Forgejo repo and webhook and the ingress before persisting — a failure at any step orphans the earlier resources (no compensation). Acceptable for now; worth a note in the roadmap for idempotent registration.domainPatternaccepts internal double dots/hyphens (e.g.a..b); harmless today but worth tightening before custom domains are a first-class flow.The blocking item is a one-line path append.
Review changes addressed in
0b8686a:Blocking issue —
main.gonow appends/webhookto the configuredwebhook.public_urlvia awebhookTargetURLhelper (trimming any trailing slash, and returning""for an empty config), sopublic_url: https://pages.example.comproduces webhook targethttps://pages.example.com/webhook. AddedTestWebhookTargetURLand a service-test assertion that the installed webhook target ends in/webhook.Non-blocking #1 — the already-exists error now wraps the typed
apperrors.ErrExistssentinel, and the handler maps it to 409 Conflict (before the text-sniffing fallback). AddedTestRegisterHandlerConflict. Validation/not-found still use the message sniff for400/404.Verified:
go build,go vet,gofmt,golangci-lint runclean; full suite passes with-race.Review Summary
The blocking issue from the previous review is resolved.
cmd/thwap-pagesd/main.gonow routescfg.Webhook.PublicURLthroughwebhookTargetURL(), which trims a trailing/and appends/webhook. Webhooks now target the actualPOST /webhookreceiver endpoint. Covered byTestWebhookTargetURL(trailing-slash, no-slash, empty) andTestRegisternow asserts the recorded hook target.Service.Registerwraps the duplicate withapperrors.ErrExists, and the handler maps it to409 Conflict(checked before message sniffing).TestRegisterHandlerConflictcovers it.Verified locally on the PR head (
44eaffae):go build,go vet,gofmt,golangci-lint runall clean; full suite passes with-race.No blocking issues.
Non-blocking observation: the
strings.HasSuffix(msg, "already exists")branch inisValidationErroris now dead for the exists case (ErrExists is matched first and is the only producer) — harmless redundancy, could be dropped when the validation errors get typed sentinels.Approving.