package server import ( "encoding/json" "errors" "net/http" "mgit.msbls.de/m/mcables/internal/db" ) // ---------------------------------------------------------------- frames type frameCreate struct { Name string `json:"name"` X float64 `json:"x"` Y float64 `json:"y"` Width float64 `json:"width"` Height float64 `json:"height"` } type framePatch struct { Name *string `json:"name,omitempty"` X *float64 `json:"x,omitempty"` Y *float64 `json:"y,omitempty"` Width *float64 `json:"width,omitempty"` Height *float64 `json:"height,omitempty"` } func (h *handlers) listFrames(w http.ResponseWriter, r *http.Request) { pid, ok := parseInt64Path(r, "pid") if !ok { writeError(w, db.ErrInvalidInput, "pid must be a positive integer") return } fs, err := h.store.ListFrames(pid) if err != nil { writeError(w, err, nil) return } writeJSON(w, http.StatusOK, fs) } func (h *handlers) createFrame(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 frameCreate if err := json.NewDecoder(r.Body).Decode(&body); err != nil { writeError(w, errors.Join(db.ErrInvalidInput, err), nil) return } f, err := h.store.CreateFrame(pid, db.FrameCreate{ Name: body.Name, X: body.X, Y: body.Y, Width: body.Width, Height: body.Height, }) if err != nil { writeError(w, err, nil) return } writeJSON(w, http.StatusCreated, f) } func (h *handlers) patchFrame(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 framePatch if err := json.NewDecoder(r.Body).Decode(&body); err != nil { writeError(w, errors.Join(db.ErrInvalidInput, err), nil) return } f, err := h.store.UpdateFrame(pid, id, db.FrameUpdate{ Name: body.Name, X: body.X, Y: body.Y, Width: body.Width, Height: body.Height, }) if err != nil { writeError(w, err, nil) return } writeJSON(w, http.StatusOK, f) } func (h *handlers) deleteFrame(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 } if err := h.store.DeleteFrame(pid, id); err != nil { writeError(w, err, nil) return } w.WriteHeader(http.StatusNoContent) } // ---------------------------------------------------------------- devices type deviceCreate struct { Name string `json:"name"` FrameID *int64 `json:"frame_id,omitempty"` TypeID *int64 `json:"type_id,omitempty"` Color string `json:"color,omitempty"` X float64 `json:"x"` Y float64 `json:"y"` Width float64 `json:"width"` Height float64 `json:"height"` } // devicePatch uses a raw `json.RawMessage` for frame_id + type_id so we // can tell "key absent" (leave alone) from "key present and null" // (set to NULL) from "key present with an int" (move to that target). // Standard encoding of nullable fields in JSON PATCH. type devicePatch struct { Name *string `json:"name,omitempty"` FrameID json.RawMessage `json:"frame_id,omitempty"` TypeID json.RawMessage `json:"type_id,omitempty"` Color *string `json:"color,omitempty"` X *float64 `json:"x,omitempty"` Y *float64 `json:"y,omitempty"` Width *float64 `json:"width,omitempty"` Height *float64 `json:"height,omitempty"` } // parseFrameRef decodes the raw frame_id field into a tri-state. func parseFrameRef(raw json.RawMessage) (db.FrameRef, error) { if len(raw) == 0 { return db.FrameRef{Set: false}, nil } // "null" → clear; otherwise expect an integer. if string(raw) == "null" { return db.FrameRef{Set: true, ID: nil}, nil } var id int64 if err := json.Unmarshal(raw, &id); err != nil { return db.FrameRef{}, err } return db.FrameRef{Set: true, ID: &id}, nil } func (h *handlers) listDevices(w http.ResponseWriter, r *http.Request) { pid, ok := parseInt64Path(r, "pid") if !ok { writeError(w, db.ErrInvalidInput, "pid must be a positive integer") return } ds, err := h.store.ListDevices(pid, nil) if err != nil { writeError(w, err, nil) return } writeJSON(w, http.StatusOK, ds) } func (h *handlers) createDevice(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 deviceCreate if err := json.NewDecoder(r.Body).Decode(&body); err != nil { writeError(w, errors.Join(db.ErrInvalidInput, err), nil) return } d, err := h.store.CreateDevice(pid, db.DeviceCreate{ Name: body.Name, FrameID: body.FrameID, TypeID: body.TypeID, Color: body.Color, X: body.X, Y: body.Y, Width: body.Width, Height: body.Height, }) if err != nil { writeError(w, err, nil) return } writeJSON(w, http.StatusCreated, d) } func (h *handlers) patchDevice(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 devicePatch if err := json.NewDecoder(r.Body).Decode(&body); err != nil { writeError(w, errors.Join(db.ErrInvalidInput, err), nil) return } ref, err := parseFrameRef(body.FrameID) if err != nil { writeError(w, errors.Join(db.ErrInvalidInput, err), "frame_id must be an integer or null") return } typeRef, err := parseFrameRef(body.TypeID) if err != nil { writeError(w, errors.Join(db.ErrInvalidInput, err), "type_id must be an integer or null") return } d, err := h.store.UpdateDevice(pid, id, db.DeviceUpdate{ Name: body.Name, FrameID: ref, TypeID: typeRef, Color: body.Color, X: body.X, Y: body.Y, Width: body.Width, Height: body.Height, }) if err != nil { writeError(w, err, nil) return } writeJSON(w, http.StatusOK, d) } func (h *handlers) deleteDevice(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 } if err := h.store.DeleteDevice(pid, id); err != nil { writeError(w, err, nil) return } w.WriteHeader(http.StatusNoContent) }