From 999bfa1026b03adb8de7bf2291dc419507454910 Mon Sep 17 00:00:00 2001 From: "Othman H. Suseno" Date: Sat, 13 Dec 2025 18:17:36 +0000 Subject: [PATCH] Add audit and snapshots tables to SQLite migrations; enhance audit_events table handling in migrations.go. Refactor admin routes in router.go for improved organization and clarity. --- internal/http/router.go | 28 ++++++++++++++------------ internal/infra/sqlite/db/migrations.go | 21 +++++++++++++++++-- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/internal/http/router.go b/internal/http/router.go index b4026f7..50c8eb7 100644 --- a/internal/http/router.go +++ b/internal/http/router.go @@ -64,21 +64,23 @@ func RegisterRoutes(r *chi.Mux, app *App) { r.With(RequirePermission(app, "iscsi.portal.create")).Post("/api/iscsi/add_portal", app.AddISCSIPortalHandler) r.With(RequirePermission(app, "iscsi.initiator.create")).Post("/api/iscsi/add_initiator", app.AddISCSIInitiatorHandler) - // Admin routes - r.Route("/admin", func(r chi.Router) { + // Admin routes - users + r.Route("/admin/users", func(r chi.Router) { r.Use(RequirePermission(app, "users.manage")) - r.Get("/users", app.UsersHandler) - r.Get("/hx/users", app.HXUsersHandler) - r.Post("/users/create", app.CreateUserHandler) - r.Post("/users/{id}/delete", app.DeleteUserHandler) - r.Post("/users/{id}/roles", app.UpdateUserRolesHandler) - + r.Get("/", app.UsersHandler) + r.Get("/hx", app.HXUsersHandler) + r.Post("/create", app.CreateUserHandler) + r.Post("/{id}/delete", app.DeleteUserHandler) + r.Post("/{id}/roles", app.UpdateUserRolesHandler) + }) + // Admin routes - roles + r.Route("/admin/roles", func(r chi.Router) { r.Use(RequirePermission(app, "roles.manage")) - r.Get("/roles", app.RolesHandler) - r.Get("/hx/roles", app.HXRolesHandler) - r.Post("/roles/create", app.CreateRoleHandler) - r.Post("/roles/{id}/delete", app.DeleteRoleHandler) - r.Post("/roles/{id}/permissions", app.UpdateRolePermissionsHandler) + r.Get("/", app.RolesHandler) + r.Get("/hx", app.HXRolesHandler) + r.Post("/create", app.CreateRoleHandler) + r.Post("/{id}/delete", app.DeleteRoleHandler) + r.Post("/{id}/permissions", app.UpdateRolePermissionsHandler) }) r.Get("/static/*", StaticHandler) diff --git a/internal/infra/sqlite/db/migrations.go b/internal/infra/sqlite/db/migrations.go index c95d0fc..0af78dc 100644 --- a/internal/infra/sqlite/db/migrations.go +++ b/internal/infra/sqlite/db/migrations.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "log" + "strings" "github.com/example/storage-appliance/internal/auth" ) @@ -27,6 +28,10 @@ func MigrateAndSeed(ctx context.Context, db *sql.DB) error { `CREATE TABLE IF NOT EXISTS iscsi_portals (id TEXT PRIMARY KEY, target_id TEXT NOT NULL, address TEXT NOT NULL, port INTEGER DEFAULT 3260, created_at DATETIME DEFAULT CURRENT_TIMESTAMP);`, `CREATE TABLE IF NOT EXISTS iscsi_initiators (id TEXT PRIMARY KEY, target_id TEXT NOT NULL, initiator_iqn TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP);`, `CREATE TABLE IF NOT EXISTS iscsi_luns (id TEXT PRIMARY KEY, target_id TEXT NOT NULL, lun_id INTEGER NOT NULL, zvol TEXT NOT NULL, size INTEGER, blocksize INTEGER, mapped INTEGER DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP);`, + // Audit and snapshots tables + `CREATE TABLE IF NOT EXISTS audit_events (id TEXT PRIMARY KEY, ts DATETIME DEFAULT CURRENT_TIMESTAMP, user_id TEXT, action TEXT, resource_type TEXT, resource_id TEXT, success INTEGER DEFAULT 1, details TEXT, actor TEXT, resource TEXT, payload_hash TEXT, result TEXT, client_ip TEXT);`, + `CREATE TABLE IF NOT EXISTS datasets (name TEXT PRIMARY KEY, pool TEXT, type TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP);`, + `CREATE TABLE IF NOT EXISTS snapshots (id TEXT PRIMARY KEY, dataset TEXT, name TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP);`, // RBAC tables `CREATE TABLE IF NOT EXISTS roles (id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, description TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP);`, `CREATE TABLE IF NOT EXISTS permissions (id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, description TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP);`, @@ -44,6 +49,7 @@ func MigrateAndSeed(ctx context.Context, db *sql.DB) error { } // Enhance audit_events table if needed (add missing columns) + // Note: audit_events table is now created with all columns above, but this handles upgrades enhanceAuditTable(ctx, tx) // Seed default roles and permissions @@ -78,6 +84,14 @@ func MigrateAndSeed(ctx context.Context, db *sql.DB) error { } func enhanceAuditTable(ctx context.Context, tx *sql.Tx) { + // Check if table exists first + var tableExists int + err := tx.QueryRowContext(ctx, `SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='audit_events'`).Scan(&tableExists) + if err != nil || tableExists == 0 { + // Table doesn't exist, it will be created above with all columns + return + } + // Check if columns exist and add them if missing // SQLite doesn't support IF NOT EXISTS for ALTER TABLE, so we'll try-catch columns := []struct { @@ -94,8 +108,11 @@ func enhanceAuditTable(ctx context.Context, tx *sql.Tx) { for _, col := range columns { _, err := tx.ExecContext(ctx, col.stmt) if err != nil { - // Column might already exist, ignore error - log.Printf("Note: %s column may already exist: %v", col.name, err) + // Column might already exist, ignore error silently + // Only log if it's not a "duplicate column" error + if !strings.Contains(err.Error(), "duplicate column") && !strings.Contains(err.Error(), "no such table") { + log.Printf("Note: %s column may already exist: %v", col.name, err) + } } } }