printjob_backend/internal/server/api/configuration.go

271 lines
7.7 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("PUT /configurations/{id}", updateConfiguration(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))
registerPDFRoutes(mux, configs, 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 updateConfiguration(configs *store.ConfigurationStore, plates *store.PlateStore, items *store.ItemStore) 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
}
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,
}
saved, err := configs.Update(id, c)
if err != nil {
writeError(w, http.StatusNotFound, err)
return
}
resp := configurationResponseFor(saved, plates, items)
if resp.PreviewError != "" {
writeError(w, http.StatusInternalServerError, errors.New(resp.PreviewError))
return
}
writeJSON(w, http.StatusOK, 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
}