219 lines
6.2 KiB
Go
219 lines
6.2 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"printer.backend/internal/layout"
|
|
"printer.backend/internal/model"
|
|
"printer.backend/internal/store"
|
|
)
|
|
|
|
func registerConfigurationRoutes(mux *http.ServeMux, configs *store.ConfigurationStore, plates *store.PlateStore, items *store.ItemStore) {
|
|
mux.HandleFunc("GET /configurations", listConfigurations(configs, plates, items))
|
|
mux.HandleFunc("POST /configurations", saveConfiguration(configs, plates, items))
|
|
mux.HandleFunc("DELETE /configurations/{id}", deleteConfiguration(configs))
|
|
mux.HandleFunc("GET /configurations/{id}/preview", previewConfiguration(configs, plates, items))
|
|
mux.HandleFunc("GET /layout/preview", layoutPreview(plates, items))
|
|
}
|
|
|
|
type configurationResponse struct {
|
|
model.Configuration
|
|
Preview *model.LayoutPreview `json:"preview,omitempty"`
|
|
PreviewError string `json:"preview_error,omitempty"`
|
|
}
|
|
|
|
func listConfigurations(configs *store.ConfigurationStore, plates *store.PlateStore, items *store.ItemStore) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, _ *http.Request) {
|
|
list, err := configs.List()
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
if list == nil {
|
|
list = []model.Configuration{}
|
|
}
|
|
|
|
out := make([]configurationResponse, 0, len(list))
|
|
for _, c := range list {
|
|
out = append(out, configurationResponseFor(c, plates, items))
|
|
}
|
|
writeJSON(w, http.StatusOK, out)
|
|
}
|
|
}
|
|
|
|
type saveConfigurationRequest struct {
|
|
Name string `json:"name"`
|
|
PlateID string `json:"plate_id"`
|
|
ItemID string `json:"item_id"`
|
|
SpacingMM float64 `json:"spacing_mm"`
|
|
}
|
|
|
|
func saveConfiguration(configs *store.ConfigurationStore, plates *store.PlateStore, items *store.ItemStore) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
var req saveConfigurationRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, http.StatusBadRequest, err)
|
|
return
|
|
}
|
|
if req.PlateID == "" || req.ItemID == "" {
|
|
writeError(w, http.StatusBadRequest, errors.New("plate_id and item_id are required"))
|
|
return
|
|
}
|
|
if _, err := findPlate(plates, req.PlateID); err != nil {
|
|
writeError(w, http.StatusBadRequest, err)
|
|
return
|
|
}
|
|
if _, err := items.Get(req.ItemID); err != nil {
|
|
writeError(w, http.StatusBadRequest, err)
|
|
return
|
|
}
|
|
if req.SpacingMM < 0 {
|
|
writeError(w, http.StatusBadRequest, errors.New("spacing_mm must be non-negative"))
|
|
return
|
|
}
|
|
|
|
c := model.Configuration{
|
|
Name: req.Name,
|
|
PlateID: req.PlateID,
|
|
ItemID: req.ItemID,
|
|
SpacingMM: req.SpacingMM,
|
|
CreatedAt: time.Now().UTC(),
|
|
}
|
|
saved, err := configs.Save(c)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
resp := configurationResponseFor(saved, plates, items)
|
|
if resp.PreviewError != "" {
|
|
writeError(w, http.StatusInternalServerError, errors.New(resp.PreviewError))
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusCreated, resp)
|
|
}
|
|
}
|
|
|
|
func deleteConfiguration(configs *store.ConfigurationStore) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
id := r.PathValue("id")
|
|
if id == "" {
|
|
writeError(w, http.StatusBadRequest, errors.New("id required"))
|
|
return
|
|
}
|
|
if err := configs.Delete(id); err != nil {
|
|
writeError(w, http.StatusNotFound, err)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
}
|
|
|
|
func previewConfiguration(configs *store.ConfigurationStore, plates *store.PlateStore, items *store.ItemStore) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
id := r.PathValue("id")
|
|
c, err := configs.Get(id)
|
|
if err != nil {
|
|
writeError(w, http.StatusNotFound, err)
|
|
return
|
|
}
|
|
preview, err := buildPreview(plates, items, c.PlateID, c.ItemID, c.SpacingMM)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, err)
|
|
return
|
|
}
|
|
preview.PlateID = c.PlateID
|
|
preview.ItemID = c.ItemID
|
|
writeJSON(w, http.StatusOK, preview)
|
|
}
|
|
}
|
|
|
|
func layoutPreview(plates *store.PlateStore, items *store.ItemStore) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
plateID := r.URL.Query().Get("plate_id")
|
|
itemID := r.URL.Query().Get("item_id")
|
|
if plateID == "" || itemID == "" {
|
|
writeError(w, http.StatusBadRequest, errors.New("plate_id and item_id query params are required"))
|
|
return
|
|
}
|
|
spacing, err := parseSpacing(r.URL.Query().Get("spacing_mm"))
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, err)
|
|
return
|
|
}
|
|
|
|
preview, err := buildPreview(plates, items, plateID, itemID, spacing)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, err)
|
|
return
|
|
}
|
|
preview.PlateID = plateID
|
|
preview.ItemID = itemID
|
|
writeJSON(w, http.StatusOK, preview)
|
|
}
|
|
}
|
|
|
|
func configurationResponseFor(c model.Configuration, plates *store.PlateStore, items *store.ItemStore) configurationResponse {
|
|
resp := configurationResponse{Configuration: c}
|
|
preview, err := buildPreview(plates, items, c.PlateID, c.ItemID, c.SpacingMM)
|
|
if err != nil {
|
|
resp.PreviewError = err.Error()
|
|
return resp
|
|
}
|
|
preview.PlateID = c.PlateID
|
|
preview.ItemID = c.ItemID
|
|
resp.Preview = &preview
|
|
return resp
|
|
}
|
|
|
|
func buildPreview(plates *store.PlateStore, items *store.ItemStore, plateID, itemID string, spacingMM float64) (model.LayoutPreview, error) {
|
|
plate, err := findPlate(plates, plateID)
|
|
if err != nil {
|
|
return model.LayoutPreview{}, err
|
|
}
|
|
item, err := items.Get(itemID)
|
|
if err != nil {
|
|
return model.LayoutPreview{}, err
|
|
}
|
|
return layout.Pack(plate, item.Spec, spacingMM), nil
|
|
}
|
|
|
|
func findPlate(plates *store.PlateStore, id string) (model.Plate, error) {
|
|
list, err := plates.List()
|
|
if err != nil {
|
|
return model.Plate{}, err
|
|
}
|
|
plate, err := storeFindPlate(list, id)
|
|
if err != nil {
|
|
return model.Plate{}, errors.New("plate not found: " + id)
|
|
}
|
|
return plate, nil
|
|
}
|
|
|
|
func storeFindPlate(list []model.Plate, id string) (model.Plate, error) {
|
|
for _, p := range list {
|
|
if p.ID == id {
|
|
return p, nil
|
|
}
|
|
}
|
|
return model.Plate{}, errors.New("not found")
|
|
}
|
|
|
|
func parseSpacing(s string) (float64, error) {
|
|
if s == "" {
|
|
return 0, nil
|
|
}
|
|
v, err := strconv.ParseFloat(s, 64)
|
|
if err != nil {
|
|
return 0, errors.New("invalid spacing_mm")
|
|
}
|
|
if v < 0 {
|
|
return 0, errors.New("spacing_mm must be non-negative")
|
|
}
|
|
return v, nil
|
|
}
|