| 1 |
package app
|
| 2 |
|
| 3 |
import (
|
| 4 |
"io"
|
| 5 |
"net/http"
|
| 6 |
"path/filepath"
|
| 7 |
"strconv"
|
| 8 |
"strings"
|
| 9 |
|
| 10 |
"spread/internal/spread"
|
| 11 |
)
|
| 12 |
|
| 13 |
const maxUpload = 8 << 20
|
| 14 |
|
| 15 |
// handleAppAnalyze accepts a customer's uploaded BOM, re-quotes it against the
|
| 16 |
// live DB catalog, persists it under their subscriber, and shows the result.
|
| 17 |
func (a *App) handleAppAnalyze(w http.ResponseWriter, r *http.Request) {
|
| 18 |
u, s, _ := current(r)
|
| 19 |
ctx := r.Context()
|
| 20 |
|
| 21 |
if err := r.ParseMultipartForm(maxUpload); err != nil {
|
| 22 |
a.redirect(w, r, flash("/app", "err", "Could not read the upload."))
|
| 23 |
return
|
| 24 |
}
|
| 25 |
f, hdr, err := r.FormFile("bom")
|
| 26 |
if err != nil {
|
| 27 |
a.redirect(w, r, flash("/app", "err", "Choose a BOM file to upload."))
|
| 28 |
return
|
| 29 |
}
|
| 30 |
defer f.Close()
|
| 31 |
data, _ := io.ReadAll(io.LimitReader(f, maxUpload))
|
| 32 |
|
| 33 |
items, err := spread.ParseBOM(hdr.Filename, data)
|
| 34 |
if err != nil {
|
| 35 |
a.redirect(w, r, flash("/app", "err", err.Error()))
|
| 36 |
return
|
| 37 |
}
|
| 38 |
|
| 39 |
cat, err := a.catalogSource(ctx)
|
| 40 |
if err != nil {
|
| 41 |
http.Error(w, err.Error(), http.StatusInternalServerError)
|
| 42 |
return
|
| 43 |
}
|
| 44 |
|
| 45 |
ref := strings.TrimSpace(strings.TrimSuffix(filepath.Base(hdr.Filename), filepath.Ext(hdr.Filename)))
|
| 46 |
if ref == "" {
|
| 47 |
ref = "BOM"
|
| 48 |
}
|
| 49 |
rep := spread.AnalyzeWith(ref, items, cat)
|
| 50 |
|
| 51 |
id, err := a.saveBOM(ctx, s.ID, u.ID, rep, hdr.Filename)
|
| 52 |
if err != nil {
|
| 53 |
http.Error(w, err.Error(), http.StatusInternalServerError)
|
| 54 |
return
|
| 55 |
}
|
| 56 |
a.redirect(w, r, "/app/bom/"+strconv.FormatInt(id, 10))
|
| 57 |
}
|
| 58 |
|
| 59 |
// handleBOM shows one saved BOM. Customers see only their own; the internal
|
| 60 |
// subscriber can view any (admin BOM review).
|
| 61 |
func (a *App) handleBOM(w http.ResponseWriter, r *http.Request) {
|
| 62 |
u, s, _ := current(r)
|
| 63 |
id, _ := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
| 64 |
|
| 65 |
d, err := a.getBOM(r.Context(), id)
|
| 66 |
if err != nil {
|
| 67 |
http.Error(w, "BOM not found", http.StatusNotFound)
|
| 68 |
return
|
| 69 |
}
|
| 70 |
if !s.IsInternal && d.SubscriberID != s.ID {
|
| 71 |
http.Redirect(w, r, "/app", http.StatusSeeOther)
|
| 72 |
return
|
| 73 |
}
|
| 74 |
|
| 75 |
back := "/app"
|
| 76 |
if s.IsInternal {
|
| 77 |
back = "/admin/boms"
|
| 78 |
}
|
| 79 |
a.render(w, r, http.StatusOK, "bom", pageData{Title: d.Ref, User: u, Sub: s, BOM: &d, BackURL: back})
|
| 80 |
}
|
| 81 |
|
| 82 |
// handleBOMs is the admin BOM-review list across all subscribers.
|
| 83 |
func (a *App) handleBOMs(w http.ResponseWriter, r *http.Request) {
|
| 84 |
boms, err := a.listBOMs(r.Context(), nil)
|
| 85 |
if err != nil {
|
| 86 |
http.Error(w, err.Error(), http.StatusInternalServerError)
|
| 87 |
return
|
| 88 |
}
|
| 89 |
a.render(w, r, http.StatusOK, "boms", pageData{Title: "BOMs", BOMs: boms})
|
| 90 |
}
|