spread

https://git.tonybtw.com/spread.git git://git.tonybtw.com/spread.git
2,290 bytes raw
1
package spread
2
3
import (
4
	"encoding/json"
5
	"io"
6
	"net/http"
7
	"path/filepath"
8
	"strings"
9
)
10
11
const maxUpload = 8 << 20 // 8 MiB
12
13
// Handler is the shared HTTP handler for POST /api/analyze. It accepts either
14
// a multipart form (field "bom") or a raw body with ?filename=. It is used
15
// both by the self-host server and the Vercel serverless function.
16
func Handler(w http.ResponseWriter, r *http.Request) {
17
	if r.Method == http.MethodOptions {
18
		writeCORS(w)
19
		w.WriteHeader(http.StatusNoContent)
20
		return
21
	}
22
	if r.Method != http.MethodPost {
23
		writeErr(w, http.StatusMethodNotAllowed, "POST a BOM file to this endpoint")
24
		return
25
	}
26
	writeCORS(w)
27
28
	filename, data, err := readUpload(r)
29
	if err != nil {
30
		writeErr(w, http.StatusBadRequest, err.Error())
31
		return
32
	}
33
34
	items, err := ParseBOM(filename, data)
35
	if err != nil {
36
		writeErr(w, http.StatusUnprocessableEntity, err.Error())
37
		return
38
	}
39
40
	rep := Analyze(bomRef(filename), items)
41
	w.Header().Set("Content-Type", "application/json")
42
	_ = json.NewEncoder(w).Encode(rep)
43
}
44
45
func readUpload(r *http.Request) (filename string, data []byte, err error) {
46
	ct := r.Header.Get("Content-Type")
47
	if strings.HasPrefix(ct, "multipart/form-data") {
48
		if err = r.ParseMultipartForm(maxUpload); err != nil {
49
			return "", nil, err
50
		}
51
		f, hdr, ferr := r.FormFile("bom")
52
		if ferr != nil {
53
			return "", nil, ferr
54
		}
55
		defer f.Close()
56
		data, err = io.ReadAll(io.LimitReader(f, maxUpload))
57
		return hdr.Filename, data, err
58
	}
59
	// raw body
60
	data, err = io.ReadAll(io.LimitReader(r.Body, maxUpload))
61
	filename = r.URL.Query().Get("filename")
62
	if filename == "" {
63
		filename = "upload.csv"
64
	}
65
	return filename, data, err
66
}
67
68
func bomRef(filename string) string {
69
	base := strings.TrimSuffix(filepath.Base(filename), filepath.Ext(filename))
70
	base = strings.TrimSpace(base)
71
	if base == "" {
72
		return "BOM-UPLOAD"
73
	}
74
	return base
75
}
76
77
func writeCORS(w http.ResponseWriter) {
78
	w.Header().Set("Access-Control-Allow-Origin", "*")
79
	w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS")
80
	w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
81
}
82
83
func writeErr(w http.ResponseWriter, code int, msg string) {
84
	w.Header().Set("Content-Type", "application/json")
85
	w.WriteHeader(code)
86
	_ = json.NewEncoder(w).Encode(map[string]string{"error": msg})
87
}