From d3d59b4e45207d1b0774157a9d778dab65d8c98a Mon Sep 17 00:00:00 2001 From: Daniel Arroyo Date: Mon, 6 Jul 2026 23:31:03 -0400 Subject: [PATCH] fix: nil slice -> empty slice in list API responses Backend: - ListStorageJobs, ListSambaShares, ListNFSExports, ListUsers, ListDirty, ListApplyLog, ListWatchedMounts: initialize with make([]T, 0) instead of var x []T to avoid JSON null on empty. Fixes "Cannot read properties of null" crash on /storage. Frontend: - Storage.tsx: defensive setJobs(res.jobs ?? []) to guard against API returning null. Tests: - Add TestListStorageJobsEmptyReturnsSlice. Version: 0.7.1 --- Makefile | 2 +- internal/db/queries.go | 6 +++--- internal/db/queries_nfs.go | 2 +- internal/db/queries_storage.go | 2 +- internal/db/queries_storage_test.go | 27 +++++++++++++++++++++++++++ internal/db/queries_users.go | 2 +- internal/db/queries_watched.go | 2 +- web/src/pages/Storage.tsx | 2 +- web/tsconfig.tsbuildinfo | 1 + 9 files changed, 37 insertions(+), 9 deletions(-) create mode 100644 internal/db/queries_storage_test.go create mode 100644 web/tsconfig.tsbuildinfo diff --git a/Makefile b/Makefile index b247914..7bf3500 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ BINARY=nasctl -VERSION?=0.7.0 +VERSION?=0.7.1 GO?=go LDFLAGS=-s -w -X github.com/darroyo/nasctl/internal/web.Version=$(VERSION) -X github.com/darroyo/nasctl/internal/web.Commit=$(shell git rev-parse --short HEAD 2>/dev/null || echo unknown) BUILD_FLAGS=CGO_ENABLED=0 diff --git a/internal/db/queries.go b/internal/db/queries.go index 0f16521..30bf17d 100644 --- a/internal/db/queries.go +++ b/internal/db/queries.go @@ -114,7 +114,7 @@ func (d *DB) ListDirty() ([]DirtyModule, error) { } defer rows.Close() - var modules []DirtyModule + modules := make([]DirtyModule, 0) for rows.Next() { var module DirtyModule var markedAt string @@ -158,7 +158,7 @@ func (d *DB) ListApplyLog(limit int) ([]ApplyLogEntry, error) { } defer rows.Close() - var entries []ApplyLogEntry + entries := make([]ApplyLogEntry, 0) for rows.Next() { var entry ApplyLogEntry var success int @@ -182,7 +182,7 @@ func (d *DB) ListSambaShares() ([]SambaShare, error) { } defer rows.Close() - var shares []SambaShare + shares := make([]SambaShare, 0) for rows.Next() { share, err := scanSambaShare(rows) if err != nil { diff --git a/internal/db/queries_nfs.go b/internal/db/queries_nfs.go index 96c5f20..2a65cf1 100644 --- a/internal/db/queries_nfs.go +++ b/internal/db/queries_nfs.go @@ -77,7 +77,7 @@ func (d *DB) ListNFSExports() ([]NFSExport, error) { } defer rows.Close() - var exports []NFSExport + exports := make([]NFSExport, 0) for rows.Next() { export, err := scanNFSExport(rows) if err != nil { diff --git a/internal/db/queries_storage.go b/internal/db/queries_storage.go index 8941905..a164099 100644 --- a/internal/db/queries_storage.go +++ b/internal/db/queries_storage.go @@ -170,7 +170,7 @@ func (d *DB) ListStorageJobs(limit int) ([]StorageJob, error) { } defer rows.Close() - var jobs []StorageJob + jobs := make([]StorageJob, 0) for rows.Next() { j, err := scanStorageJob(rows) if err != nil { diff --git a/internal/db/queries_storage_test.go b/internal/db/queries_storage_test.go new file mode 100644 index 0000000..03ff07f --- /dev/null +++ b/internal/db/queries_storage_test.go @@ -0,0 +1,27 @@ +package db + +import ( + "testing" +) + +func TestListStorageJobsEmptyReturnsSlice(t *testing.T) { + d, err := Open(":memory:") + if err != nil { + t.Fatalf("open db: %v", err) + } + defer d.Close() + if err := d.Migrate(); err != nil { + t.Fatalf("migrate: %v", err) + } + + jobs, err := d.ListStorageJobs(50) + if err != nil { + t.Fatalf("ListStorageJobs: %v", err) + } + if jobs == nil { + t.Error("ListStorageJobs returned nil, want empty slice") + } + if len(jobs) != 0 { + t.Errorf("len(jobs) = %d, want 0", len(jobs)) + } +} diff --git a/internal/db/queries_users.go b/internal/db/queries_users.go index a95d4a8..da59e38 100644 --- a/internal/db/queries_users.go +++ b/internal/db/queries_users.go @@ -53,7 +53,7 @@ func (d *DB) ListUsers() ([]User, error) { } defer rows.Close() - var users []User + users := make([]User, 0) for rows.Next() { user, err := scanUser(rows) if err != nil { diff --git a/internal/db/queries_watched.go b/internal/db/queries_watched.go index bf635a4..ca17218 100644 --- a/internal/db/queries_watched.go +++ b/internal/db/queries_watched.go @@ -24,7 +24,7 @@ func (d *DB) ListWatchedMounts() ([]WatchedMount, error) { } defer rows.Close() - var mounts []WatchedMount + mounts := make([]WatchedMount, 0) for rows.Next() { m, err := scanWatchedMount(rows) if err != nil { diff --git a/web/src/pages/Storage.tsx b/web/src/pages/Storage.tsx index f376c8a..0fa61f5 100644 --- a/web/src/pages/Storage.tsx +++ b/web/src/pages/Storage.tsx @@ -71,7 +71,7 @@ export default function Storage() { async function loadJobs() { try { const res = await api.listStorageJobs(50); - setJobs(res.jobs); + setJobs(res.jobs ?? []); } catch (e) { console.error(e); } diff --git a/web/tsconfig.tsbuildinfo b/web/tsconfig.tsbuildinfo new file mode 100644 index 0000000..d9e6c9f --- /dev/null +++ b/web/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/app.tsx","./src/dirtycontext.tsx","./src/api.ts","./src/main.tsx","./src/components/dirtybanner.tsx","./src/components/filebrowsermodal.tsx","./src/components/layout.tsx","./src/components/modal.tsx","./src/components/pathfield.tsx","./src/pages/dashboard.tsx","./src/pages/files.tsx","./src/pages/log.tsx","./src/pages/login.tsx","./src/pages/nfs.tsx","./src/pages/samba.tsx","./src/pages/settings.tsx","./src/pages/storage.tsx","./src/pages/users.tsx"],"version":"5.9.3"} \ No newline at end of file