278 lines
7.4 KiB
Go
278 lines
7.4 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
|
|
"printer.backend/internal/model"
|
|
"printer.backend/internal/store"
|
|
)
|
|
|
|
// NewHandler returns the API HTTP handler (routes under /).
|
|
func NewHandler() http.Handler {
|
|
plates := store.NewPlateStore("")
|
|
items := store.NewItemStore("")
|
|
configs := store.NewConfigurationStore("")
|
|
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("GET /health", health)
|
|
mux.HandleFunc("GET /", root)
|
|
mux.HandleFunc("GET /plates", listPlates(plates))
|
|
mux.HandleFunc("POST /plates", savePlate(plates))
|
|
mux.HandleFunc("PUT /plates/{id}", updatePlate(plates))
|
|
mux.HandleFunc("DELETE /plates/{id}", deletePlate(plates))
|
|
mux.HandleFunc("GET /items", listItems(items))
|
|
mux.HandleFunc("POST /items", saveItem(items))
|
|
mux.HandleFunc("PUT /items/{id}", updateItem(items))
|
|
mux.HandleFunc("DELETE /items/{id}", deleteItem(items))
|
|
mux.HandleFunc("GET /items/{id}/svg", serveItemSVG(items))
|
|
registerConfigurationRoutes(mux, configs, plates, items)
|
|
return withCORS(mux)
|
|
}
|
|
|
|
func withCORS(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
|
if r.Method == http.MethodOptions {
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
func health(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
|
|
}
|
|
|
|
func root(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]string{
|
|
"service": "printer-backend-api",
|
|
})
|
|
}
|
|
|
|
func listPlates(s *store.PlateStore) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, _ *http.Request) {
|
|
plates, err := s.List()
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
if plates == nil {
|
|
plates = []model.Plate{}
|
|
}
|
|
writeJSON(w, http.StatusOK, plates)
|
|
}
|
|
}
|
|
|
|
type savePlateRequest struct {
|
|
Name string `json:"name"`
|
|
WidthMM float64 `json:"width_mm"`
|
|
HeightMM float64 `json:"height_mm"`
|
|
MarginTop float64 `json:"margin_top_mm"`
|
|
MarginRight float64 `json:"margin_right_mm"`
|
|
MarginBottom float64 `json:"margin_bottom_mm"`
|
|
MarginLeft float64 `json:"margin_left_mm"`
|
|
}
|
|
|
|
func savePlate(s *store.PlateStore) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
var req savePlateRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, http.StatusBadRequest, err)
|
|
return
|
|
}
|
|
if req.WidthMM <= 0 || req.HeightMM <= 0 {
|
|
writeError(w, http.StatusBadRequest, errors.New("width_mm and height_mm must be positive"))
|
|
return
|
|
}
|
|
|
|
p := model.Plate{
|
|
Name: req.Name,
|
|
WidthMM: req.WidthMM,
|
|
HeightMM: req.HeightMM,
|
|
MarginTop: req.MarginTop,
|
|
MarginRight: req.MarginRight,
|
|
MarginBottom: req.MarginBottom,
|
|
MarginLeft: req.MarginLeft,
|
|
CreatedAt: time.Now().UTC(),
|
|
}
|
|
saved, err := s.Save(p)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusCreated, saved)
|
|
}
|
|
}
|
|
|
|
func updatePlate(s *store.PlateStore) 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 savePlateRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, http.StatusBadRequest, err)
|
|
return
|
|
}
|
|
if req.WidthMM <= 0 || req.HeightMM <= 0 {
|
|
writeError(w, http.StatusBadRequest, errors.New("width_mm and height_mm must be positive"))
|
|
return
|
|
}
|
|
p := model.Plate{
|
|
Name: req.Name,
|
|
WidthMM: req.WidthMM,
|
|
HeightMM: req.HeightMM,
|
|
MarginTop: req.MarginTop,
|
|
MarginRight: req.MarginRight,
|
|
MarginBottom: req.MarginBottom,
|
|
MarginLeft: req.MarginLeft,
|
|
}
|
|
saved, err := s.Update(id, p)
|
|
if err != nil {
|
|
writeError(w, http.StatusNotFound, err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, saved)
|
|
}
|
|
}
|
|
|
|
func deletePlate(s *store.PlateStore) 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 := s.Delete(id); err != nil {
|
|
writeError(w, http.StatusNotFound, err)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
}
|
|
|
|
func listItems(s *store.ItemStore) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, _ *http.Request) {
|
|
list, err := s.List()
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
if list == nil {
|
|
list = []model.Item{}
|
|
}
|
|
writeJSON(w, http.StatusOK, list)
|
|
}
|
|
}
|
|
|
|
type saveItemRequest struct {
|
|
Name string `json:"name"`
|
|
SizeMM float64 `json:"size_mm"`
|
|
BleedMM float64 `json:"bleed_mm"`
|
|
MarginMM float64 `json:"margin_mm"`
|
|
PaddingMM float64 `json:"padding_mm"`
|
|
}
|
|
|
|
func saveItem(s *store.ItemStore) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
var req saveItemRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, http.StatusBadRequest, err)
|
|
return
|
|
}
|
|
spec := model.ItemSpec{
|
|
SizeMM: req.SizeMM,
|
|
BleedMM: req.BleedMM,
|
|
MarginMM: req.MarginMM,
|
|
PaddingMM: req.PaddingMM,
|
|
}
|
|
saved, err := s.Create(req.Name, spec)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusCreated, saved)
|
|
}
|
|
}
|
|
|
|
func updateItem(s *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 saveItemRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, http.StatusBadRequest, err)
|
|
return
|
|
}
|
|
saved, err := s.Update(id, req.Name, model.ItemSpec{
|
|
SizeMM: req.SizeMM,
|
|
BleedMM: req.BleedMM,
|
|
MarginMM: req.MarginMM,
|
|
PaddingMM: req.PaddingMM,
|
|
})
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, saved)
|
|
}
|
|
}
|
|
|
|
func deleteItem(s *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
|
|
}
|
|
if err := s.Delete(id); err != nil {
|
|
writeError(w, http.StatusNotFound, err)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
}
|
|
|
|
func serveItemSVG(s *store.ItemStore) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
id := r.PathValue("id")
|
|
path, err := s.SVGPath(id)
|
|
if err != nil {
|
|
writeError(w, http.StatusNotFound, err)
|
|
return
|
|
}
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "image/svg+xml")
|
|
w.Header().Set("Cache-Control", "no-cache")
|
|
_, _ = w.Write(data)
|
|
}
|
|
}
|
|
|
|
func writeJSON(w http.ResponseWriter, code int, v any) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(code)
|
|
_ = json.NewEncoder(w).Encode(v)
|
|
}
|
|
|
|
func writeError(w http.ResponseWriter, code int, err error) {
|
|
writeJSON(w, code, map[string]string{"error": err.Error()})
|
|
}
|