# Server Package This package implements the HTTP server for the member-console application, including HTMX partial handlers. ## Overview The server uses Go's standard `net/http` package with: - Bootstrap 5 for styling - HTMX 2.0 for dynamic content updates - gorilla/csrf for CSRF protection - scs (`alexedwards/scs`) for server-side session management (Valkey/Redis-backed) ## Architecture The HTTP server handles several types of requests: - **Static Files** (`/static/*`) - CSS, JS, images served from embedded assets - **Page Handlers** (`/`) - Full HTML page rendering - **Partial Handlers** (`/partials/*`) - HTMX partial HTML responses - **Auth Handlers** (`/login`, `/logout`, `/callback`) - OAuth2/OIDC authentication Partial handlers interact with Temporal workflows for async operations like site provisioning. ## Files - `server.go` - Main server setup, middleware chain, route registration - `member_products.go`, `member_invoices.go`, `workspace_partials.go`, `operator*.go` - HTMX partial handlers for this package's own features Integration-owned handlers (e.g. FedWiki's member-facing API and partials) live in their own tree under `internal/integrations//web` instead of here — see `docs/building-an-integration.md` and `internal/integrations/fedwiki/web` for the worked example. The patterns below apply equally to handlers in either location. ## HTMX Integration ### Partial Handler Pattern Partial handlers return HTML fragments (not full pages) that HTMX swaps into the DOM: ```go // Handler returns HTML fragment func (h *Handler) GetItems(w http.ResponseWriter, r *http.Request) { // ... fetch data ... h.Templates.Render(w, "items.html", data) } ``` ```html
{{ range .Sites }} ... {{ end }}
``` ### HTMX Triggers Use `HX-Trigger` header to trigger events that refresh other parts of the page: ```go // After successful operation, trigger refresh w.Header().Set("HX-Trigger", `{"refreshSites": true}`) ``` ```html
``` ### CSRF with HTMX CSRF tokens are included via `hx-headers` on forms: ```html
``` Or globally in the page template using `hx-vals` or a meta tag approach. ## Synchronous Workflow Execution Unlike fire-and-forget patterns, this server waits for Temporal workflows to complete: ```go func (h *Handler) CreateSite(w http.ResponseWriter, r *http.Request) { // 1. Start the workflow we, err := h.TemporalClient.ExecuteWorkflow(ctx, options, workflow, input) // 2. Wait for completion (BLOCKING) var result WorkflowOutput err = we.Get(ctx, &result) // 3. Render result based on workflow outcome if !result.Success { h.renderError(w, result.ErrorMessage) return } h.renderSuccess(w, result) } ``` **Benefits:** - User sees accurate success/failure immediately - Spinner shows during actual operation - Error messages come from workflow **Trade-offs:** - Request blocks until workflow completes - Long operations may timeout (mitigated by workflow retry handling) ## Route Organization Routes are organized by feature using handler structs: ```go // ItemPartialsHandler groups related routes type ItemPartialsHandler struct { ... } func (h *ItemPartialsHandler) RegisterRoutes(mux *http.ServeMux) { mux.HandleFunc("GET /partials/items", h.GetItems) mux.HandleFunc("POST /partials/items", h.CreateItem) mux.HandleFunc("DELETE /partials/items/{id}", h.DeleteItem) // ... } ``` ## Error Rendering Errors are rendered back into the same partial/modal that initiated the request: ```go func (h *Handler) renderCreateFormError(w http.ResponseWriter, userID int64, errMsg string) { // Re-fetch form data data := h.getCreateFormData(userID) data.Error = errMsg // Re-render the form partial with error displayed h.Templates.Render(w, "create_form.html", data) } ``` This keeps the user in context - they see the error in the same modal/form they were using. ## Middleware Stack ```go handler = middleware.Logging(logger)(handler) // Request logging handler = middleware.Recover(logger)(handler) // Panic recovery handler = middleware.Compress()(handler) // Gzip compression handler = middleware.Decompress()(handler) // Gzip decompression handler = middleware.SecurityHeaders()(handler) // Security headers handler = middleware.RequestID()(handler) // Request ID generation handler = csrf.Protect(key, opts...)(handler) // CSRF protection ```