Files
vibetree/internal/web/helpers.go
T
Owen 42e7b2a21c Statuses import, an icon picker, folder-scoped CSV import, and site name
- Statuses can now be bulk-imported/updated via CSV (Name, Color columns;
  matches and updates existing statuses by name). New
  internal/statuses/csv.go, tested.
- Icon fields (folder create/rename, item type form) are now a searchable
  dropdown of the 50 most common Bootstrap Icons instead of a free-text
  box, via a small reusable .icon-picker widget in app.js/app.css.
- CSV asset import is now folder-scoped when triggered from the tree:
  right-click a folder (or its background) > Import CSV... imports every
  row directly into that folder, so the CSV needs no Folder column at
  all, and the option no longer appears at the top level. The original
  global, Folder-path-aware import (with auto-create-missing-folders)
  moved to a new admin-only Settings > Import / Export page, alongside
  the new Statuses import and format examples for both. assets.Import
  gained ImportOptions.TargetFolderID to drive the folder-scoped mode.
- Site Name (Settings > General) now actually renders in the navbar
  brand and <title>, via a per-App "siteName" template func bound in
  loadTemplates. The brand is plain text now, not a link to /inventory.
2026-08-17 15:57:59 -05:00

89 lines
2.0 KiB
Go

package web
import (
"encoding/json"
"net/http"
"strconv"
)
func pathID(r *http.Request, name string) (int64, error) {
return strconv.ParseInt(r.PathValue(name), 10, 64)
}
// queryOptionalID parses a query parameter as *int64; an empty or missing
// value returns nil (meaning "top level" / "unfiled" in folder/asset
// contexts).
func queryOptionalID(r *http.Request, name string) (*int64, error) {
v := r.URL.Query().Get(name)
if v == "" {
return nil, nil
}
id, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return nil, err
}
return &id, nil
}
// formOptionalID parses a POST form/multipart-form field (already parsed via
// ParseForm/ParseMultipartForm) as *int64; empty or missing returns nil.
func formOptionalID(r *http.Request, name string) (*int64, error) {
v := r.FormValue(name)
if v == "" {
return nil, nil
}
id, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return nil, err
}
return &id, nil
}
func queryInt64(r *http.Request, name string) int64 {
v := r.URL.Query().Get(name)
if v == "" {
return 0
}
id, _ := strconv.ParseInt(v, 10, 64)
return id
}
func queryInt(r *http.Request, name string, def int) int {
v := r.URL.Query().Get(name)
if v == "" {
return def
}
n, err := strconv.Atoi(v)
if err != nil {
return def
}
return n
}
type jsonEnvelope struct {
OK bool `json:"ok"`
Error string `json:"error,omitempty"`
Message string `json:"message,omitempty"`
Data any `json:"data,omitempty"`
}
func writeJSON(w http.ResponseWriter, status int, v jsonEnvelope) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(v)
}
func jsonOK(w http.ResponseWriter, data any) {
writeJSON(w, http.StatusOK, jsonEnvelope{OK: true, Data: data})
}
func jsonErr(w http.ResponseWriter, status int, err error) {
writeJSON(w, status, jsonEnvelope{OK: false, Error: err.Error()})
}
func readJSON(r *http.Request, v any) error {
dec := json.NewDecoder(r.Body)
dec.DisallowUnknownFields()
return dec.Decode(v)
}