Files
vibetree/internal/web/handlers_assets.go
T
Owen 218687a1ee Initial implementation of inventree
A small IT asset inventory system built around a nested folder tree,
matching the project brief: Go + html/template + Bootstrap 5 + vanilla JS,
SQLite storage (WAL, FKs, migrations), embedded templates/static assets,
single compiled binary.

- AJAX-driven inventory tree: lazy-loaded folder expansion with per-user
  persisted expand state, drag-and-drop move for folders/assets, safe
  delete-with-relocate-or-cascade confirmation, search with breadcrumbs.
- Assets: CRUD, duplicate, move, auto-generated IT-000001-style IDs,
  CSV import (with optional folder auto-create) and export.
- Admin-defined item types with custom fields (text/long_text/number/date/
  boolean/dropdown/url/ip_address).
- Configurable statuses, session-based auth (bcrypt, secure cookies),
  admin/user roles, user management.
- CLI: serve, version, database migrate, database backup; /healthz;
  graceful shutdown; Dockerfile/docker-compose/systemd unit; Go test suite.
2026-08-17 13:34:40 -05:00

130 lines
3.6 KiB
Go

package web
import (
"net/http"
"strconv"
"inventree/internal/models"
)
type assetRequest struct {
AssetTag string `json:"asset_id"`
Name string `json:"name"`
ItemTypeID int64 `json:"item_type_id"`
FolderID *int64 `json:"folder_id"`
StatusID int64 `json:"status_id"`
AssignedUserID *int64 `json:"assigned_user_id"`
Notes string `json:"notes"`
Fields map[string]string `json:"fields"`
}
func (req assetRequest) fieldValues() map[int64]string {
out := make(map[int64]string, len(req.Fields))
for k, v := range req.Fields {
id, err := strconv.ParseInt(k, 10, 64)
if err != nil {
continue
}
out[id] = v
}
return out
}
func (a *App) handleAssetCreate(w http.ResponseWriter, r *http.Request) {
var req assetRequest
if err := readJSON(r, &req); err != nil {
jsonErr(w, http.StatusBadRequest, err)
return
}
asset := &models.Asset{
AssetTag: req.AssetTag, Name: req.Name, ItemTypeID: req.ItemTypeID, FolderID: req.FolderID,
StatusID: req.StatusID, AssignedUserID: req.AssignedUserID, Notes: req.Notes,
}
if err := a.Assets.Create(r.Context(), asset, req.fieldValues()); err != nil {
jsonErr(w, http.StatusBadRequest, err)
return
}
jsonOK(w, map[string]any{"id": asset.ID, "asset_id": asset.AssetTag, "folder_id": asset.FolderID})
}
func (a *App) handleAssetUpdate(w http.ResponseWriter, r *http.Request) {
id, err := pathID(r, "id")
if err != nil {
jsonErr(w, http.StatusBadRequest, err)
return
}
var req assetRequest
if err := readJSON(r, &req); err != nil {
jsonErr(w, http.StatusBadRequest, err)
return
}
existing, err := a.Assets.Get(r.Context(), id)
if err != nil {
jsonErr(w, http.StatusNotFound, err)
return
}
asset := &models.Asset{
ID: id, AssetTag: req.AssetTag, Name: req.Name, ItemTypeID: existing.ItemTypeID, FolderID: req.FolderID,
StatusID: req.StatusID, AssignedUserID: req.AssignedUserID, Notes: req.Notes,
}
if err := a.Assets.Update(r.Context(), asset, req.fieldValues()); err != nil {
jsonErr(w, http.StatusBadRequest, err)
return
}
jsonOK(w, map[string]any{"id": asset.ID, "asset_id": asset.AssetTag, "folder_id": asset.FolderID})
}
func (a *App) handleAssetDelete(w http.ResponseWriter, r *http.Request) {
id, err := pathID(r, "id")
if err != nil {
jsonErr(w, http.StatusBadRequest, err)
return
}
asset, err := a.Assets.Get(r.Context(), id)
if err != nil {
jsonErr(w, http.StatusNotFound, err)
return
}
if err := a.Assets.Delete(r.Context(), id); err != nil {
jsonErr(w, http.StatusInternalServerError, err)
return
}
jsonOK(w, map[string]any{"id": id, "folder_id": asset.FolderID})
}
func (a *App) handleAssetDuplicate(w http.ResponseWriter, r *http.Request) {
id, err := pathID(r, "id")
if err != nil {
jsonErr(w, http.StatusBadRequest, err)
return
}
copyAsset, err := a.Assets.Duplicate(r.Context(), id)
if err != nil {
jsonErr(w, http.StatusBadRequest, err)
return
}
jsonOK(w, map[string]any{"id": copyAsset.ID, "asset_id": copyAsset.AssetTag, "folder_id": copyAsset.FolderID})
}
type assetMoveRequest struct {
FolderID *int64 `json:"folder_id"`
}
func (a *App) handleAssetMove(w http.ResponseWriter, r *http.Request) {
id, err := pathID(r, "id")
if err != nil {
jsonErr(w, http.StatusBadRequest, err)
return
}
var req assetMoveRequest
if err := readJSON(r, &req); err != nil {
jsonErr(w, http.StatusBadRequest, err)
return
}
if err := a.Assets.Move(r.Context(), id, req.FolderID); err != nil {
jsonErr(w, http.StatusInternalServerError, err)
return
}
jsonOK(w, map[string]any{"id": id, "folder_id": req.FolderID})
}