From 761fbaed55b48c6d9f5375a95502acf228b0bf6f Mon Sep 17 00:00:00 2001 From: Nikolay Tatarinov Date: Wed, 4 Mar 2026 13:59:00 +0300 Subject: [PATCH] Refactor Docker and Makefile configurations for improved build processes - Updated docker-compose.tools.yml to mount source code at /src and set working directory for backend tools, ensuring proper Go module caching. - Modified Dockerfile.tools to install the latest golangci-lint version compatible with Go 1.24 and adjusted working directory for build-time operations. - Enhanced Makefile to build backend tools before running tests and linting, ensuring dependencies are up-to-date and improving overall workflow efficiency. - Refactored test and handler files to include error handling for database operations, enhancing reliability and debugging capabilities. --- Dockerfile.tools | 9 +- Makefile | 12 ++- docker-compose.tools.yml | 7 +- frontend-nuxt/eslint.config.mjs | 17 ++-- internal/app/handlers/client.go | 4 +- internal/app/handlers/handlers_test.go | 28 +++--- internal/app/handlers/tile.go | 8 +- internal/app/migrations.go | 4 +- internal/app/migrations_test.go | 20 ++-- internal/app/services/admin.go | 32 ++++-- internal/app/services/admin_test.go | 30 ++++-- internal/app/services/auth.go | 24 +++-- internal/app/services/auth_test.go | 18 ++-- internal/app/services/client.go | 50 +++++++--- internal/app/services/client_test.go | 6 +- internal/app/services/export.go | 42 +++++--- internal/app/services/export_test.go | 6 +- internal/app/services/map.go | 14 ++- internal/app/services/map_test.go | 54 ++++++---- internal/app/store/db_test.go | 132 +++++++++++++++++-------- 20 files changed, 352 insertions(+), 165 deletions(-) diff --git a/Dockerfile.tools b/Dockerfile.tools index 99e1575..8981599 100644 --- a/Dockerfile.tools +++ b/Dockerfile.tools @@ -1,7 +1,10 @@ # Backend tools image: Go + golangci-lint for test, fmt, lint. -# Source is mounted at /hnh-map at run time via docker-compose.tools.yml. +# Source is mounted at /src at run time; this WORKDIR is only for build-time go mod download. FROM golang:1.24-alpine -RUN go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.61.0 +# v1.64+ required for Go 1.24 (export data format); see https://github.com/golangci/golangci-lint/issues/5225 +RUN go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.64.0 -WORKDIR /hnh-map +WORKDIR /build +COPY go.mod go.sum ./ +RUN go mod download diff --git a/Makefile b/Makefile index 361d788..905af07 100644 --- a/Makefile +++ b/Makefile @@ -11,17 +11,23 @@ build: test: test-backend test-frontend test-backend: - $(TOOLS_COMPOSE) run --rm backend-tools go test ./... + $(TOOLS_COMPOSE) build backend-tools + $(TOOLS_COMPOSE) run --rm backend-tools sh -c "go mod download && go test ./..." test-frontend: + $(TOOLS_COMPOSE) build frontend-tools $(TOOLS_COMPOSE) run --rm frontend-tools sh -c "npm ci && npm test" lint: - $(TOOLS_COMPOSE) run --rm backend-tools golangci-lint run + $(TOOLS_COMPOSE) build backend-tools + $(TOOLS_COMPOSE) build frontend-tools + $(TOOLS_COMPOSE) run --rm backend-tools sh -c "go mod download && golangci-lint run" $(TOOLS_COMPOSE) run --rm frontend-tools sh -c "npm ci && npm run lint" fmt: - $(TOOLS_COMPOSE) run --rm backend-tools go fmt ./... + $(TOOLS_COMPOSE) build backend-tools + $(TOOLS_COMPOSE) build frontend-tools + $(TOOLS_COMPOSE) run --rm backend-tools sh -c "go mod download && go fmt ./..." $(TOOLS_COMPOSE) run --rm frontend-tools sh -c "npm ci && npm run format" generate-frontend: diff --git a/docker-compose.tools.yml b/docker-compose.tools.yml index e9e9127..27ed0f5 100644 --- a/docker-compose.tools.yml +++ b/docker-compose.tools.yml @@ -1,13 +1,18 @@ # One-off tools: test, lint, fmt. Use with: docker compose -f docker-compose.tools.yml run --rm # Source is mounted so commands run against current code. +# Backend: mount at /src so the image's /go module cache (from build) is not overwritten. services: backend-tools: build: context: . dockerfile: Dockerfile.tools + working_dir: /src + environment: + GOPATH: /go + GOMODCACHE: /go/pkg/mod volumes: - - .:/hnh-map + - .:/src # Default command; override when running (e.g. go test ./..., golangci-lint run). command: ["go", "test", "./..."] diff --git a/frontend-nuxt/eslint.config.mjs b/frontend-nuxt/eslint.config.mjs index e519ca1..a24b35b 100644 --- a/frontend-nuxt/eslint.config.mjs +++ b/frontend-nuxt/eslint.config.mjs @@ -1,11 +1,14 @@ // @ts-check import withNuxt from './.nuxt/eslint.config.mjs' -export default withNuxt({ - rules: { - '@typescript-eslint/no-explicit-any': 'warn', - '@typescript-eslint/no-unused-vars': 'warn', - '@typescript-eslint/consistent-type-imports': ['warn', { prefer: 'type-imports' }], - '@typescript-eslint/no-require-imports': 'off', +export default withNuxt( + { ignores: ['eslint.config.mjs'] }, + { + rules: { + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-unused-vars': 'warn', + '@typescript-eslint/consistent-type-imports': ['warn', { prefer: 'type-imports' }], + '@typescript-eslint/no-require-imports': 'off', + }, }, -}) +) diff --git a/internal/app/handlers/client.go b/internal/app/handlers/client.go index c844fac..164c9d2 100644 --- a/internal/app/handlers/client.go +++ b/internal/app/handlers/client.go @@ -66,7 +66,7 @@ func (h *Handlers) clientLocate(rw http.ResponseWriter, req *http.Request) { } rw.Header().Set("Content-Type", "text/plain") rw.WriteHeader(http.StatusOK) - rw.Write([]byte(result)) + _, _ = rw.Write([]byte(result)) } func (h *Handlers) clientGridUpdate(rw http.ResponseWriter, req *http.Request) { @@ -85,7 +85,7 @@ func (h *Handlers) clientGridUpdate(rw http.ResponseWriter, req *http.Request) { } rw.Header().Set("Content-Type", "application/json") rw.WriteHeader(http.StatusOK) - json.NewEncoder(rw).Encode(result.Response) + _ = json.NewEncoder(rw).Encode(result.Response) } func (h *Handlers) clientGridUpload(rw http.ResponseWriter, req *http.Request) { diff --git a/internal/app/handlers/handlers_test.go b/internal/app/handlers/handlers_test.go index 4320538..3b162ea 100644 --- a/internal/app/handlers/handlers_test.go +++ b/internal/app/handlers/handlers_test.go @@ -64,9 +64,11 @@ func (env *testEnv) createUser(t *testing.T, username, password string, auths ap hash, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.MinCost) u := app.User{Pass: hash, Auths: auths} raw, _ := json.Marshal(u) - env.st.Update(context.Background(), func(tx *bbolt.Tx) error { + if err := env.st.Update(context.Background(), func(tx *bbolt.Tx) error { return env.st.PutUser(tx, username, raw) - }) + }); err != nil { + t.Fatal(err) + } } func (env *testEnv) loginSession(t *testing.T, username string) string { @@ -90,7 +92,7 @@ func TestAPISetup_NoUsers(t *testing.T) { } var resp struct{ SetupRequired bool } - json.NewDecoder(rr.Body).Decode(&resp) + _ = json.NewDecoder(rr.Body).Decode(&resp) if !resp.SetupRequired { t.Fatal("expected setupRequired=true") } @@ -105,7 +107,7 @@ func TestAPISetup_WithUsers(t *testing.T) { env.h.APISetup(rr, req) var resp struct{ SetupRequired bool } - json.NewDecoder(rr.Body).Decode(&resp) + _ = json.NewDecoder(rr.Body).Decode(&resp) if resp.SetupRequired { t.Fatal("expected setupRequired=false with users") } @@ -196,7 +198,7 @@ func TestAPIMe_Authenticated(t *testing.T) { Username string `json:"username"` Auths []string `json:"auths"` } - json.NewDecoder(rr.Body).Decode(&resp) + _ = json.NewDecoder(rr.Body).Decode(&resp) if resp.Username != "alice" { t.Fatalf("expected alice, got %s", resp.Username) } @@ -245,7 +247,7 @@ func TestAPIMeTokens_Authenticated(t *testing.T) { t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String()) } var resp struct{ Tokens []string } - json.NewDecoder(rr.Body).Decode(&resp) + _ = json.NewDecoder(rr.Body).Decode(&resp) if len(resp.Tokens) != 1 { t.Fatalf("expected 1 token, got %d", len(resp.Tokens)) } @@ -396,7 +398,7 @@ func TestAdminSettings(t *testing.T) { DefaultHide bool `json:"defaultHide"` Title string `json:"title"` } - json.NewDecoder(rr3.Body).Decode(&resp) + _ = json.NewDecoder(rr3.Body).Decode(&resp) if resp.Prefix != "pfx" || !resp.DefaultHide || resp.Title != "New Title" { t.Fatalf("unexpected settings: %+v", resp) } @@ -493,7 +495,7 @@ func TestAPIGetChars_NoMarkersAuth(t *testing.T) { t.Fatalf("expected 200, got %d", rr.Code) } var chars []interface{} - json.NewDecoder(rr.Body).Decode(&chars) + _ = json.NewDecoder(rr.Body).Decode(&chars) if len(chars) != 0 { t.Fatal("expected empty array without markers auth") } @@ -511,7 +513,7 @@ func TestAPIGetMarkers_NoMarkersAuth(t *testing.T) { t.Fatalf("expected 200, got %d", rr.Code) } var markers []interface{} - json.NewDecoder(rr.Body).Decode(&markers) + _ = json.NewDecoder(rr.Body).Decode(&markers) if len(markers) != 0 { t.Fatal("expected empty array without markers auth") } @@ -583,7 +585,7 @@ func TestAdminUserByName(t *testing.T) { Username string `json:"username"` Auths []string `json:"auths"` } - json.NewDecoder(rr.Body).Decode(&resp) + _ = json.NewDecoder(rr.Body).Decode(&resp) if resp.Username != "bob" { t.Fatalf("expected bob, got %s", resp.Username) } @@ -613,9 +615,11 @@ func TestClientRouter_Locate(t *testing.T) { } gd := app.GridData{ID: "g1", Map: 1, Coord: app.Coord{X: 2, Y: 3}} raw, _ := json.Marshal(gd) - env.st.Update(ctx, func(tx *bbolt.Tx) error { + if err := env.st.Update(ctx, func(tx *bbolt.Tx) error { return env.st.PutGrid(tx, "g1", raw) - }) + }); err != nil { + t.Fatal(err) + } req := httptest.NewRequest(http.MethodGet, "/client/"+tokens[0]+"/locate?gridID=g1", nil) rr := httptest.NewRecorder() diff --git a/internal/app/handlers/tile.go b/internal/app/handlers/tile.go index ec3ac91..da7b1a4 100644 --- a/internal/app/handlers/tile.go +++ b/internal/app/handlers/tile.go @@ -55,7 +55,7 @@ func (h *Handlers) WatchGridUpdates(rw http.ResponseWriter, req *http.Request) { tileCache := []services.TileCache{} raw, _ := json.Marshal(tileCache) fmt.Fprint(rw, "data: ") - rw.Write(raw) + _, _ = rw.Write(raw) fmt.Fprint(rw, "\n\n") flusher.Flush() @@ -93,13 +93,13 @@ func (h *Handlers) WatchGridUpdates(rw http.ResponseWriter, req *http.Request) { } fmt.Fprint(rw, "event: merge\n") fmt.Fprint(rw, "data: ") - rw.Write(raw) + _, _ = rw.Write(raw) fmt.Fprint(rw, "\n\n") flusher.Flush() case <-ticker.C: raw, _ := json.Marshal(tileCache) fmt.Fprint(rw, "data: ") - rw.Write(raw) + _, _ = rw.Write(raw) fmt.Fprint(rw, "\n\n") tileCache = tileCache[:0] flusher.Flush() @@ -152,7 +152,7 @@ func (h *Handlers) GridTile(rw http.ResponseWriter, req *http.Request) { rw.Header().Set("Content-Type", "image/png") rw.Header().Set("Cache-Control", "private, max-age=3600") rw.WriteHeader(http.StatusOK) - rw.Write(transparentPNG) + _, _ = rw.Write(transparentPNG) return } diff --git a/internal/app/migrations.go b/internal/app/migrations.go index 95cb687..f58342a 100644 --- a/internal/app/migrations.go +++ b/internal/app/migrations.go @@ -172,7 +172,9 @@ var migrations = []func(tx *bbolt.Tx) error{ if err != nil { return err } - users.Put(k, raw) + if err := users.Put(k, raw); err != nil { + return err + } } return nil }) diff --git a/internal/app/migrations_test.go b/internal/app/migrations_test.go index fc7035d..49fc9bf 100644 --- a/internal/app/migrations_test.go +++ b/internal/app/migrations_test.go @@ -26,7 +26,7 @@ func TestRunMigrations_FreshDB(t *testing.T) { t.Fatalf("migrations failed on fresh DB: %v", err) } - db.View(func(tx *bbolt.Tx) error { + if err := db.View(func(tx *bbolt.Tx) error { b := tx.Bucket(store.BucketConfig) if b == nil { t.Fatal("expected config bucket after migrations") @@ -40,13 +40,15 @@ func TestRunMigrations_FreshDB(t *testing.T) { t.Fatalf("expected default title, got %s", title) } return nil - }) + }); err != nil { + t.Fatal(err) + } if tx, _ := db.Begin(false); tx != nil { if tx.Bucket(store.BucketOAuthStates) == nil { t.Fatal("expected oauth_states bucket after migrations") } - tx.Rollback() + _ = tx.Rollback() } } @@ -61,7 +63,7 @@ func TestRunMigrations_Idempotent(t *testing.T) { t.Fatalf("second run failed: %v", err) } - db.View(func(tx *bbolt.Tx) error { + if err := db.View(func(tx *bbolt.Tx) error { b := tx.Bucket(store.BucketConfig) if b == nil { t.Fatal("expected config bucket") @@ -71,7 +73,9 @@ func TestRunMigrations_Idempotent(t *testing.T) { t.Fatal("expected version key") } return nil - }) + }); err != nil { + t.Fatal(err) + } } func TestRunMigrations_SetsVersion(t *testing.T) { @@ -81,11 +85,13 @@ func TestRunMigrations_SetsVersion(t *testing.T) { } var version string - db.View(func(tx *bbolt.Tx) error { + if err := db.View(func(tx *bbolt.Tx) error { b := tx.Bucket(store.BucketConfig) version = string(b.Get([]byte("version"))) return nil - }) + }); err != nil { + t.Fatal(err) + } if version == "" || version == "0" { t.Fatalf("expected non-zero version, got %q", version) diff --git a/internal/app/services/admin.go b/internal/app/services/admin.go index 1320cd9..6502313 100644 --- a/internal/app/services/admin.go +++ b/internal/app/services/admin.go @@ -101,7 +101,9 @@ func (s *AdminService) DeleteUser(ctx context.Context, username string) error { return err } for _, tok := range u.Tokens { - s.st.DeleteToken(tx, tok) + if err := s.st.DeleteToken(tx, tok); err != nil { + return err + } } } return s.st.DeleteUser(tx, username) @@ -129,17 +131,25 @@ func (s *AdminService) GetSettings(ctx context.Context) (prefix string, defaultH func (s *AdminService) UpdateSettings(ctx context.Context, prefix *string, defaultHide *bool, title *string) error { return s.st.Update(ctx, func(tx *bbolt.Tx) error { if prefix != nil { - s.st.PutConfig(tx, "prefix", []byte(*prefix)) + if err := s.st.PutConfig(tx, "prefix", []byte(*prefix)); err != nil { + return err + } } if defaultHide != nil { if *defaultHide { - s.st.PutConfig(tx, "defaultHide", []byte("1")) + if err := s.st.PutConfig(tx, "defaultHide", []byte("1")); err != nil { + return err + } } else { - s.st.DeleteConfig(tx, "defaultHide") + if err := s.st.DeleteConfig(tx, "defaultHide"); err != nil { + return err + } } } if title != nil { - s.st.PutConfig(tx, "title", []byte(*title)) + if err := s.st.PutConfig(tx, "title", []byte(*title)); err != nil { + return err + } } return nil }) @@ -264,7 +274,9 @@ func (s *AdminService) WipeTile(ctx context.Context, mapid, x, y int) error { return err } for _, id := range ids { - grids.Delete(id) + if err := grids.Delete(id); err != nil { + return err + } } return nil }); err != nil { @@ -310,7 +322,9 @@ func (s *AdminService) SetCoords(ctx context.Context, mapid, fx, fy, tx2, ty int g.Coord.X += diff.X g.Coord.Y += diff.Y raw, _ := json.Marshal(g) - grids.Put(k, raw) + if err := grids.Put(k, raw); err != nil { + return err + } } return nil }); err != nil { @@ -367,7 +381,9 @@ func (s *AdminService) HideMarker(ctx context.Context, markerID string) error { } m.Hidden = true raw, _ = json.Marshal(m) - grid.Put(key, raw) + if err := grid.Put(key, raw); err != nil { + return err + } return nil }) } diff --git a/internal/app/services/admin_test.go b/internal/app/services/admin_test.go index 53fed52..746609a 100644 --- a/internal/app/services/admin_test.go +++ b/internal/app/services/admin_test.go @@ -234,7 +234,7 @@ func TestToggleMapHidden(t *testing.T) { admin, _ := newTestAdmin(t) ctx := context.Background() - admin.UpdateMap(ctx, 1, "world", false, false) + _ = admin.UpdateMap(ctx, 1, "world", false, false) mi, err := admin.ToggleMapHidden(ctx, 1) if err != nil { @@ -257,19 +257,27 @@ func TestWipe(t *testing.T) { admin, st := newTestAdmin(t) ctx := context.Background() - st.Update(ctx, func(tx *bbolt.Tx) error { - st.PutGrid(tx, "g1", []byte("data")) - st.PutMap(tx, 1, []byte("data")) - st.PutTile(tx, 1, 0, "0_0", []byte("data")) - st.CreateMarkersBuckets(tx) - return nil - }) + if err := st.Update(ctx, func(tx *bbolt.Tx) error { + if err := st.PutGrid(tx, "g1", []byte("data")); err != nil { + return err + } + if err := st.PutMap(tx, 1, []byte("data")); err != nil { + return err + } + if err := st.PutTile(tx, 1, 0, "0_0", []byte("data")); err != nil { + return err + } + _, _, err := st.CreateMarkersBuckets(tx) + return err + }); err != nil { + t.Fatal(err) + } if err := admin.Wipe(ctx); err != nil { t.Fatal(err) } - st.View(ctx, func(tx *bbolt.Tx) error { + if err := st.View(ctx, func(tx *bbolt.Tx) error { if st.GetGrid(tx, "g1") != nil { t.Fatal("expected grids wiped") } @@ -283,7 +291,9 @@ func TestWipe(t *testing.T) { t.Fatal("expected markers wiped") } return nil - }) + }); err != nil { + t.Fatal(err) + } } func TestGetMap_NotFound(t *testing.T) { diff --git a/internal/app/services/auth.go b/internal/app/services/auth.go index 233777b..ff6ce32 100644 --- a/internal/app/services/auth.go +++ b/internal/app/services/auth.go @@ -53,7 +53,7 @@ func (s *AuthService) GetSession(ctx context.Context, req *http.Request) *app.Se return nil } var sess *app.Session - s.st.View(ctx, func(tx *bbolt.Tx) error { + if err := s.st.View(ctx, func(tx *bbolt.Tx) error { raw := s.st.GetSession(tx, c.Value) if raw == nil { return nil @@ -77,7 +77,9 @@ func (s *AuthService) GetSession(ctx context.Context, req *http.Request) *app.Se } sess.Auths = u.Auths return nil - }) + }); err != nil { + return nil + } return sess } @@ -165,7 +167,7 @@ func (s *AuthService) GetUserByUsername(ctx context.Context, username string) *a // SetupRequired returns true if no users exist (first run). func (s *AuthService) SetupRequired(ctx context.Context) bool { var required bool - s.st.View(ctx, func(tx *bbolt.Tx) error { + _ = s.st.View(ctx, func(tx *bbolt.Tx) error { if s.st.UserCount(tx) == 0 { required = true } @@ -181,7 +183,7 @@ func (s *AuthService) BootstrapAdmin(ctx context.Context, username, pass, bootst } var created bool var u *app.User - s.st.Update(ctx, func(tx *bbolt.Tx) error { + if err := s.st.Update(ctx, func(tx *bbolt.Tx) error { if s.st.GetUser(tx, "admin") != nil { return nil } @@ -200,7 +202,9 @@ func (s *AuthService) BootstrapAdmin(ctx context.Context, username, pass, bootst created = true u = &user return nil - }) + }); err != nil { + return nil + } if created { return u } @@ -239,7 +243,7 @@ func (s *AuthService) GenerateTokenForUser(ctx context.Context, username string) } token := hex.EncodeToString(tokenRaw) var tokens []string - s.st.Update(ctx, func(tx *bbolt.Tx) error { + _ = s.st.Update(ctx, func(tx *bbolt.Tx) error { uRaw := s.st.GetUser(tx, username) u := app.User{} if uRaw != nil { @@ -250,7 +254,9 @@ func (s *AuthService) GenerateTokenForUser(ctx context.Context, username string) u.Tokens = append(u.Tokens, token) tokens = u.Tokens buf, _ := json.Marshal(u) - s.st.PutUser(tx, username, buf) + if err := s.st.PutUser(tx, username, buf); err != nil { + return err + } return s.st.PutToken(tx, token, username) }) return tokens @@ -522,7 +528,9 @@ func (s *AuthService) findOrCreateOAuthUser(ctx context.Context, provider, sub, user.Email = email } raw, _ = json.Marshal(user) - s.st.PutUser(tx, username, raw) + if err := s.st.PutUser(tx, username, raw); err != nil { + return err + } } return nil } diff --git a/internal/app/services/auth_test.go b/internal/app/services/auth_test.go index 24a9a45..aacadba 100644 --- a/internal/app/services/auth_test.go +++ b/internal/app/services/auth_test.go @@ -41,9 +41,11 @@ func createUser(t *testing.T, st *store.Store, username, password string, auths } u := app.User{Pass: hash, Auths: auths} raw, _ := json.Marshal(u) - st.Update(context.Background(), func(tx *bbolt.Tx) error { + if err := st.Update(context.Background(), func(tx *bbolt.Tx) error { return st.PutUser(tx, username, raw) - }) + }); err != nil { + t.Fatal(err) + } } func TestSetupRequired_EmptyDB(t *testing.T) { @@ -246,9 +248,11 @@ func TestGetUserTokensAndPrefix(t *testing.T) { ctx := context.Background() createUser(t, st, "alice", "pass", app.Auths{app.AUTH_UPLOAD}) - st.Update(ctx, func(tx *bbolt.Tx) error { + if err := st.Update(ctx, func(tx *bbolt.Tx) error { return st.PutConfig(tx, "prefix", []byte("myprefix")) - }) + }); err != nil { + t.Fatal(err) + } auth.GenerateTokenForUser(ctx, "alice") tokens, prefix := auth.GetUserTokensAndPrefix(ctx, "alice") @@ -288,9 +292,11 @@ func TestValidateClientToken_NoUploadPerm(t *testing.T) { ctx := context.Background() createUser(t, st, "alice", "pass", app.Auths{app.AUTH_MAP}) - st.Update(ctx, func(tx *bbolt.Tx) error { + if err := st.Update(ctx, func(tx *bbolt.Tx) error { return st.PutToken(tx, "tok123", "alice") - }) + }); err != nil { + t.Fatal(err) + } _, err := auth.ValidateClientToken(ctx, "tok123") if err == nil { diff --git a/internal/app/services/client.go b/internal/app/services/client.go index fdb6cba..01fc3c5 100644 --- a/internal/app/services/client.go +++ b/internal/app/services/client.go @@ -141,7 +141,9 @@ func (s *ClientService) ProcessGridUpdate(ctx context.Context, grup GridUpdate) if err != nil { return err } - grids.Put([]byte(grid), raw) + if err := grids.Put([]byte(grid), raw); err != nil { + return err + } greq.GridRequests = append(greq.GridRequests, grid) } } @@ -192,7 +194,9 @@ func (s *ClientService) ProcessGridUpdate(ctx context.Context, grup GridUpdate) if err != nil { return err } - grids.Put([]byte(grid), raw) + if err := grids.Put([]byte(grid), raw); err != nil { + return err + } greq.GridRequests = append(greq.GridRequests, grid) } } @@ -207,7 +211,7 @@ func (s *ClientService) ProcessGridUpdate(ctx context.Context, grup GridUpdate) } } if len(maps) > 1 { - grids.ForEach(func(k, v []byte) error { + if err := grids.ForEach(func(k, v []byte) error { gd := app.GridData{} if err := json.Unmarshal(v, &gd); err != nil { return err @@ -244,16 +248,22 @@ func (s *ClientService) ProcessGridUpdate(ctx context.Context, grup GridUpdate) File: td.File, }) } - grids.Put(k, raw) + if err := grids.Put(k, raw); err != nil { + return err + } } return nil - }) + }); err != nil { + return err + } } for mergeid, merge := range maps { if mapid == mergeid { continue } - mapB.Delete([]byte(strconv.Itoa(mergeid))) + if err := mapB.Delete([]byte(strconv.Itoa(mergeid))); err != nil { + return err + } slog.Info("reporting merge", "from", mergeid, "to", mapid) s.mapSvc.ReportMerge(mergeid, mapid, app.Coord{X: offset.X - merge.X, Y: offset.Y - merge.Y}) } @@ -271,10 +281,10 @@ func (s *ClientService) ProcessGridUpdate(ctx context.Context, grup GridUpdate) func (s *ClientService) ProcessGridUpload(ctx context.Context, id string, extraData string, fileReader io.Reader) error { if extraData != "" { ed := ExtraData{} - json.Unmarshal([]byte(extraData), &ed) + _ = json.Unmarshal([]byte(extraData), &ed) if ed.Season == 3 { needTile := false - s.st.Update(ctx, func(tx *bbolt.Tx) error { + if err := s.st.Update(ctx, func(tx *bbolt.Tx) error { raw := s.st.GetGrid(tx, id) if raw == nil { return fmt.Errorf("unknown grid id: %s", id) @@ -301,7 +311,9 @@ func (s *ClientService) ProcessGridUpload(ctx context.Context, id string, extraD } raw, _ = json.Marshal(cur) return s.st.PutGrid(tx, id, raw) - }) + }); err != nil { + return err + } if !needTile { slog.Debug("ignoring tile upload: winter") return nil @@ -316,7 +328,7 @@ func (s *ClientService) ProcessGridUpload(ctx context.Context, id string, extraD cur := app.GridData{} mapid := 0 - s.st.Update(ctx, func(tx *bbolt.Tx) error { + if err := s.st.Update(ctx, func(tx *bbolt.Tx) error { raw := s.st.GetGrid(tx, id) if raw == nil { return fmt.Errorf("unknown grid id: %s", id) @@ -331,7 +343,9 @@ func (s *ClientService) ProcessGridUpload(ctx context.Context, id string, extraD } raw, _ = json.Marshal(cur) return s.st.PutGrid(tx, id, raw) - }) + }); err != nil { + return err + } if updateTile { gridDir := fmt.Sprintf("%s/grids", s.mapSvc.GridStorage()) @@ -374,7 +388,7 @@ func (s *ClientService) UpdatePositions(ctx context.Context, data []byte) error } gridDataByID := make(map[string]app.GridData) - s.st.View(ctx, func(tx *bbolt.Tx) error { + if err := s.st.View(ctx, func(tx *bbolt.Tx) error { for _, craw := range craws { raw := s.st.GetGrid(tx, craw.GridID) if raw != nil { @@ -385,7 +399,9 @@ func (s *ClientService) UpdatePositions(ctx context.Context, data []byte) error } } return nil - }) + }); err != nil { + return err + } username, _ := ctx.Value(app.ClientUsernameKey).(string) @@ -478,8 +494,12 @@ func (s *ClientService) UploadMarkers(ctx context.Context, data []byte) error { Image: img, } raw, _ := json.Marshal(m) - grid.Put(key, raw) - idB.Put(idKey, key) + if err := grid.Put(key, raw); err != nil { + return err + } + if err := idB.Put(idKey, key); err != nil { + return err + } } return nil }) diff --git a/internal/app/services/client_test.go b/internal/app/services/client_test.go index 3b23b83..e19fd5a 100644 --- a/internal/app/services/client_test.go +++ b/internal/app/services/client_test.go @@ -58,9 +58,11 @@ func TestClientLocate_Found(t *testing.T) { ctx := context.Background() gd := app.GridData{ID: "g1", Map: 1, Coord: app.Coord{X: 2, Y: 3}} raw, _ := json.Marshal(gd) - st.Update(ctx, func(tx *bbolt.Tx) error { + if err := st.Update(ctx, func(tx *bbolt.Tx) error { return st.PutGrid(tx, "g1", raw) - }) + }); err != nil { + t.Fatal(err) + } result, err := client.Locate(ctx, "g1") if err != nil { t.Fatal(err) diff --git a/internal/app/services/export.go b/internal/app/services/export.go index 633bcb1..a4b2620 100644 --- a/internal/app/services/export.go +++ b/internal/app/services/export.go @@ -111,7 +111,7 @@ func (s *ExportService) Export(ctx context.Context, w io.Writer) error { if markersb != nil { markersgrid := markersb.Bucket(store.BucketMarkersGrid) if markersgrid != nil { - markersgrid.ForEach(func(k, v []byte) error { + if err := markersgrid.ForEach(func(k, v []byte) error { select { case <-ctx.Done(): return ctx.Err() @@ -125,7 +125,9 @@ func (s *ExportService) Export(ctx context.Context, w io.Writer) error { maps[gridMap[marker.GridID]].Markers[marker.GridID] = append(maps[gridMap[marker.GridID]].Markers[marker.GridID], marker) } return nil - }) + }); err != nil { + return err + } } } return nil @@ -218,7 +220,11 @@ func (s *ExportService) Merge(ctx context.Context, zr *zip.Reader) error { f.Close() return err } - io.Copy(f, r) + if _, err := io.Copy(f, r); err != nil { + r.Close() + f.Close() + return err + } r.Close() f.Close() newTiles[strings.TrimSuffix(filepath.Base(fhdr.Name), ".png")] = struct{}{} @@ -290,8 +296,12 @@ func (s *ExportService) processMergeJSON( Image: img, } raw, _ := json.Marshal(m) - mgrid.Put(key, raw) - idB.Put(idKey, key) + if err := mgrid.Put(key, raw); err != nil { + return err + } + if err := idB.Put(idKey, key); err != nil { + return err + } } } @@ -333,7 +343,9 @@ func (s *ExportService) processMergeJSON( if err != nil { return err } - grids.Put([]byte(grid), raw) + if err := grids.Put([]byte(grid), raw); err != nil { + return err + } } return nil } @@ -372,11 +384,13 @@ func (s *ExportService) processMergeJSON( if err != nil { return err } - grids.Put([]byte(grid), raw) + if err := grids.Put([]byte(grid), raw); err != nil { + return err + } } if len(existingMaps) > 1 { - grids.ForEach(func(k, v []byte) error { + if err := grids.ForEach(func(k, v []byte) error { gd := app.GridData{} if err := json.Unmarshal(v, &gd); err != nil { return err @@ -413,16 +427,22 @@ func (s *ExportService) processMergeJSON( File: td.File, }) } - grids.Put(k, raw) + if err := grids.Put(k, raw); err != nil { + return err + } } return nil - }) + }); err != nil { + return err + } } for mergeid, merge := range existingMaps { if mapid == mergeid { continue } - mapB.Delete([]byte(strconv.Itoa(mergeid))) + if err := mapB.Delete([]byte(strconv.Itoa(mergeid))); err != nil { + return err + } slog.Info("reporting merge", "from", mergeid, "to", mapid) s.mapSvc.ReportMerge(mergeid, mapid, app.Coord{X: offset.X - merge.X, Y: offset.Y - merge.Y}) } diff --git a/internal/app/services/export_test.go b/internal/app/services/export_test.go index ad848c1..47a0426 100644 --- a/internal/app/services/export_test.go +++ b/internal/app/services/export_test.go @@ -47,12 +47,14 @@ func TestExport_WithGrid(t *testing.T) { gdRaw, _ := json.Marshal(gd) mi := app.MapInfo{ID: 1, Name: "test", Hidden: false} miRaw, _ := json.Marshal(mi) - st.Update(ctx, func(tx *bbolt.Tx) error { + if err := st.Update(ctx, func(tx *bbolt.Tx) error { if err := st.PutGrid(tx, "g1", gdRaw); err != nil { return err } return st.PutMap(tx, 1, miRaw) - }) + }); err != nil { + t.Fatal(err) + } var buf bytes.Buffer err := export.Export(ctx, &buf) if err != nil { diff --git a/internal/app/services/map.go b/internal/app/services/map.go index 6f7549a..d04c7d9 100644 --- a/internal/app/services/map.go +++ b/internal/app/services/map.go @@ -218,7 +218,7 @@ func (s *MapService) getSubTiles(ctx context.Context, mapid int, c app.Coord, z // SaveTile persists a tile and broadcasts the update. func (s *MapService) SaveTile(ctx context.Context, mapid int, c app.Coord, z int, f string, t int64) { - s.st.Update(ctx, func(tx *bbolt.Tx) error { + _ = s.st.Update(ctx, func(tx *bbolt.Tx) error { td := &app.TileData{ MapID: mapid, Coord: c, @@ -293,7 +293,7 @@ func (s *MapService) RebuildZooms(ctx context.Context) error { if b == nil { return nil } - b.ForEach(func(k, v []byte) error { + if err := b.ForEach(func(k, v []byte) error { select { case <-ctx.Done(): return ctx.Err() @@ -306,8 +306,12 @@ func (s *MapService) RebuildZooms(ctx context.Context) error { needProcess[zoomproc{grid.Coord.Parent(), grid.Map}] = struct{}{} saveGrid[zoomproc{grid.Coord, grid.Map}] = grid.ID return nil - }) - tx.DeleteBucket(store.BucketTiles) + }); err != nil { + return err + } + if err := tx.DeleteBucket(store.BucketTiles); err != nil { + return err + } return nil }); err != nil { slog.Error("RebuildZooms: failed to update store", "error", err) @@ -364,7 +368,7 @@ func (s *MapService) WatchMerges() chan *app.Merge { // GetAllTileCache returns all tiles for the initial SSE cache dump. func (s *MapService) GetAllTileCache(ctx context.Context) []TileCache { var cache []TileCache - s.st.View(ctx, func(tx *bbolt.Tx) error { + _ = s.st.View(ctx, func(tx *bbolt.Tx) error { return s.st.ForEachTile(tx, func(mapK, zoomK, coordK, v []byte) error { select { case <-ctx.Done(): diff --git a/internal/app/services/map_test.go b/internal/app/services/map_test.go index 888c197..a62bdec 100644 --- a/internal/app/services/map_test.go +++ b/internal/app/services/map_test.go @@ -57,9 +57,11 @@ func TestGetConfig(t *testing.T) { svc, st := newTestMapService(t) ctx := context.Background() - st.Update(ctx, func(tx *bbolt.Tx) error { + if err := st.Update(ctx, func(tx *bbolt.Tx) error { return st.PutConfig(tx, "title", []byte("Test Map")) - }) + }); err != nil { + t.Fatal(err) + } config, err := svc.GetConfig(ctx, app.Auths{app.AUTH_MAP}) if err != nil { @@ -93,9 +95,11 @@ func TestGetConfig_Empty(t *testing.T) { func TestGetPage(t *testing.T) { svc, st := newTestMapService(t) ctx := context.Background() - st.Update(ctx, func(tx *bbolt.Tx) error { + if err := st.Update(ctx, func(tx *bbolt.Tx) error { return st.PutConfig(tx, "title", []byte("Map Page")) - }) + }); err != nil { + t.Fatal(err) + } page, err := svc.GetPage(ctx) if err != nil { @@ -112,9 +116,11 @@ func TestGetGrid(t *testing.T) { gd := app.GridData{ID: "g1", Map: 1, Coord: app.Coord{X: 5, Y: 10}} raw, _ := json.Marshal(gd) - st.Update(ctx, func(tx *bbolt.Tx) error { + if err := st.Update(ctx, func(tx *bbolt.Tx) error { return st.PutGrid(tx, "g1", raw) - }) + }); err != nil { + t.Fatal(err) + } got, err := svc.GetGrid(ctx, "g1") if err != nil { @@ -158,11 +164,17 @@ func TestGetMaps_HiddenFilter(t *testing.T) { mi2 := app.MapInfo{ID: 2, Name: "hidden", Hidden: true} raw1, _ := json.Marshal(mi1) raw2, _ := json.Marshal(mi2) - st.Update(ctx, func(tx *bbolt.Tx) error { - st.PutMap(tx, 1, raw1) - st.PutMap(tx, 2, raw2) + if err := st.Update(ctx, func(tx *bbolt.Tx) error { + if err := st.PutMap(tx, 1, raw1); err != nil { + return err + } + if err := st.PutMap(tx, 2, raw2); err != nil { + return err + } return nil - }) + }); err != nil { + t.Fatal(err) + } maps, err := svc.GetMaps(ctx, false) if err != nil { @@ -201,14 +213,18 @@ func TestGetMarkers_WithData(t *testing.T) { m := app.Marker{Name: "Tower", ID: 1, GridID: "g1", Position: app.Position{X: 10, Y: 20}, Image: "gfx/terobjs/mm/tower"} mRaw, _ := json.Marshal(m) - st.Update(ctx, func(tx *bbolt.Tx) error { - st.PutGrid(tx, "g1", gdRaw) + if err := st.Update(ctx, func(tx *bbolt.Tx) error { + if err := st.PutGrid(tx, "g1", gdRaw); err != nil { + return err + } grid, _, err := st.CreateMarkersBuckets(tx) if err != nil { return err } return grid.Put([]byte("g1_10_20"), mRaw) - }) + }); err != nil { + t.Fatal(err) + } markers, err := svc.GetMarkers(ctx) if err != nil { @@ -233,9 +249,11 @@ func TestGetTile(t *testing.T) { td := app.TileData{MapID: 1, Coord: app.Coord{X: 0, Y: 0}, Zoom: 0, File: "grids/g1.png", Cache: 12345} raw, _ := json.Marshal(td) - st.Update(ctx, func(tx *bbolt.Tx) error { + if err := st.Update(ctx, func(tx *bbolt.Tx) error { return st.PutTile(tx, 1, 0, "0_0", raw) - }) + }); err != nil { + t.Fatal(err) + } got := svc.GetTile(ctx, 1, app.Coord{X: 0, Y: 0}, 0) if got == nil { @@ -287,9 +305,11 @@ func TestGetAllTileCache_WithData(t *testing.T) { td := app.TileData{MapID: 1, Coord: app.Coord{X: 1, Y: 2}, Zoom: 0, Cache: 999} raw, _ := json.Marshal(td) - st.Update(ctx, func(tx *bbolt.Tx) error { + if err := st.Update(ctx, func(tx *bbolt.Tx) error { return st.PutTile(tx, 1, 0, "1_2", raw) - }) + }); err != nil { + t.Fatal(err) + } cache := svc.GetAllTileCache(ctx) if len(cache) != 1 { diff --git a/internal/app/store/db_test.go b/internal/app/store/db_test.go index 7733474..87f1bfb 100644 --- a/internal/app/store/db_test.go +++ b/internal/app/store/db_test.go @@ -25,12 +25,14 @@ func TestUserCRUD(t *testing.T) { ctx := context.Background() // Verify user doesn't exist on empty DB. - st.View(ctx, func(tx *bbolt.Tx) error { + if err := st.View(ctx, func(tx *bbolt.Tx) error { if got := st.GetUser(tx, "alice"); got != nil { t.Fatal("expected nil user before creation") } return nil - }) + }); err != nil { + t.Fatal(err) + } // Create user. if err := st.Update(ctx, func(tx *bbolt.Tx) error { @@ -40,7 +42,7 @@ func TestUserCRUD(t *testing.T) { } // Verify user exists and count is correct (separate transaction for accurate Stats). - st.View(ctx, func(tx *bbolt.Tx) error { + if err := st.View(ctx, func(tx *bbolt.Tx) error { got := st.GetUser(tx, "alice") if got == nil || string(got) != `{"pass":"hash"}` { t.Fatalf("expected user data, got %s", got) @@ -49,7 +51,9 @@ func TestUserCRUD(t *testing.T) { t.Fatalf("expected 1 user, got %d", c) } return nil - }) + }); err != nil { + t.Fatal(err) + } // Delete user. if err := st.Update(ctx, func(tx *bbolt.Tx) error { @@ -58,31 +62,41 @@ func TestUserCRUD(t *testing.T) { t.Fatal(err) } - st.View(ctx, func(tx *bbolt.Tx) error { + if err := st.View(ctx, func(tx *bbolt.Tx) error { if got := st.GetUser(tx, "alice"); got != nil { t.Fatal("expected nil user after deletion") } return nil - }) + }); err != nil { + t.Fatal(err) + } } func TestForEachUser(t *testing.T) { st := newTestStore(t) ctx := context.Background() - st.Update(ctx, func(tx *bbolt.Tx) error { - st.PutUser(tx, "alice", []byte("1")) - st.PutUser(tx, "bob", []byte("2")) + if err := st.Update(ctx, func(tx *bbolt.Tx) error { + if err := st.PutUser(tx, "alice", []byte("1")); err != nil { + return err + } + if err := st.PutUser(tx, "bob", []byte("2")); err != nil { + return err + } return nil - }) + }); err != nil { + t.Fatal(err) + } var names []string - st.View(ctx, func(tx *bbolt.Tx) error { + if err := st.View(ctx, func(tx *bbolt.Tx) error { return st.ForEachUser(tx, func(k, _ []byte) error { names = append(names, string(k)) return nil }) - }) + }); err != nil { + t.Fatal(err) + } if len(names) != 2 { t.Fatalf("expected 2 users, got %d", len(names)) } @@ -91,12 +105,14 @@ func TestForEachUser(t *testing.T) { func TestUserCountEmptyBucket(t *testing.T) { st := newTestStore(t) ctx := context.Background() - st.View(ctx, func(tx *bbolt.Tx) error { + if err := st.View(ctx, func(tx *bbolt.Tx) error { if c := st.UserCount(tx); c != 0 { t.Fatalf("expected 0 users on empty db, got %d", c) } return nil - }) + }); err != nil { + t.Fatal(err) + } } func TestSessionCRUD(t *testing.T) { @@ -211,19 +227,27 @@ func TestForEachMap(t *testing.T) { st := newTestStore(t) ctx := context.Background() - st.Update(ctx, func(tx *bbolt.Tx) error { - st.PutMap(tx, 1, []byte("a")) - st.PutMap(tx, 2, []byte("b")) + if err := st.Update(ctx, func(tx *bbolt.Tx) error { + if err := st.PutMap(tx, 1, []byte("a")); err != nil { + return err + } + if err := st.PutMap(tx, 2, []byte("b")); err != nil { + return err + } return nil - }) + }); err != nil { + t.Fatal(err) + } var count int - st.View(ctx, func(tx *bbolt.Tx) error { + if err := st.View(ctx, func(tx *bbolt.Tx) error { return st.ForEachMap(tx, func(_, _ []byte) error { count++ return nil }) - }) + }); err != nil { + t.Fatal(err) + } if count != 2 { t.Fatalf("expected 2 maps, got %d", count) } @@ -290,20 +314,30 @@ func TestForEachTile(t *testing.T) { st := newTestStore(t) ctx := context.Background() - st.Update(ctx, func(tx *bbolt.Tx) error { - st.PutTile(tx, 1, 0, "0_0", []byte("a")) - st.PutTile(tx, 1, 1, "0_0", []byte("b")) - st.PutTile(tx, 2, 0, "1_1", []byte("c")) + if err := st.Update(ctx, func(tx *bbolt.Tx) error { + if err := st.PutTile(tx, 1, 0, "0_0", []byte("a")); err != nil { + return err + } + if err := st.PutTile(tx, 1, 1, "0_0", []byte("b")); err != nil { + return err + } + if err := st.PutTile(tx, 2, 0, "1_1", []byte("c")); err != nil { + return err + } return nil - }) + }); err != nil { + t.Fatal(err) + } var count int - st.View(ctx, func(tx *bbolt.Tx) error { + if err := st.View(ctx, func(tx *bbolt.Tx) error { return st.ForEachTile(tx, func(_, _, _, _ []byte) error { count++ return nil }) - }) + }); err != nil { + t.Fatal(err) + } if count != 3 { t.Fatalf("expected 3 tiles, got %d", count) } @@ -313,7 +347,7 @@ func TestTilesMapBucket(t *testing.T) { st := newTestStore(t) ctx := context.Background() - st.Update(ctx, func(tx *bbolt.Tx) error { + if err := st.Update(ctx, func(tx *bbolt.Tx) error { if b := st.GetTilesMapBucket(tx, 1); b != nil { t.Fatal("expected nil bucket before creation") } @@ -328,31 +362,39 @@ func TestTilesMapBucket(t *testing.T) { t.Fatal("expected non-nil after create") } return st.DeleteTilesMapBucket(tx, 1) - }) + }); err != nil { + t.Fatal(err) + } } func TestDeleteTilesBucket(t *testing.T) { st := newTestStore(t) ctx := context.Background() - st.Update(ctx, func(tx *bbolt.Tx) error { - st.PutTile(tx, 1, 0, "0_0", []byte("a")) + if err := st.Update(ctx, func(tx *bbolt.Tx) error { + if err := st.PutTile(tx, 1, 0, "0_0", []byte("a")); err != nil { + return err + } return st.DeleteTilesBucket(tx) - }) + }); err != nil { + t.Fatal(err) + } - st.View(ctx, func(tx *bbolt.Tx) error { + if err := st.View(ctx, func(tx *bbolt.Tx) error { if got := st.GetTile(tx, 1, 0, "0_0"); got != nil { t.Fatal("expected nil after bucket deletion") } return nil - }) + }); err != nil { + t.Fatal(err) + } } func TestMarkerBuckets(t *testing.T) { st := newTestStore(t) ctx := context.Background() - st.Update(ctx, func(tx *bbolt.Tx) error { + if err := st.Update(ctx, func(tx *bbolt.Tx) error { if b := st.GetMarkersGridBucket(tx); b != nil { t.Fatal("expected nil grid bucket before creation") } @@ -376,7 +418,9 @@ func TestMarkerBuckets(t *testing.T) { t.Fatal("expected non-zero sequence") } return nil - }) + }); err != nil { + t.Fatal(err) + } } func TestOAuthStateCRUD(t *testing.T) { @@ -408,23 +452,29 @@ func TestBucketExistsAndDelete(t *testing.T) { st := newTestStore(t) ctx := context.Background() - st.Update(ctx, func(tx *bbolt.Tx) error { + if err := st.Update(ctx, func(tx *bbolt.Tx) error { if st.BucketExists(tx, store.BucketUsers) { t.Fatal("expected bucket to not exist") } - st.PutUser(tx, "alice", []byte("x")) + if err := st.PutUser(tx, "alice", []byte("x")); err != nil { + return err + } if !st.BucketExists(tx, store.BucketUsers) { t.Fatal("expected bucket to exist") } return st.DeleteBucket(tx, store.BucketUsers) - }) + }); err != nil { + t.Fatal(err) + } - st.View(ctx, func(tx *bbolt.Tx) error { + if err := st.View(ctx, func(tx *bbolt.Tx) error { if st.BucketExists(tx, store.BucketUsers) { t.Fatal("expected bucket to be deleted") } return nil - }) + }); err != nil { + t.Fatal(err) + } } func TestDeleteBucketNonExistent(t *testing.T) {