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.
92 lines
2.2 KiB
Go
92 lines
2.2 KiB
Go
// Package web implements the HTTP server: routing, session middleware,
|
|
// template rendering, and all page/API handlers for inventree.
|
|
package web
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"log/slog"
|
|
"net/http"
|
|
"time"
|
|
|
|
"inventree/internal/assets"
|
|
"inventree/internal/auth"
|
|
"inventree/internal/folders"
|
|
"inventree/internal/itemtypes"
|
|
"inventree/internal/settings"
|
|
"inventree/internal/statuses"
|
|
"inventree/internal/users"
|
|
)
|
|
|
|
// App holds every dependency HTTP handlers need: data stores, templates, and
|
|
// server-wide configuration.
|
|
type App struct {
|
|
DB *sql.DB
|
|
|
|
Auth *auth.Store
|
|
Users *users.Store
|
|
Folders *folders.Store
|
|
Assets *assets.Store
|
|
ItemTypes *itemtypes.Store
|
|
Statuses *statuses.Store
|
|
Settings *settings.Store
|
|
|
|
templates *templateSet
|
|
Log *slog.Logger
|
|
|
|
// SecureCookies controls the Secure flag on the session cookie; set
|
|
// true when serving over HTTPS.
|
|
SecureCookies bool
|
|
}
|
|
|
|
// NewApp wires all stores together from a shared database handle.
|
|
func NewApp(db *sql.DB, log *slog.Logger, secureCookies bool) (*App, error) {
|
|
userStore := users.NewStore(db)
|
|
folderStore := folders.NewStore(db)
|
|
settingsStore := settings.NewStore(db)
|
|
|
|
app := &App{
|
|
DB: db,
|
|
Users: userStore,
|
|
Folders: folderStore,
|
|
ItemTypes: itemtypes.NewStore(db),
|
|
Statuses: statuses.NewStore(db),
|
|
Settings: settingsStore,
|
|
Assets: assets.NewStore(db, settingsStore, folderStore),
|
|
Auth: auth.NewStore(db, userStore),
|
|
Log: log,
|
|
SecureCookies: secureCookies,
|
|
}
|
|
|
|
tmpl, err := loadTemplates()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
app.templates = tmpl
|
|
|
|
return app, nil
|
|
}
|
|
|
|
// StartSessionJanitor periodically removes expired sessions until ctx (via
|
|
// the returned stop channel) signals shutdown.
|
|
func (a *App) StartSessionJanitor(stop <-chan struct{}) {
|
|
ticker := time.NewTicker(1 * time.Hour)
|
|
go func() {
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
if err := a.Auth.CleanExpired(context.Background()); err != nil {
|
|
a.Log.Warn("session cleanup failed", "error", err)
|
|
}
|
|
case <-stop:
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
func writeErr(w http.ResponseWriter, status int, msg string) {
|
|
http.Error(w, msg, status)
|
|
}
|