package client import ( "bytes" "encoding/json" "fmt" "net/http" "net/url" "time" ) // Part mirrors the API's IPart document. type Part struct { ID string `json:"_id"` Title string `json:"title"` Type string `json:"type"` Category string `json:"category"` Manufacturer string `json:"manufacturer,omitempty"` Dimensions *Dimensions `json:"dimensions,omitempty"` Quantity int `json:"quantity"` Location string `json:"location,omitempty"` Notes string `json:"notes,omitempty"` Tags []string `json:"tags"` Properties map[string]interface{} `json:"properties"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` } // Dimensions holds optional physical size info. type Dimensions struct { Width *float64 `json:"width,omitempty"` Length *float64 `json:"length,omitempty"` Height *float64 `json:"height,omitempty"` Unit string `json:"unit,omitempty"` } // ListResponse is the shape returned by GET /api/parts. type ListResponse struct { Total int `json:"total"` Parts []Part `json:"parts"` } // Client wraps HTTP calls to the parts API. type Client struct { BaseURL string HTTPClient *http.Client } // New creates a Client with a 15-second timeout. func New(baseURL string) *Client { return &Client{ BaseURL: baseURL, HTTPClient: &http.Client{Timeout: 15 * time.Second}, } } // ListParts fetches parts with optional full-text query and field filters. func (c *Client) ListParts(query, partType, category string) (*ListResponse, error) { u, err := url.Parse(c.BaseURL + "/api/parts") if err != nil { return nil, err } params := url.Values{} if query != "" { params.Set("q", query) } if partType != "" { params.Set("type", partType) } if category != "" { params.Set("category", category) } u.RawQuery = params.Encode() resp, err := c.HTTPClient.Get(u.String()) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("API returned %d", resp.StatusCode) } var result ListResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, err } return &result, nil } // GetPart fetches a single part by ID. func (c *Client) GetPart(id string) (*Part, error) { resp, err := c.HTTPClient.Get(c.BaseURL + "/api/parts/" + id) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { return nil, fmt.Errorf("part %s not found", id) } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("API returned %d", resp.StatusCode) } var part Part if err := json.NewDecoder(resp.Body).Decode(&part); err != nil { return nil, err } return &part, nil } // CreatePart posts a new part to the API. func (c *Client) CreatePart(part map[string]interface{}) (*Part, error) { body, err := json.Marshal(part) if err != nil { return nil, err } resp, err := c.HTTPClient.Post( c.BaseURL+"/api/parts", "application/json", bytes.NewReader(body), ) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusCreated { var apiErr map[string]string _ = json.NewDecoder(resp.Body).Decode(&apiErr) if msg, ok := apiErr["error"]; ok { return nil, fmt.Errorf("API error: %s", msg) } return nil, fmt.Errorf("API returned %d", resp.StatusCode) } var created Part if err := json.NewDecoder(resp.Body).Decode(&created); err != nil { return nil, err } return &created, nil } // UpdatePart sends a PUT request with the provided fields. func (c *Client) UpdatePart(id string, updates map[string]interface{}) (*Part, error) { body, err := json.Marshal(updates) if err != nil { return nil, err } req, err := http.NewRequest(http.MethodPut, c.BaseURL+"/api/parts/"+id, bytes.NewReader(body)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") resp, err := c.HTTPClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { return nil, fmt.Errorf("part %s not found", id) } if resp.StatusCode != http.StatusOK { var apiErr map[string]string _ = json.NewDecoder(resp.Body).Decode(&apiErr) if msg, ok := apiErr["error"]; ok { return nil, fmt.Errorf("API error: %s", msg) } return nil, fmt.Errorf("API returned %d", resp.StatusCode) } var updated Part if err := json.NewDecoder(resp.Body).Decode(&updated); err != nil { return nil, err } return &updated, nil } // DeletePart sends a DELETE request for the given part ID. func (c *Client) DeletePart(id string) error { req, err := http.NewRequest(http.MethodDelete, c.BaseURL+"/api/parts/"+id, nil) if err != nil { return err } resp, err := c.HTTPClient.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { return fmt.Errorf("part %s not found", id) } if resp.StatusCode != http.StatusOK { return fmt.Errorf("API returned %d", resp.StatusCode) } return nil }