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.
This commit is contained in:
Owen
2026-08-17 13:34:40 -05:00
commit 218687a1ee
71 changed files with 8102 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
package assets
import (
"context"
"inventree/internal/models"
)
// Search performs a global, case-insensitive substring search across asset
// ID, name, notes, folder name, item type name, assigned username, and
// custom field values, returning fully-loaded assets (with folder path and
// custom fields) ready for display with breadcrumbs.
func (s *Store) Search(ctx context.Context, query string, limit int) ([]models.Asset, error) {
if query == "" {
return nil, nil
}
if limit <= 0 || limit > 200 {
limit = 50
}
like := "%" + query + "%"
rows, err := s.db.QueryContext(ctx, `
SELECT DISTINCT a.id
FROM assets a
LEFT JOIN folders fo ON fo.id = a.folder_id
LEFT JOIN users au ON au.id = a.assigned_user_id
JOIN item_types it ON it.id = a.item_type_id
WHERE a.asset_id LIKE ? COLLATE NOCASE
OR a.name LIKE ? COLLATE NOCASE
OR a.notes LIKE ? COLLATE NOCASE
OR fo.name LIKE ? COLLATE NOCASE
OR it.name LIKE ? COLLATE NOCASE
OR au.username LIKE ? COLLATE NOCASE
OR EXISTS (SELECT 1 FROM custom_field_values v WHERE v.asset_id = a.id AND v.value LIKE ? COLLATE NOCASE)
ORDER BY a.name COLLATE NOCASE ASC
LIMIT ?`,
like, like, like, like, like, like, like, limit)
if err != nil {
return nil, err
}
var ids []int64
for rows.Next() {
var id int64
if err := rows.Scan(&id); err != nil {
rows.Close()
return nil, err
}
ids = append(ids, id)
}
rows.Close()
if err := rows.Err(); err != nil {
return nil, err
}
var out []models.Asset
for _, id := range ids {
a, err := s.Get(ctx, id)
if err != nil {
return nil, err
}
out = append(out, *a)
}
return out, nil
}