Added Model Orders and Picture Upload

This commit is contained in:
2026-05-26 17:47:54 +02:00
parent aeeefc89c1
commit 9467ff5f6b
6 changed files with 856 additions and 6 deletions
+2
View File
@@ -16,6 +16,7 @@ func NewHandler() http.Handler {
plates := store.NewPlateStore("")
items := store.NewItemStore("")
configs := store.NewConfigurationStore("")
orders := store.NewOrderStore("")
mux := http.NewServeMux()
mux.HandleFunc("GET /health", health)
@@ -30,6 +31,7 @@ func NewHandler() http.Handler {
mux.HandleFunc("DELETE /items/{id}", deleteItem(items))
mux.HandleFunc("GET /items/{id}/svg", serveItemSVG(items))
registerConfigurationRoutes(mux, configs, plates, items)
registerOrderRoutes(mux, orders)
return withCORS(mux)
}
+213
View File
@@ -0,0 +1,213 @@
package api
import (
"encoding/json"
"errors"
"io"
"mime"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"printer.backend/internal/model"
"printer.backend/internal/store"
)
func registerOrderRoutes(mux *http.ServeMux, orders *store.OrderStore) {
mux.HandleFunc("GET /orders", listOrders(orders))
mux.HandleFunc("POST /orders", saveOrder(orders))
mux.HandleFunc("GET /orders/{id}", getOrder(orders))
mux.HandleFunc("PUT /orders/{id}", updateOrder(orders))
mux.HandleFunc("DELETE /orders/{id}", deleteOrder(orders))
mux.HandleFunc("POST /orders/{id}/images", addOrderImages(orders))
mux.HandleFunc("DELETE /orders/{id}/images/{imageId}", deleteOrderImage(orders))
mux.HandleFunc("GET /orders/{id}/images/{imageId}", serveOrderImage(orders))
}
func listOrders(s *store.OrderStore) 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.Order{}
}
for i := range list {
if list[i].Images == nil {
list[i].Images = []model.OrderImage{}
}
}
writeJSON(w, http.StatusOK, list)
}
}
type saveOrderRequest struct {
Name string `json:"name"`
}
func saveOrder(s *store.OrderStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req saveOrderRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, err)
return
}
saved, err := s.Save(model.Order{Name: req.Name})
if err != nil {
writeError(w, http.StatusInternalServerError, err)
return
}
writeJSON(w, http.StatusCreated, saved)
}
}
func getOrder(s *store.OrderStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
o, err := s.Get(id)
if err != nil {
writeError(w, http.StatusNotFound, err)
return
}
writeJSON(w, http.StatusOK, o)
}
}
func updateOrder(s *store.OrderStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
var req saveOrderRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, err)
return
}
saved, err := s.Update(id, model.Order{Name: req.Name})
if err != nil {
writeError(w, http.StatusNotFound, err)
return
}
writeJSON(w, http.StatusOK, saved)
}
}
func deleteOrder(s *store.OrderStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
if err := s.Delete(id); err != nil {
writeError(w, http.StatusNotFound, err)
return
}
w.WriteHeader(http.StatusNoContent)
}
}
func addOrderImages(s *store.OrderStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
orderID := r.PathValue("id")
if _, err := s.Get(orderID); err != nil {
writeError(w, http.StatusNotFound, err)
return
}
if err := r.ParseMultipartForm(32 << 20); err != nil {
writeError(w, http.StatusBadRequest, err)
return
}
var files []*multipart.FileHeader
if r.MultipartForm != nil && len(r.MultipartForm.File["images"]) > 0 {
files = r.MultipartForm.File["images"]
} else if _, fh, err := r.FormFile("image"); err == nil {
files = []*multipart.FileHeader{fh}
} else {
writeError(w, http.StatusBadRequest, errors.New("image or images field required"))
return
}
added := make([]model.OrderImage, 0, len(files))
for _, fh := range files {
data, contentType, err := readUpload(fh)
if err != nil {
writeError(w, http.StatusBadRequest, err)
return
}
img, err := s.AddImage(orderID, fh.Filename, data, contentType)
if err != nil {
writeError(w, http.StatusBadRequest, err)
return
}
added = append(added, img)
}
order, err := s.Get(orderID)
if err != nil {
writeError(w, http.StatusInternalServerError, err)
return
}
writeJSON(w, http.StatusCreated, map[string]any{
"added": added,
"order": order,
})
}
}
func readUpload(fh *multipart.FileHeader) ([]byte, string, error) {
rc, err := fh.Open()
if err != nil {
return nil, "", err
}
defer rc.Close()
const maxImageSize = 32 << 20
data, err := io.ReadAll(io.LimitReader(rc, maxImageSize+1))
if err != nil {
return nil, "", err
}
if len(data) > maxImageSize {
return nil, "", errors.New("image too large")
}
contentType := http.DetectContentType(data)
return data, contentType, nil
}
func deleteOrderImage(s *store.OrderStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
orderID := r.PathValue("id")
imageID := r.PathValue("imageId")
if err := s.DeleteImage(orderID, imageID); err != nil {
writeError(w, http.StatusNotFound, err)
return
}
w.WriteHeader(http.StatusNoContent)
}
}
func serveOrderImage(s *store.OrderStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
orderID := r.PathValue("id")
imageID := r.PathValue("imageId")
path, img, err := s.ImagePath(orderID, imageID)
if err != nil {
writeError(w, http.StatusNotFound, err)
return
}
data, err := os.ReadFile(path)
if err != nil {
writeError(w, http.StatusInternalServerError, err)
return
}
contentType := img.ContentType
if contentType == "" {
contentType = mime.TypeByExtension(filepath.Ext(path))
}
if contentType == "" || contentType == "application/octet-stream" {
contentType = http.DetectContentType(data)
}
w.Header().Set("Content-Type", contentType)
w.Header().Set("Cache-Control", "no-cache")
_, _ = w.Write(data)
}
}