package server import ( "encoding/json" "errors" "net/http" "mgit.msbls.de/m/cablegui/internal/db" ) func (h *handlers) solve(w http.ResponseWriter, r *http.Request) { pid, ok := parseInt64Path(r, "pid") if !ok { writeError(w, db.ErrInvalidInput, "pid must be a positive integer") return } preview := r.URL.Query().Get("preview") == "1" res, err := h.store.Solve(pid, preview) if err != nil { writeError(w, err, nil) return } writeJSON(w, http.StatusOK, res) } // ports-and-resolve combo: POST a new port to a device + re-run solve in // the same request. Used by the inspector quick-fix. type portsAndResolveBody struct { TypeID int64 `json:"type_id"` Label string `json:"label,omitempty"` XOffset float64 `json:"x_offset,omitempty"` YOffset float64 `json:"y_offset,omitempty"` } func (h *handlers) portsAndResolve(w http.ResponseWriter, r *http.Request) { pid, ok := parseInt64Path(r, "pid") if !ok { writeError(w, db.ErrInvalidInput, "pid must be a positive integer") return } id, ok := parseInt64Path(r, "id") if !ok { writeError(w, db.ErrInvalidInput, "id must be a positive integer") return } var body portsAndResolveBody if err := json.NewDecoder(r.Body).Decode(&body); err != nil { writeError(w, errors.Join(db.ErrInvalidInput, err), nil) return } res, err := h.store.PortsAndResolve(pid, id, body.TypeID, body.Label, body.XOffset, body.YOffset) if err != nil { writeError(w, err, nil) return } writeJSON(w, http.StatusOK, res) } // -------------------------------------------------------- setup templates func (h *handlers) listSetupTemplates(w http.ResponseWriter, _ *http.Request) { ts, err := h.store.ListSetupTemplates() if err != nil { writeError(w, err, nil) return } writeJSON(w, http.StatusOK, ts) } type applyTemplateBody struct { TemplateID int64 `json:"template_id"` NameOverrides map[string]string `json:"name_overrides,omitempty"` SkipDevices []int64 `json:"skip_devices,omitempty"` OriginX float64 `json:"origin_x,omitempty"` OriginY float64 `json:"origin_y,omitempty"` } func (h *handlers) applyTemplate(w http.ResponseWriter, r *http.Request) { pid, ok := parseInt64Path(r, "pid") if !ok { writeError(w, db.ErrInvalidInput, "pid must be a positive integer") return } var body applyTemplateBody if err := json.NewDecoder(r.Body).Decode(&body); err != nil { writeError(w, errors.Join(db.ErrInvalidInput, err), nil) return } opts := db.ApplyTemplateOptions{ NameOverrides: map[int64]string{}, SkipDevices: map[int64]bool{}, OriginX: body.OriginX, OriginY: body.OriginY, } // JSON keys are strings; parse to int64. for k, v := range body.NameOverrides { var tid int64 _, _ = fmtSscan(k, &tid) if tid > 0 { opts.NameOverrides[tid] = v } } for _, tid := range body.SkipDevices { opts.SkipDevices[tid] = true } res, err := h.store.ApplyTemplate(pid, body.TemplateID, opts) if err != nil { writeError(w, err, nil) return } // Auto-solve by default. ?solve=0 opts out for power users who want // to inspect the seeded devices/requirements before the solver runs. // This is THE fix for the v6 UX hole: m hit Apply, saw an empty // canvas because nothing reloaded *and* nothing solved. With the // frontend re-snapshotting after the POST returns and the response // already carrying solver output, m sees the wired diagram in one click. skipSolve := r.URL.Query().Get("solve") == "0" combined := map[string]any{"template_apply": res} if !skipSolve { solveRes, err := h.store.Solve(pid, false) if err != nil { // Apply succeeded but Solve failed — don't 500 the whole // call. Return template_apply with the solve error inline so // the UI can recover (devices are there; m can re-solve). combined["solve_error"] = err.Error() } else { combined["solve"] = solveRes } } writeJSON(w, http.StatusOK, combined) } // fmtSscan parses a base-10 int from a string, returning (n, nil) on success. // Inline so handlers don't pull in strconv just for one call site. func fmtSscan(s string, out *int64) (int, error) { var v int64 read := 0 for i := 0; i < len(s); i++ { c := s[i] if c < '0' || c > '9' { break } v = v*10 + int64(c-'0') read++ } *out = v return read, nil }