diff --git a/Makefile b/Makefile index de240d0..31ee1f5 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all build lint fmt fmt-check test check clean +.PHONY: all build lint fmt fmt-check test check clean docker hooks BINARY := upaasd VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev") @@ -22,12 +22,22 @@ fmt-check: @test -z "$$(gofmt -l .)" || (echo "Files not formatted:" && gofmt -l . && exit 1) test: - go test -v -race -cover ./... + go test -v -race -cover -timeout 30s ./... # Check runs all validation without making changes # Used by CI and Docker build - fails if anything is wrong check: fmt-check lint test @echo "==> All checks passed!" +docker: + docker build . + +hooks: + @echo "Installing pre-commit hook..." + @mkdir -p .git/hooks + @printf '#!/bin/sh\nmake check\n' > .git/hooks/pre-commit + @chmod +x .git/hooks/pre-commit + @echo "Pre-commit hook installed." + clean: rm -rf bin/ diff --git a/README.md b/README.md index 8e86720..91877d7 100644 --- a/README.md +++ b/README.md @@ -110,11 +110,14 @@ chi Router ──► Middleware Stack ──► Handler ### Commands ```bash -make fmt # Format code -make lint # Run comprehensive linting -make test # Run tests with race detection -make check # Verify everything passes (lint, test, build, format) -make build # Build binary +make fmt # Format code +make fmt-check # Check formatting (read-only, fails if unformatted) +make lint # Run comprehensive linting +make test # Run tests with race detection (30s timeout) +make check # Verify everything passes (fmt-check, lint, test) +make build # Build binary +make docker # Build Docker image +make hooks # Install pre-commit hook (runs make check) ``` ### Commit Requirements diff --git a/internal/handlers/webhook_events.go b/internal/handlers/webhook_events.go new file mode 100644 index 0000000..d455cda --- /dev/null +++ b/internal/handlers/webhook_events.go @@ -0,0 +1,56 @@ +package handlers + +import ( + "net/http" + + "github.com/go-chi/chi/v5" + + "sneak.berlin/go/upaas/internal/models" + "sneak.berlin/go/upaas/templates" +) + +// webhookEventsLimit is the number of webhook events to show in history. +const webhookEventsLimit = 100 + +// HandleAppWebhookEvents returns the webhook event history handler. +func (h *Handlers) HandleAppWebhookEvents() http.HandlerFunc { + tmpl := templates.GetParsed() + + return func(writer http.ResponseWriter, request *http.Request) { + appID := chi.URLParam(request, "id") + + application, findErr := models.FindApp(request.Context(), h.db, appID) + if findErr != nil { + h.log.Error("failed to find app", "error", findErr) + http.Error(writer, "Internal Server Error", http.StatusInternalServerError) + + return + } + + if application == nil { + http.NotFound(writer, request) + + return + } + + events, eventsErr := application.GetWebhookEvents( + request.Context(), + webhookEventsLimit, + ) + if eventsErr != nil { + h.log.Error("failed to get webhook events", + "error", eventsErr, + "app", appID, + ) + + events = []*models.WebhookEvent{} + } + + data := h.addGlobals(map[string]any{ + "App": application, + "Events": events, + }, request) + + h.renderTemplate(writer, tmpl, "webhook_events.html", data) + } +} diff --git a/internal/models/webhook_event.go b/internal/models/webhook_event.go index dd8352e..8c7f14a 100644 --- a/internal/models/webhook_event.go +++ b/internal/models/webhook_event.go @@ -52,6 +52,20 @@ func (w *WebhookEvent) Reload(ctx context.Context) error { return w.scan(row) } +// ShortCommit returns a truncated commit SHA for display. +func (w *WebhookEvent) ShortCommit() string { + if !w.CommitSHA.Valid { + return "" + } + + sha := w.CommitSHA.String + if len(sha) > shortCommitLength { + return sha[:shortCommitLength] + } + + return sha +} + func (w *WebhookEvent) insert(ctx context.Context) error { query := ` INSERT INTO webhook_events ( diff --git a/internal/server/routes.go b/internal/server/routes.go index 376b19d..f02636d 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -70,6 +70,7 @@ func (s *Server) SetupRoutes() { r.Post("/apps/{id}/deploy", s.handlers.HandleAppDeploy()) r.Post("/apps/{id}/deployments/cancel", s.handlers.HandleCancelDeploy()) r.Get("/apps/{id}/deployments", s.handlers.HandleAppDeployments()) + r.Get("/apps/{id}/webhooks", s.handlers.HandleAppWebhookEvents()) r.Get("/apps/{id}/deployments/{deploymentID}/logs", s.handlers.HandleDeploymentLogsAPI()) r.Get("/apps/{id}/deployments/{deploymentID}/download", s.handlers.HandleDeploymentLogDownload()) r.Get("/apps/{id}/logs", s.handlers.HandleAppLogs()) diff --git a/templates/app_detail.html b/templates/app_detail.html index 48234ac..2d62de9 100644 --- a/templates/app_detail.html +++ b/templates/app_detail.html @@ -77,7 +77,10 @@
-

Webhook URL

+
+

Webhook URL

+ Event History +

Add this URL as a push webhook in your Gitea repository:

{{.WebhookURL}} diff --git a/templates/templates.go b/templates/templates.go index 5f733d1..ab46739 100644 --- a/templates/templates.go +++ b/templates/templates.go @@ -44,6 +44,7 @@ func initTemplates() { "app_detail.html", "app_edit.html", "deployments.html", + "webhook_events.html", } pageTemplates = make(map[string]*template.Template) diff --git a/templates/webhook_events.html b/templates/webhook_events.html new file mode 100644 index 0000000..23fb295 --- /dev/null +++ b/templates/webhook_events.html @@ -0,0 +1,79 @@ +{{template "base" .}} + +{{define "title"}}Webhook Events - {{.App.Name}} - µPaaS{{end}} + +{{define "content"}} +{{template "nav" .}} + +
+ + +
+

Webhook Events

+
+ + {{if .Events}} +
+ + + + + + + + + + + + {{range .Events}} + + + + + + + + {{end}} + +
TimeEventBranchCommitStatus
+ + {{.EventType}}{{.Branch}} + {{if and .CommitSHA.Valid .CommitURL.Valid}} + {{.ShortCommit}} + {{else if .CommitSHA.Valid}} + {{.ShortCommit}} + {{else}} + - + {{end}} + + {{if .Matched}} + {{if .Processed}} + Matched + {{else}} + Matched (pending) + {{end}} + {{else}} + No match + {{end}} +
+
+ {{else}} +
+
+ + + +

No webhook events yet

+

Webhook events will appear here once your repository sends push notifications.

+
+
+ {{end}} +
+{{end}}