Files
member-console/internal/server/README.md
2025-12-19 15:47:32 -06:00

159 lines
4.5 KiB
Markdown

# 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
- gorilla/sessions for session management
## 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
- `fedwiki_partials.go` - HTMX partial handlers for FedWiki site management
## 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) GetSites(w http.ResponseWriter, r *http.Request) {
// ... fetch data ...
w.Header().Set("Content-Type", "text/html")
h.Templates.ExecuteTemplate(w, "fedwiki_sites.html", data)
}
```
```html
<!-- Template returns partial HTML -->
<div class="table-responsive">
<table class="table">
{{ range .Sites }}
<tr>...</tr>
{{ end }}
</table>
</div>
```
### 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
<!-- Element listens for the trigger -->
<div hx-get="/partials/fedwiki/sites"
hx-trigger="load, refreshSites from:body">
</div>
```
### CSRF with HTMX
CSRF tokens are included via `hx-headers` on forms:
```html
<form hx-post="/partials/fedwiki/sites"
hx-headers='{"X-CSRF-Token": "{{ .CSRFToken }}"}'>
```
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
// FedWikiPartialsHandler groups related routes
type FedWikiPartialsHandler struct { ... }
func (h *FedWikiPartialsHandler) RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("GET /partials/fedwiki/sites", h.GetSites)
mux.HandleFunc("POST /partials/fedwiki/sites", h.CreateSite)
mux.HandleFunc("DELETE /partials/fedwiki/sites/{domain}", h.DeleteSite)
// ...
}
```
## 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
w.Header().Set("Content-Type", "text/html")
h.Templates.ExecuteTemplate(w, "fedwiki_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
```