214 lines
5.5 KiB
Go

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)
}
}