159 lines
4.5 KiB
Markdown
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
|
|
```
|