Files
member-console/internal/server

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:

// 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)
}
<!-- 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:

// After successful operation, trigger refresh
w.Header().Set("HX-Trigger", `{"refreshSites": true}`)
<!-- 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:

<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:

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:

// 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:

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

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