Full project rename per m's call. Single atomic commit because the codebase rename is a coupled change — go module path, env vars, DB default, Docker artefact names, and on-disk mDock paths all flip together. - go.mod: module mgit.msbls.de/m/mcables → mgit.msbls.de/m/cablegui - cmd/mcables → cmd/cablegui (git mv) - All Go imports rewritten to the new module path - Env vars: MCABLES_ADDR/MCABLES_DB → CABLEGUI_ADDR/CABLEGUI_DB - DB default path: data/mcables.db → data/cablegui.db - Dockerfile + docker-compose.yml: image, container_name, env vars, bind-mount /home/m/stacks/mcables → /home/m/stacks/cablegui, secrets /home/m/secrets/mcables → /home/m/secrets/cablegui - Makefile: bin target + run/build commands point at cmd/cablegui - .gitignore + .dockerignore: /mcables → /cablegui - README, docs/design.md, CLAUDE.md: prose + paths + image name - web/static/index.html: <title> + brand - web/static/main.js + web/web.go: header comment - internal/exporter: Scene.Source "mcables" → "cablegui" - internal/server/export.go: error-detail secrets path - internal/db/migrations/*.sql: header comments (mCables vN → CableGUI vN) Memory group_id kept as "mcables" to preserve existing memory continuity. Documented as historical in CLAUDE.md. go build ./... clean; go test -race ./... green
129 lines
6.2 KiB
Go
129 lines
6.2 KiB
Go
// Package server wires the HTTP API + the embedded frontend onto a
|
|
// single net/http handler. Routes use Go 1.22 ServeMux pattern matching
|
|
// (no router framework).
|
|
package server
|
|
|
|
import (
|
|
"io/fs"
|
|
"net/http"
|
|
|
|
"mgit.msbls.de/m/cablegui/internal/db"
|
|
)
|
|
|
|
// New returns an http.Handler serving the CableGUI API at /api/ and the
|
|
// embedded frontend at /. The frontend FS should be rooted such that
|
|
// "index.html" is at its root.
|
|
func New(store *db.Store, frontend fs.FS) http.Handler {
|
|
mux := http.NewServeMux()
|
|
h := &handlers{store: store}
|
|
|
|
// Health
|
|
mux.HandleFunc("GET /api/healthz", h.healthz)
|
|
|
|
// Projects
|
|
mux.HandleFunc("GET /api/projects", h.listProjects)
|
|
mux.HandleFunc("POST /api/projects", h.createProject)
|
|
mux.HandleFunc("GET /api/projects/{pid}", h.getProject)
|
|
mux.HandleFunc("PATCH /api/projects/{pid}", h.patchProject)
|
|
mux.HandleFunc("DELETE /api/projects/{pid}", h.deleteProject)
|
|
|
|
// Cable types (global)
|
|
mux.HandleFunc("GET /api/cable-types", h.listCableTypes)
|
|
mux.HandleFunc("POST /api/cable-types", h.createCableType)
|
|
mux.HandleFunc("PATCH /api/cable-types/{id}", h.patchCableType)
|
|
mux.HandleFunc("DELETE /api/cable-types/{id}", h.deleteCableType)
|
|
|
|
// Frames (project-scoped)
|
|
mux.HandleFunc("GET /api/projects/{pid}/frames", h.listFrames)
|
|
mux.HandleFunc("POST /api/projects/{pid}/frames", h.createFrame)
|
|
mux.HandleFunc("PATCH /api/projects/{pid}/frames/{id}", h.patchFrame)
|
|
mux.HandleFunc("DELETE /api/projects/{pid}/frames/{id}", h.deleteFrame)
|
|
|
|
// Devices (project-scoped)
|
|
mux.HandleFunc("GET /api/projects/{pid}/devices", h.listDevices)
|
|
mux.HandleFunc("POST /api/projects/{pid}/devices", h.createDevice)
|
|
mux.HandleFunc("PATCH /api/projects/{pid}/devices/{id}", h.patchDevice)
|
|
mux.HandleFunc("DELETE /api/projects/{pid}/devices/{id}", h.deleteDevice)
|
|
|
|
// IO markers (project-scoped) — wall-outlet terminators
|
|
mux.HandleFunc("GET /api/projects/{pid}/io-markers", h.listIOMarkers)
|
|
mux.HandleFunc("POST /api/projects/{pid}/io-markers", h.createIOMarker)
|
|
mux.HandleFunc("PATCH /api/projects/{pid}/io-markers/{id}", h.patchIOMarker)
|
|
mux.HandleFunc("DELETE /api/projects/{pid}/io-markers/{id}", h.deleteIOMarker)
|
|
|
|
// Ports — slice 7 lets m add/edit/remove instance ports on a device.
|
|
mux.HandleFunc("GET /api/projects/{pid}/devices/{id}/ports", h.listPortsForDevice)
|
|
mux.HandleFunc("POST /api/projects/{pid}/devices/{id}/ports", h.createPort)
|
|
mux.HandleFunc("PATCH /api/projects/{pid}/ports/{id}", h.patchPort)
|
|
mux.HandleFunc("DELETE /api/projects/{pid}/ports/{id}", h.deletePort)
|
|
|
|
// Device-type catalog. Built-ins are read-only; project-custom rows
|
|
// support full CRUD scoped to the project.
|
|
mux.HandleFunc("GET /api/device-types", h.listBuiltInDeviceTypes)
|
|
mux.HandleFunc("GET /api/projects/{pid}/device-types", h.listDeviceTypes)
|
|
mux.HandleFunc("POST /api/projects/{pid}/device-types", h.createDeviceType)
|
|
mux.HandleFunc("PATCH /api/projects/{pid}/device-types/{id}", h.patchDeviceType)
|
|
mux.HandleFunc("DELETE /api/projects/{pid}/device-types/{id}", h.deleteDeviceType)
|
|
|
|
// Connection requirements — the solver's per-project input.
|
|
mux.HandleFunc("GET /api/projects/{pid}/connection-requirements", h.listConnectionRequirements)
|
|
mux.HandleFunc("POST /api/projects/{pid}/connection-requirements", h.createConnectionRequirement)
|
|
mux.HandleFunc("PATCH /api/projects/{pid}/connection-requirements/{id}", h.patchConnectionRequirement)
|
|
mux.HandleFunc("DELETE /api/projects/{pid}/connection-requirements/{id}", h.deleteConnectionRequirement)
|
|
|
|
// Cables — slice 6: solver writes here with auto=1; slice 7 lets m
|
|
// hand-draw with auto=0. PATCH supports `promote: true` to flip auto→0.
|
|
mux.HandleFunc("GET /api/projects/{pid}/cables", h.listCables)
|
|
mux.HandleFunc("POST /api/projects/{pid}/cables", h.createCable)
|
|
mux.HandleFunc("PATCH /api/projects/{pid}/cables/{id}", h.patchCable)
|
|
mux.HandleFunc("DELETE /api/projects/{pid}/cables/{id}", h.deleteCable)
|
|
|
|
// Bundles — manual + auto.
|
|
mux.HandleFunc("GET /api/projects/{pid}/bundles", h.listBundles)
|
|
mux.HandleFunc("POST /api/projects/{pid}/bundles", h.createBundle)
|
|
mux.HandleFunc("PATCH /api/projects/{pid}/bundles/{id}", h.patchBundle)
|
|
mux.HandleFunc("DELETE /api/projects/{pid}/bundles/{id}", h.deleteBundle)
|
|
|
|
// Solver + quick-fix combo + setup templates.
|
|
mux.HandleFunc("POST /api/projects/{pid}/solve", h.solve)
|
|
mux.HandleFunc("POST /api/projects/{pid}/devices/{id}/ports-and-resolve", h.portsAndResolve)
|
|
mux.HandleFunc("GET /api/setup-templates", h.listSetupTemplates)
|
|
mux.HandleFunc("POST /api/projects/{pid}/apply-template", h.applyTemplate)
|
|
|
|
// Slice 8 — export to mxdrw.msbls.de
|
|
mux.HandleFunc("POST /api/projects/{pid}/sync/export", h.syncExport)
|
|
|
|
// v5 — clamps + cable routing.
|
|
mux.HandleFunc("GET /api/projects/{pid}/clamps", h.listClamps)
|
|
mux.HandleFunc("POST /api/projects/{pid}/clamps", h.createClamp)
|
|
mux.HandleFunc("PATCH /api/projects/{pid}/clamps/{id}", h.patchClamp)
|
|
mux.HandleFunc("DELETE /api/projects/{pid}/clamps/{id}", h.deleteClamp)
|
|
mux.HandleFunc("POST /api/projects/{pid}/cables/{cid}/clamps", h.attachClampToCable)
|
|
mux.HandleFunc("PUT /api/projects/{pid}/cables/{cid}/clamps", h.reorderCableClamps)
|
|
mux.HandleFunc("DELETE /api/projects/{pid}/cables/{cid}/clamps/{cmid}", h.detachClampFromCable)
|
|
|
|
// Frontend (embedded). Serve "/" → index.html via http.FileServerFS.
|
|
// Wrap in noCache so the browser revalidates with the ETag/Last-Modified
|
|
// the file server already emits — without this, browsers cache aggressively
|
|
// and m sees the old main.js after every redeploy until hard-reload.
|
|
mux.Handle("/", noCache(http.FileServerFS(frontend)))
|
|
|
|
return mux
|
|
}
|
|
|
|
// noCache wraps a static handler so each response carries
|
|
// Cache-Control: no-cache. Combined with the ETag/Last-Modified headers
|
|
// http.FileServer(FS) already emits, this turns every fetch into a
|
|
// cheap revalidation request — the browser uses its cached body when
|
|
// the ETag matches but always asks first, so freshly-built assets show
|
|
// up on the next page load without a hard-reload.
|
|
//
|
|
// Applied to the static-asset handler only — API responses write their
|
|
// own headers and aren't routed through this.
|
|
func noCache(h http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Cache-Control", "no-cache")
|
|
h.ServeHTTP(w, r)
|
|
})
|
|
}
|