From f2cb9022f2ab35b8da53ff096103cc40076ef649 Mon Sep 17 00:00:00 2001 From: Rostislav Dugin Date: Fri, 13 Mar 2026 17:23:00 +0300 Subject: [PATCH 1/9] FEATURE (agent): Setup agent directory, pre-commit and CI\CD workflow --- .github/workflows/ci-release.yml | 60 +++++- .pre-commit-config.yaml | 17 ++ Dockerfile | 43 +++++ agent/.env.example | 1 + agent/.gitignore | 23 +++ agent/.golangci.yml | 19 ++ agent/Makefile | 12 ++ agent/cmd/main.go | 173 ++++++++++++++++++ agent/go.mod | 11 ++ agent/go.sum | 10 + agent/internal/config/config.go | 136 ++++++++++++++ agent/internal/config/config_test.go | 162 ++++++++++++++++ agent/internal/config/dto.go | 9 + agent/internal/features/start/start.go | 37 ++++ agent/internal/features/upgrade/upgrader.go | 142 ++++++++++++++ agent/internal/logger/logger.go | 47 +++++ backend/cmd/main.go | 2 + .../features/system/agent/controller.go | 48 +++++ backend/internal/features/system/agent/di.go | 7 + 19 files changed, 957 insertions(+), 2 deletions(-) create mode 100644 agent/.env.example create mode 100644 agent/.gitignore create mode 100644 agent/.golangci.yml create mode 100644 agent/Makefile create mode 100644 agent/cmd/main.go create mode 100644 agent/go.mod create mode 100644 agent/go.sum create mode 100644 agent/internal/config/config.go create mode 100644 agent/internal/config/config_test.go create mode 100644 agent/internal/config/dto.go create mode 100644 agent/internal/features/start/start.go create mode 100644 agent/internal/features/upgrade/upgrader.go create mode 100644 agent/internal/logger/logger.go create mode 100644 backend/internal/features/system/agent/controller.go create mode 100644 backend/internal/features/system/agent/di.go diff --git a/.github/workflows/ci-release.yml b/.github/workflows/ci-release.yml index 9fcc3cd..54f5e47 100644 --- a/.github/workflows/ci-release.yml +++ b/.github/workflows/ci-release.yml @@ -86,6 +86,39 @@ jobs: cd frontend npm run build + lint-agent: + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.24.9" + cache-dependency-path: agent/go.sum + + - name: Download Go modules + run: | + cd agent + go mod download + + - name: Install golangci-lint + run: | + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.7.2 + echo "$(go env GOPATH)/bin" >> $GITHUB_PATH + + - name: Run golangci-lint + run: | + cd agent + golangci-lint run + + - name: Verify go mod tidy + run: | + cd agent + go mod tidy + git diff --exit-code go.mod go.sum || (echo "go mod tidy made changes, please run 'go mod tidy' and commit the changes" && exit 1) + test-frontend: runs-on: ubuntu-latest needs: [lint-frontend] @@ -108,6 +141,29 @@ jobs: cd frontend npm run test + test-agent: + runs-on: ubuntu-latest + needs: [lint-agent] + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.24.9" + cache-dependency-path: agent/go.sum + + - name: Download Go modules + run: | + cd agent + go mod download + + - name: Run Go tests + run: | + cd agent + go test -count=1 -failfast ./internal/... + test-backend: runs-on: self-hosted needs: [lint-backend] @@ -441,7 +497,7 @@ jobs: runs-on: self-hosted container: image: node:20 - needs: [test-backend, test-frontend] + needs: [test-backend, test-frontend, test-agent] if: ${{ github.ref == 'refs/heads/main' && !contains(github.event.head_commit.message, '[skip-release]') }} outputs: should_release: ${{ steps.version_bump.outputs.should_release }} @@ -534,7 +590,7 @@ jobs: build-only: runs-on: self-hosted - needs: [test-backend, test-frontend] + needs: [test-backend, test-frontend, test-agent] if: ${{ github.ref == 'refs/heads/main' && contains(github.event.head_commit.message, '[skip-release]') }} steps: - name: Clean workspace diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 96b5f73..dafbf90 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -41,3 +41,20 @@ repos: language: system files: ^backend/.*\.go$ pass_filenames: false + + # Agent checks + - repo: local + hooks: + - id: agent-format-and-lint + name: Agent Format & Lint (golangci-lint) + entry: bash -c "cd agent && golangci-lint fmt ./internal/... ./cmd/... && golangci-lint run ./internal/... ./cmd/..." + language: system + files: ^agent/.*\.go$ + pass_filenames: false + + - id: agent-go-mod-tidy + name: Agent Go Mod Tidy + entry: bash -c "cd agent && go mod tidy" + language: system + files: ^agent/.*\.go$ + pass_filenames: false diff --git a/Dockerfile b/Dockerfile index 91ed847..4c64d2b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -66,6 +66,43 @@ RUN CGO_ENABLED=0 \ go build -o /app/main ./cmd/main.go +# ========= BUILD AGENT ========= +# Builds the databasus-agent CLI binary for BOTH x86_64 and ARM64. +# Both architectures are always built because: +# - Databasus server runs on one arch (e.g. amd64) +# - The agent runs on remote PostgreSQL servers that may be on a +# different arch (e.g. arm64) +# - The backend serves the correct binary based on the agent's +# ?arch= query parameter +# +# We cross-compile from the build platform (no QEMU needed) because the +# agent is pure Go with zero C dependencies. +# CGO_ENABLED=0 produces fully static binaries — no glibc/musl dependency, +# so the agent runs on any Linux distro (Alpine, Debian, Ubuntu, RHEL, etc.). +# APP_VERSION is baked into the binary via -ldflags so the agent can +# compare its version against the server and auto-update when needed. +FROM --platform=$BUILDPLATFORM golang:1.24.9 AS agent-build + +ARG APP_VERSION=dev + +WORKDIR /agent + +COPY agent/go.mod ./ +RUN go mod download + +COPY agent/ ./ + +# Build for x86_64 (amd64) — static binary, no glibc dependency +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ + go build -ldflags "-X main.Version=${APP_VERSION}" \ + -o /agent-binaries/databasus-agent-linux-amd64 ./cmd/main.go + +# Build for ARM64 (arm64) — static binary, no glibc dependency +RUN CGO_ENABLED=0 GOOS=linux GOARCH=arm64 \ + go build -ldflags "-X main.Version=${APP_VERSION}" \ + -o /agent-binaries/databasus-agent-linux-arm64 ./cmd/main.go + + # ========= RUNTIME ========= FROM debian:bookworm-slim @@ -220,6 +257,10 @@ COPY backend/migrations ./migrations # Copy UI files COPY --from=backend-build /app/ui/build ./ui/build +# Copy agent binaries (both architectures) — served by the backend +# at GET /api/v1/system/agent?arch=amd64|arm64 +COPY --from=agent-build /agent-binaries ./agent-binaries + # Copy .env file (with fallback to .env.production.example) COPY backend/.env* /app/ RUN if [ ! -f /app/.env ]; then \ @@ -397,6 +438,8 @@ fi # Create database and set password for postgres user echo "Setting up database and user..." gosu postgres \$PG_BIN/psql -p 5437 -h localhost -d postgres << 'SQL' + +# We use stub password, because internal DB is not exposed outside container ALTER USER postgres WITH PASSWORD 'Q1234567'; SELECT 'CREATE DATABASE databasus OWNER postgres' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'databasus') diff --git a/agent/.env.example b/agent/.env.example new file mode 100644 index 0000000..f655030 --- /dev/null +++ b/agent/.env.example @@ -0,0 +1 @@ +ENV_MODE=development diff --git a/agent/.gitignore b/agent/.gitignore new file mode 100644 index 0000000..074fbf7 --- /dev/null +++ b/agent/.gitignore @@ -0,0 +1,23 @@ +main +.env +docker-compose.yml +pgdata +pgdata_test/ +mysqldata/ +mariadbdata/ +main.exe +swagger/ +swagger/* +swagger/docs.go +swagger/swagger.json +swagger/swagger.yaml +postgresus-backend.exe +databasus-backend.exe +ui/build/* +pgdata-for-restore/ +temp/ +cmd.exe +temp/ +valkey-data/ +victoria-logs-data/ +databasus.json \ No newline at end of file diff --git a/agent/.golangci.yml b/agent/.golangci.yml new file mode 100644 index 0000000..b339d97 --- /dev/null +++ b/agent/.golangci.yml @@ -0,0 +1,19 @@ +version: "2" + +run: + timeout: 5m + tests: false + concurrency: 4 + +linters: + default: standard + + settings: + errcheck: + check-type-assertions: true + +formatters: + enable: + - gofmt + - golines + - goimports diff --git a/agent/Makefile b/agent/Makefile new file mode 100644 index 0000000..297aff8 --- /dev/null +++ b/agent/Makefile @@ -0,0 +1,12 @@ +# Usage: make run ARGS="start --pg-host localhost" +run: + go run cmd/main.go $(ARGS) + +build: + CGO_ENABLED=0 go build -ldflags "-X main.Version=$(VERSION)" -o databasus-agent ./cmd/main.go + +test: + go test -count=1 -failfast ./internal/... + +lint: + golangci-lint fmt ./cmd/... ./internal/... && golangci-lint run ./cmd/... ./internal/... diff --git a/agent/cmd/main.go b/agent/cmd/main.go new file mode 100644 index 0000000..0855d5c --- /dev/null +++ b/agent/cmd/main.go @@ -0,0 +1,173 @@ +package main + +import ( + "flag" + "fmt" + "os" + "path/filepath" + "strings" + + "databasus-agent/internal/config" + "databasus-agent/internal/features/start" + "databasus-agent/internal/features/upgrade" + "databasus-agent/internal/logger" +) + +var Version = "dev" + +func main() { + if len(os.Args) < 2 { + printUsage() + os.Exit(1) + } + + switch os.Args[1] { + case "start": + runStart(os.Args[2:]) + case "stop": + runStop() + case "status": + runStatus() + case "restore": + runRestore(os.Args[2:]) + case "version": + fmt.Println(Version) + default: + fmt.Fprintf(os.Stderr, "unknown command: %s\n", os.Args[1]) + printUsage() + os.Exit(1) + } +} + +func runStart(args []string) { + fs := flag.NewFlagSet("start", flag.ExitOnError) + + isDebug := fs.Bool("debug", false, "Enable debug logging") + isSkipUpdate := fs.Bool("skip-update", false, "Skip auto-update check") + + cfg := &config.Config{} + cfg.LoadFromJSONAndArgs(fs, args) + + if err := cfg.SaveToJSON(); err != nil { + fmt.Fprintf(os.Stderr, "Failed to save config: %v\n", err) + } + + logger.Init(*isDebug) + log := logger.GetLogger() + + isDev := checkIsDevelopment() + runUpdateCheck(cfg.DatabasusHost, *isSkipUpdate, isDev, log) + + if err := start.Run(cfg, log); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } +} + +func runStop() { + logger.Init(false) + logger.GetLogger().Info("stop: stub — not yet implemented") +} + +func runStatus() { + logger.Init(false) + logger.GetLogger().Info("status: stub — not yet implemented") +} + +func runRestore(args []string) { + fs := flag.NewFlagSet("restore", flag.ExitOnError) + + targetDir := fs.String("target-dir", "", "Target pgdata directory") + backupID := fs.String("backup-id", "", "Full backup UUID (optional)") + targetTime := fs.String("target-time", "", "PITR target time in RFC3339 (optional)") + isYes := fs.Bool("yes", false, "Skip confirmation prompt") + isDebug := fs.Bool("debug", false, "Enable debug logging") + isSkipUpdate := fs.Bool("skip-update", false, "Skip auto-update check") + + cfg := &config.Config{} + cfg.LoadFromJSONAndArgs(fs, args) + + if err := cfg.SaveToJSON(); err != nil { + fmt.Fprintf(os.Stderr, "Failed to save config: %v\n", err) + } + + logger.Init(*isDebug) + log := logger.GetLogger() + + isDev := checkIsDevelopment() + runUpdateCheck(cfg.DatabasusHost, *isSkipUpdate, isDev, log) + + log.Info("restore: stub — not yet implemented", + "targetDir", *targetDir, + "backupId", *backupID, + "targetTime", *targetTime, + "yes", *isYes, + ) +} + +func printUsage() { + fmt.Fprintln(os.Stderr, "Usage: databasus-agent [flags]") + fmt.Fprintln(os.Stderr, "") + fmt.Fprintln(os.Stderr, "Commands:") + fmt.Fprintln(os.Stderr, " start Start the agent (WAL archiving + basebackups)") + fmt.Fprintln(os.Stderr, " stop Stop a running agent") + fmt.Fprintln(os.Stderr, " status Show agent status") + fmt.Fprintln(os.Stderr, " restore Restore a database from backup") + fmt.Fprintln(os.Stderr, " version Print agent version") +} + +func runUpdateCheck(host string, isSkipUpdate bool, isDev bool, log interface { + Info(string, ...any) + Warn(string, ...any) + Error(string, ...any) +}) { + if isSkipUpdate { + return + } + + if host == "" { + return + } + + if err := upgrade.CheckAndUpdate(host, Version, isDev, log); err != nil { + log.Error("Auto-update failed", "error", err) + os.Exit(1) + } +} + +func checkIsDevelopment() bool { + dir, err := os.Getwd() + if err != nil { + return false + } + + for range 3 { + if data, err := os.ReadFile(filepath.Join(dir, ".env")); err == nil { + return parseEnvMode(data) + } + + if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil { + return false + } + + dir = filepath.Dir(dir) + } + + return false +} + +func parseEnvMode(data []byte) bool { + for line := range strings.SplitSeq(string(data), "\n") { + line = strings.TrimSpace(line) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + + parts := strings.SplitN(line, "=", 2) + if len(parts) == 2 && strings.TrimSpace(parts[0]) == "ENV_MODE" { + return strings.TrimSpace(parts[1]) == "development" + } + } + + return false +} diff --git a/agent/go.mod b/agent/go.mod new file mode 100644 index 0000000..5dbe5bf --- /dev/null +++ b/agent/go.mod @@ -0,0 +1,11 @@ +module databasus-agent + +go 1.24.9 + +require github.com/stretchr/testify v1.11.1 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/agent/go.sum b/agent/go.sum new file mode 100644 index 0000000..c4c1710 --- /dev/null +++ b/agent/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/agent/internal/config/config.go b/agent/internal/config/config.go new file mode 100644 index 0000000..3e37d77 --- /dev/null +++ b/agent/internal/config/config.go @@ -0,0 +1,136 @@ +package config + +import ( + "encoding/json" + "flag" + "os" + + "databasus-agent/internal/logger" +) + +var log = logger.GetLogger() + +const configFileName = "databasus.json" + +type Config struct { + DatabasusHost string `json:"databasusHost"` + DbID string `json:"dbId"` + Token string `json:"token"` + + flags parsedFlags +} + +// LoadFromJSONAndArgs reads databasus.json into the struct +// and overrides JSON values with any explicitly provided CLI flags. +func (c *Config) LoadFromJSONAndArgs(fs *flag.FlagSet, args []string) { + c.loadFromJSON() + c.initSources() + + c.flags.host = fs.String( + "databasus-host", + "", + "Databasus server URL (e.g. http://your-server:4005)", + ) + c.flags.dbID = fs.String("db-id", "", "Database ID") + c.flags.token = fs.String("token", "", "Agent token") + + if err := fs.Parse(args); err != nil { + os.Exit(1) + } + + c.applyFlags() + log.Info("========= Loading config ============") + c.logConfigSources() + log.Info("========= Config has been loaded ====") +} + +// SaveToJSON writes the current struct to databasus.json. +func (c *Config) SaveToJSON() error { + data, err := json.MarshalIndent(c, "", " ") + if err != nil { + return err + } + + return os.WriteFile(configFileName, data, 0644) +} + +func (c *Config) loadFromJSON() { + data, err := os.ReadFile(configFileName) + if err != nil { + if os.IsNotExist(err) { + log.Info("No databasus.json found, will create on save") + return + } + + log.Warn("Failed to read databasus.json", "error", err) + + return + } + + if err := json.Unmarshal(data, c); err != nil { + log.Warn("Failed to parse databasus.json", "error", err) + + return + } + + log.Info("Configuration loaded from " + configFileName) +} + +func (c *Config) initSources() { + c.flags.sources = map[string]string{ + "databasus-host": "not configured", + "db-id": "not configured", + "token": "not configured", + } + + if c.DatabasusHost != "" { + c.flags.sources["databasus-host"] = configFileName + } + + if c.DbID != "" { + c.flags.sources["db-id"] = configFileName + } + + if c.Token != "" { + c.flags.sources["token"] = configFileName + } +} + +func (c *Config) applyFlags() { + if c.flags.host != nil && *c.flags.host != "" { + c.DatabasusHost = *c.flags.host + c.flags.sources["databasus-host"] = "command line args" + } + + if c.flags.dbID != nil && *c.flags.dbID != "" { + c.DbID = *c.flags.dbID + c.flags.sources["db-id"] = "command line args" + } + + if c.flags.token != nil && *c.flags.token != "" { + c.Token = *c.flags.token + c.flags.sources["token"] = "command line args" + } +} + +func (c *Config) logConfigSources() { + log.Info( + "databasus-host", + "value", + c.DatabasusHost, + "source", + c.flags.sources["databasus-host"], + ) + log.Info("db-id", "value", c.DbID, "source", c.flags.sources["db-id"]) + log.Info("token", "value", maskSensitive(c.Token), "source", c.flags.sources["token"]) +} + +func maskSensitive(value string) string { + if value == "" { + return "(not set)" + } + + visibleLen := max(len(value)/4, 1) + + return value[:visibleLen] + "***" +} diff --git a/agent/internal/config/config_test.go b/agent/internal/config/config_test.go new file mode 100644 index 0000000..cb5f14b --- /dev/null +++ b/agent/internal/config/config_test.go @@ -0,0 +1,162 @@ +package config + +import ( + "encoding/json" + "flag" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_LoadFromJSONAndArgs_ValuesLoadedFromJSON(t *testing.T) { + dir := setupTempDir(t) + writeConfigJSON(t, dir, Config{ + DatabasusHost: "http://json-host:4005", + DbID: "json-db-id", + Token: "json-token", + }) + + cfg := &Config{} + fs := flag.NewFlagSet("test", flag.ContinueOnError) + cfg.LoadFromJSONAndArgs(fs, []string{}) + + assert.Equal(t, "http://json-host:4005", cfg.DatabasusHost) + assert.Equal(t, "json-db-id", cfg.DbID) + assert.Equal(t, "json-token", cfg.Token) +} + +func Test_LoadFromJSONAndArgs_ValuesLoadedFromArgs_WhenNoJSON(t *testing.T) { + setupTempDir(t) + + cfg := &Config{} + fs := flag.NewFlagSet("test", flag.ContinueOnError) + cfg.LoadFromJSONAndArgs(fs, []string{ + "--databasus-host", "http://arg-host:4005", + "--db-id", "arg-db-id", + "--token", "arg-token", + }) + + assert.Equal(t, "http://arg-host:4005", cfg.DatabasusHost) + assert.Equal(t, "arg-db-id", cfg.DbID) + assert.Equal(t, "arg-token", cfg.Token) +} + +func Test_LoadFromJSONAndArgs_ArgsOverrideJSON(t *testing.T) { + dir := setupTempDir(t) + writeConfigJSON(t, dir, Config{ + DatabasusHost: "http://json-host:4005", + DbID: "json-db-id", + Token: "json-token", + }) + + cfg := &Config{} + fs := flag.NewFlagSet("test", flag.ContinueOnError) + cfg.LoadFromJSONAndArgs(fs, []string{ + "--databasus-host", "http://arg-host:9999", + "--db-id", "arg-db-id-override", + "--token", "arg-token-override", + }) + + assert.Equal(t, "http://arg-host:9999", cfg.DatabasusHost) + assert.Equal(t, "arg-db-id-override", cfg.DbID) + assert.Equal(t, "arg-token-override", cfg.Token) +} + +func Test_LoadFromJSONAndArgs_PartialArgsOverrideJSON(t *testing.T) { + dir := setupTempDir(t) + writeConfigJSON(t, dir, Config{ + DatabasusHost: "http://json-host:4005", + DbID: "json-db-id", + Token: "json-token", + }) + + cfg := &Config{} + fs := flag.NewFlagSet("test", flag.ContinueOnError) + cfg.LoadFromJSONAndArgs(fs, []string{ + "--databasus-host", "http://arg-host-only:4005", + }) + + assert.Equal(t, "http://arg-host-only:4005", cfg.DatabasusHost) + assert.Equal(t, "json-db-id", cfg.DbID) + assert.Equal(t, "json-token", cfg.Token) +} + +func Test_SaveToJSON_ConfigSavedCorrectly(t *testing.T) { + setupTempDir(t) + + cfg := &Config{ + DatabasusHost: "http://save-host:4005", + DbID: "save-db-id", + Token: "save-token", + } + + err := cfg.SaveToJSON() + require.NoError(t, err) + + saved := readConfigJSON(t) + + assert.Equal(t, "http://save-host:4005", saved.DatabasusHost) + assert.Equal(t, "save-db-id", saved.DbID) + assert.Equal(t, "save-token", saved.Token) +} + +func Test_SaveToJSON_AfterArgsOverrideJSON_SavedFileContainsMergedValues(t *testing.T) { + dir := setupTempDir(t) + writeConfigJSON(t, dir, Config{ + DatabasusHost: "http://json-host:4005", + DbID: "json-db-id", + Token: "json-token", + }) + + cfg := &Config{} + fs := flag.NewFlagSet("test", flag.ContinueOnError) + cfg.LoadFromJSONAndArgs(fs, []string{ + "--databasus-host", "http://override-host:9999", + }) + + err := cfg.SaveToJSON() + require.NoError(t, err) + + saved := readConfigJSON(t) + + assert.Equal(t, "http://override-host:9999", saved.DatabasusHost) + assert.Equal(t, "json-db-id", saved.DbID) + assert.Equal(t, "json-token", saved.Token) +} + +func setupTempDir(t *testing.T) string { + t.Helper() + + origDir, err := os.Getwd() + require.NoError(t, err) + + dir := t.TempDir() + require.NoError(t, os.Chdir(dir)) + + t.Cleanup(func() { os.Chdir(origDir) }) + + return dir +} + +func writeConfigJSON(t *testing.T, dir string, cfg Config) { + t.Helper() + + data, err := json.MarshalIndent(cfg, "", " ") + require.NoError(t, err) + + require.NoError(t, os.WriteFile(dir+"/"+configFileName, data, 0644)) +} + +func readConfigJSON(t *testing.T) Config { + t.Helper() + + data, err := os.ReadFile(configFileName) + require.NoError(t, err) + + var cfg Config + require.NoError(t, json.Unmarshal(data, &cfg)) + + return cfg +} diff --git a/agent/internal/config/dto.go b/agent/internal/config/dto.go new file mode 100644 index 0000000..65a6743 --- /dev/null +++ b/agent/internal/config/dto.go @@ -0,0 +1,9 @@ +package config + +type parsedFlags struct { + host *string + dbID *string + token *string + + sources map[string]string +} diff --git a/agent/internal/features/start/start.go b/agent/internal/features/start/start.go new file mode 100644 index 0000000..d70d3b2 --- /dev/null +++ b/agent/internal/features/start/start.go @@ -0,0 +1,37 @@ +package start + +import ( + "errors" + "log/slog" + + "databasus-agent/internal/config" +) + +func Run(cfg *config.Config, log *slog.Logger) error { + if err := validateConfig(cfg); err != nil { + return err + } + + log.Info("start: stub — not yet implemented", + "dbId", cfg.DbID, + "hasToken", cfg.Token != "", + ) + + return nil +} + +func validateConfig(cfg *config.Config) error { + if cfg.DatabasusHost == "" { + return errors.New("argument databasus-host is required") + } + + if cfg.DbID == "" { + return errors.New("argument db-id is required") + } + + if cfg.Token == "" { + return errors.New("argument token is required") + } + + return nil +} diff --git a/agent/internal/features/upgrade/upgrader.go b/agent/internal/features/upgrade/upgrader.go new file mode 100644 index 0000000..425dbcb --- /dev/null +++ b/agent/internal/features/upgrade/upgrader.go @@ -0,0 +1,142 @@ +package upgrade + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "os/exec" + "runtime" + "strings" + "syscall" + "time" +) + +type Logger interface { + Info(msg string, args ...any) + Warn(msg string, args ...any) + Error(msg string, args ...any) +} + +type versionResponse struct { + Version string `json:"version"` +} + +func CheckAndUpdate(databasusHost, currentVersion string, isDev bool, log Logger) error { + if isDev { + log.Info("Skipping update check (development mode)") + return nil + } + + serverVersion, err := fetchServerVersion(databasusHost, log) + if err != nil { + return nil + } + + if serverVersion == currentVersion { + log.Info("Agent version is up to date", "version", currentVersion) + return nil + } + + log.Info("Updating agent...", "current", currentVersion, "target", serverVersion) + + selfPath, err := os.Executable() + if err != nil { + return fmt.Errorf("failed to determine executable path: %w", err) + } + + tempPath := selfPath + ".update" + + defer func() { + _ = os.Remove(tempPath) + }() + + if err := downloadBinary(databasusHost, tempPath); err != nil { + return fmt.Errorf("failed to download update: %w", err) + } + + if err := os.Chmod(tempPath, 0755); err != nil { + return fmt.Errorf("failed to set permissions on update: %w", err) + } + + if err := verifyBinary(tempPath, serverVersion); err != nil { + return fmt.Errorf("update verification failed: %w", err) + } + + if err := os.Rename(tempPath, selfPath); err != nil { + return fmt.Errorf("failed to replace binary (try --skip-update if this persists): %w", err) + } + + log.Info("Update complete, re-executing...") + + return syscall.Exec(selfPath, os.Args, os.Environ()) +} + +func fetchServerVersion(host string, log Logger) (string, error) { + client := &http.Client{Timeout: 10 * time.Second} + + resp, err := client.Get(host + "/api/v1/system/version") + if err != nil { + log.Warn("Could not reach server for update check, continuing", "error", err) + return "", err + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + log.Warn( + "Server returned non-OK status for version check, continuing", + "status", + resp.StatusCode, + ) + return "", fmt.Errorf("status %d", resp.StatusCode) + } + + var ver versionResponse + if err := json.NewDecoder(resp.Body).Decode(&ver); err != nil { + log.Warn("Failed to parse server version response, continuing", "error", err) + return "", err + } + + return ver.Version, nil +} + +func downloadBinary(host, destPath string) error { + url := fmt.Sprintf("%s/api/v1/system/agent?arch=%s", host, runtime.GOARCH) + + resp, err := http.Get(url) + if err != nil { + return err + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("server returned %d for agent download", resp.StatusCode) + } + + f, err := os.Create(destPath) + if err != nil { + return err + } + defer func() { _ = f.Close() }() + + _, err = io.Copy(f, resp.Body) + + return err +} + +func verifyBinary(binaryPath, expectedVersion string) error { + cmd := exec.Command(binaryPath, "version") + + output, err := cmd.Output() + if err != nil { + return fmt.Errorf("binary failed to execute: %w", err) + } + + got := strings.TrimSpace(string(output)) + if got != expectedVersion { + return fmt.Errorf("version mismatch: expected %q, got %q", expectedVersion, got) + } + + return nil +} diff --git a/agent/internal/logger/logger.go b/agent/internal/logger/logger.go new file mode 100644 index 0000000..078ac75 --- /dev/null +++ b/agent/internal/logger/logger.go @@ -0,0 +1,47 @@ +package logger + +import ( + "log/slog" + "os" + "sync" + "time" +) + +var ( + loggerInstance *slog.Logger + once sync.Once +) + +func Init(isDebug bool) { + level := slog.LevelInfo + if isDebug { + level = slog.LevelDebug + } + + once.Do(func() { + loggerInstance = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ + Level: level, + ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { + if a.Key == slog.TimeKey { + a.Value = slog.StringValue(time.Now().Format("2006/01/02 15:04:05")) + } + if a.Key == slog.LevelKey { + return slog.Attr{} + } + + return a + }, + })) + + loggerInstance.Info("Text structured logger initialized") + }) +} + +// GetLogger returns a singleton slog.Logger that logs to the console +func GetLogger() *slog.Logger { + if loggerInstance == nil { + Init(false) + } + + return loggerInstance +} diff --git a/backend/cmd/main.go b/backend/cmd/main.go index 6f472dc..1cfc726 100644 --- a/backend/cmd/main.go +++ b/backend/cmd/main.go @@ -28,6 +28,7 @@ import ( "databasus-backend/internal/features/restores" "databasus-backend/internal/features/restores/restoring" "databasus-backend/internal/features/storages" + system_agent "databasus-backend/internal/features/system/agent" system_healthcheck "databasus-backend/internal/features/system/healthcheck" system_version "databasus-backend/internal/features/system/version" task_cancellation "databasus-backend/internal/features/tasks/cancellation" @@ -212,6 +213,7 @@ func setUpRoutes(r *gin.Engine) { userController.RegisterRoutes(v1) system_healthcheck.GetHealthcheckController().RegisterRoutes(v1) system_version.GetVersionController().RegisterRoutes(v1) + system_agent.GetAgentController().RegisterRoutes(v1) backups_controllers.GetBackupController().RegisterPublicRoutes(v1) backups_controllers.GetPostgresWalBackupController().RegisterRoutes(v1) databases.GetDatabaseController().RegisterPublicRoutes(v1) diff --git a/backend/internal/features/system/agent/controller.go b/backend/internal/features/system/agent/controller.go new file mode 100644 index 0000000..ad1ebd1 --- /dev/null +++ b/backend/internal/features/system/agent/controller.go @@ -0,0 +1,48 @@ +package system_agent + +import ( + "net/http" + "os" + "path/filepath" + + "github.com/gin-gonic/gin" +) + +type AgentController struct{} + +func (c *AgentController) RegisterRoutes(router *gin.RouterGroup) { + router.GET("/system/agent", c.DownloadAgent) +} + +// DownloadAgent +// @Summary Download agent binary +// @Description Download the databasus-agent binary for the specified architecture +// @Tags system/agent +// @Produce octet-stream +// @Param arch query string true "Target architecture" Enums(amd64, arm64) +// @Success 200 {file} binary +// @Failure 400 {object} map[string]string +// @Failure 404 {object} map[string]string +// @Router /system/agent [get] +func (c *AgentController) DownloadAgent(ctx *gin.Context) { + arch := ctx.Query("arch") + if arch != "amd64" && arch != "arm64" { + ctx.JSON(http.StatusBadRequest, gin.H{"error": "arch must be amd64 or arm64"}) + return + } + + binaryName := "databasus-agent-linux-" + arch + binaryPath := filepath.Join("agent-binaries", binaryName) + + if _, err := os.Stat(binaryPath); os.IsNotExist(err) { + ctx.JSON( + http.StatusNotFound, + gin.H{"error": "agent binary not found for architecture: " + arch}, + ) + return + } + + ctx.Header("Content-Type", "application/octet-stream") + ctx.Header("Content-Disposition", "attachment; filename=databasus-agent") + ctx.File(binaryPath) +} diff --git a/backend/internal/features/system/agent/di.go b/backend/internal/features/system/agent/di.go new file mode 100644 index 0000000..9145f19 --- /dev/null +++ b/backend/internal/features/system/agent/di.go @@ -0,0 +1,7 @@ +package system_agent + +var agentController = &AgentController{} + +func GetAgentController() *AgentController { + return agentController +} From f60e3d956b2d3917bea83e080825a35074e47f02 Mon Sep 17 00:00:00 2001 From: Rostislav Dugin Date: Fri, 13 Mar 2026 17:37:39 +0300 Subject: [PATCH 2/9] FEAUTRE (go): Upgrade Go version to 1.26.1 --- .github/workflows/ci-release.yml | 8 ++++---- Dockerfile | 4 ++-- agent/go.mod | 2 +- backend/go.mod | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci-release.yml b/.github/workflows/ci-release.yml index 54f5e47..4ecf93c 100644 --- a/.github/workflows/ci-release.yml +++ b/.github/workflows/ci-release.yml @@ -11,7 +11,7 @@ jobs: lint-backend: runs-on: self-hosted container: - image: golang:1.24.9 + image: golang:1.26.1 volumes: - /runner-cache/go-pkg:/go/pkg/mod - /runner-cache/go-build:/root/.cache/go-build @@ -95,7 +95,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: "1.24.9" + go-version: "1.26.1" cache-dependency-path: agent/go.sum - name: Download Go modules @@ -151,7 +151,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: "1.24.9" + go-version: "1.26.1" cache-dependency-path: agent/go.sum - name: Download Go modules @@ -168,7 +168,7 @@ jobs: runs-on: self-hosted needs: [lint-backend] container: - image: golang:1.24.9 + image: golang:1.26.1 options: --privileged -v /var/run/docker.sock:/var/run/docker.sock --add-host=host.docker.internal:host-gateway volumes: - /runner-cache/go-pkg:/go/pkg/mod diff --git a/Dockerfile b/Dockerfile index 4c64d2b..1358c32 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,7 +22,7 @@ RUN npm run build # ========= BUILD BACKEND ========= # Backend build stage -FROM --platform=$BUILDPLATFORM golang:1.24.9 AS backend-build +FROM --platform=$BUILDPLATFORM golang:1.26.1 AS backend-build # Make TARGET args available early so tools built here match the final image arch ARG TARGETOS @@ -81,7 +81,7 @@ RUN CGO_ENABLED=0 \ # so the agent runs on any Linux distro (Alpine, Debian, Ubuntu, RHEL, etc.). # APP_VERSION is baked into the binary via -ldflags so the agent can # compare its version against the server and auto-update when needed. -FROM --platform=$BUILDPLATFORM golang:1.24.9 AS agent-build +FROM --platform=$BUILDPLATFORM golang:1.26.1 AS agent-build ARG APP_VERSION=dev diff --git a/agent/go.mod b/agent/go.mod index 5dbe5bf..6edcc5d 100644 --- a/agent/go.mod +++ b/agent/go.mod @@ -1,6 +1,6 @@ module databasus-agent -go 1.24.9 +go 1.26.1 require github.com/stretchr/testify v1.11.1 diff --git a/backend/go.mod b/backend/go.mod index fd76d9c..439df25 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -1,6 +1,6 @@ module databasus-backend -go 1.24.9 +go 1.26.1 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 From c8edff8046b4e9e526048834ed85dd08acefe35c Mon Sep 17 00:00:00 2001 From: Rostislav Dugin Date: Fri, 13 Mar 2026 17:41:14 +0300 Subject: [PATCH 3/9] FEATURE (golangci-lint): Upgrade golangci-lint to 2.11.3 in CI \ CD --- .github/workflows/ci-release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-release.yml b/.github/workflows/ci-release.yml index 4ecf93c..0a900f1 100644 --- a/.github/workflows/ci-release.yml +++ b/.github/workflows/ci-release.yml @@ -32,7 +32,7 @@ jobs: - name: Install golangci-lint run: | - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.7.2 + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.11.3 echo "$(go env GOPATH)/bin" >> $GITHUB_PATH - name: Install swag for swagger generation @@ -105,7 +105,7 @@ jobs: - name: Install golangci-lint run: | - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.7.2 + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.11.3 echo "$(go env GOPATH)/bin" >> $GITHUB_PATH - name: Run golangci-lint From 1b511410a670622e6d1b8e6c33f995da85c2d368 Mon Sep 17 00:00:00 2001 From: Rostislav Dugin Date: Fri, 13 Mar 2026 17:47:29 +0300 Subject: [PATCH 4/9] FEATURE (formatters): Fix config of golines --- agent/.golangci.yml | 4 ++++ backend/.golangci.yml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/agent/.golangci.yml b/agent/.golangci.yml index b339d97..a773c8b 100644 --- a/agent/.golangci.yml +++ b/agent/.golangci.yml @@ -17,3 +17,7 @@ formatters: - gofmt - golines - goimports + + settings: + golines: + max-len: 120 diff --git a/backend/.golangci.yml b/backend/.golangci.yml index 6645186..c10b4aa 100644 --- a/backend/.golangci.yml +++ b/backend/.golangci.yml @@ -17,3 +17,7 @@ formatters: - gofmt - golines - goimports + + settings: + golines: + max-len: 120 From ca4d483f2c8b8017f474ab49441d9b7b0a60179d Mon Sep 17 00:00:00 2001 From: Rostislav Dugin Date: Fri, 13 Mar 2026 17:47:46 +0300 Subject: [PATCH 5/9] REFACTOR (golines): Apply golines fixes --- backend/internal/config/config.go | 2 +- backend/internal/features/backups/config/model.go | 2 +- backend/internal/features/intervals/model.go | 6 +++--- backend/internal/features/users/models/users_settings.go | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go index bdbc76b..e2f7ff0 100644 --- a/backend/internal/config/config.go +++ b/backend/internal/config/config.go @@ -29,7 +29,7 @@ type EnvVariables struct { MongodbInstallDir string `env:"MONGODB_INSTALL_DIR"` // Internal database - DatabaseDsn string `env:"DATABASE_DSN" required:"true"` + DatabaseDsn string `env:"DATABASE_DSN" required:"true"` // Internal Valkey ValkeyHost string `env:"VALKEY_HOST" required:"true"` ValkeyPort string `env:"VALKEY_PORT" required:"true"` diff --git a/backend/internal/features/backups/config/model.go b/backend/internal/features/backups/config/model.go index 57fbc0c..d0f0430 100644 --- a/backend/internal/features/backups/config/model.go +++ b/backend/internal/features/backups/config/model.go @@ -43,7 +43,7 @@ type BackupConfig struct { Encryption BackupEncryption `json:"encryption" gorm:"column:encryption;type:text;not null;default:'NONE'"` // MaxBackupSizeMB limits individual backup size. 0 = unlimited. - MaxBackupSizeMB int64 `json:"maxBackupSizeMb" gorm:"column:max_backup_size_mb;type:int;not null"` + MaxBackupSizeMB int64 `json:"maxBackupSizeMb" gorm:"column:max_backup_size_mb;type:int;not null"` // MaxBackupsTotalSizeMB limits total size of all backups. 0 = unlimited. MaxBackupsTotalSizeMB int64 `json:"maxBackupsTotalSizeMb" gorm:"column:max_backups_total_size_mb;type:int;not null"` } diff --git a/backend/internal/features/intervals/model.go b/backend/internal/features/intervals/model.go index d852e17..77c6d13 100644 --- a/backend/internal/features/intervals/model.go +++ b/backend/internal/features/intervals/model.go @@ -13,11 +13,11 @@ type Interval struct { ID uuid.UUID `json:"id" gorm:"primaryKey;type:uuid;default:gen_random_uuid()"` Interval IntervalType `json:"interval" gorm:"type:text;not null"` - TimeOfDay *string `json:"timeOfDay" gorm:"type:text;"` + TimeOfDay *string `json:"timeOfDay" gorm:"type:text;"` // only for WEEKLY - Weekday *int `json:"weekday,omitempty" gorm:"type:int"` + Weekday *int `json:"weekday,omitempty" gorm:"type:int"` // only for MONTHLY - DayOfMonth *int `json:"dayOfMonth,omitempty" gorm:"type:int"` + DayOfMonth *int `json:"dayOfMonth,omitempty" gorm:"type:int"` // only for CRON CronExpression *string `json:"cronExpression,omitempty" gorm:"type:text"` } diff --git a/backend/internal/features/users/models/users_settings.go b/backend/internal/features/users/models/users_settings.go index 5e75d8a..23c882a 100644 --- a/backend/internal/features/users/models/users_settings.go +++ b/backend/internal/features/users/models/users_settings.go @@ -3,11 +3,11 @@ package users_models import "github.com/google/uuid" type UsersSettings struct { - ID uuid.UUID `json:"id" gorm:"type:uuid;primary_key;default:gen_random_uuid()"` + ID uuid.UUID `json:"id" gorm:"type:uuid;primary_key;default:gen_random_uuid()"` // means that any user can register via sign up form without invitation - IsAllowExternalRegistrations bool `json:"isAllowExternalRegistrations" gorm:"column:is_allow_external_registrations"` + IsAllowExternalRegistrations bool `json:"isAllowExternalRegistrations" gorm:"column:is_allow_external_registrations"` // means that any user with role MEMBER can invite other users - IsAllowMemberInvitations bool `json:"isAllowMemberInvitations" gorm:"column:is_allow_member_invitations"` + IsAllowMemberInvitations bool `json:"isAllowMemberInvitations" gorm:"column:is_allow_member_invitations"` // means that any user with role MEMBER can create their own workspaces IsMemberAllowedToCreateWorkspaces bool `json:"isMemberAllowedToCreateWorkspaces" gorm:"column:is_member_allowed_to_create_workspaces"` } From 880a7488e919625a820bccd85bc2f5793f095fbb Mon Sep 17 00:00:00 2001 From: Rostislav Dugin Date: Fri, 13 Mar 2026 17:50:07 +0300 Subject: [PATCH 6/9] FEATURE (formatters): Add gofumpt and gci formatters --- agent/.golangci.yml | 12 ++++++++++-- backend/.golangci.yml | 12 ++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/agent/.golangci.yml b/agent/.golangci.yml index a773c8b..ea2a327 100644 --- a/agent/.golangci.yml +++ b/agent/.golangci.yml @@ -14,10 +14,18 @@ linters: formatters: enable: - - gofmt + - gofumpt - golines - - goimports + - gci settings: golines: max-len: 120 + gofumpt: + module-path: databasus-agent + extra-rules: true + gci: + sections: + - standard + - default + - localmodule diff --git a/backend/.golangci.yml b/backend/.golangci.yml index c10b4aa..6a13c51 100644 --- a/backend/.golangci.yml +++ b/backend/.golangci.yml @@ -14,10 +14,18 @@ linters: formatters: enable: - - gofmt + - gofumpt - golines - - goimports + - gci settings: golines: max-len: 120 + gofumpt: + module-path: databasus-backend + extra-rules: true + gci: + sections: + - standard + - default + - localmodule From bcd7d8e1aa7a2ecf8a46c880a4229f3f66ac1c5f Mon Sep 17 00:00:00 2001 From: Rostislav Dugin Date: Fri, 13 Mar 2026 17:53:00 +0300 Subject: [PATCH 7/9] REFACTOR (formatters): Apply formatters auto fixes --- agent/cmd/main.go | 5 +-- agent/internal/config/config.go | 2 +- agent/internal/config/config_test.go | 2 +- agent/internal/features/upgrade/upgrader.go | 2 +- backend/cmd/main.go | 15 ++++----- backend/internal/config/config.go | 7 ++-- .../audit_logs/background_service_test.go | 8 ++--- .../features/audit_logs/controller.go | 4 +-- .../features/audit_logs/controller_test.go | 8 ++--- backend/internal/features/audit_logs/di.go | 14 +++++--- .../features/audit_logs/repository.go | 9 ++--- .../internal/features/audit_logs/service.go | 4 +-- .../features/audit_logs/service_test.go | 6 ++-- .../backups/backuping/backuper_test.go | 6 ++-- .../backups/backuping/cleaner_gfs_test.go | 6 ++-- .../backups/backups/backuping/cleaner_test.go | 6 ++-- .../backups/backups/backuping/mocks.go | 6 ++-- .../backups/backups/backuping/registry.go | 8 ++--- .../backups/backuping/registry_test.go | 22 ++++++------- .../backups/backups/backuping/scheduler.go | 2 +- .../backups/backuping/scheduler_test.go | 11 ++++--- .../backups/backups/backuping/testing.go | 6 ++-- .../features/backups/backups/common/dto.go | 3 +- .../backups/backups/controllers/controller.go | 15 +++++---- .../controllers/postgres_wal_controller.go | 6 ++-- .../postgres_wal_controller_test.go | 10 +++--- .../backups/backups/controllers/testing.go | 6 ++-- .../features/backups/backups/core/model.go | 4 +-- .../backups/backups/core/repository.go | 4 +-- .../features/backups/backups/download/di.go | 8 +++-- .../backups/backups/download/repository.go | 4 +-- .../backups/backups/download/tracking.go | 7 ++-- .../features/backups/backups/dto/dto.go | 5 +-- .../features/backups/backups/services/di.go | 5 +-- .../backups/services/postgres_wal_service.go | 6 ++-- .../backups/backups/services/service.go | 4 +-- .../usecases/mariadb/create_backup_uc.go | 8 ++--- .../usecases/mysql/create_backup_uc.go | 8 ++--- .../usecases/postgresql/create_backup_uc.go | 12 +++---- .../features/backups/config/controller.go | 4 +-- .../internal/features/backups/config/di.go | 23 +++++++------ .../internal/features/backups/config/model.go | 11 ++++--- .../features/backups/config/model_test.go | 6 ++-- .../features/backups/config/repository.go | 4 +-- .../features/backups/config/service.go | 4 +-- .../features/backups/config/testing.go | 4 +-- .../internal/features/databases/controller.go | 7 ++-- .../features/databases/controller_test.go | 8 ++--- .../databases/databases/mariadb/model.go | 8 ++--- .../databases/databases/mongodb/model.go | 7 ++-- .../databases/databases/mysql/model.go | 8 ++--- .../databases/databases/postgresql/model.go | 9 ++--- .../internal/features/databases/interfaces.go | 3 +- backend/internal/features/databases/model.go | 11 ++++--- .../internal/features/databases/repository.go | 10 +++--- .../internal/features/databases/service.go | 4 +-- .../internal/features/databases/testing.go | 4 +-- backend/internal/features/disk/service.go | 3 +- backend/internal/features/email/di.go | 6 ++-- .../features/encryption/secrets/service.go | 10 +++--- .../attempt/check_database_health_uc.go | 8 ++--- .../attempt/check_database_health_uc_test.go | 6 ++-- .../healthcheck/attempt/controller.go | 3 +- .../features/healthcheck/attempt/di.go | 15 +++++---- .../healthcheck/attempt/interfaces.go | 4 +-- .../features/healthcheck/attempt/mocks.go | 6 ++-- .../features/healthcheck/attempt/model.go | 3 +- .../healthcheck/attempt/repository.go | 3 +- .../features/healthcheck/attempt/service.go | 7 ++-- .../features/healthcheck/config/controller.go | 3 +- .../features/healthcheck/config/di.go | 19 ++++++----- .../features/healthcheck/config/repository.go | 3 +- .../features/healthcheck/config/service.go | 9 ++--- .../internal/features/notifiers/controller.go | 6 ++-- .../features/notifiers/controller_test.go | 24 +++++++------- backend/internal/features/notifiers/di.go | 21 +++++++----- .../internal/features/notifiers/interfaces.go | 3 +- backend/internal/features/notifiers/model.go | 9 ++--- .../notifiers/models/discord/model.go | 3 +- .../notifiers/models/email_notifier/model.go | 3 +- .../features/notifiers/models/slack/model.go | 3 +- .../features/notifiers/models/teams/model.go | 3 +- .../notifiers/models/telegram/model.go | 3 +- .../notifiers/models/webhook/model.go | 4 +-- .../internal/features/notifiers/repository.go | 6 ++-- .../internal/features/notifiers/service.go | 4 +-- .../internal/features/notifiers/testing.go | 4 +-- backend/internal/features/plan/model.go | 4 +-- backend/internal/features/plan/repository.go | 4 +-- backend/internal/features/plan/service.go | 5 +-- .../internal/features/restores/controller.go | 5 +-- .../internal/features/restores/core/model.go | 7 ++-- .../features/restores/core/repository.go | 4 +-- backend/internal/features/restores/di.go | 33 ++++++++++--------- .../features/restores/restoring/dto.go | 7 ++-- .../features/restores/restoring/registry.go | 6 ++-- .../restores/restoring/registry_test.go | 22 ++++++------- .../features/restores/restoring/restorer.go | 1 - .../features/restores/restoring/scheduler.go | 2 +- .../restores/restoring/scheduler_test.go | 6 ++-- backend/internal/features/restores/service.go | 13 ++++---- .../usecases/mariadb/restore_backup_uc.go | 8 ++--- .../usecases/mysql/restore_backup_uc.go | 8 ++--- .../usecases/postgresql/restore_backup_uc.go | 12 +++---- .../internal/features/storages/controller.go | 6 ++-- .../features/storages/controller_test.go | 30 ++++++++--------- backend/internal/features/storages/di.go | 19 ++++++----- .../internal/features/storages/interfaces.go | 3 +- backend/internal/features/storages/model.go | 11 ++++--- .../internal/features/storages/model_test.go | 25 +++++++------- .../storages/models/azure_blob/model.go | 3 +- .../features/storages/models/ftp/model.go | 3 +- .../storages/models/google_drive/model.go | 5 ++- .../features/storages/models/local/model.go | 7 ++-- .../features/storages/models/nas/model.go | 5 +-- .../features/storages/models/rclone/model.go | 4 +-- .../features/storages/models/s3/model.go | 5 +-- .../features/storages/models/sftp/model.go | 3 +- .../internal/features/storages/repository.go | 5 ++- backend/internal/features/storages/service.go | 4 +-- backend/internal/features/storages/testing.go | 4 +-- .../features/system/healthcheck/di.go | 1 + .../features/system/healthcheck/service.go | 7 ++-- .../tasks/cancellation/cancel_manager.go | 3 +- .../features/tasks/cancellation/di.go | 4 +-- .../tests/mariadb_backup_restore_test.go | 2 +- .../tests/mysql_backup_restore_test.go | 2 +- .../tests/postgresql_backup_restore_test.go | 14 ++++---- .../features/users/controllers/e2e_test.go | 8 ++--- .../controllers/management_controller.go | 6 ++-- .../controllers/management_controller_test.go | 8 ++--- .../users/controllers/password_reset_test.go | 8 ++--- .../users/controllers/settings_controller.go | 4 +-- .../controllers/settings_controller_test.go | 4 +-- .../users/controllers/user_controller.go | 4 +-- .../users/controllers/user_controller_test.go | 8 ++--- backend/internal/features/users/dto/dto.go | 4 +-- .../internal/features/users/errors/errors.go | 4 +-- .../features/users/interfaces/interfaces.go | 2 +- .../features/users/middleware/middleware.go | 7 ++-- .../internal/features/users/models/user.go | 3 +- .../features/users/repositories/di.go | 8 +++-- .../repositories/password_reset_repository.go | 5 ++- .../users/repositories/user_repository.go | 9 ++--- .../repositories/users_settings_repository.go | 6 ++-- .../internal/features/users/services/di.go | 2 ++ .../users/services/management_service.go | 4 +-- .../features/users/services/oauth_testing.go | 4 +-- .../features/users/services/user_services.go | 4 +-- .../features/users/testing/user_utils.go | 4 +-- .../workspaces/controllers/e2e_test.go | 6 ++-- .../controllers/membership_controller.go | 6 ++-- .../controllers/membership_controller_test.go | 6 ++-- .../controllers/workspace_controller.go | 6 ++-- .../controllers/workspace_controller_test.go | 6 ++-- .../internal/features/workspaces/dto/dto.go | 4 +-- .../workspaces/models/workspace_membership.go | 4 +-- .../repositories/membership_repository.go | 8 ++--- .../repositories/workspace_repository.go | 4 +-- .../features/workspaces/services/di.go | 6 ++-- .../workspaces/services/membership_service.go | 4 +-- .../workspaces/services/workspace_service.go | 6 ++-- .../features/workspaces/testing/testing.go | 6 ++-- backend/internal/storage/storage.go | 5 +-- backend/internal/util/cache/cache.go | 3 +- backend/internal/util/cache/pubsub.go | 6 ++-- .../encryption/secret_key_field_encryptor.go | 3 +- backend/internal/util/files/creator.go | 2 +- 168 files changed, 603 insertions(+), 539 deletions(-) diff --git a/agent/cmd/main.go b/agent/cmd/main.go index 0855d5c..b83c622 100644 --- a/agent/cmd/main.go +++ b/agent/cmd/main.go @@ -116,11 +116,12 @@ func printUsage() { fmt.Fprintln(os.Stderr, " version Print agent version") } -func runUpdateCheck(host string, isSkipUpdate bool, isDev bool, log interface { +func runUpdateCheck(host string, isSkipUpdate, isDev bool, log interface { Info(string, ...any) Warn(string, ...any) Error(string, ...any) -}) { +}, +) { if isSkipUpdate { return } diff --git a/agent/internal/config/config.go b/agent/internal/config/config.go index 3e37d77..1530b68 100644 --- a/agent/internal/config/config.go +++ b/agent/internal/config/config.go @@ -51,7 +51,7 @@ func (c *Config) SaveToJSON() error { return err } - return os.WriteFile(configFileName, data, 0644) + return os.WriteFile(configFileName, data, 0o644) } func (c *Config) loadFromJSON() { diff --git a/agent/internal/config/config_test.go b/agent/internal/config/config_test.go index cb5f14b..4bfbc90 100644 --- a/agent/internal/config/config_test.go +++ b/agent/internal/config/config_test.go @@ -146,7 +146,7 @@ func writeConfigJSON(t *testing.T, dir string, cfg Config) { data, err := json.MarshalIndent(cfg, "", " ") require.NoError(t, err) - require.NoError(t, os.WriteFile(dir+"/"+configFileName, data, 0644)) + require.NoError(t, os.WriteFile(dir+"/"+configFileName, data, 0o644)) } func readConfigJSON(t *testing.T) Config { diff --git a/agent/internal/features/upgrade/upgrader.go b/agent/internal/features/upgrade/upgrader.go index 425dbcb..6dd28c4 100644 --- a/agent/internal/features/upgrade/upgrader.go +++ b/agent/internal/features/upgrade/upgrader.go @@ -56,7 +56,7 @@ func CheckAndUpdate(databasusHost, currentVersion string, isDev bool, log Logger return fmt.Errorf("failed to download update: %w", err) } - if err := os.Chmod(tempPath, 0755); err != nil { + if err := os.Chmod(tempPath, 0o755); err != nil { return fmt.Errorf("failed to set permissions on update: %w", err) } diff --git a/backend/cmd/main.go b/backend/cmd/main.go index 1cfc726..611eaad 100644 --- a/backend/cmd/main.go +++ b/backend/cmd/main.go @@ -12,6 +12,12 @@ import ( "syscall" "time" + "github.com/gin-contrib/cors" + "github.com/gin-contrib/gzip" + "github.com/gin-gonic/gin" + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" + "databasus-backend/internal/config" "databasus-backend/internal/features/audit_logs" "databasus-backend/internal/features/backups/backups/backuping" @@ -41,12 +47,6 @@ import ( files_utils "databasus-backend/internal/util/files" "databasus-backend/internal/util/logger" _ "databasus-backend/swagger" // swagger docs - - "github.com/gin-contrib/cors" - "github.com/gin-contrib/gzip" - "github.com/gin-gonic/gin" - swaggerFiles "github.com/swaggo/files" - ginSwagger "github.com/swaggo/gin-swagger" ) // @title Databasus Backend API @@ -83,7 +83,6 @@ func main() { config.GetEnv().TempFolder, config.GetEnv().DataFolder, }) - if err != nil { log.Error("Failed to ensure directories", "error", err) os.Exit(1) @@ -150,7 +149,7 @@ func handlePasswordReset(log *slog.Logger) { resetPassword(*email, *newPassword, log) } -func resetPassword(email string, newPassword string, log *slog.Logger) { +func resetPassword(email, newPassword string, log *slog.Logger) { log.Info("Resetting password...") userService := users_services.GetUserService() diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go index e2f7ff0..3ca4a06 100644 --- a/backend/internal/config/config.go +++ b/backend/internal/config/config.go @@ -1,9 +1,6 @@ package config import ( - env_utils "databasus-backend/internal/util/env" - "databasus-backend/internal/util/logger" - "databasus-backend/internal/util/tools" "os" "path/filepath" "strings" @@ -11,6 +8,10 @@ import ( "github.com/ilyakaznacheev/cleanenv" "github.com/joho/godotenv" + + env_utils "databasus-backend/internal/util/env" + "databasus-backend/internal/util/logger" + "databasus-backend/internal/util/tools" ) var log = logger.GetLogger() diff --git a/backend/internal/features/audit_logs/background_service_test.go b/backend/internal/features/audit_logs/background_service_test.go index 8ed1af3..9087010 100644 --- a/backend/internal/features/audit_logs/background_service_test.go +++ b/backend/internal/features/audit_logs/background_service_test.go @@ -1,17 +1,17 @@ package audit_logs import ( - "databasus-backend/internal/storage" "fmt" "testing" "time" - user_enums "databasus-backend/internal/features/users/enums" - users_testing "databasus-backend/internal/features/users/testing" - "github.com/google/uuid" "github.com/stretchr/testify/assert" "gorm.io/gorm" + + user_enums "databasus-backend/internal/features/users/enums" + users_testing "databasus-backend/internal/features/users/testing" + "databasus-backend/internal/storage" ) func Test_CleanOldAuditLogs_DeletesLogsOlderThanOneYear(t *testing.T) { diff --git a/backend/internal/features/audit_logs/controller.go b/backend/internal/features/audit_logs/controller.go index e0a641f..f4961a9 100644 --- a/backend/internal/features/audit_logs/controller.go +++ b/backend/internal/features/audit_logs/controller.go @@ -4,10 +4,10 @@ import ( "errors" "net/http" - user_models "databasus-backend/internal/features/users/models" - "github.com/gin-gonic/gin" "github.com/google/uuid" + + user_models "databasus-backend/internal/features/users/models" ) type AuditLogController struct { diff --git a/backend/internal/features/audit_logs/controller_test.go b/backend/internal/features/audit_logs/controller_test.go index d1fc228..89a0a10 100644 --- a/backend/internal/features/audit_logs/controller_test.go +++ b/backend/internal/features/audit_logs/controller_test.go @@ -6,15 +6,15 @@ import ( "testing" "time" + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + user_enums "databasus-backend/internal/features/users/enums" users_middleware "databasus-backend/internal/features/users/middleware" users_services "databasus-backend/internal/features/users/services" users_testing "databasus-backend/internal/features/users/testing" test_utils "databasus-backend/internal/util/testing" - - "github.com/gin-gonic/gin" - "github.com/google/uuid" - "github.com/stretchr/testify/assert" ) func Test_GetGlobalAuditLogs_WithDifferentUserRoles_EnforcesPermissionsCorrectly(t *testing.T) { diff --git a/backend/internal/features/audit_logs/di.go b/backend/internal/features/audit_logs/di.go index 135945d..9264780 100644 --- a/backend/internal/features/audit_logs/di.go +++ b/backend/internal/features/audit_logs/di.go @@ -8,14 +8,18 @@ import ( "databasus-backend/internal/util/logger" ) -var auditLogRepository = &AuditLogRepository{} -var auditLogService = &AuditLogService{ - auditLogRepository, - logger.GetLogger(), -} +var ( + auditLogRepository = &AuditLogRepository{} + auditLogService = &AuditLogService{ + auditLogRepository, + logger.GetLogger(), + } +) + var auditLogController = &AuditLogController{ auditLogService, } + var auditLogBackgroundService = &AuditLogBackgroundService{ auditLogService: auditLogService, logger: logger.GetLogger(), diff --git a/backend/internal/features/audit_logs/repository.go b/backend/internal/features/audit_logs/repository.go index 9823519..d6264c8 100644 --- a/backend/internal/features/audit_logs/repository.go +++ b/backend/internal/features/audit_logs/repository.go @@ -1,10 +1,11 @@ package audit_logs import ( - "databasus-backend/internal/storage" "time" "github.com/google/uuid" + + "databasus-backend/internal/storage" ) type AuditLogRepository struct{} @@ -21,7 +22,7 @@ func (r *AuditLogRepository) GetGlobal( limit, offset int, beforeDate *time.Time, ) ([]*AuditLogDTO, error) { - var auditLogs = make([]*AuditLogDTO, 0) + auditLogs := make([]*AuditLogDTO, 0) sql := ` SELECT @@ -57,7 +58,7 @@ func (r *AuditLogRepository) GetByUser( limit, offset int, beforeDate *time.Time, ) ([]*AuditLogDTO, error) { - var auditLogs = make([]*AuditLogDTO, 0) + auditLogs := make([]*AuditLogDTO, 0) sql := ` SELECT @@ -94,7 +95,7 @@ func (r *AuditLogRepository) GetByWorkspace( limit, offset int, beforeDate *time.Time, ) ([]*AuditLogDTO, error) { - var auditLogs = make([]*AuditLogDTO, 0) + auditLogs := make([]*AuditLogDTO, 0) sql := ` SELECT diff --git a/backend/internal/features/audit_logs/service.go b/backend/internal/features/audit_logs/service.go index 39e6bc7..87df9f9 100644 --- a/backend/internal/features/audit_logs/service.go +++ b/backend/internal/features/audit_logs/service.go @@ -4,10 +4,10 @@ import ( "log/slog" "time" + "github.com/google/uuid" + user_enums "databasus-backend/internal/features/users/enums" user_models "databasus-backend/internal/features/users/models" - - "github.com/google/uuid" ) type AuditLogService struct { diff --git a/backend/internal/features/audit_logs/service_test.go b/backend/internal/features/audit_logs/service_test.go index c2874d7..764f48c 100644 --- a/backend/internal/features/audit_logs/service_test.go +++ b/backend/internal/features/audit_logs/service_test.go @@ -4,11 +4,11 @@ import ( "testing" "time" - user_enums "databasus-backend/internal/features/users/enums" - users_testing "databasus-backend/internal/features/users/testing" - "github.com/google/uuid" "github.com/stretchr/testify/assert" + + user_enums "databasus-backend/internal/features/users/enums" + users_testing "databasus-backend/internal/features/users/testing" ) func Test_AuditLogs_WorkspaceSpecificLogs(t *testing.T) { diff --git a/backend/internal/features/backups/backups/backuping/backuper_test.go b/backend/internal/features/backups/backups/backuping/backuper_test.go index 45b9ed6..eb1c470 100644 --- a/backend/internal/features/backups/backups/backuping/backuper_test.go +++ b/backend/internal/features/backups/backups/backuping/backuper_test.go @@ -5,6 +5,9 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + backups_core "databasus-backend/internal/features/backups/backups/core" backups_config "databasus-backend/internal/features/backups/config" "databasus-backend/internal/features/databases" @@ -14,9 +17,6 @@ import ( users_testing "databasus-backend/internal/features/users/testing" workspaces_testing "databasus-backend/internal/features/workspaces/testing" cache_utils "databasus-backend/internal/util/cache" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" ) func Test_BackupExecuted_NotificationSent(t *testing.T) { diff --git a/backend/internal/features/backups/backups/backuping/cleaner_gfs_test.go b/backend/internal/features/backups/backups/backuping/cleaner_gfs_test.go index 551db5c..995a72a 100644 --- a/backend/internal/features/backups/backups/backuping/cleaner_gfs_test.go +++ b/backend/internal/features/backups/backups/backuping/cleaner_gfs_test.go @@ -5,6 +5,9 @@ import ( "testing" "time" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + backups_core "databasus-backend/internal/features/backups/backups/core" backups_config "databasus-backend/internal/features/backups/config" "databasus-backend/internal/features/databases" @@ -13,9 +16,6 @@ import ( users_enums "databasus-backend/internal/features/users/enums" users_testing "databasus-backend/internal/features/users/testing" workspaces_testing "databasus-backend/internal/features/workspaces/testing" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" ) func Test_BuildGFSKeepSet(t *testing.T) { diff --git a/backend/internal/features/backups/backups/backuping/cleaner_test.go b/backend/internal/features/backups/backups/backuping/cleaner_test.go index bb8bc0e..c8995dd 100644 --- a/backend/internal/features/backups/backups/backuping/cleaner_test.go +++ b/backend/internal/features/backups/backups/backuping/cleaner_test.go @@ -4,6 +4,9 @@ import ( "testing" "time" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + backups_core "databasus-backend/internal/features/backups/backups/core" backups_config "databasus-backend/internal/features/backups/config" "databasus-backend/internal/features/databases" @@ -15,9 +18,6 @@ import ( workspaces_testing "databasus-backend/internal/features/workspaces/testing" "databasus-backend/internal/storage" "databasus-backend/internal/util/period" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" ) func Test_CleanOldBackups_DeletesBackupsOlderThanRetentionTimePeriod(t *testing.T) { diff --git a/backend/internal/features/backups/backups/backuping/mocks.go b/backend/internal/features/backups/backups/backuping/mocks.go index 83065e1..e453ce3 100644 --- a/backend/internal/features/backups/backups/backuping/mocks.go +++ b/backend/internal/features/backups/backups/backuping/mocks.go @@ -6,15 +6,15 @@ import ( "sync/atomic" "time" + "github.com/google/uuid" + "github.com/stretchr/testify/mock" + common "databasus-backend/internal/features/backups/backups/common" backups_core "databasus-backend/internal/features/backups/backups/core" backups_config "databasus-backend/internal/features/backups/config" "databasus-backend/internal/features/databases" "databasus-backend/internal/features/notifiers" "databasus-backend/internal/features/storages" - - "github.com/google/uuid" - "github.com/stretchr/testify/mock" ) type MockNotificationSender struct { diff --git a/backend/internal/features/backups/backups/backuping/registry.go b/backend/internal/features/backups/backups/backuping/registry.go index 69afe38..415d41d 100644 --- a/backend/internal/features/backups/backups/backuping/registry.go +++ b/backend/internal/features/backups/backups/backuping/registry.go @@ -10,10 +10,10 @@ import ( "sync/atomic" "time" - cache_utils "databasus-backend/internal/util/cache" - "github.com/google/uuid" "github.com/valkey-io/valkey-go" + + cache_utils "databasus-backend/internal/util/cache" ) const ( @@ -415,7 +415,7 @@ func (r *BackupNodesRegistry) UnsubscribeNodeForBackupsAssignments() error { return nil } -func (r *BackupNodesRegistry) PublishBackupCompletion(nodeID uuid.UUID, backupID uuid.UUID) error { +func (r *BackupNodesRegistry) PublishBackupCompletion(nodeID, backupID uuid.UUID) error { ctx := context.Background() message := BackupCompletionMessage{ @@ -437,7 +437,7 @@ func (r *BackupNodesRegistry) PublishBackupCompletion(nodeID uuid.UUID, backupID } func (r *BackupNodesRegistry) SubscribeForBackupsCompletions( - handler func(nodeID uuid.UUID, backupID uuid.UUID), + handler func(nodeID, backupID uuid.UUID), ) error { ctx := context.Background() diff --git a/backend/internal/features/backups/backups/backuping/registry_test.go b/backend/internal/features/backups/backups/backuping/registry_test.go index cefd995..3c614fd 100644 --- a/backend/internal/features/backups/backups/backuping/registry_test.go +++ b/backend/internal/features/backups/backups/backuping/registry_test.go @@ -9,11 +9,11 @@ import ( "testing" "time" - cache_utils "databasus-backend/internal/util/cache" - "databasus-backend/internal/util/logger" - "github.com/google/uuid" "github.com/stretchr/testify/assert" + + cache_utils "databasus-backend/internal/util/cache" + "databasus-backend/internal/util/logger" ) func Test_HearthbeatNodeInRegistry_RegistersNodeWithTTL(t *testing.T) { @@ -903,7 +903,7 @@ func Test_SubscribeForBackupsCompletions_ReceivesCompletedBackups(t *testing.T) receivedBackupID := make(chan uuid.UUID, 1) receivedNodeID := make(chan uuid.UUID, 1) - handler := func(nodeID uuid.UUID, backupID uuid.UUID) { + handler := func(nodeID, backupID uuid.UUID) { receivedNodeID <- nodeID receivedBackupID <- backupID } @@ -940,7 +940,7 @@ func Test_SubscribeForBackupsCompletions_ParsesJsonCorrectly(t *testing.T) { defer registry.UnsubscribeForBackupsCompletions() receivedBackups := make(chan uuid.UUID, 2) - handler := func(nodeID uuid.UUID, backupID uuid.UUID) { + handler := func(nodeID, backupID uuid.UUID) { receivedBackups <- backupID } @@ -969,7 +969,7 @@ func Test_SubscribeForBackupsCompletions_HandlesInvalidJson(t *testing.T) { defer registry.UnsubscribeForBackupsCompletions() receivedBackupID := make(chan uuid.UUID, 1) - handler := func(nodeID uuid.UUID, backupID uuid.UUID) { + handler := func(nodeID, backupID uuid.UUID) { receivedBackupID <- backupID } @@ -997,7 +997,7 @@ func Test_UnsubscribeForBackupsCompletions_StopsReceivingMessages(t *testing.T) backupID2 := uuid.New() receivedBackupID := make(chan uuid.UUID, 2) - handler := func(nodeID uuid.UUID, backupID uuid.UUID) { + handler := func(nodeID, backupID uuid.UUID) { receivedBackupID <- backupID } @@ -1032,7 +1032,7 @@ func Test_SubscribeForBackupsCompletions_WhenAlreadySubscribed_ReturnsError(t *t registry := createTestRegistry() defer registry.UnsubscribeForBackupsCompletions() - handler := func(nodeID uuid.UUID, backupID uuid.UUID) {} + handler := func(nodeID, backupID uuid.UUID) {} err := registry.SubscribeForBackupsCompletions(handler) assert.NoError(t, err) @@ -1064,9 +1064,9 @@ func Test_MultipleSubscribers_EachReceivesCompletionMessages(t *testing.T) { receivedBackups2 := make(chan uuid.UUID, 3) receivedBackups3 := make(chan uuid.UUID, 3) - handler1 := func(nodeID uuid.UUID, backupID uuid.UUID) { receivedBackups1 <- backupID } - handler2 := func(nodeID uuid.UUID, backupID uuid.UUID) { receivedBackups2 <- backupID } - handler3 := func(nodeID uuid.UUID, backupID uuid.UUID) { receivedBackups3 <- backupID } + handler1 := func(nodeID, backupID uuid.UUID) { receivedBackups1 <- backupID } + handler2 := func(nodeID, backupID uuid.UUID) { receivedBackups2 <- backupID } + handler3 := func(nodeID, backupID uuid.UUID) { receivedBackups3 <- backupID } err := registry1.SubscribeForBackupsCompletions(handler1) assert.NoError(t, err) diff --git a/backend/internal/features/backups/backups/backuping/scheduler.go b/backend/internal/features/backups/backups/backuping/scheduler.go index 9113609..4e032c9 100644 --- a/backend/internal/features/backups/backups/backuping/scheduler.go +++ b/backend/internal/features/backups/backups/backuping/scheduler.go @@ -441,7 +441,7 @@ func (s *BackupsScheduler) calculateLeastBusyNode() (*uuid.UUID, error) { return &bestNode.ID, nil } -func (s *BackupsScheduler) onBackupCompleted(nodeID uuid.UUID, backupID uuid.UUID) { +func (s *BackupsScheduler) onBackupCompleted(nodeID, backupID uuid.UUID) { // Verify this task is actually a backup (registry contains multiple task types) _, err := s.backupRepository.FindByID(backupID) if err != nil { diff --git a/backend/internal/features/backups/backups/backuping/scheduler_test.go b/backend/internal/features/backups/backups/backuping/scheduler_test.go index e3a36dd..75bc16d 100644 --- a/backend/internal/features/backups/backups/backuping/scheduler_test.go +++ b/backend/internal/features/backups/backups/backuping/scheduler_test.go @@ -1,6 +1,12 @@ package backuping import ( + "testing" + "time" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + backups_core "databasus-backend/internal/features/backups/backups/core" backups_config "databasus-backend/internal/features/backups/config" "databasus-backend/internal/features/databases" @@ -12,11 +18,6 @@ import ( workspaces_testing "databasus-backend/internal/features/workspaces/testing" cache_utils "databasus-backend/internal/util/cache" "databasus-backend/internal/util/period" - "testing" - "time" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" ) func Test_RunPendingBackups_WhenLastBackupWasYesterday_CreatesNewBackup(t *testing.T) { diff --git a/backend/internal/features/backups/backups/backuping/testing.go b/backend/internal/features/backups/backups/backuping/testing.go index e98be60..edd816c 100644 --- a/backend/internal/features/backups/backups/backuping/testing.go +++ b/backend/internal/features/backups/backups/backuping/testing.go @@ -8,6 +8,9 @@ import ( "testing" "time" + "github.com/gin-gonic/gin" + "github.com/google/uuid" + backups_core "databasus-backend/internal/features/backups/backups/core" "databasus-backend/internal/features/backups/backups/usecases" backups_config "databasus-backend/internal/features/backups/config" @@ -19,9 +22,6 @@ import ( workspaces_testing "databasus-backend/internal/features/workspaces/testing" "databasus-backend/internal/util/encryption" "databasus-backend/internal/util/logger" - - "github.com/gin-gonic/gin" - "github.com/google/uuid" ) func CreateTestRouter() *gin.Engine { diff --git a/backend/internal/features/backups/backups/common/dto.go b/backend/internal/features/backups/backups/common/dto.go index b453622..a0782ca 100644 --- a/backend/internal/features/backups/backups/common/dto.go +++ b/backend/internal/features/backups/backups/common/dto.go @@ -1,10 +1,11 @@ package common import ( - backups_config "databasus-backend/internal/features/backups/config" "errors" "github.com/google/uuid" + + backups_config "databasus-backend/internal/features/backups/config" ) type BackupMetadata struct { diff --git a/backend/internal/features/backups/backups/controllers/controller.go b/backend/internal/features/backups/backups/controllers/controller.go index 8530816..2e7d9d2 100644 --- a/backend/internal/features/backups/backups/controllers/controller.go +++ b/backend/internal/features/backups/backups/controllers/controller.go @@ -2,13 +2,6 @@ package backups_controllers import ( "context" - backups_core "databasus-backend/internal/features/backups/backups/core" - backups_download "databasus-backend/internal/features/backups/backups/download" - backups_dto "databasus-backend/internal/features/backups/backups/dto" - backups_services "databasus-backend/internal/features/backups/backups/services" - "databasus-backend/internal/features/databases" - users_middleware "databasus-backend/internal/features/users/middleware" - files_utils "databasus-backend/internal/util/files" "fmt" "io" "net/http" @@ -16,6 +9,14 @@ import ( "github.com/gin-gonic/gin" "github.com/google/uuid" + + backups_core "databasus-backend/internal/features/backups/backups/core" + backups_download "databasus-backend/internal/features/backups/backups/download" + backups_dto "databasus-backend/internal/features/backups/backups/dto" + backups_services "databasus-backend/internal/features/backups/backups/services" + "databasus-backend/internal/features/databases" + users_middleware "databasus-backend/internal/features/users/middleware" + files_utils "databasus-backend/internal/util/files" ) type BackupController struct { diff --git a/backend/internal/features/backups/backups/controllers/postgres_wal_controller.go b/backend/internal/features/backups/backups/controllers/postgres_wal_controller.go index 4de5c53..d14706d 100644 --- a/backend/internal/features/backups/backups/controllers/postgres_wal_controller.go +++ b/backend/internal/features/backups/backups/controllers/postgres_wal_controller.go @@ -5,13 +5,13 @@ import ( "net/http" "strconv" + "github.com/gin-gonic/gin" + "github.com/google/uuid" + backups_core "databasus-backend/internal/features/backups/backups/core" backups_dto "databasus-backend/internal/features/backups/backups/dto" backups_services "databasus-backend/internal/features/backups/backups/services" "databasus-backend/internal/features/databases" - - "github.com/gin-gonic/gin" - "github.com/google/uuid" ) // PostgreWalBackupController handles WAL backup endpoints used by the databasus-cli agent. diff --git a/backend/internal/features/backups/backups/controllers/postgres_wal_controller_test.go b/backend/internal/features/backups/backups/controllers/postgres_wal_controller_test.go index da77d40..15764c9 100644 --- a/backend/internal/features/backups/backups/controllers/postgres_wal_controller_test.go +++ b/backend/internal/features/backups/backups/controllers/postgres_wal_controller_test.go @@ -10,6 +10,11 @@ import ( "testing" "time" + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + backups_core "databasus-backend/internal/features/backups/backups/core" backups_dto "databasus-backend/internal/features/backups/backups/dto" backups_config "databasus-backend/internal/features/backups/config" @@ -23,11 +28,6 @@ import ( workspaces_controllers "databasus-backend/internal/features/workspaces/controllers" workspaces_testing "databasus-backend/internal/features/workspaces/testing" test_utils "databasus-backend/internal/util/testing" - - "github.com/gin-gonic/gin" - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func Test_WalUpload_InProgressStatusSetBeforeStream(t *testing.T) { diff --git a/backend/internal/features/backups/backups/controllers/testing.go b/backend/internal/features/backups/backups/controllers/testing.go index 71fa33d..25d2c85 100644 --- a/backend/internal/features/backups/backups/controllers/testing.go +++ b/backend/internal/features/backups/backups/controllers/testing.go @@ -4,14 +4,14 @@ import ( "testing" "time" + "github.com/gin-gonic/gin" + "github.com/google/uuid" + backups_core "databasus-backend/internal/features/backups/backups/core" backups_config "databasus-backend/internal/features/backups/config" "databasus-backend/internal/features/databases" workspaces_controllers "databasus-backend/internal/features/workspaces/controllers" workspaces_testing "databasus-backend/internal/features/workspaces/testing" - - "github.com/gin-gonic/gin" - "github.com/google/uuid" ) func CreateTestRouter() *gin.Engine { diff --git a/backend/internal/features/backups/backups/core/model.go b/backend/internal/features/backups/backups/core/model.go index cd8e214..9a4123a 100644 --- a/backend/internal/features/backups/backups/core/model.go +++ b/backend/internal/features/backups/backups/core/model.go @@ -4,10 +4,10 @@ import ( "fmt" "time" + "github.com/google/uuid" + backups_config "databasus-backend/internal/features/backups/config" files_utils "databasus-backend/internal/util/files" - - "github.com/google/uuid" ) type PgWalBackupType string diff --git a/backend/internal/features/backups/backups/core/repository.go b/backend/internal/features/backups/backups/core/repository.go index a1c8247..da506d2 100644 --- a/backend/internal/features/backups/backups/core/repository.go +++ b/backend/internal/features/backups/backups/core/repository.go @@ -1,13 +1,13 @@ package backups_core import ( - "databasus-backend/internal/storage" "errors" - "time" "github.com/google/uuid" "gorm.io/gorm" + + "databasus-backend/internal/storage" ) type BackupRepository struct{} diff --git a/backend/internal/features/backups/backups/download/di.go b/backend/internal/features/backups/backups/download/di.go index 5181ef1..80c72b6 100644 --- a/backend/internal/features/backups/backups/download/di.go +++ b/backend/internal/features/backups/backups/download/di.go @@ -13,9 +13,11 @@ var downloadTokenRepository = &DownloadTokenRepository{} var downloadTracker = NewDownloadTracker(cache_utils.GetValkeyClient()) -var bandwidthManager *BandwidthManager -var downloadTokenService *DownloadTokenService -var downloadTokenBackgroundService *DownloadTokenBackgroundService +var ( + bandwidthManager *BandwidthManager + downloadTokenService *DownloadTokenService + downloadTokenBackgroundService *DownloadTokenBackgroundService +) func init() { env := config.GetEnv() diff --git a/backend/internal/features/backups/backups/download/repository.go b/backend/internal/features/backups/backups/download/repository.go index 359846f..c1e5707 100644 --- a/backend/internal/features/backups/backups/download/repository.go +++ b/backend/internal/features/backups/backups/download/repository.go @@ -2,12 +2,13 @@ package backups_download import ( "crypto/rand" - "databasus-backend/internal/storage" "encoding/base64" "time" "github.com/google/uuid" "gorm.io/gorm" + + "databasus-backend/internal/storage" ) type DownloadTokenRepository struct{} @@ -28,7 +29,6 @@ func (r *DownloadTokenRepository) FindByToken(token string) (*DownloadToken, err err := storage.GetDb(). Where("token = ?", token). First(&downloadToken).Error - if err != nil { if err == gorm.ErrRecordNotFound { return nil, nil diff --git a/backend/internal/features/backups/backups/download/tracking.go b/backend/internal/features/backups/backups/download/tracking.go index 64fc6eb..a14ca31 100644 --- a/backend/internal/features/backups/backups/download/tracking.go +++ b/backend/internal/features/backups/backups/download/tracking.go @@ -1,12 +1,13 @@ package backups_download import ( - cache_utils "databasus-backend/internal/util/cache" "errors" "time" "github.com/google/uuid" "github.com/valkey-io/valkey-go" + + cache_utils "databasus-backend/internal/util/cache" ) const ( @@ -16,9 +17,7 @@ const ( downloadHeartbeatDelay = 3 * time.Second ) -var ( - ErrDownloadAlreadyInProgress = errors.New("download already in progress for this user") -) +var ErrDownloadAlreadyInProgress = errors.New("download already in progress for this user") type DownloadTracker struct { cache *cache_utils.CacheUtil[string] diff --git a/backend/internal/features/backups/backups/dto/dto.go b/backend/internal/features/backups/backups/dto/dto.go index f6813d3..805c646 100644 --- a/backend/internal/features/backups/backups/dto/dto.go +++ b/backend/internal/features/backups/backups/dto/dto.go @@ -1,12 +1,13 @@ package backups_dto import ( - backups_core "databasus-backend/internal/features/backups/backups/core" - "databasus-backend/internal/features/backups/backups/encryption" "io" "time" "github.com/google/uuid" + + backups_core "databasus-backend/internal/features/backups/backups/core" + "databasus-backend/internal/features/backups/backups/encryption" ) type GetBackupsRequest struct { diff --git a/backend/internal/features/backups/backups/services/di.go b/backend/internal/features/backups/backups/services/di.go index 2613f39..a0b5cfc 100644 --- a/backend/internal/features/backups/backups/services/di.go +++ b/backend/internal/features/backups/backups/services/di.go @@ -1,6 +1,9 @@ package backups_services import ( + "sync" + "sync/atomic" + audit_logs "databasus-backend/internal/features/audit_logs" "databasus-backend/internal/features/backups/backups/backuping" backups_core "databasus-backend/internal/features/backups/backups/core" @@ -15,8 +18,6 @@ import ( workspaces_services "databasus-backend/internal/features/workspaces/services" "databasus-backend/internal/util/encryption" "databasus-backend/internal/util/logger" - "sync" - "sync/atomic" ) var taskCancelManager = task_cancellation.GetTaskCancelManager() diff --git a/backend/internal/features/backups/backups/services/postgres_wal_service.go b/backend/internal/features/backups/backups/services/postgres_wal_service.go index b906254..7e3bba1 100644 --- a/backend/internal/features/backups/backups/services/postgres_wal_service.go +++ b/backend/internal/features/backups/backups/services/postgres_wal_service.go @@ -7,6 +7,8 @@ import ( "log/slog" "time" + "github.com/google/uuid" + backups_core "databasus-backend/internal/features/backups/backups/core" backups_dto "databasus-backend/internal/features/backups/backups/dto" backup_encryption "databasus-backend/internal/features/backups/backups/encryption" @@ -16,8 +18,6 @@ import ( encryption_secrets "databasus-backend/internal/features/encryption/secrets" util_encryption "databasus-backend/internal/util/encryption" util_wal "databasus-backend/internal/util/wal" - - "github.com/google/uuid" ) // PostgreWalBackupService handles WAL segment and basebackup uploads from the databasus-cli agent. @@ -609,5 +609,5 @@ func (cr *countingReader) Read(p []byte) (n int, err error) { n, err = cr.r.Read(p) cr.n += int64(n) - return + return n, err } diff --git a/backend/internal/features/backups/backups/services/service.go b/backend/internal/features/backups/backups/services/service.go index 1cd738e..026999f 100644 --- a/backend/internal/features/backups/backups/services/service.go +++ b/backend/internal/features/backups/backups/services/service.go @@ -7,6 +7,8 @@ import ( "io" "log/slog" + "github.com/google/uuid" + audit_logs "databasus-backend/internal/features/audit_logs" "databasus-backend/internal/features/backups/backups/backuping" backups_core "databasus-backend/internal/features/backups/backups/core" @@ -23,8 +25,6 @@ import ( workspaces_services "databasus-backend/internal/features/workspaces/services" util_encryption "databasus-backend/internal/util/encryption" files_utils "databasus-backend/internal/util/files" - - "github.com/google/uuid" ) type BackupService struct { diff --git a/backend/internal/features/backups/backups/usecases/mariadb/create_backup_uc.go b/backend/internal/features/backups/backups/usecases/mariadb/create_backup_uc.go index 09ba178..d36ddd1 100644 --- a/backend/internal/features/backups/backups/usecases/mariadb/create_backup_uc.go +++ b/backend/internal/features/backups/backups/usecases/mariadb/create_backup_uc.go @@ -279,10 +279,10 @@ func (uc *CreateMariadbBackupUsecase) createTempMyCnfFile( password string, ) (string, error) { tempFolder := config.GetEnv().TempFolder - if err := os.MkdirAll(tempFolder, 0700); err != nil { + if err := os.MkdirAll(tempFolder, 0o700); err != nil { return "", fmt.Errorf("failed to ensure temp folder exists: %w", err) } - if err := os.Chmod(tempFolder, 0700); err != nil { + if err := os.Chmod(tempFolder, 0o700); err != nil { return "", fmt.Errorf("failed to set temp folder permissions: %w", err) } @@ -291,7 +291,7 @@ func (uc *CreateMariadbBackupUsecase) createTempMyCnfFile( return "", fmt.Errorf("failed to create temp directory: %w", err) } - if err := os.Chmod(tempDir, 0700); err != nil { + if err := os.Chmod(tempDir, 0o700); err != nil { _ = os.RemoveAll(tempDir) return "", fmt.Errorf("failed to set temp directory permissions: %w", err) } @@ -311,7 +311,7 @@ port=%d content += "ssl=false\n" } - err = os.WriteFile(myCnfFile, []byte(content), 0600) + err = os.WriteFile(myCnfFile, []byte(content), 0o600) if err != nil { _ = os.RemoveAll(tempDir) return "", fmt.Errorf("failed to write .my.cnf: %w", err) diff --git a/backend/internal/features/backups/backups/usecases/mysql/create_backup_uc.go b/backend/internal/features/backups/backups/usecases/mysql/create_backup_uc.go index f729503..083fac9 100644 --- a/backend/internal/features/backups/backups/usecases/mysql/create_backup_uc.go +++ b/backend/internal/features/backups/backups/usecases/mysql/create_backup_uc.go @@ -298,10 +298,10 @@ func (uc *CreateMysqlBackupUsecase) createTempMyCnfFile( password string, ) (string, error) { tempFolder := config.GetEnv().TempFolder - if err := os.MkdirAll(tempFolder, 0700); err != nil { + if err := os.MkdirAll(tempFolder, 0o700); err != nil { return "", fmt.Errorf("failed to ensure temp folder exists: %w", err) } - if err := os.Chmod(tempFolder, 0700); err != nil { + if err := os.Chmod(tempFolder, 0o700); err != nil { return "", fmt.Errorf("failed to set temp folder permissions: %w", err) } @@ -310,7 +310,7 @@ func (uc *CreateMysqlBackupUsecase) createTempMyCnfFile( return "", fmt.Errorf("failed to create temp directory: %w", err) } - if err := os.Chmod(tempDir, 0700); err != nil { + if err := os.Chmod(tempDir, 0o700); err != nil { _ = os.RemoveAll(tempDir) return "", fmt.Errorf("failed to set temp directory permissions: %w", err) } @@ -328,7 +328,7 @@ port=%d content += "ssl-mode=REQUIRED\n" } - err = os.WriteFile(myCnfFile, []byte(content), 0600) + err = os.WriteFile(myCnfFile, []byte(content), 0o600) if err != nil { _ = os.RemoveAll(tempDir) return "", fmt.Errorf("failed to write .my.cnf: %w", err) diff --git a/backend/internal/features/backups/backups/usecases/postgresql/create_backup_uc.go b/backend/internal/features/backups/backups/usecases/postgresql/create_backup_uc.go index b656478..df0400e 100644 --- a/backend/internal/features/backups/backups/usecases/postgresql/create_backup_uc.go +++ b/backend/internal/features/backups/backups/usecases/postgresql/create_backup_uc.go @@ -13,6 +13,8 @@ import ( "strings" "time" + "github.com/google/uuid" + "databasus-backend/internal/config" common "databasus-backend/internal/features/backups/backups/common" backups_core "databasus-backend/internal/features/backups/backups/core" @@ -24,8 +26,6 @@ import ( "databasus-backend/internal/features/storages" "databasus-backend/internal/util/encryption" "databasus-backend/internal/util/tools" - - "github.com/google/uuid" ) const ( @@ -748,10 +748,10 @@ func (uc *CreatePostgresqlBackupUsecase) createTempPgpassFile( ) tempFolder := config.GetEnv().TempFolder - if err := os.MkdirAll(tempFolder, 0700); err != nil { + if err := os.MkdirAll(tempFolder, 0o700); err != nil { return "", fmt.Errorf("failed to ensure temp folder exists: %w", err) } - if err := os.Chmod(tempFolder, 0700); err != nil { + if err := os.Chmod(tempFolder, 0o700); err != nil { return "", fmt.Errorf("failed to set temp folder permissions: %w", err) } @@ -760,13 +760,13 @@ func (uc *CreatePostgresqlBackupUsecase) createTempPgpassFile( return "", fmt.Errorf("failed to create temporary directory: %w", err) } - if err := os.Chmod(tempDir, 0700); err != nil { + if err := os.Chmod(tempDir, 0o700); err != nil { _ = os.RemoveAll(tempDir) return "", fmt.Errorf("failed to set temporary directory permissions: %w", err) } pgpassFile := filepath.Join(tempDir, ".pgpass") - err = os.WriteFile(pgpassFile, []byte(pgpassContent), 0600) + err = os.WriteFile(pgpassFile, []byte(pgpassContent), 0o600) if err != nil { _ = os.RemoveAll(tempDir) return "", fmt.Errorf("failed to write temporary .pgpass file: %w", err) diff --git a/backend/internal/features/backups/config/controller.go b/backend/internal/features/backups/config/controller.go index 8c82be2..943f1fd 100644 --- a/backend/internal/features/backups/config/controller.go +++ b/backend/internal/features/backups/config/controller.go @@ -4,10 +4,10 @@ import ( "errors" "net/http" - users_middleware "databasus-backend/internal/features/users/middleware" - "github.com/gin-gonic/gin" "github.com/google/uuid" + + users_middleware "databasus-backend/internal/features/users/middleware" ) type BackupConfigController struct { diff --git a/backend/internal/features/backups/config/di.go b/backend/internal/features/backups/config/di.go index 822a76f..5ffc266 100644 --- a/backend/internal/features/backups/config/di.go +++ b/backend/internal/features/backups/config/di.go @@ -12,16 +12,19 @@ import ( "databasus-backend/internal/util/logger" ) -var backupConfigRepository = &BackupConfigRepository{} -var backupConfigService = &BackupConfigService{ - backupConfigRepository, - databases.GetDatabaseService(), - storages.GetStorageService(), - notifiers.GetNotifierService(), - workspaces_services.GetWorkspaceService(), - plans.GetDatabasePlanService(), - nil, -} +var ( + backupConfigRepository = &BackupConfigRepository{} + backupConfigService = &BackupConfigService{ + backupConfigRepository, + databases.GetDatabaseService(), + storages.GetStorageService(), + notifiers.GetNotifierService(), + workspaces_services.GetWorkspaceService(), + plans.GetDatabasePlanService(), + nil, + } +) + var backupConfigController = &BackupConfigController{ backupConfigService, } diff --git a/backend/internal/features/backups/config/model.go b/backend/internal/features/backups/config/model.go index d0f0430..ade396b 100644 --- a/backend/internal/features/backups/config/model.go +++ b/backend/internal/features/backups/config/model.go @@ -1,16 +1,17 @@ package backups_config import ( - "databasus-backend/internal/config" - "databasus-backend/internal/features/intervals" - plans "databasus-backend/internal/features/plan" - "databasus-backend/internal/features/storages" - "databasus-backend/internal/util/period" "errors" "strings" "github.com/google/uuid" "gorm.io/gorm" + + "databasus-backend/internal/config" + "databasus-backend/internal/features/intervals" + plans "databasus-backend/internal/features/plan" + "databasus-backend/internal/features/storages" + "databasus-backend/internal/util/period" ) type BackupConfig struct { diff --git a/backend/internal/features/backups/config/model_test.go b/backend/internal/features/backups/config/model_test.go index 90c6cff..6d18fba 100644 --- a/backend/internal/features/backups/config/model_test.go +++ b/backend/internal/features/backups/config/model_test.go @@ -3,12 +3,12 @@ package backups_config import ( "testing" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "databasus-backend/internal/features/intervals" plans "databasus-backend/internal/features/plan" "databasus-backend/internal/util/period" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" ) func Test_Validate_WhenRetentionTimePeriodIsWeekAndPlanAllowsMonth_ValidationPasses(t *testing.T) { diff --git a/backend/internal/features/backups/config/repository.go b/backend/internal/features/backups/config/repository.go index 2de68b4..7c31d57 100644 --- a/backend/internal/features/backups/config/repository.go +++ b/backend/internal/features/backups/config/repository.go @@ -1,11 +1,12 @@ package backups_config import ( - "databasus-backend/internal/storage" "errors" "github.com/google/uuid" "gorm.io/gorm" + + "databasus-backend/internal/storage" ) type BackupConfigRepository struct{} @@ -47,7 +48,6 @@ func (r *BackupConfigRepository) Save( return nil }) - if err != nil { return nil, err } diff --git a/backend/internal/features/backups/config/service.go b/backend/internal/features/backups/config/service.go index 090c10c..61a0493 100644 --- a/backend/internal/features/backups/config/service.go +++ b/backend/internal/features/backups/config/service.go @@ -3,6 +3,8 @@ package backups_config import ( "errors" + "github.com/google/uuid" + "databasus-backend/internal/features/databases" "databasus-backend/internal/features/intervals" "databasus-backend/internal/features/notifiers" @@ -10,8 +12,6 @@ import ( "databasus-backend/internal/features/storages" users_models "databasus-backend/internal/features/users/models" workspaces_services "databasus-backend/internal/features/workspaces/services" - - "github.com/google/uuid" ) type BackupConfigService struct { diff --git a/backend/internal/features/backups/config/testing.go b/backend/internal/features/backups/config/testing.go index 208b2ee..0e02bb8 100644 --- a/backend/internal/features/backups/config/testing.go +++ b/backend/internal/features/backups/config/testing.go @@ -1,11 +1,11 @@ package backups_config import ( + "github.com/google/uuid" + "databasus-backend/internal/features/intervals" "databasus-backend/internal/features/storages" "databasus-backend/internal/util/period" - - "github.com/google/uuid" ) func EnableBackupsForTestDatabase( diff --git a/backend/internal/features/databases/controller.go b/backend/internal/features/databases/controller.go index ec11bf8..16bc9f2 100644 --- a/backend/internal/features/databases/controller.go +++ b/backend/internal/features/databases/controller.go @@ -1,13 +1,14 @@ package databases import ( - users_middleware "databasus-backend/internal/features/users/middleware" - users_services "databasus-backend/internal/features/users/services" - workspaces_services "databasus-backend/internal/features/workspaces/services" "net/http" "github.com/gin-gonic/gin" "github.com/google/uuid" + + users_middleware "databasus-backend/internal/features/users/middleware" + users_services "databasus-backend/internal/features/users/services" + workspaces_services "databasus-backend/internal/features/workspaces/services" ) type DatabaseController struct { diff --git a/backend/internal/features/databases/controller_test.go b/backend/internal/features/databases/controller_test.go index 214cba3..496881e 100644 --- a/backend/internal/features/databases/controller_test.go +++ b/backend/internal/features/databases/controller_test.go @@ -862,7 +862,7 @@ func Test_DatabaseSensitiveDataLifecycle_AllTypes(t *testing.T) { name string databaseType DatabaseType createDatabase func(workspaceID uuid.UUID) *Database - updateDatabase func(workspaceID uuid.UUID, databaseID uuid.UUID) *Database + updateDatabase func(workspaceID, databaseID uuid.UUID) *Database verifySensitiveData func(t *testing.T, database *Database) verifyHiddenData func(t *testing.T, database *Database) }{ @@ -878,7 +878,7 @@ func Test_DatabaseSensitiveDataLifecycle_AllTypes(t *testing.T) { Postgresql: pgConfig, } }, - updateDatabase: func(workspaceID uuid.UUID, databaseID uuid.UUID) *Database { + updateDatabase: func(workspaceID, databaseID uuid.UUID) *Database { pgConfig := getTestPostgresConfig() pgConfig.Password = "" return &Database{ @@ -914,7 +914,7 @@ func Test_DatabaseSensitiveDataLifecycle_AllTypes(t *testing.T) { Mariadb: mariaConfig, } }, - updateDatabase: func(workspaceID uuid.UUID, databaseID uuid.UUID) *Database { + updateDatabase: func(workspaceID, databaseID uuid.UUID) *Database { mariaConfig := getTestMariadbConfig() mariaConfig.Password = "" return &Database{ @@ -950,7 +950,7 @@ func Test_DatabaseSensitiveDataLifecycle_AllTypes(t *testing.T) { Mongodb: mongoConfig, } }, - updateDatabase: func(workspaceID uuid.UUID, databaseID uuid.UUID) *Database { + updateDatabase: func(workspaceID, databaseID uuid.UUID) *Database { mongoConfig := getTestMongodbConfig() mongoConfig.Password = "" return &Database{ diff --git a/backend/internal/features/databases/databases/mariadb/model.go b/backend/internal/features/databases/databases/mariadb/model.go index 9a97af1..54baa4e 100644 --- a/backend/internal/features/databases/databases/mariadb/model.go +++ b/backend/internal/features/databases/databases/mariadb/model.go @@ -12,11 +12,11 @@ import ( "strings" "time" - "databasus-backend/internal/util/encryption" - "databasus-backend/internal/util/tools" - "github.com/go-sql-driver/mysql" "github.com/google/uuid" + + "databasus-backend/internal/util/encryption" + "databasus-backend/internal/util/tools" ) type MariadbDatabase struct { @@ -399,7 +399,7 @@ func HasPrivilege(privileges, priv string) bool { return false } -func (m *MariadbDatabase) buildDSN(password string, database string) string { +func (m *MariadbDatabase) buildDSN(password, database string) string { tlsConfig := "false" if m.IsHttps { diff --git a/backend/internal/features/databases/databases/mongodb/model.go b/backend/internal/features/databases/databases/mongodb/model.go index 9d2f06e..c167ec2 100644 --- a/backend/internal/features/databases/databases/mongodb/model.go +++ b/backend/internal/features/databases/databases/mongodb/model.go @@ -10,13 +10,13 @@ import ( "strings" "time" - "databasus-backend/internal/util/encryption" - "databasus-backend/internal/util/tools" - "github.com/google/uuid" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" + + "databasus-backend/internal/util/encryption" + "databasus-backend/internal/util/tools" ) type MongodbDatabase struct { @@ -434,7 +434,6 @@ func (m *MongodbDatabase) CreateReadOnlyUser( }, }}, }).Err() - if err != nil { if attempt < maxRetries-1 { continue diff --git a/backend/internal/features/databases/databases/mysql/model.go b/backend/internal/features/databases/databases/mysql/model.go index f653443..92aabb7 100644 --- a/backend/internal/features/databases/databases/mysql/model.go +++ b/backend/internal/features/databases/databases/mysql/model.go @@ -12,11 +12,11 @@ import ( "strings" "time" - "databasus-backend/internal/util/encryption" - "databasus-backend/internal/util/tools" - "github.com/go-sql-driver/mysql" "github.com/google/uuid" + + "databasus-backend/internal/util/encryption" + "databasus-backend/internal/util/tools" ) type MysqlDatabase struct { @@ -403,7 +403,7 @@ func HasPrivilege(privileges, priv string) bool { return false } -func (m *MysqlDatabase) buildDSN(password string, database string) string { +func (m *MysqlDatabase) buildDSN(password, database string) string { tlsConfig := "false" allowCleartext := "" diff --git a/backend/internal/features/databases/databases/postgresql/model.go b/backend/internal/features/databases/databases/postgresql/model.go index 7d8a0ba..01414e2 100644 --- a/backend/internal/features/databases/databases/postgresql/model.go +++ b/backend/internal/features/databases/databases/postgresql/model.go @@ -2,9 +2,6 @@ package postgresql import ( "context" - "databasus-backend/internal/config" - "databasus-backend/internal/util/encryption" - "databasus-backend/internal/util/tools" "errors" "fmt" "log/slog" @@ -16,6 +13,10 @@ import ( "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" "gorm.io/gorm" + + "databasus-backend/internal/config" + "databasus-backend/internal/util/encryption" + "databasus-backend/internal/util/tools" ) type PostgresBackupType string @@ -1112,7 +1113,7 @@ func checkBackupPermissions( } // buildConnectionStringForDB builds connection string for specific database -func buildConnectionStringForDB(p *PostgresqlDatabase, dbName string, password string) string { +func buildConnectionStringForDB(p *PostgresqlDatabase, dbName, password string) string { sslMode := "disable" if p.IsHttps { sslMode = "require" diff --git a/backend/internal/features/databases/interfaces.go b/backend/internal/features/databases/interfaces.go index 3c514f8..4851810 100644 --- a/backend/internal/features/databases/interfaces.go +++ b/backend/internal/features/databases/interfaces.go @@ -1,10 +1,11 @@ package databases import ( - "databasus-backend/internal/util/encryption" "log/slog" "github.com/google/uuid" + + "databasus-backend/internal/util/encryption" ) type DatabaseValidator interface { diff --git a/backend/internal/features/databases/model.go b/backend/internal/features/databases/model.go index 7306b59..0107dd7 100644 --- a/backend/internal/features/databases/model.go +++ b/backend/internal/features/databases/model.go @@ -2,17 +2,18 @@ package databases import ( "context" + "errors" + "log/slog" + "time" + + "github.com/google/uuid" + "databasus-backend/internal/features/databases/databases/mariadb" "databasus-backend/internal/features/databases/databases/mongodb" "databasus-backend/internal/features/databases/databases/mysql" "databasus-backend/internal/features/databases/databases/postgresql" "databasus-backend/internal/features/notifiers" "databasus-backend/internal/util/encryption" - "errors" - "log/slog" - "time" - - "github.com/google/uuid" ) type Database struct { diff --git a/backend/internal/features/databases/repository.go b/backend/internal/features/databases/repository.go index 1277c05..92cd297 100644 --- a/backend/internal/features/databases/repository.go +++ b/backend/internal/features/databases/repository.go @@ -1,15 +1,16 @@ package databases import ( + "errors" + + "github.com/google/uuid" + "gorm.io/gorm" + "databasus-backend/internal/features/databases/databases/mariadb" "databasus-backend/internal/features/databases/databases/mongodb" "databasus-backend/internal/features/databases/databases/mysql" "databasus-backend/internal/features/databases/databases/postgresql" "databasus-backend/internal/storage" - "errors" - - "github.com/google/uuid" - "gorm.io/gorm" ) type DatabaseRepository struct{} @@ -120,7 +121,6 @@ func (r *DatabaseRepository) Save(database *Database) (*Database, error) { return nil }) - if err != nil { return nil, err } diff --git a/backend/internal/features/databases/service.go b/backend/internal/features/databases/service.go index 6ad99aa..a21ed60 100644 --- a/backend/internal/features/databases/service.go +++ b/backend/internal/features/databases/service.go @@ -9,6 +9,8 @@ import ( "strings" "time" + "github.com/google/uuid" + "databasus-backend/internal/config" audit_logs "databasus-backend/internal/features/audit_logs" "databasus-backend/internal/features/databases/databases/mariadb" @@ -19,8 +21,6 @@ import ( users_models "databasus-backend/internal/features/users/models" workspaces_services "databasus-backend/internal/features/workspaces/services" "databasus-backend/internal/util/encryption" - - "github.com/google/uuid" ) type DatabaseService struct { diff --git a/backend/internal/features/databases/testing.go b/backend/internal/features/databases/testing.go index 3a0699c..0b6c305 100644 --- a/backend/internal/features/databases/testing.go +++ b/backend/internal/features/databases/testing.go @@ -4,6 +4,8 @@ import ( "fmt" "strconv" + "github.com/google/uuid" + "databasus-backend/internal/config" "databasus-backend/internal/features/databases/databases/mariadb" "databasus-backend/internal/features/databases/databases/mongodb" @@ -12,8 +14,6 @@ import ( "databasus-backend/internal/features/storages" "databasus-backend/internal/storage" "databasus-backend/internal/util/tools" - - "github.com/google/uuid" ) func GetTestPostgresConfig() *postgresql.PostgresqlDatabase { diff --git a/backend/internal/features/disk/service.go b/backend/internal/features/disk/service.go index 2b712eb..a8b573d 100644 --- a/backend/internal/features/disk/service.go +++ b/backend/internal/features/disk/service.go @@ -1,12 +1,13 @@ package disk import ( - "databasus-backend/internal/config" "fmt" "path/filepath" "runtime" "github.com/shirou/gopsutil/v4/disk" + + "databasus-backend/internal/config" ) type DiskService struct{} diff --git a/backend/internal/features/email/di.go b/backend/internal/features/email/di.go index 89841fa..7e593dd 100644 --- a/backend/internal/features/email/di.go +++ b/backend/internal/features/email/di.go @@ -5,8 +5,10 @@ import ( "databasus-backend/internal/util/logger" ) -var env = config.GetEnv() -var log = logger.GetLogger() +var ( + env = config.GetEnv() + log = logger.GetLogger() +) var emailSMTPSender = &EmailSMTPSender{ log, diff --git a/backend/internal/features/encryption/secrets/service.go b/backend/internal/features/encryption/secrets/service.go index 8bfb86d..c6edf35 100644 --- a/backend/internal/features/encryption/secrets/service.go +++ b/backend/internal/features/encryption/secrets/service.go @@ -5,12 +5,12 @@ import ( "fmt" "os" + "github.com/google/uuid" + "gorm.io/gorm" + "databasus-backend/internal/config" user_models "databasus-backend/internal/features/users/models" "databasus-backend/internal/storage" - - "github.com/google/uuid" - "gorm.io/gorm" ) type SecretKeyService struct { @@ -33,7 +33,7 @@ func (s *SecretKeyService) MigrateKeyFromDbToFileIfExist() error { } secretKeyPath := config.GetEnv().SecretKeyPath - if err := os.WriteFile(secretKeyPath, []byte(secretKey.Secret), 0600); err != nil { + if err := os.WriteFile(secretKeyPath, []byte(secretKey.Secret), 0o600); err != nil { return fmt.Errorf("failed to write secret key to file: %w", err) } @@ -54,7 +54,7 @@ func (s *SecretKeyService) GetSecretKey() (string, error) { if err != nil { if os.IsNotExist(err) { newKey := s.generateNewSecretKey() - if err := os.WriteFile(secretKeyPath, []byte(newKey), 0600); err != nil { + if err := os.WriteFile(secretKeyPath, []byte(newKey), 0o600); err != nil { return "", fmt.Errorf("failed to write new secret key: %w", err) } s.cachedKey = &newKey diff --git a/backend/internal/features/healthcheck/attempt/check_database_health_uc.go b/backend/internal/features/healthcheck/attempt/check_database_health_uc.go index d06a4b2..2d31347 100644 --- a/backend/internal/features/healthcheck/attempt/check_database_health_uc.go +++ b/backend/internal/features/healthcheck/attempt/check_database_health_uc.go @@ -1,9 +1,6 @@ package healthcheck_attempt import ( - "databasus-backend/internal/features/databases" - healthcheck_config "databasus-backend/internal/features/healthcheck/config" - "databasus-backend/internal/util/logger" "errors" "fmt" "log/slog" @@ -11,6 +8,10 @@ import ( "github.com/google/uuid" "gorm.io/gorm" + + "databasus-backend/internal/features/databases" + healthcheck_config "databasus-backend/internal/features/healthcheck/config" + "databasus-backend/internal/util/logger" ) type CheckDatabaseHealthUseCase struct { @@ -251,5 +252,4 @@ func (uc *CheckDatabaseHealthUseCase) sendDbStatusNotification( messageBody, ) } - } diff --git a/backend/internal/features/healthcheck/attempt/check_database_health_uc_test.go b/backend/internal/features/healthcheck/attempt/check_database_health_uc_test.go index fa0afa7..7c7564d 100644 --- a/backend/internal/features/healthcheck/attempt/check_database_health_uc_test.go +++ b/backend/internal/features/healthcheck/attempt/check_database_health_uc_test.go @@ -6,6 +6,9 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "databasus-backend/internal/features/databases" healthcheck_config "databasus-backend/internal/features/healthcheck/config" "databasus-backend/internal/features/notifiers" @@ -13,9 +16,6 @@ import ( users_enums "databasus-backend/internal/features/users/enums" users_testing "databasus-backend/internal/features/users/testing" workspaces_testing "databasus-backend/internal/features/workspaces/testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" ) func Test_CheckDatabaseHealthUseCase(t *testing.T) { diff --git a/backend/internal/features/healthcheck/attempt/controller.go b/backend/internal/features/healthcheck/attempt/controller.go index 3613b4a..742a5a9 100644 --- a/backend/internal/features/healthcheck/attempt/controller.go +++ b/backend/internal/features/healthcheck/attempt/controller.go @@ -1,12 +1,13 @@ package healthcheck_attempt import ( - users_middleware "databasus-backend/internal/features/users/middleware" "net/http" "time" "github.com/gin-gonic/gin" "github.com/google/uuid" + + users_middleware "databasus-backend/internal/features/users/middleware" ) type HealthcheckAttemptController struct { diff --git a/backend/internal/features/healthcheck/attempt/di.go b/backend/internal/features/healthcheck/attempt/di.go index aedfde1..f1d8d15 100644 --- a/backend/internal/features/healthcheck/attempt/di.go +++ b/backend/internal/features/healthcheck/attempt/di.go @@ -11,12 +11,14 @@ import ( "databasus-backend/internal/util/logger" ) -var healthcheckAttemptRepository = &HealthcheckAttemptRepository{} -var healthcheckAttemptService = &HealthcheckAttemptService{ - healthcheckAttemptRepository, - databases.GetDatabaseService(), - workspaces_services.GetWorkspaceService(), -} +var ( + healthcheckAttemptRepository = &HealthcheckAttemptRepository{} + healthcheckAttemptService = &HealthcheckAttemptService{ + healthcheckAttemptRepository, + databases.GetDatabaseService(), + workspaces_services.GetWorkspaceService(), + } +) var checkDatabaseHealthUseCase = &CheckDatabaseHealthUseCase{ healthcheckAttemptRepository, @@ -31,6 +33,7 @@ var healthcheckAttemptBackgroundService = &HealthcheckAttemptBackgroundService{ runOnce: sync.Once{}, hasRun: atomic.Bool{}, } + var healthcheckAttemptController = &HealthcheckAttemptController{ healthcheckAttemptService, } diff --git a/backend/internal/features/healthcheck/attempt/interfaces.go b/backend/internal/features/healthcheck/attempt/interfaces.go index 4963e2e..3d2cad6 100644 --- a/backend/internal/features/healthcheck/attempt/interfaces.go +++ b/backend/internal/features/healthcheck/attempt/interfaces.go @@ -1,10 +1,10 @@ package healthcheck_attempt import ( + "github.com/google/uuid" + "databasus-backend/internal/features/databases" "databasus-backend/internal/features/notifiers" - - "github.com/google/uuid" ) type HealthcheckAttemptSender interface { diff --git a/backend/internal/features/healthcheck/attempt/mocks.go b/backend/internal/features/healthcheck/attempt/mocks.go index 38d3f6c..68faf41 100644 --- a/backend/internal/features/healthcheck/attempt/mocks.go +++ b/backend/internal/features/healthcheck/attempt/mocks.go @@ -1,11 +1,11 @@ package healthcheck_attempt import ( - "databasus-backend/internal/features/databases" - "databasus-backend/internal/features/notifiers" - "github.com/google/uuid" "github.com/stretchr/testify/mock" + + "databasus-backend/internal/features/databases" + "databasus-backend/internal/features/notifiers" ) type MockHealthcheckAttemptSender struct { diff --git a/backend/internal/features/healthcheck/attempt/model.go b/backend/internal/features/healthcheck/attempt/model.go index 42e49ca..0516b17 100644 --- a/backend/internal/features/healthcheck/attempt/model.go +++ b/backend/internal/features/healthcheck/attempt/model.go @@ -1,10 +1,11 @@ package healthcheck_attempt import ( - "databasus-backend/internal/features/databases" "time" "github.com/google/uuid" + + "databasus-backend/internal/features/databases" ) type HealthcheckAttempt struct { diff --git a/backend/internal/features/healthcheck/attempt/repository.go b/backend/internal/features/healthcheck/attempt/repository.go index 301b989..9485d57 100644 --- a/backend/internal/features/healthcheck/attempt/repository.go +++ b/backend/internal/features/healthcheck/attempt/repository.go @@ -1,10 +1,11 @@ package healthcheck_attempt import ( - "databasus-backend/internal/storage" "time" "github.com/google/uuid" + + "databasus-backend/internal/storage" ) type HealthcheckAttemptRepository struct{} diff --git a/backend/internal/features/healthcheck/attempt/service.go b/backend/internal/features/healthcheck/attempt/service.go index 07cc381..18ad4d3 100644 --- a/backend/internal/features/healthcheck/attempt/service.go +++ b/backend/internal/features/healthcheck/attempt/service.go @@ -1,13 +1,14 @@ package healthcheck_attempt import ( - "databasus-backend/internal/features/databases" - users_models "databasus-backend/internal/features/users/models" - workspaces_services "databasus-backend/internal/features/workspaces/services" "errors" "time" "github.com/google/uuid" + + "databasus-backend/internal/features/databases" + users_models "databasus-backend/internal/features/users/models" + workspaces_services "databasus-backend/internal/features/workspaces/services" ) type HealthcheckAttemptService struct { diff --git a/backend/internal/features/healthcheck/config/controller.go b/backend/internal/features/healthcheck/config/controller.go index 8a023af..1456c2b 100644 --- a/backend/internal/features/healthcheck/config/controller.go +++ b/backend/internal/features/healthcheck/config/controller.go @@ -1,11 +1,12 @@ package healthcheck_config import ( - users_middleware "databasus-backend/internal/features/users/middleware" "net/http" "github.com/gin-gonic/gin" "github.com/google/uuid" + + users_middleware "databasus-backend/internal/features/users/middleware" ) type HealthcheckConfigController struct { diff --git a/backend/internal/features/healthcheck/config/di.go b/backend/internal/features/healthcheck/config/di.go index ce5744b..bb2a4a6 100644 --- a/backend/internal/features/healthcheck/config/di.go +++ b/backend/internal/features/healthcheck/config/di.go @@ -10,14 +10,17 @@ import ( "databasus-backend/internal/util/logger" ) -var healthcheckConfigRepository = &HealthcheckConfigRepository{} -var healthcheckConfigService = &HealthcheckConfigService{ - databases.GetDatabaseService(), - healthcheckConfigRepository, - workspaces_services.GetWorkspaceService(), - audit_logs.GetAuditLogService(), - logger.GetLogger(), -} +var ( + healthcheckConfigRepository = &HealthcheckConfigRepository{} + healthcheckConfigService = &HealthcheckConfigService{ + databases.GetDatabaseService(), + healthcheckConfigRepository, + workspaces_services.GetWorkspaceService(), + audit_logs.GetAuditLogService(), + logger.GetLogger(), + } +) + var healthcheckConfigController = &HealthcheckConfigController{ healthcheckConfigService, } diff --git a/backend/internal/features/healthcheck/config/repository.go b/backend/internal/features/healthcheck/config/repository.go index c411704..cdd46f2 100644 --- a/backend/internal/features/healthcheck/config/repository.go +++ b/backend/internal/features/healthcheck/config/repository.go @@ -1,11 +1,12 @@ package healthcheck_config import ( - "databasus-backend/internal/storage" "errors" "github.com/google/uuid" "gorm.io/gorm" + + "databasus-backend/internal/storage" ) type HealthcheckConfigRepository struct{} diff --git a/backend/internal/features/healthcheck/config/service.go b/backend/internal/features/healthcheck/config/service.go index 7809f2e..4b57ab3 100644 --- a/backend/internal/features/healthcheck/config/service.go +++ b/backend/internal/features/healthcheck/config/service.go @@ -1,15 +1,16 @@ package healthcheck_config import ( - "databasus-backend/internal/features/audit_logs" - "databasus-backend/internal/features/databases" - users_models "databasus-backend/internal/features/users/models" - workspaces_services "databasus-backend/internal/features/workspaces/services" "errors" "fmt" "log/slog" "github.com/google/uuid" + + "databasus-backend/internal/features/audit_logs" + "databasus-backend/internal/features/databases" + users_models "databasus-backend/internal/features/users/models" + workspaces_services "databasus-backend/internal/features/workspaces/services" ) type HealthcheckConfigService struct { diff --git a/backend/internal/features/notifiers/controller.go b/backend/internal/features/notifiers/controller.go index 27b78dd..ef62a9b 100644 --- a/backend/internal/features/notifiers/controller.go +++ b/backend/internal/features/notifiers/controller.go @@ -2,13 +2,13 @@ package notifiers import ( "errors" - - users_middleware "databasus-backend/internal/features/users/middleware" - workspaces_services "databasus-backend/internal/features/workspaces/services" "net/http" "github.com/gin-gonic/gin" "github.com/google/uuid" + + users_middleware "databasus-backend/internal/features/users/middleware" + workspaces_services "databasus-backend/internal/features/workspaces/services" ) type NotifierController struct { diff --git a/backend/internal/features/notifiers/controller_test.go b/backend/internal/features/notifiers/controller_test.go index 034d58f..36c0373 100644 --- a/backend/internal/features/notifiers/controller_test.go +++ b/backend/internal/features/notifiers/controller_test.go @@ -5,6 +5,10 @@ import ( "net/http" "testing" + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "databasus-backend/internal/config" audit_logs "databasus-backend/internal/features/audit_logs" discord_notifier "databasus-backend/internal/features/notifiers/models/discord" @@ -20,10 +24,6 @@ import ( workspaces_controllers "databasus-backend/internal/features/workspaces/controllers" workspaces_testing "databasus-backend/internal/features/workspaces/testing" test_utils "databasus-backend/internal/util/testing" - - "github.com/gin-gonic/gin" - "github.com/google/uuid" - "github.com/stretchr/testify/assert" ) func Test_SaveNewNotifier_NotifierReturnedViaGet(t *testing.T) { @@ -455,7 +455,7 @@ func Test_NotifierSensitiveDataLifecycle_AllTypes(t *testing.T) { name string notifierType NotifierType createNotifier func(workspaceID uuid.UUID) *Notifier - updateNotifier func(workspaceID uuid.UUID, notifierID uuid.UUID) *Notifier + updateNotifier func(workspaceID, notifierID uuid.UUID) *Notifier verifySensitiveData func(t *testing.T, notifier *Notifier) verifyHiddenData func(t *testing.T, notifier *Notifier) }{ @@ -473,7 +473,7 @@ func Test_NotifierSensitiveDataLifecycle_AllTypes(t *testing.T) { }, } }, - updateNotifier: func(workspaceID uuid.UUID, notifierID uuid.UUID) *Notifier { + updateNotifier: func(workspaceID, notifierID uuid.UUID) *Notifier { return &Notifier{ ID: notifierID, WorkspaceID: workspaceID, @@ -515,7 +515,7 @@ func Test_NotifierSensitiveDataLifecycle_AllTypes(t *testing.T) { }, } }, - updateNotifier: func(workspaceID uuid.UUID, notifierID uuid.UUID) *Notifier { + updateNotifier: func(workspaceID, notifierID uuid.UUID) *Notifier { return &Notifier{ ID: notifierID, WorkspaceID: workspaceID, @@ -557,7 +557,7 @@ func Test_NotifierSensitiveDataLifecycle_AllTypes(t *testing.T) { }, } }, - updateNotifier: func(workspaceID uuid.UUID, notifierID uuid.UUID) *Notifier { + updateNotifier: func(workspaceID, notifierID uuid.UUID) *Notifier { return &Notifier{ ID: notifierID, WorkspaceID: workspaceID, @@ -595,7 +595,7 @@ func Test_NotifierSensitiveDataLifecycle_AllTypes(t *testing.T) { }, } }, - updateNotifier: func(workspaceID uuid.UUID, notifierID uuid.UUID) *Notifier { + updateNotifier: func(workspaceID, notifierID uuid.UUID) *Notifier { return &Notifier{ ID: notifierID, WorkspaceID: workspaceID, @@ -636,7 +636,7 @@ func Test_NotifierSensitiveDataLifecycle_AllTypes(t *testing.T) { }, } }, - updateNotifier: func(workspaceID uuid.UUID, notifierID uuid.UUID) *Notifier { + updateNotifier: func(workspaceID, notifierID uuid.UUID) *Notifier { return &Notifier{ ID: notifierID, WorkspaceID: workspaceID, @@ -682,7 +682,7 @@ func Test_NotifierSensitiveDataLifecycle_AllTypes(t *testing.T) { }, } }, - updateNotifier: func(workspaceID uuid.UUID, notifierID uuid.UUID) *Notifier { + updateNotifier: func(workspaceID, notifierID uuid.UUID) *Notifier { return &Notifier{ ID: notifierID, WorkspaceID: workspaceID, @@ -1265,7 +1265,7 @@ func createTelegramNotifier(workspaceID uuid.UUID) *Notifier { } } -func verifyNotifierData(t *testing.T, expected *Notifier, actual *Notifier) { +func verifyNotifierData(t *testing.T, expected, actual *Notifier) { assert.Equal(t, expected.Name, actual.Name) assert.Equal(t, expected.NotifierType, actual.NotifierType) assert.Equal(t, expected.WorkspaceID, actual.WorkspaceID) diff --git a/backend/internal/features/notifiers/di.go b/backend/internal/features/notifiers/di.go index 71e63a4..c86f02a 100644 --- a/backend/internal/features/notifiers/di.go +++ b/backend/internal/features/notifiers/di.go @@ -10,15 +10,18 @@ import ( "databasus-backend/internal/util/logger" ) -var notifierRepository = &NotifierRepository{} -var notifierService = &NotifierService{ - notifierRepository, - logger.GetLogger(), - workspaces_services.GetWorkspaceService(), - audit_logs.GetAuditLogService(), - encryption.GetFieldEncryptor(), - nil, -} +var ( + notifierRepository = &NotifierRepository{} + notifierService = &NotifierService{ + notifierRepository, + logger.GetLogger(), + workspaces_services.GetWorkspaceService(), + audit_logs.GetAuditLogService(), + encryption.GetFieldEncryptor(), + nil, + } +) + var notifierController = &NotifierController{ notifierService, workspaces_services.GetWorkspaceService(), diff --git a/backend/internal/features/notifiers/interfaces.go b/backend/internal/features/notifiers/interfaces.go index cd96108..c082afa 100644 --- a/backend/internal/features/notifiers/interfaces.go +++ b/backend/internal/features/notifiers/interfaces.go @@ -1,10 +1,11 @@ package notifiers import ( - "databasus-backend/internal/util/encryption" "log/slog" "github.com/google/uuid" + + "databasus-backend/internal/util/encryption" ) type NotificationSender interface { diff --git a/backend/internal/features/notifiers/model.go b/backend/internal/features/notifiers/model.go index b8b50a4..1aa9d3d 100644 --- a/backend/internal/features/notifiers/model.go +++ b/backend/internal/features/notifiers/model.go @@ -1,6 +1,11 @@ package notifiers import ( + "errors" + "log/slog" + + "github.com/google/uuid" + discord_notifier "databasus-backend/internal/features/notifiers/models/discord" "databasus-backend/internal/features/notifiers/models/email_notifier" slack_notifier "databasus-backend/internal/features/notifiers/models/slack" @@ -8,10 +13,6 @@ import ( telegram_notifier "databasus-backend/internal/features/notifiers/models/telegram" webhook_notifier "databasus-backend/internal/features/notifiers/models/webhook" "databasus-backend/internal/util/encryption" - "errors" - "log/slog" - - "github.com/google/uuid" ) type Notifier struct { diff --git a/backend/internal/features/notifiers/models/discord/model.go b/backend/internal/features/notifiers/models/discord/model.go index 7ee8c68..55f8757 100644 --- a/backend/internal/features/notifiers/models/discord/model.go +++ b/backend/internal/features/notifiers/models/discord/model.go @@ -2,7 +2,6 @@ package discord_notifier import ( "bytes" - "databasus-backend/internal/util/encryption" "encoding/json" "errors" "fmt" @@ -11,6 +10,8 @@ import ( "net/http" "github.com/google/uuid" + + "databasus-backend/internal/util/encryption" ) type DiscordNotifier struct { diff --git a/backend/internal/features/notifiers/models/email_notifier/model.go b/backend/internal/features/notifiers/models/email_notifier/model.go index eeb9368..a7f93a3 100644 --- a/backend/internal/features/notifiers/models/email_notifier/model.go +++ b/backend/internal/features/notifiers/models/email_notifier/model.go @@ -2,7 +2,6 @@ package email_notifier import ( "crypto/tls" - "databasus-backend/internal/util/encryption" "errors" "fmt" "log/slog" @@ -13,6 +12,8 @@ import ( "time" "github.com/google/uuid" + + "databasus-backend/internal/util/encryption" ) const ( diff --git a/backend/internal/features/notifiers/models/slack/model.go b/backend/internal/features/notifiers/models/slack/model.go index 3ff5ab9..3b7d336 100644 --- a/backend/internal/features/notifiers/models/slack/model.go +++ b/backend/internal/features/notifiers/models/slack/model.go @@ -2,7 +2,6 @@ package slack_notifier import ( "bytes" - "databasus-backend/internal/util/encryption" "encoding/json" "errors" "fmt" @@ -14,6 +13,8 @@ import ( "time" "github.com/google/uuid" + + "databasus-backend/internal/util/encryption" ) type SlackNotifier struct { diff --git a/backend/internal/features/notifiers/models/teams/model.go b/backend/internal/features/notifiers/models/teams/model.go index e339f64..34fedf7 100644 --- a/backend/internal/features/notifiers/models/teams/model.go +++ b/backend/internal/features/notifiers/models/teams/model.go @@ -2,7 +2,6 @@ package teams_notifier import ( "bytes" - "databasus-backend/internal/util/encryption" "encoding/json" "errors" "fmt" @@ -11,6 +10,8 @@ import ( "net/url" "github.com/google/uuid" + + "databasus-backend/internal/util/encryption" ) type TeamsNotifier struct { diff --git a/backend/internal/features/notifiers/models/telegram/model.go b/backend/internal/features/notifiers/models/telegram/model.go index de32e68..adf9f29 100644 --- a/backend/internal/features/notifiers/models/telegram/model.go +++ b/backend/internal/features/notifiers/models/telegram/model.go @@ -1,7 +1,6 @@ package telegram_notifier import ( - "databasus-backend/internal/util/encryption" "errors" "fmt" "io" @@ -12,6 +11,8 @@ import ( "strings" "github.com/google/uuid" + + "databasus-backend/internal/util/encryption" ) type TelegramNotifier struct { diff --git a/backend/internal/features/notifiers/models/webhook/model.go b/backend/internal/features/notifiers/models/webhook/model.go index 2bca189..6dbbe87 100644 --- a/backend/internal/features/notifiers/models/webhook/model.go +++ b/backend/internal/features/notifiers/models/webhook/model.go @@ -2,7 +2,6 @@ package webhook_notifier import ( "bytes" - "databasus-backend/internal/util/encryption" "encoding/json" "errors" "fmt" @@ -14,6 +13,8 @@ import ( "github.com/google/uuid" "gorm.io/gorm" + + "databasus-backend/internal/util/encryption" ) type WebhookHeader struct { @@ -42,7 +43,6 @@ func (t *WebhookNotifier) TableName() string { func (t *WebhookNotifier) BeforeSave(_ *gorm.DB) error { if len(t.Headers) > 0 { data, err := json.Marshal(t.Headers) - if err != nil { return err } diff --git a/backend/internal/features/notifiers/repository.go b/backend/internal/features/notifiers/repository.go index 0d77bcc..50361b2 100644 --- a/backend/internal/features/notifiers/repository.go +++ b/backend/internal/features/notifiers/repository.go @@ -1,10 +1,10 @@ package notifiers import ( - "databasus-backend/internal/storage" - "github.com/google/uuid" "gorm.io/gorm" + + "databasus-backend/internal/storage" ) type NotifierRepository struct{} @@ -13,7 +13,6 @@ func (r *NotifierRepository) Save(notifier *Notifier) (*Notifier, error) { db := storage.GetDb() err := db.Transaction(func(tx *gorm.DB) error { - switch notifier.NotifierType { case NotifierTypeTelegram: if notifier.TelegramNotifier != nil { @@ -116,7 +115,6 @@ func (r *NotifierRepository) Save(notifier *Notifier) (*Notifier, error) { return nil }) - if err != nil { return nil, err } diff --git a/backend/internal/features/notifiers/service.go b/backend/internal/features/notifiers/service.go index 1b24d37..027a51e 100644 --- a/backend/internal/features/notifiers/service.go +++ b/backend/internal/features/notifiers/service.go @@ -4,12 +4,12 @@ import ( "fmt" "log/slog" + "github.com/google/uuid" + audit_logs "databasus-backend/internal/features/audit_logs" users_models "databasus-backend/internal/features/users/models" workspaces_services "databasus-backend/internal/features/workspaces/services" "databasus-backend/internal/util/encryption" - - "github.com/google/uuid" ) type NotifierService struct { diff --git a/backend/internal/features/notifiers/testing.go b/backend/internal/features/notifiers/testing.go index f61c54c..61cfea4 100644 --- a/backend/internal/features/notifiers/testing.go +++ b/backend/internal/features/notifiers/testing.go @@ -1,9 +1,9 @@ package notifiers import ( - webhook_notifier "databasus-backend/internal/features/notifiers/models/webhook" - "github.com/google/uuid" + + webhook_notifier "databasus-backend/internal/features/notifiers/models/webhook" ) func CreateTestNotifier(workspaceID uuid.UUID) *Notifier { diff --git a/backend/internal/features/plan/model.go b/backend/internal/features/plan/model.go index ac14da9..235b2a5 100644 --- a/backend/internal/features/plan/model.go +++ b/backend/internal/features/plan/model.go @@ -1,9 +1,9 @@ package plans import ( - "databasus-backend/internal/util/period" - "github.com/google/uuid" + + "databasus-backend/internal/util/period" ) type DatabasePlan struct { diff --git a/backend/internal/features/plan/repository.go b/backend/internal/features/plan/repository.go index e92f31e..ba156a9 100644 --- a/backend/internal/features/plan/repository.go +++ b/backend/internal/features/plan/repository.go @@ -1,9 +1,9 @@ package plans import ( - "databasus-backend/internal/storage" - "github.com/google/uuid" + + "databasus-backend/internal/storage" ) type DatabasePlanRepository struct{} diff --git a/backend/internal/features/plan/service.go b/backend/internal/features/plan/service.go index 1a34235..1f7b94b 100644 --- a/backend/internal/features/plan/service.go +++ b/backend/internal/features/plan/service.go @@ -1,11 +1,12 @@ package plans import ( - "databasus-backend/internal/config" - "databasus-backend/internal/util/period" "log/slog" "github.com/google/uuid" + + "databasus-backend/internal/config" + "databasus-backend/internal/util/period" ) type DatabasePlanService struct { diff --git a/backend/internal/features/restores/controller.go b/backend/internal/features/restores/controller.go index e3b2663..c4d8dbe 100644 --- a/backend/internal/features/restores/controller.go +++ b/backend/internal/features/restores/controller.go @@ -1,12 +1,13 @@ package restores import ( - restores_core "databasus-backend/internal/features/restores/core" - users_middleware "databasus-backend/internal/features/users/middleware" "net/http" "github.com/gin-gonic/gin" "github.com/google/uuid" + + restores_core "databasus-backend/internal/features/restores/core" + users_middleware "databasus-backend/internal/features/users/middleware" ) type RestoreController struct { diff --git a/backend/internal/features/restores/core/model.go b/backend/internal/features/restores/core/model.go index d3d5d00..cb762cb 100644 --- a/backend/internal/features/restores/core/model.go +++ b/backend/internal/features/restores/core/model.go @@ -1,14 +1,15 @@ package restores_core import ( + "time" + + "github.com/google/uuid" + backups_core "databasus-backend/internal/features/backups/backups/core" "databasus-backend/internal/features/databases/databases/mariadb" "databasus-backend/internal/features/databases/databases/mongodb" "databasus-backend/internal/features/databases/databases/mysql" "databasus-backend/internal/features/databases/databases/postgresql" - "time" - - "github.com/google/uuid" ) type Restore struct { diff --git a/backend/internal/features/restores/core/repository.go b/backend/internal/features/restores/core/repository.go index 7e23bc5..13dde94 100644 --- a/backend/internal/features/restores/core/repository.go +++ b/backend/internal/features/restores/core/repository.go @@ -1,9 +1,9 @@ package restores_core import ( - "databasus-backend/internal/storage" - "github.com/google/uuid" + + "databasus-backend/internal/storage" ) type RestoreRepository struct{} diff --git a/backend/internal/features/restores/di.go b/backend/internal/features/restores/di.go index b74cc12..d9e05d1 100644 --- a/backend/internal/features/restores/di.go +++ b/backend/internal/features/restores/di.go @@ -19,21 +19,24 @@ import ( "databasus-backend/internal/util/logger" ) -var restoreRepository = &restores_core.RestoreRepository{} -var restoreService = &RestoreService{ - backups_services.GetBackupService(), - restoreRepository, - storages.GetStorageService(), - backups_config.GetBackupConfigService(), - usecases.GetRestoreBackupUsecase(), - databases.GetDatabaseService(), - logger.GetLogger(), - workspaces_services.GetWorkspaceService(), - audit_logs.GetAuditLogService(), - encryption.GetFieldEncryptor(), - disk.GetDiskService(), - tasks_cancellation.GetTaskCancelManager(), -} +var ( + restoreRepository = &restores_core.RestoreRepository{} + restoreService = &RestoreService{ + backups_services.GetBackupService(), + restoreRepository, + storages.GetStorageService(), + backups_config.GetBackupConfigService(), + usecases.GetRestoreBackupUsecase(), + databases.GetDatabaseService(), + logger.GetLogger(), + workspaces_services.GetWorkspaceService(), + audit_logs.GetAuditLogService(), + encryption.GetFieldEncryptor(), + disk.GetDiskService(), + tasks_cancellation.GetTaskCancelManager(), + } +) + var restoreController = &RestoreController{ restoreService, } diff --git a/backend/internal/features/restores/restoring/dto.go b/backend/internal/features/restores/restoring/dto.go index eb4a3cb..fb17e70 100644 --- a/backend/internal/features/restores/restoring/dto.go +++ b/backend/internal/features/restores/restoring/dto.go @@ -1,13 +1,14 @@ package restoring import ( + "time" + + "github.com/google/uuid" + "databasus-backend/internal/features/databases/databases/mariadb" "databasus-backend/internal/features/databases/databases/mongodb" "databasus-backend/internal/features/databases/databases/mysql" "databasus-backend/internal/features/databases/databases/postgresql" - "time" - - "github.com/google/uuid" ) type RestoreDatabaseCache struct { diff --git a/backend/internal/features/restores/restoring/registry.go b/backend/internal/features/restores/restoring/registry.go index 01b72bd..950dfc0 100644 --- a/backend/internal/features/restores/restoring/registry.go +++ b/backend/internal/features/restores/restoring/registry.go @@ -10,10 +10,10 @@ import ( "sync/atomic" "time" - cache_utils "databasus-backend/internal/util/cache" - "github.com/google/uuid" "github.com/valkey-io/valkey-go" + + cache_utils "databasus-backend/internal/util/cache" ) const ( @@ -453,7 +453,7 @@ func (r *RestoreNodesRegistry) PublishRestoreCompletion( } func (r *RestoreNodesRegistry) SubscribeForRestoresCompletions( - handler func(nodeID uuid.UUID, restoreID uuid.UUID), + handler func(nodeID, restoreID uuid.UUID), ) error { ctx := context.Background() diff --git a/backend/internal/features/restores/restoring/registry_test.go b/backend/internal/features/restores/restoring/registry_test.go index 2b7e87f..653b491 100644 --- a/backend/internal/features/restores/restoring/registry_test.go +++ b/backend/internal/features/restores/restoring/registry_test.go @@ -9,11 +9,11 @@ import ( "testing" "time" - cache_utils "databasus-backend/internal/util/cache" - "databasus-backend/internal/util/logger" - "github.com/google/uuid" "github.com/stretchr/testify/assert" + + cache_utils "databasus-backend/internal/util/cache" + "databasus-backend/internal/util/logger" ) func Test_HearthbeatNodeInRegistry_RegistersNodeWithTTL(t *testing.T) { @@ -905,7 +905,7 @@ func Test_SubscribeForRestoresCompletions_ReceivesCompletedRestores(t *testing.T receivedRestoreID := make(chan uuid.UUID, 1) receivedNodeID := make(chan uuid.UUID, 1) - handler := func(nodeID uuid.UUID, restoreID uuid.UUID) { + handler := func(nodeID, restoreID uuid.UUID) { receivedNodeID <- nodeID receivedRestoreID <- restoreID } @@ -942,7 +942,7 @@ func Test_SubscribeForRestoresCompletions_ParsesJsonCorrectly(t *testing.T) { defer registry.UnsubscribeForRestoresCompletions() receivedRestores := make(chan uuid.UUID, 2) - handler := func(nodeID uuid.UUID, restoreID uuid.UUID) { + handler := func(nodeID, restoreID uuid.UUID) { receivedRestores <- restoreID } @@ -971,7 +971,7 @@ func Test_SubscribeForRestoresCompletions_HandlesInvalidJson(t *testing.T) { defer registry.UnsubscribeForRestoresCompletions() receivedRestoreID := make(chan uuid.UUID, 1) - handler := func(nodeID uuid.UUID, restoreID uuid.UUID) { + handler := func(nodeID, restoreID uuid.UUID) { receivedRestoreID <- restoreID } @@ -999,7 +999,7 @@ func Test_UnsubscribeForRestoresCompletions_StopsReceivingMessages(t *testing.T) restoreID2 := uuid.New() receivedRestoreID := make(chan uuid.UUID, 2) - handler := func(nodeID uuid.UUID, restoreID uuid.UUID) { + handler := func(nodeID, restoreID uuid.UUID) { receivedRestoreID <- restoreID } @@ -1034,7 +1034,7 @@ func Test_SubscribeForRestoresCompletions_WhenAlreadySubscribed_ReturnsError(t * registry := createTestRegistry() defer registry.UnsubscribeForRestoresCompletions() - handler := func(nodeID uuid.UUID, restoreID uuid.UUID) {} + handler := func(nodeID, restoreID uuid.UUID) {} err := registry.SubscribeForRestoresCompletions(handler) assert.NoError(t, err) @@ -1066,9 +1066,9 @@ func Test_MultipleSubscribers_EachReceivesCompletionMessages(t *testing.T) { receivedRestores2 := make(chan uuid.UUID, 3) receivedRestores3 := make(chan uuid.UUID, 3) - handler1 := func(nodeID uuid.UUID, restoreID uuid.UUID) { receivedRestores1 <- restoreID } - handler2 := func(nodeID uuid.UUID, restoreID uuid.UUID) { receivedRestores2 <- restoreID } - handler3 := func(nodeID uuid.UUID, restoreID uuid.UUID) { receivedRestores3 <- restoreID } + handler1 := func(nodeID, restoreID uuid.UUID) { receivedRestores1 <- restoreID } + handler2 := func(nodeID, restoreID uuid.UUID) { receivedRestores2 <- restoreID } + handler3 := func(nodeID, restoreID uuid.UUID) { receivedRestores3 <- restoreID } err := registry1.SubscribeForRestoresCompletions(handler1) assert.NoError(t, err) diff --git a/backend/internal/features/restores/restoring/restorer.go b/backend/internal/features/restores/restoring/restorer.go index 092df7a..1ad89cc 100644 --- a/backend/internal/features/restores/restoring/restorer.go +++ b/backend/internal/features/restores/restoring/restorer.go @@ -237,7 +237,6 @@ func (n *RestorerNode) MakeRestore(restoreID uuid.UUID) { storage, isExcludeExtensions, ) - if err != nil { errMsg := err.Error() diff --git a/backend/internal/features/restores/restoring/scheduler.go b/backend/internal/features/restores/restoring/scheduler.go index 58faf62..0f36766 100644 --- a/backend/internal/features/restores/restoring/scheduler.go +++ b/backend/internal/features/restores/restoring/scheduler.go @@ -265,7 +265,7 @@ func (s *RestoresScheduler) calculateLeastBusyNode() (*uuid.UUID, error) { return &bestNode.ID, nil } -func (s *RestoresScheduler) onRestoreCompleted(nodeID uuid.UUID, restoreID uuid.UUID) { +func (s *RestoresScheduler) onRestoreCompleted(nodeID, restoreID uuid.UUID) { // Verify this task is actually a restore (registry contains multiple task types) _, err := s.restoreRepository.FindByID(restoreID) if err != nil { diff --git a/backend/internal/features/restores/restoring/scheduler_test.go b/backend/internal/features/restores/restoring/scheduler_test.go index 66899cf..67f3f11 100644 --- a/backend/internal/features/restores/restoring/scheduler_test.go +++ b/backend/internal/features/restores/restoring/scheduler_test.go @@ -4,6 +4,9 @@ import ( "testing" "time" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "databasus-backend/internal/config" backups_controllers "databasus-backend/internal/features/backups/backups/controllers" backups_core "databasus-backend/internal/features/backups/backups/core" @@ -18,9 +21,6 @@ import ( workspaces_testing "databasus-backend/internal/features/workspaces/testing" cache_utils "databasus-backend/internal/util/cache" "databasus-backend/internal/util/encryption" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" ) func Test_CheckDeadNodesAndFailRestores_NodeDies_FailsRestoreAndCleansUpRegistry(t *testing.T) { diff --git a/backend/internal/features/restores/service.go b/backend/internal/features/restores/service.go index 139eaad..1807a5d 100644 --- a/backend/internal/features/restores/service.go +++ b/backend/internal/features/restores/service.go @@ -1,6 +1,13 @@ package restores import ( + "errors" + "fmt" + "log/slog" + "time" + + "github.com/google/uuid" + "databasus-backend/internal/config" audit_logs "databasus-backend/internal/features/audit_logs" backups_core "databasus-backend/internal/features/backups/backups/core" @@ -17,12 +24,6 @@ import ( workspaces_services "databasus-backend/internal/features/workspaces/services" "databasus-backend/internal/util/encryption" "databasus-backend/internal/util/tools" - "errors" - "fmt" - "log/slog" - "time" - - "github.com/google/uuid" ) type RestoreService struct { diff --git a/backend/internal/features/restores/usecases/mariadb/restore_backup_uc.go b/backend/internal/features/restores/usecases/mariadb/restore_backup_uc.go index d6a0335..9996e93 100644 --- a/backend/internal/features/restores/usecases/mariadb/restore_backup_uc.go +++ b/backend/internal/features/restores/usecases/mariadb/restore_backup_uc.go @@ -286,10 +286,10 @@ func (uc *RestoreMariadbBackupUsecase) createTempMyCnfFile( password string, ) (string, error) { tempFolder := config.GetEnv().TempFolder - if err := os.MkdirAll(tempFolder, 0700); err != nil { + if err := os.MkdirAll(tempFolder, 0o700); err != nil { return "", fmt.Errorf("failed to ensure temp folder exists: %w", err) } - if err := os.Chmod(tempFolder, 0700); err != nil { + if err := os.Chmod(tempFolder, 0o700); err != nil { return "", fmt.Errorf("failed to set temp folder permissions: %w", err) } @@ -298,7 +298,7 @@ func (uc *RestoreMariadbBackupUsecase) createTempMyCnfFile( return "", fmt.Errorf("failed to create temp directory: %w", err) } - if err := os.Chmod(tempDir, 0700); err != nil { + if err := os.Chmod(tempDir, 0o700); err != nil { _ = os.RemoveAll(tempDir) return "", fmt.Errorf("failed to set temp directory permissions: %w", err) } @@ -318,7 +318,7 @@ port=%d content += "ssl=false\n" } - err = os.WriteFile(myCnfFile, []byte(content), 0600) + err = os.WriteFile(myCnfFile, []byte(content), 0o600) if err != nil { _ = os.RemoveAll(tempDir) return "", fmt.Errorf("failed to write .my.cnf: %w", err) diff --git a/backend/internal/features/restores/usecases/mysql/restore_backup_uc.go b/backend/internal/features/restores/usecases/mysql/restore_backup_uc.go index 2f11760..46680ad 100644 --- a/backend/internal/features/restores/usecases/mysql/restore_backup_uc.go +++ b/backend/internal/features/restores/usecases/mysql/restore_backup_uc.go @@ -277,10 +277,10 @@ func (uc *RestoreMysqlBackupUsecase) createTempMyCnfFile( password string, ) (string, error) { tempFolder := config.GetEnv().TempFolder - if err := os.MkdirAll(tempFolder, 0700); err != nil { + if err := os.MkdirAll(tempFolder, 0o700); err != nil { return "", fmt.Errorf("failed to ensure temp folder exists: %w", err) } - if err := os.Chmod(tempFolder, 0700); err != nil { + if err := os.Chmod(tempFolder, 0o700); err != nil { return "", fmt.Errorf("failed to set temp folder permissions: %w", err) } @@ -289,7 +289,7 @@ func (uc *RestoreMysqlBackupUsecase) createTempMyCnfFile( return "", fmt.Errorf("failed to create temp directory: %w", err) } - if err := os.Chmod(tempDir, 0700); err != nil { + if err := os.Chmod(tempDir, 0o700); err != nil { _ = os.RemoveAll(tempDir) return "", fmt.Errorf("failed to set temp directory permissions: %w", err) } @@ -307,7 +307,7 @@ port=%d content += "ssl-mode=REQUIRED\n" } - err = os.WriteFile(myCnfFile, []byte(content), 0600) + err = os.WriteFile(myCnfFile, []byte(content), 0o600) if err != nil { _ = os.RemoveAll(tempDir) return "", fmt.Errorf("failed to write .my.cnf: %w", err) diff --git a/backend/internal/features/restores/usecases/postgresql/restore_backup_uc.go b/backend/internal/features/restores/usecases/postgresql/restore_backup_uc.go index 8b5ef80..6ce9172 100644 --- a/backend/internal/features/restores/usecases/postgresql/restore_backup_uc.go +++ b/backend/internal/features/restores/usecases/postgresql/restore_backup_uc.go @@ -14,6 +14,8 @@ import ( "strings" "time" + "github.com/google/uuid" + "databasus-backend/internal/config" backups_core "databasus-backend/internal/features/backups/backups/core" "databasus-backend/internal/features/backups/backups/encryption" @@ -25,8 +27,6 @@ import ( "databasus-backend/internal/features/storages" util_encryption "databasus-backend/internal/util/encryption" "databasus-backend/internal/util/tools" - - "github.com/google/uuid" ) type RestorePostgresqlBackupUsecase struct { @@ -992,10 +992,10 @@ func (uc *RestorePostgresqlBackupUsecase) createTempPgpassFile( ) tempFolder := config.GetEnv().TempFolder - if err := os.MkdirAll(tempFolder, 0700); err != nil { + if err := os.MkdirAll(tempFolder, 0o700); err != nil { return "", fmt.Errorf("failed to ensure temp folder exists: %w", err) } - if err := os.Chmod(tempFolder, 0700); err != nil { + if err := os.Chmod(tempFolder, 0o700); err != nil { return "", fmt.Errorf("failed to set temp folder permissions: %w", err) } @@ -1004,13 +1004,13 @@ func (uc *RestorePostgresqlBackupUsecase) createTempPgpassFile( return "", fmt.Errorf("failed to create temporary directory: %w", err) } - if err := os.Chmod(tempDir, 0700); err != nil { + if err := os.Chmod(tempDir, 0o700); err != nil { _ = os.RemoveAll(tempDir) return "", fmt.Errorf("failed to set temporary directory permissions: %w", err) } pgpassFile := filepath.Join(tempDir, ".pgpass") - err = os.WriteFile(pgpassFile, []byte(pgpassContent), 0600) + err = os.WriteFile(pgpassFile, []byte(pgpassContent), 0o600) if err != nil { _ = os.RemoveAll(tempDir) return "", fmt.Errorf("failed to write temporary .pgpass file: %w", err) diff --git a/backend/internal/features/storages/controller.go b/backend/internal/features/storages/controller.go index 1ecc54a..5828ae0 100644 --- a/backend/internal/features/storages/controller.go +++ b/backend/internal/features/storages/controller.go @@ -2,13 +2,13 @@ package storages import ( "errors" - - users_middleware "databasus-backend/internal/features/users/middleware" - workspaces_services "databasus-backend/internal/features/workspaces/services" "net/http" "github.com/gin-gonic/gin" "github.com/google/uuid" + + users_middleware "databasus-backend/internal/features/users/middleware" + workspaces_services "databasus-backend/internal/features/workspaces/services" ) type StorageController struct { diff --git a/backend/internal/features/storages/controller_test.go b/backend/internal/features/storages/controller_test.go index 1bb892e..786b91c 100644 --- a/backend/internal/features/storages/controller_test.go +++ b/backend/internal/features/storages/controller_test.go @@ -6,6 +6,10 @@ import ( "strings" "testing" + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "databasus-backend/internal/config" audit_logs "databasus-backend/internal/features/audit_logs" azure_blob_storage "databasus-backend/internal/features/storages/models/azure_blob" @@ -25,10 +29,6 @@ import ( workspaces_testing "databasus-backend/internal/features/workspaces/testing" "databasus-backend/internal/util/encryption" test_utils "databasus-backend/internal/util/testing" - - "github.com/gin-gonic/gin" - "github.com/google/uuid" - "github.com/stretchr/testify/assert" ) type mockStorageDatabaseCounter struct{} @@ -1128,7 +1128,7 @@ func Test_StorageSensitiveDataLifecycle_AllTypes(t *testing.T) { name string storageType StorageType createStorage func(workspaceID uuid.UUID) *Storage - updateStorage func(workspaceID uuid.UUID, storageID uuid.UUID) *Storage + updateStorage func(workspaceID, storageID uuid.UUID) *Storage verifySensitiveData func(t *testing.T, storage *Storage) verifyHiddenData func(t *testing.T, storage *Storage) }{ @@ -1149,7 +1149,7 @@ func Test_StorageSensitiveDataLifecycle_AllTypes(t *testing.T) { }, } }, - updateStorage: func(workspaceID uuid.UUID, storageID uuid.UUID) *Storage { + updateStorage: func(workspaceID, storageID uuid.UUID) *Storage { return &Storage{ ID: storageID, WorkspaceID: workspaceID, @@ -1195,7 +1195,7 @@ func Test_StorageSensitiveDataLifecycle_AllTypes(t *testing.T) { LocalStorage: &local_storage.LocalStorage{}, } }, - updateStorage: func(workspaceID uuid.UUID, storageID uuid.UUID) *Storage { + updateStorage: func(workspaceID, storageID uuid.UUID) *Storage { return &Storage{ ID: storageID, WorkspaceID: workspaceID, @@ -1229,7 +1229,7 @@ func Test_StorageSensitiveDataLifecycle_AllTypes(t *testing.T) { }, } }, - updateStorage: func(workspaceID uuid.UUID, storageID uuid.UUID) *Storage { + updateStorage: func(workspaceID, storageID uuid.UUID) *Storage { return &Storage{ ID: storageID, WorkspaceID: workspaceID, @@ -1277,7 +1277,7 @@ func Test_StorageSensitiveDataLifecycle_AllTypes(t *testing.T) { }, } }, - updateStorage: func(workspaceID uuid.UUID, storageID uuid.UUID) *Storage { + updateStorage: func(workspaceID, storageID uuid.UUID) *Storage { return &Storage{ ID: storageID, WorkspaceID: workspaceID, @@ -1327,7 +1327,7 @@ func Test_StorageSensitiveDataLifecycle_AllTypes(t *testing.T) { }, } }, - updateStorage: func(workspaceID uuid.UUID, storageID uuid.UUID) *Storage { + updateStorage: func(workspaceID, storageID uuid.UUID) *Storage { return &Storage{ ID: storageID, WorkspaceID: workspaceID, @@ -1375,7 +1375,7 @@ func Test_StorageSensitiveDataLifecycle_AllTypes(t *testing.T) { }, } }, - updateStorage: func(workspaceID uuid.UUID, storageID uuid.UUID) *Storage { + updateStorage: func(workspaceID, storageID uuid.UUID) *Storage { return &Storage{ ID: storageID, WorkspaceID: workspaceID, @@ -1436,7 +1436,7 @@ func Test_StorageSensitiveDataLifecycle_AllTypes(t *testing.T) { }, } }, - updateStorage: func(workspaceID uuid.UUID, storageID uuid.UUID) *Storage { + updateStorage: func(workspaceID, storageID uuid.UUID) *Storage { return &Storage{ ID: storageID, WorkspaceID: workspaceID, @@ -1484,7 +1484,7 @@ func Test_StorageSensitiveDataLifecycle_AllTypes(t *testing.T) { }, } }, - updateStorage: func(workspaceID uuid.UUID, storageID uuid.UUID) *Storage { + updateStorage: func(workspaceID, storageID uuid.UUID) *Storage { return &Storage{ ID: storageID, WorkspaceID: workspaceID, @@ -1535,7 +1535,7 @@ func Test_StorageSensitiveDataLifecycle_AllTypes(t *testing.T) { }, } }, - updateStorage: func(workspaceID uuid.UUID, storageID uuid.UUID) *Storage { + updateStorage: func(workspaceID, storageID uuid.UUID) *Storage { return &Storage{ ID: storageID, WorkspaceID: workspaceID, @@ -2136,7 +2136,7 @@ func createNewStorage(workspaceID uuid.UUID) *Storage { } } -func verifyStorageData(t *testing.T, expected *Storage, actual *Storage) { +func verifyStorageData(t *testing.T, expected, actual *Storage) { assert.Equal(t, expected.Name, actual.Name) assert.Equal(t, expected.Type, actual.Type) assert.Equal(t, expected.WorkspaceID, actual.WorkspaceID) diff --git a/backend/internal/features/storages/di.go b/backend/internal/features/storages/di.go index 57822b8..e3b6ad9 100644 --- a/backend/internal/features/storages/di.go +++ b/backend/internal/features/storages/di.go @@ -10,14 +10,17 @@ import ( "databasus-backend/internal/util/logger" ) -var storageRepository = &StorageRepository{} -var storageService = &StorageService{ - storageRepository, - workspaces_services.GetWorkspaceService(), - audit_logs.GetAuditLogService(), - encryption.GetFieldEncryptor(), - nil, -} +var ( + storageRepository = &StorageRepository{} + storageService = &StorageService{ + storageRepository, + workspaces_services.GetWorkspaceService(), + audit_logs.GetAuditLogService(), + encryption.GetFieldEncryptor(), + nil, + } +) + var storageController = &StorageController{ storageService, workspaces_services.GetWorkspaceService(), diff --git a/backend/internal/features/storages/interfaces.go b/backend/internal/features/storages/interfaces.go index 67421f6..39fb757 100644 --- a/backend/internal/features/storages/interfaces.go +++ b/backend/internal/features/storages/interfaces.go @@ -2,11 +2,12 @@ package storages import ( "context" - "databasus-backend/internal/util/encryption" "io" "log/slog" "github.com/google/uuid" + + "databasus-backend/internal/util/encryption" ) type StorageFileSaver interface { diff --git a/backend/internal/features/storages/model.go b/backend/internal/features/storages/model.go index 3ab6a99..a762bda 100644 --- a/backend/internal/features/storages/model.go +++ b/backend/internal/features/storages/model.go @@ -2,6 +2,12 @@ package storages import ( "context" + "errors" + "io" + "log/slog" + + "github.com/google/uuid" + azure_blob_storage "databasus-backend/internal/features/storages/models/azure_blob" ftp_storage "databasus-backend/internal/features/storages/models/ftp" google_drive_storage "databasus-backend/internal/features/storages/models/google_drive" @@ -11,11 +17,6 @@ import ( s3_storage "databasus-backend/internal/features/storages/models/s3" sftp_storage "databasus-backend/internal/features/storages/models/sftp" "databasus-backend/internal/util/encryption" - "errors" - "io" - "log/slog" - - "github.com/google/uuid" ) type Storage struct { diff --git a/backend/internal/features/storages/model_test.go b/backend/internal/features/storages/model_test.go index ee9bb17..3958921 100644 --- a/backend/internal/features/storages/model_test.go +++ b/backend/internal/features/storages/model_test.go @@ -3,17 +3,6 @@ package storages import ( "bytes" "context" - "databasus-backend/internal/config" - azure_blob_storage "databasus-backend/internal/features/storages/models/azure_blob" - ftp_storage "databasus-backend/internal/features/storages/models/ftp" - google_drive_storage "databasus-backend/internal/features/storages/models/google_drive" - local_storage "databasus-backend/internal/features/storages/models/local" - nas_storage "databasus-backend/internal/features/storages/models/nas" - rclone_storage "databasus-backend/internal/features/storages/models/rclone" - s3_storage "databasus-backend/internal/features/storages/models/s3" - sftp_storage "databasus-backend/internal/features/storages/models/sftp" - "databasus-backend/internal/util/encryption" - "databasus-backend/internal/util/logger" "fmt" "io" "os" @@ -28,6 +17,18 @@ import ( "github.com/minio/minio-go/v7/pkg/credentials" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "databasus-backend/internal/config" + azure_blob_storage "databasus-backend/internal/features/storages/models/azure_blob" + ftp_storage "databasus-backend/internal/features/storages/models/ftp" + google_drive_storage "databasus-backend/internal/features/storages/models/google_drive" + local_storage "databasus-backend/internal/features/storages/models/local" + nas_storage "databasus-backend/internal/features/storages/models/nas" + rclone_storage "databasus-backend/internal/features/storages/models/rclone" + s3_storage "databasus-backend/internal/features/storages/models/s3" + sftp_storage "databasus-backend/internal/features/storages/models/sftp" + "databasus-backend/internal/util/encryption" + "databasus-backend/internal/util/logger" ) type S3Container struct { @@ -283,7 +284,7 @@ func setupTestFile() (string, error) { testData := []byte("This is test data for storage testing") // 0644 means: owner can read/write - err := os.WriteFile(testFilePath, testData, 0644) + err := os.WriteFile(testFilePath, testData, 0o644) if err != nil { return "", fmt.Errorf("failed to create test file: %w", err) } diff --git a/backend/internal/features/storages/models/azure_blob/model.go b/backend/internal/features/storages/models/azure_blob/model.go index 302779d..32f5d42 100644 --- a/backend/internal/features/storages/models/azure_blob/model.go +++ b/backend/internal/features/storages/models/azure_blob/model.go @@ -3,7 +3,6 @@ package azure_blob_storage import ( "bytes" "context" - "databasus-backend/internal/util/encryption" "encoding/base64" "errors" "fmt" @@ -19,6 +18,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blockblob" "github.com/google/uuid" + + "databasus-backend/internal/util/encryption" ) const ( diff --git a/backend/internal/features/storages/models/ftp/model.go b/backend/internal/features/storages/models/ftp/model.go index f17e1d8..1066a97 100644 --- a/backend/internal/features/storages/models/ftp/model.go +++ b/backend/internal/features/storages/models/ftp/model.go @@ -3,7 +3,6 @@ package ftp_storage import ( "context" "crypto/tls" - "databasus-backend/internal/util/encryption" "errors" "fmt" "io" @@ -13,6 +12,8 @@ import ( "github.com/google/uuid" "github.com/jlaffaye/ftp" + + "databasus-backend/internal/util/encryption" ) const ( diff --git a/backend/internal/features/storages/models/google_drive/model.go b/backend/internal/features/storages/models/google_drive/model.go index 2194b4a..74f2424 100644 --- a/backend/internal/features/storages/models/google_drive/model.go +++ b/backend/internal/features/storages/models/google_drive/model.go @@ -2,7 +2,6 @@ package google_drive_storage import ( "context" - "databasus-backend/internal/util/encryption" "encoding/json" "errors" "fmt" @@ -16,10 +15,11 @@ import ( "github.com/google/uuid" "golang.org/x/oauth2" "golang.org/x/oauth2/google" - drive "google.golang.org/api/drive/v3" "google.golang.org/api/googleapi" "google.golang.org/api/option" + + "databasus-backend/internal/util/encryption" ) const ( @@ -633,7 +633,6 @@ func (s *GoogleDriveStorage) deleteByName( return nil }) - if err != nil { return fmt.Errorf("failed to delete %q: %w", name, err) } diff --git a/backend/internal/features/storages/models/local/model.go b/backend/internal/features/storages/models/local/model.go index a08da63..79a4f95 100644 --- a/backend/internal/features/storages/models/local/model.go +++ b/backend/internal/features/storages/models/local/model.go @@ -2,9 +2,6 @@ package local_storage import ( "context" - "databasus-backend/internal/config" - "databasus-backend/internal/util/encryption" - files_utils "databasus-backend/internal/util/files" "fmt" "io" "log/slog" @@ -12,6 +9,10 @@ import ( "path/filepath" "github.com/google/uuid" + + "databasus-backend/internal/config" + "databasus-backend/internal/util/encryption" + files_utils "databasus-backend/internal/util/files" ) const ( diff --git a/backend/internal/features/storages/models/nas/model.go b/backend/internal/features/storages/models/nas/model.go index c09e8cf..2ccec4c 100644 --- a/backend/internal/features/storages/models/nas/model.go +++ b/backend/internal/features/storages/models/nas/model.go @@ -3,7 +3,6 @@ package nas_storage import ( "context" "crypto/tls" - "databasus-backend/internal/util/encryption" "errors" "fmt" "io" @@ -15,6 +14,8 @@ import ( "github.com/google/uuid" "github.com/hirochachacha/go-smb2" + + "databasus-backend/internal/util/encryption" ) const ( @@ -399,7 +400,7 @@ func (n *NASStorage) ensureDirectory(fs *smb2.Share, path string) error { _, err := fs.Stat(currentPath) if err != nil { // Directory doesn't exist, try to create it - err = fs.Mkdir(currentPath, 0755) + err = fs.Mkdir(currentPath, 0o755) if err != nil { return fmt.Errorf("failed to create directory '%s': %w", currentPath, err) } diff --git a/backend/internal/features/storages/models/rclone/model.go b/backend/internal/features/storages/models/rclone/model.go index 8ba7e14..6b9f9fb 100644 --- a/backend/internal/features/storages/models/rclone/model.go +++ b/backend/internal/features/storages/models/rclone/model.go @@ -3,7 +3,6 @@ package rclone_storage import ( "bufio" "context" - "databasus-backend/internal/util/encryption" "errors" "fmt" "io" @@ -13,11 +12,12 @@ import ( "time" "github.com/google/uuid" + _ "github.com/rclone/rclone/backend/all" "github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs/config" "github.com/rclone/rclone/fs/operations" - _ "github.com/rclone/rclone/backend/all" + "databasus-backend/internal/util/encryption" ) const ( diff --git a/backend/internal/features/storages/models/s3/model.go b/backend/internal/features/storages/models/s3/model.go index 77763bb..5b1e9d5 100644 --- a/backend/internal/features/storages/models/s3/model.go +++ b/backend/internal/features/storages/models/s3/model.go @@ -5,7 +5,6 @@ import ( "context" "crypto/md5" "crypto/tls" - "databasus-backend/internal/util/encryption" "encoding/base64" "errors" "fmt" @@ -19,6 +18,8 @@ import ( "github.com/google/uuid" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" + + "databasus-backend/internal/util/encryption" ) const ( @@ -423,7 +424,7 @@ func (s *S3Storage) getCoreClient(encryptor encryption.FieldEncryptor) (*minio.C func (s *S3Storage) getClientParams( encryptor encryption.FieldEncryptor, -) (endpoint string, useSSL bool, accessKey string, secretKey string, bucketLookup minio.BucketLookupType, transport *http.Transport, err error) { +) (endpoint string, useSSL bool, accessKey, secretKey string, bucketLookup minio.BucketLookupType, transport *http.Transport, err error) { endpoint = s.S3Endpoint useSSL = true diff --git a/backend/internal/features/storages/models/sftp/model.go b/backend/internal/features/storages/models/sftp/model.go index 29de298..b3f56db 100644 --- a/backend/internal/features/storages/models/sftp/model.go +++ b/backend/internal/features/storages/models/sftp/model.go @@ -2,7 +2,6 @@ package sftp_storage import ( "context" - "databasus-backend/internal/util/encryption" "errors" "fmt" "io" @@ -14,6 +13,8 @@ import ( "github.com/google/uuid" "github.com/pkg/sftp" "golang.org/x/crypto/ssh" + + "databasus-backend/internal/util/encryption" ) const ( diff --git a/backend/internal/features/storages/repository.go b/backend/internal/features/storages/repository.go index 1aae3dc..a042cb5 100644 --- a/backend/internal/features/storages/repository.go +++ b/backend/internal/features/storages/repository.go @@ -1,10 +1,10 @@ package storages import ( - db "databasus-backend/internal/storage" - "github.com/google/uuid" "gorm.io/gorm" + + db "databasus-backend/internal/storage" ) type StorageRepository struct{} @@ -123,7 +123,6 @@ func (r *StorageRepository) Save(storage *Storage) (*Storage, error) { return nil }) - if err != nil { return nil, err } diff --git a/backend/internal/features/storages/service.go b/backend/internal/features/storages/service.go index d918ded..46fb3ab 100644 --- a/backend/internal/features/storages/service.go +++ b/backend/internal/features/storages/service.go @@ -3,14 +3,14 @@ package storages import ( "fmt" + "github.com/google/uuid" + "databasus-backend/internal/config" audit_logs "databasus-backend/internal/features/audit_logs" users_enums "databasus-backend/internal/features/users/enums" users_models "databasus-backend/internal/features/users/models" workspaces_services "databasus-backend/internal/features/workspaces/services" "databasus-backend/internal/util/encryption" - - "github.com/google/uuid" ) type StorageService struct { diff --git a/backend/internal/features/storages/testing.go b/backend/internal/features/storages/testing.go index b51ba70..768e287 100644 --- a/backend/internal/features/storages/testing.go +++ b/backend/internal/features/storages/testing.go @@ -1,9 +1,9 @@ package storages import ( - local_storage "databasus-backend/internal/features/storages/models/local" - "github.com/google/uuid" + + local_storage "databasus-backend/internal/features/storages/models/local" ) func CreateTestStorage(workspaceID uuid.UUID) *Storage { diff --git a/backend/internal/features/system/healthcheck/di.go b/backend/internal/features/system/healthcheck/di.go index e5e85c0..234b625 100644 --- a/backend/internal/features/system/healthcheck/di.go +++ b/backend/internal/features/system/healthcheck/di.go @@ -10,6 +10,7 @@ var healthcheckService = &HealthcheckService{ backuping.GetBackupsScheduler(), backuping.GetBackuperNode(), } + var healthcheckController = &HealthcheckController{ healthcheckService, } diff --git a/backend/internal/features/system/healthcheck/service.go b/backend/internal/features/system/healthcheck/service.go index a308ff0..fd41de5 100644 --- a/backend/internal/features/system/healthcheck/service.go +++ b/backend/internal/features/system/healthcheck/service.go @@ -2,13 +2,14 @@ package system_healthcheck import ( "context" + "errors" + "time" + "databasus-backend/internal/config" "databasus-backend/internal/features/backups/backups/backuping" "databasus-backend/internal/features/disk" "databasus-backend/internal/storage" cache_utils "databasus-backend/internal/util/cache" - "errors" - "time" ) type HealthcheckService struct { @@ -43,7 +44,6 @@ func (s *HealthcheckService) performHealthCheck() error { db := storage.GetDb() err = db.Raw("SELECT 1").Error - if err != nil { return errors.New("cannot connect to the database") } @@ -61,7 +61,6 @@ func (s *HealthcheckService) performHealthCheck() error { if config.GetEnv().IsProcessingNode { if !s.backuperNode.IsBackuperRunning() { return errors.New("backuper node is not running for more than 5 minutes") - } } diff --git a/backend/internal/features/tasks/cancellation/cancel_manager.go b/backend/internal/features/tasks/cancellation/cancel_manager.go index 5a98eac..992ac22 100644 --- a/backend/internal/features/tasks/cancellation/cancel_manager.go +++ b/backend/internal/features/tasks/cancellation/cancel_manager.go @@ -2,11 +2,12 @@ package task_cancellation import ( "context" - cache_utils "databasus-backend/internal/util/cache" "log/slog" "sync" "github.com/google/uuid" + + cache_utils "databasus-backend/internal/util/cache" ) const taskCancelChannel = "task:cancel" diff --git a/backend/internal/features/tasks/cancellation/di.go b/backend/internal/features/tasks/cancellation/di.go index 1763521..e302653 100644 --- a/backend/internal/features/tasks/cancellation/di.go +++ b/backend/internal/features/tasks/cancellation/di.go @@ -5,10 +5,10 @@ import ( "sync" "sync/atomic" + "github.com/google/uuid" + cache_utils "databasus-backend/internal/util/cache" "databasus-backend/internal/util/logger" - - "github.com/google/uuid" ) var taskCancelManager = &TaskCancelManager{ diff --git a/backend/internal/features/tests/mariadb_backup_restore_test.go b/backend/internal/features/tests/mariadb_backup_restore_test.go index 43f2cf7..4b05b93 100644 --- a/backend/internal/features/tests/mariadb_backup_restore_test.go +++ b/backend/internal/features/tests/mariadb_backup_restore_test.go @@ -587,7 +587,7 @@ func waitForMariadbRestoreCompletion( } } -func verifyMariadbDataIntegrity(t *testing.T, originalDB *sqlx.DB, restoredDB *sqlx.DB) { +func verifyMariadbDataIntegrity(t *testing.T, originalDB, restoredDB *sqlx.DB) { var originalData []MariadbTestDataItem var restoredData []MariadbTestDataItem diff --git a/backend/internal/features/tests/mysql_backup_restore_test.go b/backend/internal/features/tests/mysql_backup_restore_test.go index 71d09b5..b14e326 100644 --- a/backend/internal/features/tests/mysql_backup_restore_test.go +++ b/backend/internal/features/tests/mysql_backup_restore_test.go @@ -542,7 +542,7 @@ func waitForMysqlRestoreCompletion( } } -func verifyMysqlDataIntegrity(t *testing.T, originalDB *sqlx.DB, restoredDB *sqlx.DB) { +func verifyMysqlDataIntegrity(t *testing.T, originalDB, restoredDB *sqlx.DB) { var originalData []MysqlTestDataItem var restoredData []MysqlTestDataItem diff --git a/backend/internal/features/tests/postgresql_backup_restore_test.go b/backend/internal/features/tests/postgresql_backup_restore_test.go index 5e78a3d..3dbc7be 100644 --- a/backend/internal/features/tests/postgresql_backup_restore_test.go +++ b/backend/internal/features/tests/postgresql_backup_restore_test.go @@ -376,7 +376,7 @@ func Test_BackupAndRestorePostgresql_WithReadOnlyUser_RestoreIsSuccessful(t *tes } } -func testBackupRestoreForVersion(t *testing.T, pgVersion string, port string, cpuCount int) { +func testBackupRestoreForVersion(t *testing.T, pgVersion, port string, cpuCount int) { container, err := connectToPostgresContainer(pgVersion, port) assert.NoError(t, err) defer func() { @@ -478,7 +478,7 @@ func testBackupRestoreForVersion(t *testing.T, pgVersion string, port string, cp workspaces_testing.RemoveTestWorkspace(workspace, router) } -func testSchemaSelectionAllSchemasForVersion(t *testing.T, pgVersion string, port string) { +func testSchemaSelectionAllSchemasForVersion(t *testing.T, pgVersion, port string) { container, err := connectToPostgresContainer(pgVersion, port) if err != nil { t.Fatalf("Failed to connect to PostgreSQL container: %v", err) @@ -607,7 +607,7 @@ func testSchemaSelectionAllSchemasForVersion(t *testing.T, pgVersion string, por workspaces_testing.RemoveTestWorkspace(workspace, router) } -func testBackupRestoreWithExcludeExtensionsForVersion(t *testing.T, pgVersion string, port string) { +func testBackupRestoreWithExcludeExtensionsForVersion(t *testing.T, pgVersion, port string) { container, err := connectToPostgresContainer(pgVersion, port) if err != nil { t.Fatalf("Failed to connect to PostgreSQL container: %v", err) @@ -885,7 +885,7 @@ func testBackupRestoreWithoutExcludeExtensionsForVersion( workspaces_testing.RemoveTestWorkspace(workspace, router) } -func testBackupRestoreWithReadOnlyUserForVersion(t *testing.T, pgVersion string, port string) { +func testBackupRestoreWithReadOnlyUserForVersion(t *testing.T, pgVersion, port string) { container, err := connectToPostgresContainer(pgVersion, port) assert.NoError(t, err) defer func() { @@ -1128,7 +1128,7 @@ func testSchemaSelectionOnlySpecifiedSchemasForVersion( workspaces_testing.RemoveTestWorkspace(workspace, router) } -func testBackupRestoreWithEncryptionForVersion(t *testing.T, pgVersion string, port string) { +func testBackupRestoreWithEncryptionForVersion(t *testing.T, pgVersion, port string) { container, err := connectToPostgresContainer(pgVersion, port) assert.NoError(t, err) defer func() { @@ -1673,7 +1673,7 @@ func createSupabaseRestoreViaAPI( ) } -func verifyDataIntegrity(t *testing.T, originalDB *sqlx.DB, restoredDB *sqlx.DB, tableName string) { +func verifyDataIntegrity(t *testing.T, originalDB, restoredDB *sqlx.DB, tableName string) { var originalData []TestDataItem var restoredData []TestDataItem @@ -1755,7 +1755,7 @@ func updateDatabaseCredentialsViaAPI( return &updatedDatabase } -func connectToPostgresContainer(version string, port string) (*PostgresContainer, error) { +func connectToPostgresContainer(version, port string) (*PostgresContainer, error) { dbName := "testdb" password := "testpassword" username := "testuser" diff --git a/backend/internal/features/users/controllers/e2e_test.go b/backend/internal/features/users/controllers/e2e_test.go index 06dd892..7981dff 100644 --- a/backend/internal/features/users/controllers/e2e_test.go +++ b/backend/internal/features/users/controllers/e2e_test.go @@ -4,16 +4,16 @@ import ( "net/http" "testing" + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + users_dto "databasus-backend/internal/features/users/dto" users_enums "databasus-backend/internal/features/users/enums" users_middleware "databasus-backend/internal/features/users/middleware" users_services "databasus-backend/internal/features/users/services" users_testing "databasus-backend/internal/features/users/testing" test_utils "databasus-backend/internal/util/testing" - - "github.com/gin-gonic/gin" - "github.com/google/uuid" - "github.com/stretchr/testify/assert" ) func Test_AdminLifecycleE2E_CompletesSuccessfully(t *testing.T) { diff --git a/backend/internal/features/users/controllers/management_controller.go b/backend/internal/features/users/controllers/management_controller.go index f4c3f98..66b4aa2 100644 --- a/backend/internal/features/users/controllers/management_controller.go +++ b/backend/internal/features/users/controllers/management_controller.go @@ -4,13 +4,13 @@ import ( "fmt" "net/http" + "github.com/gin-gonic/gin" + "github.com/google/uuid" + user_dto "databasus-backend/internal/features/users/dto" user_enums "databasus-backend/internal/features/users/enums" user_middleware "databasus-backend/internal/features/users/middleware" users_services "databasus-backend/internal/features/users/services" - - "github.com/gin-gonic/gin" - "github.com/google/uuid" ) type ManagementController struct { diff --git a/backend/internal/features/users/controllers/management_controller_test.go b/backend/internal/features/users/controllers/management_controller_test.go index a68ef31..3681e30 100644 --- a/backend/internal/features/users/controllers/management_controller_test.go +++ b/backend/internal/features/users/controllers/management_controller_test.go @@ -5,6 +5,10 @@ import ( "testing" "time" + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "databasus-backend/internal/features/audit_logs" users_dto "databasus-backend/internal/features/users/dto" users_enums "databasus-backend/internal/features/users/enums" @@ -15,10 +19,6 @@ import ( workspaces_dto "databasus-backend/internal/features/workspaces/dto" workspaces_testing "databasus-backend/internal/features/workspaces/testing" test_utils "databasus-backend/internal/util/testing" - - "github.com/gin-gonic/gin" - "github.com/google/uuid" - "github.com/stretchr/testify/assert" ) func Test_GetUsersList_WhenUserIsAdmin_ReturnsUsers(t *testing.T) { diff --git a/backend/internal/features/users/controllers/password_reset_test.go b/backend/internal/features/users/controllers/password_reset_test.go index 65a3f54..c56cd2d 100644 --- a/backend/internal/features/users/controllers/password_reset_test.go +++ b/backend/internal/features/users/controllers/password_reset_test.go @@ -5,6 +5,10 @@ import ( "testing" "time" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "golang.org/x/crypto/bcrypt" + users_dto "databasus-backend/internal/features/users/dto" users_enums "databasus-backend/internal/features/users/enums" users_models "databasus-backend/internal/features/users/models" @@ -12,10 +16,6 @@ import ( users_testing "databasus-backend/internal/features/users/testing" "databasus-backend/internal/storage" test_utils "databasus-backend/internal/util/testing" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - "golang.org/x/crypto/bcrypt" ) func Test_SendResetPasswordCode_WithValidEmail_CodeSent(t *testing.T) { diff --git a/backend/internal/features/users/controllers/settings_controller.go b/backend/internal/features/users/controllers/settings_controller.go index a9fbaef..7bf4608 100644 --- a/backend/internal/features/users/controllers/settings_controller.go +++ b/backend/internal/features/users/controllers/settings_controller.go @@ -3,12 +3,12 @@ package users_controllers import ( "net/http" + "github.com/gin-gonic/gin" + user_enums "databasus-backend/internal/features/users/enums" user_middleware "databasus-backend/internal/features/users/middleware" user_models "databasus-backend/internal/features/users/models" users_services "databasus-backend/internal/features/users/services" - - "github.com/gin-gonic/gin" ) type SettingsController struct { diff --git a/backend/internal/features/users/controllers/settings_controller_test.go b/backend/internal/features/users/controllers/settings_controller_test.go index 5a45ad3..0db8ccd 100644 --- a/backend/internal/features/users/controllers/settings_controller_test.go +++ b/backend/internal/features/users/controllers/settings_controller_test.go @@ -4,12 +4,12 @@ import ( "net/http" "testing" + "github.com/stretchr/testify/assert" + users_enums "databasus-backend/internal/features/users/enums" users_models "databasus-backend/internal/features/users/models" users_testing "databasus-backend/internal/features/users/testing" test_utils "databasus-backend/internal/util/testing" - - "github.com/stretchr/testify/assert" ) func Test_GetUserSettings_WhenUserIsAdmin_ReturnsSettings(t *testing.T) { diff --git a/backend/internal/features/users/controllers/user_controller.go b/backend/internal/features/users/controllers/user_controller.go index 0845583..cd80ca4 100644 --- a/backend/internal/features/users/controllers/user_controller.go +++ b/backend/internal/features/users/controllers/user_controller.go @@ -5,6 +5,8 @@ import ( "net/http" "time" + "github.com/gin-gonic/gin" + "databasus-backend/internal/config" user_dto "databasus-backend/internal/features/users/dto" users_errors "databasus-backend/internal/features/users/errors" @@ -12,8 +14,6 @@ import ( users_services "databasus-backend/internal/features/users/services" cache_utils "databasus-backend/internal/util/cache" cloudflare_turnstile "databasus-backend/internal/util/cloudflare_turnstile" - - "github.com/gin-gonic/gin" ) type UserController struct { diff --git a/backend/internal/features/users/controllers/user_controller_test.go b/backend/internal/features/users/controllers/user_controller_test.go index 6c8be5c..cdad166 100644 --- a/backend/internal/features/users/controllers/user_controller_test.go +++ b/backend/internal/features/users/controllers/user_controller_test.go @@ -7,15 +7,15 @@ import ( "net/http/httptest" "testing" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "golang.org/x/oauth2" + users_dto "databasus-backend/internal/features/users/dto" users_enums "databasus-backend/internal/features/users/enums" users_services "databasus-backend/internal/features/users/services" users_testing "databasus-backend/internal/features/users/testing" test_utils "databasus-backend/internal/util/testing" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - "golang.org/x/oauth2" ) func Test_SignUpUser_WithValidData_UserCreated(t *testing.T) { diff --git a/backend/internal/features/users/dto/dto.go b/backend/internal/features/users/dto/dto.go index 5e224f9..dc092be 100644 --- a/backend/internal/features/users/dto/dto.go +++ b/backend/internal/features/users/dto/dto.go @@ -3,9 +3,9 @@ package users_dto import ( "time" - users_enums "databasus-backend/internal/features/users/enums" - "github.com/google/uuid" + + users_enums "databasus-backend/internal/features/users/enums" ) type SignUpRequestDTO struct { diff --git a/backend/internal/features/users/errors/errors.go b/backend/internal/features/users/errors/errors.go index d25ea8a..e404589 100644 --- a/backend/internal/features/users/errors/errors.go +++ b/backend/internal/features/users/errors/errors.go @@ -2,6 +2,4 @@ package users_errors import "errors" -var ( - ErrInsufficientPermissionsToInviteUsers = errors.New("insufficient permissions to invite users") -) +var ErrInsufficientPermissionsToInviteUsers = errors.New("insufficient permissions to invite users") diff --git a/backend/internal/features/users/interfaces/interfaces.go b/backend/internal/features/users/interfaces/interfaces.go index 07c9acd..4b60637 100644 --- a/backend/internal/features/users/interfaces/interfaces.go +++ b/backend/internal/features/users/interfaces/interfaces.go @@ -5,7 +5,7 @@ import ( ) type AuditLogWriter interface { - WriteAuditLog(message string, userID *uuid.UUID, workspaceID *uuid.UUID) + WriteAuditLog(message string, userID, workspaceID *uuid.UUID) } type EmailSender interface { diff --git a/backend/internal/features/users/middleware/middleware.go b/backend/internal/features/users/middleware/middleware.go index 3bb710a..43e41ce 100644 --- a/backend/internal/features/users/middleware/middleware.go +++ b/backend/internal/features/users/middleware/middleware.go @@ -1,12 +1,13 @@ package users_middleware import ( - users_enums "databasus-backend/internal/features/users/enums" - users_models "databasus-backend/internal/features/users/models" - users_services "databasus-backend/internal/features/users/services" "net/http" "github.com/gin-gonic/gin" + + users_enums "databasus-backend/internal/features/users/enums" + users_models "databasus-backend/internal/features/users/models" + users_services "databasus-backend/internal/features/users/services" ) // AuthMiddleware validates JWT token and adds user to context diff --git a/backend/internal/features/users/models/user.go b/backend/internal/features/users/models/user.go index e4f218c..592b293 100644 --- a/backend/internal/features/users/models/user.go +++ b/backend/internal/features/users/models/user.go @@ -1,10 +1,11 @@ package users_models import ( - users_enums "databasus-backend/internal/features/users/enums" "time" "github.com/google/uuid" + + users_enums "databasus-backend/internal/features/users/enums" ) type User struct { diff --git a/backend/internal/features/users/repositories/di.go b/backend/internal/features/users/repositories/di.go index b4b4e6a..af67623 100644 --- a/backend/internal/features/users/repositories/di.go +++ b/backend/internal/features/users/repositories/di.go @@ -1,8 +1,10 @@ package users_repositories -var userRepository = &UserRepository{} -var usersSettingsRepository = &UsersSettingsRepository{} -var passwordResetRepository = &PasswordResetRepository{} +var ( + userRepository = &UserRepository{} + usersSettingsRepository = &UsersSettingsRepository{} + passwordResetRepository = &PasswordResetRepository{} +) func GetUserRepository() *UserRepository { return userRepository diff --git a/backend/internal/features/users/repositories/password_reset_repository.go b/backend/internal/features/users/repositories/password_reset_repository.go index 1c85104..1ccdeb8 100644 --- a/backend/internal/features/users/repositories/password_reset_repository.go +++ b/backend/internal/features/users/repositories/password_reset_repository.go @@ -3,10 +3,10 @@ package users_repositories import ( "time" + "github.com/google/uuid" + users_models "databasus-backend/internal/features/users/models" "databasus-backend/internal/storage" - - "github.com/google/uuid" ) type PasswordResetRepository struct{} @@ -27,7 +27,6 @@ func (r *PasswordResetRepository) GetValidCodeByUserID( Where("user_id = ? AND is_used = ? AND expires_at > ?", userID, false, time.Now().UTC()). Order("created_at DESC"). First(&code).Error - if err != nil { return nil, err } diff --git a/backend/internal/features/users/repositories/user_repository.go b/backend/internal/features/users/repositories/user_repository.go index 2318158..10830ca 100644 --- a/backend/internal/features/users/repositories/user_repository.go +++ b/backend/internal/features/users/repositories/user_repository.go @@ -1,14 +1,15 @@ package users_repositories import ( - users_enums "databasus-backend/internal/features/users/enums" - users_models "databasus-backend/internal/features/users/models" - "databasus-backend/internal/storage" "fmt" "time" "github.com/google/uuid" "gorm.io/gorm" + + users_enums "databasus-backend/internal/features/users/enums" + users_models "databasus-backend/internal/features/users/models" + "databasus-backend/internal/storage" ) type UserRepository struct{} @@ -156,7 +157,7 @@ func (r *UserRepository) RenameUserEmailForTests(oldEmail, newEmail string) erro return nil } -func (r *UserRepository) UpdateUserInfo(userID uuid.UUID, name *string, email *string) error { +func (r *UserRepository) UpdateUserInfo(userID uuid.UUID, name, email *string) error { updates := make(map[string]any) if name != nil { diff --git a/backend/internal/features/users/repositories/users_settings_repository.go b/backend/internal/features/users/repositories/users_settings_repository.go index 82e5f93..e15fc10 100644 --- a/backend/internal/features/users/repositories/users_settings_repository.go +++ b/backend/internal/features/users/repositories/users_settings_repository.go @@ -1,11 +1,11 @@ package users_repositories import ( - user_models "databasus-backend/internal/features/users/models" - "databasus-backend/internal/storage" - "github.com/google/uuid" "gorm.io/gorm" + + user_models "databasus-backend/internal/features/users/models" + "databasus-backend/internal/storage" ) type UsersSettingsRepository struct{} diff --git a/backend/internal/features/users/services/di.go b/backend/internal/features/users/services/di.go index 43aa2b8..893f8e4 100644 --- a/backend/internal/features/users/services/di.go +++ b/backend/internal/features/users/services/di.go @@ -14,10 +14,12 @@ var userService = &UserService{ email.GetEmailSMTPSender(), users_repositories.GetPasswordResetRepository(), } + var settingsService = &SettingsService{ users_repositories.GetUsersSettingsRepository(), nil, } + var managementService = &UserManagementService{ users_repositories.GetUserRepository(), nil, diff --git a/backend/internal/features/users/services/management_service.go b/backend/internal/features/users/services/management_service.go index 3a039e2..b2dc09d 100644 --- a/backend/internal/features/users/services/management_service.go +++ b/backend/internal/features/users/services/management_service.go @@ -5,12 +5,12 @@ import ( "fmt" "time" + "github.com/google/uuid" + user_enums "databasus-backend/internal/features/users/enums" user_interfaces "databasus-backend/internal/features/users/interfaces" user_models "databasus-backend/internal/features/users/models" user_repositories "databasus-backend/internal/features/users/repositories" - - "github.com/google/uuid" ) type UserManagementService struct { diff --git a/backend/internal/features/users/services/oauth_testing.go b/backend/internal/features/users/services/oauth_testing.go index 97f10e8..e5eef16 100644 --- a/backend/internal/features/users/services/oauth_testing.go +++ b/backend/internal/features/users/services/oauth_testing.go @@ -1,9 +1,9 @@ package users_services import ( - users_dto "databasus-backend/internal/features/users/dto" - "golang.org/x/oauth2" + + users_dto "databasus-backend/internal/features/users/dto" ) func (s *UserService) HandleGitHubOAuthWithMockEndpoint( diff --git a/backend/internal/features/users/services/user_services.go b/backend/internal/features/users/services/user_services.go index 2fbd12c..f283745 100644 --- a/backend/internal/features/users/services/user_services.go +++ b/backend/internal/features/users/services/user_services.go @@ -194,7 +194,6 @@ func (s *UserService) GetUserFromToken(token string) (*users_models.User, error) } return []byte(secretKey), nil }) - if err != nil { return nil, fmt.Errorf("invalid token: %w", err) } @@ -275,7 +274,6 @@ func (s *UserService) CreateInitialAdmin() error { func (s *UserService) IsRootAdminHasPassword() (bool, error) { admin, err := s.userRepository.GetUserByEmail("admin") - if err != nil { return false, fmt.Errorf("failed to get admin user: %w", err) } @@ -321,7 +319,7 @@ func (s *UserService) SetRootAdminPassword(password string) error { return nil } -func (s *UserService) ChangeUserPasswordByEmail(email string, newPassword string) error { +func (s *UserService) ChangeUserPasswordByEmail(email, newPassword string) error { user, err := s.userRepository.GetUserByEmail(email) if err != nil { return fmt.Errorf("failed to get user: %w", err) diff --git a/backend/internal/features/users/testing/user_utils.go b/backend/internal/features/users/testing/user_utils.go index 50a6c4d..6f4add2 100644 --- a/backend/internal/features/users/testing/user_utils.go +++ b/backend/internal/features/users/testing/user_utils.go @@ -5,13 +5,13 @@ import ( "strings" "time" + "github.com/google/uuid" + users_dto "databasus-backend/internal/features/users/dto" users_enums "databasus-backend/internal/features/users/enums" users_models "databasus-backend/internal/features/users/models" users_repositories "databasus-backend/internal/features/users/repositories" users_services "databasus-backend/internal/features/users/services" - - "github.com/google/uuid" ) func CreateTestUser(role users_enums.UserRole) *users_dto.SignInResponseDTO { diff --git a/backend/internal/features/workspaces/controllers/e2e_test.go b/backend/internal/features/workspaces/controllers/e2e_test.go index 44b6454..d897c24 100644 --- a/backend/internal/features/workspaces/controllers/e2e_test.go +++ b/backend/internal/features/workspaces/controllers/e2e_test.go @@ -4,15 +4,15 @@ import ( "net/http" "testing" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + users_enums "databasus-backend/internal/features/users/enums" users_testing "databasus-backend/internal/features/users/testing" workspaces_dto "databasus-backend/internal/features/workspaces/dto" workspaces_models "databasus-backend/internal/features/workspaces/models" workspaces_testing "databasus-backend/internal/features/workspaces/testing" test_utils "databasus-backend/internal/util/testing" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" ) func Test_WorkspaceLifecycleE2E_CompletesSuccessfully(t *testing.T) { diff --git a/backend/internal/features/workspaces/controllers/membership_controller.go b/backend/internal/features/workspaces/controllers/membership_controller.go index 8297270..8315d0a 100644 --- a/backend/internal/features/workspaces/controllers/membership_controller.go +++ b/backend/internal/features/workspaces/controllers/membership_controller.go @@ -4,13 +4,13 @@ import ( "errors" "net/http" + "github.com/gin-gonic/gin" + "github.com/google/uuid" + users_middleware "databasus-backend/internal/features/users/middleware" workspaces_dto "databasus-backend/internal/features/workspaces/dto" workspaces_errors "databasus-backend/internal/features/workspaces/errors" workspaces_services "databasus-backend/internal/features/workspaces/services" - - "github.com/gin-gonic/gin" - "github.com/google/uuid" ) type MembershipController struct { diff --git a/backend/internal/features/workspaces/controllers/membership_controller_test.go b/backend/internal/features/workspaces/controllers/membership_controller_test.go index cb73439..f2fcaff 100644 --- a/backend/internal/features/workspaces/controllers/membership_controller_test.go +++ b/backend/internal/features/workspaces/controllers/membership_controller_test.go @@ -6,14 +6,14 @@ import ( "strings" "testing" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + users_enums "databasus-backend/internal/features/users/enums" users_testing "databasus-backend/internal/features/users/testing" workspaces_dto "databasus-backend/internal/features/workspaces/dto" workspaces_testing "databasus-backend/internal/features/workspaces/testing" test_utils "databasus-backend/internal/util/testing" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" ) // ListMembers Tests diff --git a/backend/internal/features/workspaces/controllers/workspace_controller.go b/backend/internal/features/workspaces/controllers/workspace_controller.go index faac0d5..cf75753 100644 --- a/backend/internal/features/workspaces/controllers/workspace_controller.go +++ b/backend/internal/features/workspaces/controllers/workspace_controller.go @@ -4,15 +4,15 @@ import ( "errors" "net/http" + "github.com/gin-gonic/gin" + "github.com/google/uuid" + audit_logs "databasus-backend/internal/features/audit_logs" users_middleware "databasus-backend/internal/features/users/middleware" workspaces_dto "databasus-backend/internal/features/workspaces/dto" workspaces_errors "databasus-backend/internal/features/workspaces/errors" workspaces_models "databasus-backend/internal/features/workspaces/models" workspaces_services "databasus-backend/internal/features/workspaces/services" - - "github.com/gin-gonic/gin" - "github.com/google/uuid" ) type WorkspaceController struct { diff --git a/backend/internal/features/workspaces/controllers/workspace_controller_test.go b/backend/internal/features/workspaces/controllers/workspace_controller_test.go index 826de08..671fab4 100644 --- a/backend/internal/features/workspaces/controllers/workspace_controller_test.go +++ b/backend/internal/features/workspaces/controllers/workspace_controller_test.go @@ -5,6 +5,9 @@ import ( "net/http" "testing" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + audit_logs "databasus-backend/internal/features/audit_logs" users_dto "databasus-backend/internal/features/users/dto" users_enums "databasus-backend/internal/features/users/enums" @@ -15,9 +18,6 @@ import ( workspaces_models "databasus-backend/internal/features/workspaces/models" workspaces_testing "databasus-backend/internal/features/workspaces/testing" test_utils "databasus-backend/internal/util/testing" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" ) func Test_CreateWorkspace_PermissionsEnforced(t *testing.T) { diff --git a/backend/internal/features/workspaces/dto/dto.go b/backend/internal/features/workspaces/dto/dto.go index bcff4e1..7579007 100644 --- a/backend/internal/features/workspaces/dto/dto.go +++ b/backend/internal/features/workspaces/dto/dto.go @@ -3,9 +3,9 @@ package workspaces_dto import ( "time" - users_enums "databasus-backend/internal/features/users/enums" - "github.com/google/uuid" + + users_enums "databasus-backend/internal/features/users/enums" ) type AddMemberStatus string diff --git a/backend/internal/features/workspaces/models/workspace_membership.go b/backend/internal/features/workspaces/models/workspace_membership.go index ea067d1..d47ca3d 100644 --- a/backend/internal/features/workspaces/models/workspace_membership.go +++ b/backend/internal/features/workspaces/models/workspace_membership.go @@ -3,9 +3,9 @@ package workspaces_models import ( "time" - users_enums "databasus-backend/internal/features/users/enums" - "github.com/google/uuid" + + users_enums "databasus-backend/internal/features/users/enums" ) type WorkspaceMembership struct { diff --git a/backend/internal/features/workspaces/repositories/membership_repository.go b/backend/internal/features/workspaces/repositories/membership_repository.go index 128efac..3b00872 100644 --- a/backend/internal/features/workspaces/repositories/membership_repository.go +++ b/backend/internal/features/workspaces/repositories/membership_repository.go @@ -4,13 +4,13 @@ import ( "errors" "time" + "github.com/google/uuid" + "gorm.io/gorm" + users_enums "databasus-backend/internal/features/users/enums" workspaces_dto "databasus-backend/internal/features/workspaces/dto" workspaces_models "databasus-backend/internal/features/workspaces/models" "databasus-backend/internal/storage" - - "github.com/google/uuid" - "gorm.io/gorm" ) type MembershipRepository struct{} @@ -82,7 +82,6 @@ func (r *MembershipRepository) GetUserWorkspaceRole( err := storage.GetDb(). Where("workspace_id = ? AND user_id = ?", workspaceID, userID). First(&membership).Error - if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, nil @@ -102,7 +101,6 @@ func (r *MembershipRepository) GetWorkspaceOwner( err := storage.GetDb(). Where("workspace_id = ? AND role = ?", workspaceID, users_enums.WorkspaceRoleOwner). First(&membership).Error - if err != nil { return nil, err } diff --git a/backend/internal/features/workspaces/repositories/workspace_repository.go b/backend/internal/features/workspaces/repositories/workspace_repository.go index c8385aa..71bdca2 100644 --- a/backend/internal/features/workspaces/repositories/workspace_repository.go +++ b/backend/internal/features/workspaces/repositories/workspace_repository.go @@ -3,10 +3,10 @@ package workspaces_repositories import ( "time" + "github.com/google/uuid" + workspaces_models "databasus-backend/internal/features/workspaces/models" "databasus-backend/internal/storage" - - "github.com/google/uuid" ) type WorkspaceRepository struct{} diff --git a/backend/internal/features/workspaces/services/di.go b/backend/internal/features/workspaces/services/di.go index e03867c..fc48001 100644 --- a/backend/internal/features/workspaces/services/di.go +++ b/backend/internal/features/workspaces/services/di.go @@ -9,8 +9,10 @@ import ( "databasus-backend/internal/util/logger" ) -var workspaceRepository = &workspaces_repositories.WorkspaceRepository{} -var membershipRepository = &workspaces_repositories.MembershipRepository{} +var ( + workspaceRepository = &workspaces_repositories.WorkspaceRepository{} + membershipRepository = &workspaces_repositories.MembershipRepository{} +) var workspaceService = &WorkspaceService{ workspaceRepository, diff --git a/backend/internal/features/workspaces/services/membership_service.go b/backend/internal/features/workspaces/services/membership_service.go index b47fbf2..b39d83d 100644 --- a/backend/internal/features/workspaces/services/membership_service.go +++ b/backend/internal/features/workspaces/services/membership_service.go @@ -4,6 +4,8 @@ import ( "fmt" "log/slog" + "github.com/google/uuid" + "databasus-backend/internal/config" audit_logs "databasus-backend/internal/features/audit_logs" users_dto "databasus-backend/internal/features/users/dto" @@ -15,8 +17,6 @@ import ( workspaces_interfaces "databasus-backend/internal/features/workspaces/interfaces" workspaces_models "databasus-backend/internal/features/workspaces/models" workspaces_repositories "databasus-backend/internal/features/workspaces/repositories" - - "github.com/google/uuid" ) type MembershipService struct { diff --git a/backend/internal/features/workspaces/services/workspace_service.go b/backend/internal/features/workspaces/services/workspace_service.go index 2e7c16d..937a77e 100644 --- a/backend/internal/features/workspaces/services/workspace_service.go +++ b/backend/internal/features/workspaces/services/workspace_service.go @@ -4,6 +4,8 @@ import ( "fmt" "time" + "github.com/google/uuid" + audit_logs "databasus-backend/internal/features/audit_logs" users_enums "databasus-backend/internal/features/users/enums" users_models "databasus-backend/internal/features/users/models" @@ -13,8 +15,6 @@ import ( workspaces_interfaces "databasus-backend/internal/features/workspaces/interfaces" workspaces_models "databasus-backend/internal/features/workspaces/models" workspaces_repositories "databasus-backend/internal/features/workspaces/repositories" - - "github.com/google/uuid" ) type WorkspaceService struct { @@ -37,7 +37,6 @@ func (s *WorkspaceService) CreateWorkspace( creator *users_models.User, ) (*workspaces_dto.WorkspaceResponseDTO, error) { settings, err := s.settingsService.GetSettings() - if err != nil { return nil, fmt.Errorf("failed to get settings: %w", err) } @@ -116,7 +115,6 @@ func (s *WorkspaceService) UpdateWorkspace( user *users_models.User, ) (*workspaces_models.Workspace, error) { canManage, err := s.CanUserManageWorkspace(workspaceID, user) - if err != nil { return nil, err } diff --git a/backend/internal/features/workspaces/testing/testing.go b/backend/internal/features/workspaces/testing/testing.go index b4fb60e..cafaebd 100644 --- a/backend/internal/features/workspaces/testing/testing.go +++ b/backend/internal/features/workspaces/testing/testing.go @@ -7,6 +7,9 @@ import ( "net/http" "net/http/httptest" + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "databasus-backend/internal/features/audit_logs" users_dto "databasus-backend/internal/features/users/dto" users_enums "databasus-backend/internal/features/users/enums" @@ -16,9 +19,6 @@ import ( workspaces_dto "databasus-backend/internal/features/workspaces/dto" workspaces_models "databasus-backend/internal/features/workspaces/models" workspaces_repositories "databasus-backend/internal/features/workspaces/repositories" - - "github.com/gin-gonic/gin" - "github.com/google/uuid" ) func CreateTestRouter(controllers ...ControllerInterface) *gin.Engine { diff --git a/backend/internal/storage/storage.go b/backend/internal/storage/storage.go index f04688d..c549ce8 100644 --- a/backend/internal/storage/storage.go +++ b/backend/internal/storage/storage.go @@ -1,14 +1,15 @@ package storage import ( - "databasus-backend/internal/config" - "databasus-backend/internal/util/logger" "os" "sync" "gorm.io/driver/postgres" "gorm.io/gorm" gormLogger "gorm.io/gorm/logger" + + "databasus-backend/internal/config" + "databasus-backend/internal/util/logger" ) var log = logger.GetLogger() diff --git a/backend/internal/util/cache/cache.go b/backend/internal/util/cache/cache.go index 6ea6760..0ab5aa8 100644 --- a/backend/internal/util/cache/cache.go +++ b/backend/internal/util/cache/cache.go @@ -3,10 +3,11 @@ package cache_utils import ( "context" "crypto/tls" - "databasus-backend/internal/config" "sync" "github.com/valkey-io/valkey-go" + + "databasus-backend/internal/config" ) var ( diff --git a/backend/internal/util/cache/pubsub.go b/backend/internal/util/cache/pubsub.go index 965c463..8b7f949 100644 --- a/backend/internal/util/cache/pubsub.go +++ b/backend/internal/util/cache/pubsub.go @@ -6,9 +6,9 @@ import ( "log/slog" "sync" - "databasus-backend/internal/util/logger" - "github.com/valkey-io/valkey-go" + + "databasus-backend/internal/util/logger" ) type PubSubManager struct { @@ -46,7 +46,7 @@ func (m *PubSubManager) Subscribe( return nil } -func (m *PubSubManager) Publish(ctx context.Context, channel string, message string) error { +func (m *PubSubManager) Publish(ctx context.Context, channel, message string) error { cmd := m.client.B().Publish().Channel(channel).Message(message).Build() result := m.client.Do(ctx, cmd) diff --git a/backend/internal/util/encryption/secret_key_field_encryptor.go b/backend/internal/util/encryption/secret_key_field_encryptor.go index cee5f47..d01dcd3 100644 --- a/backend/internal/util/encryption/secret_key_field_encryptor.go +++ b/backend/internal/util/encryption/secret_key_field_encryptor.go @@ -5,13 +5,14 @@ import ( "crypto/cipher" "crypto/hmac" "crypto/sha256" - "databasus-backend/internal/features/encryption/secrets" "encoding/base64" "errors" "fmt" "strings" "github.com/google/uuid" + + "databasus-backend/internal/features/encryption/secrets" ) const encryptedPrefix = "enc:" diff --git a/backend/internal/util/files/creator.go b/backend/internal/util/files/creator.go index 77b65fa..092dd5e 100644 --- a/backend/internal/util/files/creator.go +++ b/backend/internal/util/files/creator.go @@ -6,7 +6,7 @@ import ( ) func EnsureDirectories(directories []string) error { - const directoryPermissions = 0755 + const directoryPermissions = 0o755 for _, directory := range directories { if _, err := os.Stat(directory); os.IsNotExist(err) { From f712e3a437b0eeeb8812760498c72d756fd282d5 Mon Sep 17 00:00:00 2001 From: Rostislav Dugin Date: Fri, 13 Mar 2026 18:03:38 +0300 Subject: [PATCH 8/9] FEATURE (linters): Introduce more strict linters --- agent/.golangci.yml | 12 ++++++++++++ backend/.golangci.yml | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/agent/.golangci.yml b/agent/.golangci.yml index ea2a327..9e54a7d 100644 --- a/agent/.golangci.yml +++ b/agent/.golangci.yml @@ -7,6 +7,18 @@ run: linters: default: standard + enable: + - funcorder + - gosec + - bodyclose + - errorlint + - gocritic + - unconvert + - misspell + - dupword + - errname + - noctx + - modernize settings: errcheck: diff --git a/backend/.golangci.yml b/backend/.golangci.yml index 6a13c51..18e8b00 100644 --- a/backend/.golangci.yml +++ b/backend/.golangci.yml @@ -7,6 +7,18 @@ run: linters: default: standard + enable: + - funcorder + - gosec + - bodyclose + - errorlint + - gocritic + - unconvert + - misspell + - dupword + - errname + - noctx + - modernize settings: errcheck: From 7e209ff537d8da4b727798545c2945d8305585be Mon Sep 17 00:00:00 2001 From: Rostislav Dugin Date: Fri, 13 Mar 2026 18:50:57 +0300 Subject: [PATCH 9/9] REFACTOR (linters): Apply linters fixes --- agent/.golangci.yml | 2 - agent/internal/features/upgrade/upgrader.go | 23 ++- backend/.golangci.yml | 2 - backend/cmd/main.go | 6 +- .../features/audit_logs/repository.go | 6 +- .../backups/backups/backuping/cleaner.go | 16 +- .../backups/backups/common/interfaces.go | 8 +- .../backups/backups/controllers/controller.go | 5 +- .../backups/backups/core/repository.go | 2 +- .../backups/backups/download/rate_limiter.go | 4 +- .../backups/backups/download/repository.go | 3 +- .../backups/encryption/decrypting_reader.go | 3 +- .../backups/services/postgres_wal_service.go | 148 +++++++++--------- .../usecases/mariadb/create_backup_uc.go | 4 +- .../usecases/mysql/create_backup_uc.go | 4 +- .../usecases/postgresql/create_backup_uc.go | 4 +- .../features/backups/config/service.go | 73 ++++----- .../databases/databases/mariadb/model.go | 2 +- .../databases/databases/mongodb/model.go | 84 +++++----- .../databases/databases/postgresql/model.go | 4 +- backend/internal/features/email/email.go | 3 +- .../notifiers/models/discord/model.go | 5 +- .../notifiers/models/email_notifier/model.go | 3 +- .../features/notifiers/models/slack/model.go | 6 +- .../features/notifiers/models/teams/model.go | 3 +- .../notifiers/models/telegram/model.go | 3 +- .../notifiers/models/webhook/model.go | 5 +- .../internal/features/notifiers/service.go | 6 +- .../restores/restoring/restorer_test.go | 4 + .../features/restores/restoring/scheduler.go | 42 ++--- .../features/restores/restoring/testing.go | 6 +- backend/internal/features/restores/service.go | 97 ++++++------ .../usecases/postgresql/restore_backup_uc.go | 32 ++-- .../storages/models/azure_blob/model.go | 4 +- .../storages/models/google_drive/model.go | 6 +- .../features/storages/models/nas/model.go | 2 +- .../features/storages/models/s3/model.go | 12 +- .../features/storages/models/sftp/model.go | 7 +- backend/internal/features/storages/service.go | 6 +- .../internal/features/test_once_protection.go | 13 +- .../users/repositories/user_repository.go | 7 +- .../repositories/users_settings_repository.go | 4 +- .../features/users/services/user_services.go | 18 ++- .../internal/features/users/testing/mocks.go | 14 +- .../features/workspaces/testing/mocks.go | 14 +- .../features/workspaces/testing/testing.go | 3 +- .../cloudflare_turnstile_service.go | 12 +- backend/internal/util/logger/multi_handler.go | 2 +- .../util/logger/victorialogs_writer.go | 44 +++--- backend/internal/util/testing/requests.go | 27 ++-- 50 files changed, 429 insertions(+), 384 deletions(-) diff --git a/agent/.golangci.yml b/agent/.golangci.yml index 9e54a7d..5b27d77 100644 --- a/agent/.golangci.yml +++ b/agent/.golangci.yml @@ -9,13 +9,11 @@ linters: default: standard enable: - funcorder - - gosec - bodyclose - errorlint - gocritic - unconvert - misspell - - dupword - errname - noctx - modernize diff --git a/agent/internal/features/upgrade/upgrader.go b/agent/internal/features/upgrade/upgrader.go index 6dd28c4..f44decf 100644 --- a/agent/internal/features/upgrade/upgrader.go +++ b/agent/internal/features/upgrade/upgrader.go @@ -1,6 +1,7 @@ package upgrade import ( + "context" "encoding/json" "fmt" "io" @@ -74,9 +75,17 @@ func CheckAndUpdate(databasusHost, currentVersion string, isDev bool, log Logger } func fetchServerVersion(host string, log Logger) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + client := &http.Client{Timeout: 10 * time.Second} - resp, err := client.Get(host + "/api/v1/system/version") + req, err := http.NewRequestWithContext(ctx, http.MethodGet, host+"/api/v1/system/version", nil) + if err != nil { + return "", err + } + + resp, err := client.Do(req) if err != nil { log.Warn("Could not reach server for update check, continuing", "error", err) return "", err @@ -104,7 +113,15 @@ func fetchServerVersion(host string, log Logger) (string, error) { func downloadBinary(host, destPath string) error { url := fmt.Sprintf("%s/api/v1/system/agent?arch=%s", host, runtime.GOARCH) - resp, err := http.Get(url) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return err + } + + resp, err := http.DefaultClient.Do(req) if err != nil { return err } @@ -126,7 +143,7 @@ func downloadBinary(host, destPath string) error { } func verifyBinary(binaryPath, expectedVersion string) error { - cmd := exec.Command(binaryPath, "version") + cmd := exec.CommandContext(context.Background(), binaryPath, "version") output, err := cmd.Output() if err != nil { diff --git a/backend/.golangci.yml b/backend/.golangci.yml index 18e8b00..d9de713 100644 --- a/backend/.golangci.yml +++ b/backend/.golangci.yml @@ -9,13 +9,11 @@ linters: default: standard enable: - funcorder - - gosec - bodyclose - errorlint - gocritic - unconvert - misspell - - dupword - errname - noctx - modernize diff --git a/backend/cmd/main.go b/backend/cmd/main.go index 611eaad..7b0b189 100644 --- a/backend/cmd/main.go +++ b/backend/cmd/main.go @@ -353,7 +353,9 @@ func generateSwaggerDocs(log *slog.Logger) { return } - cmd := exec.Command("swag", "init", "-d", currentDir, "-g", "cmd/main.go", "-o", "swagger") + cmd := exec.CommandContext( + context.Background(), "swag", "init", "-d", currentDir, "-g", "cmd/main.go", "-o", "swagger", + ) output, err := cmd.CombinedOutput() if err != nil { @@ -367,7 +369,7 @@ func generateSwaggerDocs(log *slog.Logger) { func runMigrations(log *slog.Logger) { log.Info("Running database migrations...") - cmd := exec.Command("goose", "-dir", "./migrations", "up") + cmd := exec.CommandContext(context.Background(), "goose", "-dir", "./migrations", "up") cmd.Env = append( os.Environ(), "GOOSE_DRIVER=postgres", diff --git a/backend/internal/features/audit_logs/repository.go b/backend/internal/features/audit_logs/repository.go index d6264c8..9d1d38e 100644 --- a/backend/internal/features/audit_logs/repository.go +++ b/backend/internal/features/audit_logs/repository.go @@ -38,7 +38,7 @@ func (r *AuditLogRepository) GetGlobal( LEFT JOIN users u ON al.user_id = u.id LEFT JOIN workspaces w ON al.workspace_id = w.id` - args := []interface{}{} + args := []any{} if beforeDate != nil { sql += " WHERE al.created_at < ?" @@ -75,7 +75,7 @@ func (r *AuditLogRepository) GetByUser( LEFT JOIN workspaces w ON al.workspace_id = w.id WHERE al.user_id = ?` - args := []interface{}{userID} + args := []any{userID} if beforeDate != nil { sql += " AND al.created_at < ?" @@ -112,7 +112,7 @@ func (r *AuditLogRepository) GetByWorkspace( LEFT JOIN workspaces w ON al.workspace_id = w.id WHERE al.workspace_id = ?` - args := []interface{}{workspaceID} + args := []any{workspaceID} if beforeDate != nil { sql += " AND al.created_at < ?" diff --git a/backend/internal/features/backups/backups/backuping/cleaner.go b/backend/internal/features/backups/backups/backuping/cleaner.go index 0eff38a..44c3c1b 100644 --- a/backend/internal/features/backups/backups/backuping/cleaner.go +++ b/backend/internal/features/backups/backups/backuping/cleaner.go @@ -446,22 +446,24 @@ func buildGFSKeepSet( } dailyCutoff := rawDailyCutoff - if weeks > 0 { + switch { + case weeks > 0: dailyCutoff = laterOf(dailyCutoff, weeklyCutoff) - } else if months > 0 { + case months > 0: dailyCutoff = laterOf(dailyCutoff, monthlyCutoff) - } else if years > 0 { + case years > 0: dailyCutoff = laterOf(dailyCutoff, yearlyCutoff) } hourlyCutoff := rawHourlyCutoff - if days > 0 { + switch { + case days > 0: hourlyCutoff = laterOf(hourlyCutoff, dailyCutoff) - } else if weeks > 0 { + case weeks > 0: hourlyCutoff = laterOf(hourlyCutoff, weeklyCutoff) - } else if months > 0 { + case months > 0: hourlyCutoff = laterOf(hourlyCutoff, monthlyCutoff) - } else if years > 0 { + case years > 0: hourlyCutoff = laterOf(hourlyCutoff, yearlyCutoff) } diff --git a/backend/internal/features/backups/backups/common/interfaces.go b/backend/internal/features/backups/backups/common/interfaces.go index c9a0852..6e57475 100644 --- a/backend/internal/features/backups/backups/common/interfaces.go +++ b/backend/internal/features/backups/backups/common/interfaces.go @@ -7,6 +7,10 @@ type CountingWriter struct { BytesWritten int64 } +func NewCountingWriter(writer io.Writer) *CountingWriter { + return &CountingWriter{Writer: writer} +} + func (cw *CountingWriter) Write(p []byte) (n int, err error) { n, err = cw.Writer.Write(p) cw.BytesWritten += int64(n) @@ -16,7 +20,3 @@ func (cw *CountingWriter) Write(p []byte) (n int, err error) { func (cw *CountingWriter) GetBytesWritten() int64 { return cw.BytesWritten } - -func NewCountingWriter(writer io.Writer) *CountingWriter { - return &CountingWriter{Writer: writer} -} diff --git a/backend/internal/features/backups/backups/controllers/controller.go b/backend/internal/features/backups/backups/controllers/controller.go index 2e7d9d2..20aa7a8 100644 --- a/backend/internal/features/backups/backups/controllers/controller.go +++ b/backend/internal/features/backups/backups/controllers/controller.go @@ -2,6 +2,7 @@ package backups_controllers import ( "context" + "errors" "fmt" "io" "net/http" @@ -198,7 +199,7 @@ func (c *BackupController) GenerateDownloadToken(ctx *gin.Context) { response, err := c.backupService.GenerateDownloadToken(user, id) if err != nil { - if err == backups_download.ErrDownloadAlreadyInProgress { + if errors.Is(err, backups_download.ErrDownloadAlreadyInProgress) { ctx.JSON( http.StatusConflict, gin.H{ @@ -249,7 +250,7 @@ func (c *BackupController) GetFile(ctx *gin.Context) { downloadToken, rateLimiter, err := c.backupService.ValidateDownloadToken(token) if err != nil { - if err == backups_download.ErrDownloadAlreadyInProgress { + if errors.Is(err, backups_download.ErrDownloadAlreadyInProgress) { ctx.JSON( http.StatusConflict, gin.H{ diff --git a/backend/internal/features/backups/backups/core/repository.go b/backend/internal/features/backups/backups/core/repository.go index da506d2..c422f80 100644 --- a/backend/internal/features/backups/backups/core/repository.go +++ b/backend/internal/features/backups/backups/core/repository.go @@ -88,7 +88,7 @@ func (r *BackupRepository) FindLastByDatabaseID(databaseID uuid.UUID) (*Backup, Where("database_id = ?", databaseID). Order("created_at DESC"). First(&backup).Error; err != nil { - if err == gorm.ErrRecordNotFound { + if errors.Is(err, gorm.ErrRecordNotFound) { return nil, nil } diff --git a/backend/internal/features/backups/backups/download/rate_limiter.go b/backend/internal/features/backups/backups/download/rate_limiter.go index df5e73f..54209ea 100644 --- a/backend/internal/features/backups/backups/download/rate_limiter.go +++ b/backend/internal/features/backups/backups/download/rate_limiter.go @@ -66,9 +66,7 @@ func (rl *RateLimiter) Wait(bytes int64) { tokensNeeded := float64(bytes) - rl.availableTokens waitTime := time.Duration(tokensNeeded/float64(rl.bytesPerSecond)*1000) * time.Millisecond - if waitTime < time.Millisecond { - waitTime = time.Millisecond - } + waitTime = max(waitTime, time.Millisecond) rl.mu.Unlock() time.Sleep(waitTime) diff --git a/backend/internal/features/backups/backups/download/repository.go b/backend/internal/features/backups/backups/download/repository.go index c1e5707..34e0631 100644 --- a/backend/internal/features/backups/backups/download/repository.go +++ b/backend/internal/features/backups/backups/download/repository.go @@ -3,6 +3,7 @@ package backups_download import ( "crypto/rand" "encoding/base64" + "errors" "time" "github.com/google/uuid" @@ -30,7 +31,7 @@ func (r *DownloadTokenRepository) FindByToken(token string) (*DownloadToken, err Where("token = ?", token). First(&downloadToken).Error if err != nil { - if err == gorm.ErrRecordNotFound { + if errors.Is(err, gorm.ErrRecordNotFound) { return nil, nil } return nil, err diff --git a/backend/internal/features/backups/backups/encryption/decrypting_reader.go b/backend/internal/features/backups/backups/encryption/decrypting_reader.go index 1b9b766..ea61935 100644 --- a/backend/internal/features/backups/backups/encryption/decrypting_reader.go +++ b/backend/internal/features/backups/backups/encryption/decrypting_reader.go @@ -4,6 +4,7 @@ import ( "crypto/aes" "crypto/cipher" "encoding/binary" + "errors" "fmt" "io" @@ -69,7 +70,7 @@ func NewDecryptionReader( func (r *DecryptionReader) Read(p []byte) (n int, err error) { for len(r.buffer) < len(p) && !r.eof { if err := r.readAndDecryptChunk(); err != nil { - if err == io.EOF { + if errors.Is(err, io.EOF) { r.eof = true break } diff --git a/backend/internal/features/backups/backups/services/postgres_wal_service.go b/backend/internal/features/backups/backups/services/postgres_wal_service.go index 7e3bba1..452733e 100644 --- a/backend/internal/features/backups/backups/services/postgres_wal_service.go +++ b/backend/internal/features/backups/backups/services/postgres_wal_service.go @@ -225,6 +225,80 @@ func (s *PostgreWalBackupService) DownloadBackupFile( return s.backupService.GetBackupReader(backupID) } +func (s *PostgreWalBackupService) GetNextFullBackupTime( + database *databases.Database, +) (*backups_dto.GetNextFullBackupTimeResponse, error) { + if err := s.validateWalBackupType(database); err != nil { + return nil, err + } + + backupConfig, err := s.backupConfigService.GetBackupConfigByDbId(database.ID) + if err != nil { + return nil, fmt.Errorf("failed to get backup config: %w", err) + } + + if backupConfig.BackupInterval == nil { + return nil, fmt.Errorf("no backup interval configured for database %s", database.ID) + } + + lastFullBackup, err := s.backupRepository.FindLastCompletedFullWalBackupByDatabaseID( + database.ID, + ) + if err != nil { + return nil, fmt.Errorf("failed to query last full backup: %w", err) + } + + var lastBackupTime *time.Time + if lastFullBackup != nil { + lastBackupTime = &lastFullBackup.CreatedAt + } + + now := time.Now().UTC() + nextTime := backupConfig.BackupInterval.NextTriggerTime(now, lastBackupTime) + + return &backups_dto.GetNextFullBackupTimeResponse{ + NextFullBackupTime: nextTime, + }, nil +} + +// ReportError creates a FAILED backup record with the agent's error message. +func (s *PostgreWalBackupService) ReportError( + database *databases.Database, + errorMsg string, +) error { + if err := s.validateWalBackupType(database); err != nil { + return err + } + + backupConfig, err := s.backupConfigService.GetBackupConfigByDbId(database.ID) + if err != nil { + return fmt.Errorf("failed to get backup config: %w", err) + } + + if backupConfig.Storage == nil { + return fmt.Errorf("no storage configured for database %s", database.ID) + } + + now := time.Now().UTC() + backup := &backups_core.Backup{ + ID: uuid.New(), + DatabaseID: database.ID, + StorageID: backupConfig.Storage.ID, + Status: backups_core.BackupStatusFailed, + FailMessage: &errorMsg, + Encryption: backupConfig.Encryption, + CreatedAt: now, + } + + backup.GenerateFilename(database.Name) + + if err := s.backupRepository.Save(backup); err != nil { + return fmt.Errorf("failed to save error backup record: %w", err) + } + + return nil +} + func (s *PostgreWalBackupService) validateWalChain( databaseID uuid.UUID, incomingSegment string, @@ -432,80 +506,6 @@ func (s *PostgreWalBackupService) markFailed(backup *backups_core.Backup, errMsg } } -func (s *PostgreWalBackupService) GetNextFullBackupTime( - database *databases.Database, -) (*backups_dto.GetNextFullBackupTimeResponse, error) { - if err := s.validateWalBackupType(database); err != nil { - return nil, err - } - - backupConfig, err := s.backupConfigService.GetBackupConfigByDbId(database.ID) - if err != nil { - return nil, fmt.Errorf("failed to get backup config: %w", err) - } - - if backupConfig.BackupInterval == nil { - return nil, fmt.Errorf("no backup interval configured for database %s", database.ID) - } - - lastFullBackup, err := s.backupRepository.FindLastCompletedFullWalBackupByDatabaseID( - database.ID, - ) - if err != nil { - return nil, fmt.Errorf("failed to query last full backup: %w", err) - } - - var lastBackupTime *time.Time - if lastFullBackup != nil { - lastBackupTime = &lastFullBackup.CreatedAt - } - - now := time.Now().UTC() - nextTime := backupConfig.BackupInterval.NextTriggerTime(now, lastBackupTime) - - return &backups_dto.GetNextFullBackupTimeResponse{ - NextFullBackupTime: nextTime, - }, nil -} - -// ReportError creates a FAILED backup record with the agent's error message. -func (s *PostgreWalBackupService) ReportError( - database *databases.Database, - errorMsg string, -) error { - if err := s.validateWalBackupType(database); err != nil { - return err - } - - backupConfig, err := s.backupConfigService.GetBackupConfigByDbId(database.ID) - if err != nil { - return fmt.Errorf("failed to get backup config: %w", err) - } - - if backupConfig.Storage == nil { - return fmt.Errorf("no storage configured for database %s", database.ID) - } - - now := time.Now().UTC() - backup := &backups_core.Backup{ - ID: uuid.New(), - DatabaseID: database.ID, - StorageID: backupConfig.Storage.ID, - Status: backups_core.BackupStatusFailed, - FailMessage: &errorMsg, - Encryption: backupConfig.Encryption, - CreatedAt: now, - } - - backup.GenerateFilename(database.Name) - - if err := s.backupRepository.Save(backup); err != nil { - return fmt.Errorf("failed to save error backup record: %w", err) - } - - return nil -} - func (s *PostgreWalBackupService) resolveFullBackup( databaseID uuid.UUID, backupID *uuid.UUID, diff --git a/backend/internal/features/backups/backups/usecases/mariadb/create_backup_uc.go b/backend/internal/features/backups/backups/usecases/mariadb/create_backup_uc.go index d36ddd1..79ae639 100644 --- a/backend/internal/features/backups/backups/usecases/mariadb/create_backup_uc.go +++ b/backend/internal/features/backups/backups/usecases/mariadb/create_backup_uc.go @@ -548,8 +548,8 @@ func (uc *CreateMariadbBackupUsecase) buildMariadbDumpErrorMessage( stderrStr, ) - exitErr, ok := waitErr.(*exec.ExitError) - if !ok { + var exitErr *exec.ExitError + if !errors.As(waitErr, &exitErr) { return errors.New(errorMsg) } diff --git a/backend/internal/features/backups/backups/usecases/mysql/create_backup_uc.go b/backend/internal/features/backups/backups/usecases/mysql/create_backup_uc.go index 083fac9..0163f40 100644 --- a/backend/internal/features/backups/backups/usecases/mysql/create_backup_uc.go +++ b/backend/internal/features/backups/backups/usecases/mysql/create_backup_uc.go @@ -565,8 +565,8 @@ func (uc *CreateMysqlBackupUsecase) buildMysqldumpErrorMessage( stderrStr, ) - exitErr, ok := waitErr.(*exec.ExitError) - if !ok { + var exitErr *exec.ExitError + if !errors.As(waitErr, &exitErr) { return errors.New(errorMsg) } diff --git a/backend/internal/features/backups/backups/usecases/postgresql/create_backup_uc.go b/backend/internal/features/backups/backups/usecases/postgresql/create_backup_uc.go index df0400e..3d4fafd 100644 --- a/backend/internal/features/backups/backups/usecases/postgresql/create_backup_uc.go +++ b/backend/internal/features/backups/backups/usecases/postgresql/create_backup_uc.go @@ -595,8 +595,8 @@ func (uc *CreatePostgresqlBackupUsecase) buildPgDumpErrorMessage( stderrStr := string(stderrOutput) errorMsg := fmt.Sprintf("%s failed: %v – stderr: %s", filepath.Base(pgBin), waitErr, stderrStr) - exitErr, ok := waitErr.(*exec.ExitError) - if !ok { + var exitErr *exec.ExitError + if !errors.As(waitErr, &exitErr) { return errors.New(errorMsg) } diff --git a/backend/internal/features/backups/config/service.go b/backend/internal/features/backups/config/service.go index 61a0493..ff86b6c 100644 --- a/backend/internal/features/backups/config/service.go +++ b/backend/internal/features/backups/config/service.go @@ -214,39 +214,6 @@ func (s *BackupConfigService) CreateDisabledBackupConfig(databaseID uuid.UUID) e return s.initializeDefaultConfig(databaseID) } -func (s *BackupConfigService) initializeDefaultConfig( - databaseID uuid.UUID, -) error { - plan, err := s.databasePlanService.GetDatabasePlan(databaseID) - if err != nil { - return err - } - - timeOfDay := "04:00" - - _, err = s.backupConfigRepository.Save(&BackupConfig{ - DatabaseID: databaseID, - IsBackupsEnabled: false, - RetentionPolicyType: RetentionPolicyTypeTimePeriod, - RetentionTimePeriod: plan.MaxStoragePeriod, - MaxBackupSizeMB: plan.MaxBackupSizeMB, - MaxBackupsTotalSizeMB: plan.MaxBackupsTotalSizeMB, - BackupInterval: &intervals.Interval{ - Interval: intervals.IntervalDaily, - TimeOfDay: &timeOfDay, - }, - SendNotificationsOn: []BackupNotificationType{ - NotificationBackupFailed, - NotificationBackupSuccess, - }, - IsRetryIfFailed: true, - MaxFailedTriesCount: 3, - Encryption: BackupEncryptionNone, - }) - - return err -} - func (s *BackupConfigService) TransferDatabaseToWorkspace( user *users_models.User, databaseID uuid.UUID, @@ -290,7 +257,8 @@ func (s *BackupConfigService) TransferDatabaseToWorkspace( s.transferNotifiers(user, database, request.TargetWorkspaceID) } - if request.IsTransferWithStorage { + switch { + case request.IsTransferWithStorage: if backupConfig.StorageID == nil { return ErrDatabaseHasNoStorage } @@ -315,7 +283,7 @@ func (s *BackupConfigService) TransferDatabaseToWorkspace( if err != nil { return err } - } else if request.TargetStorageID != nil { + case request.TargetStorageID != nil: targetStorage, err := s.storageService.GetStorageByID(*request.TargetStorageID) if err != nil { return err @@ -332,7 +300,7 @@ func (s *BackupConfigService) TransferDatabaseToWorkspace( if err != nil { return err } - } else { + default: return ErrTargetStorageNotSpecified } @@ -351,6 +319,39 @@ func (s *BackupConfigService) TransferDatabaseToWorkspace( return nil } +func (s *BackupConfigService) initializeDefaultConfig( + databaseID uuid.UUID, +) error { + plan, err := s.databasePlanService.GetDatabasePlan(databaseID) + if err != nil { + return err + } + + timeOfDay := "04:00" + + _, err = s.backupConfigRepository.Save(&BackupConfig{ + DatabaseID: databaseID, + IsBackupsEnabled: false, + RetentionPolicyType: RetentionPolicyTypeTimePeriod, + RetentionTimePeriod: plan.MaxStoragePeriod, + MaxBackupSizeMB: plan.MaxBackupSizeMB, + MaxBackupsTotalSizeMB: plan.MaxBackupsTotalSizeMB, + BackupInterval: &intervals.Interval{ + Interval: intervals.IntervalDaily, + TimeOfDay: &timeOfDay, + }, + SendNotificationsOn: []BackupNotificationType{ + NotificationBackupFailed, + NotificationBackupSuccess, + }, + IsRetryIfFailed: true, + MaxFailedTriesCount: 3, + Encryption: BackupEncryptionNone, + }) + + return err +} + func (s *BackupConfigService) transferNotifiers( user *users_models.User, database *databases.Database, diff --git a/backend/internal/features/databases/databases/mariadb/model.go b/backend/internal/features/databases/databases/mariadb/model.go index 54baa4e..aa84f99 100644 --- a/backend/internal/features/databases/databases/mariadb/model.go +++ b/backend/internal/features/databases/databases/mariadb/model.go @@ -391,7 +391,7 @@ func (m *MariadbDatabase) HasPrivilege(priv string) bool { } func HasPrivilege(privileges, priv string) bool { - for _, p := range strings.Split(privileges, ",") { + for p := range strings.SplitSeq(privileges, ",") { if strings.TrimSpace(p) == priv { return true } diff --git a/backend/internal/features/databases/databases/mongodb/model.go b/backend/internal/features/databases/databases/mongodb/model.go index c167ec2..60a44ec 100644 --- a/backend/internal/features/databases/databases/mongodb/model.go +++ b/backend/internal/features/databases/databases/mongodb/model.go @@ -451,6 +451,48 @@ func (m *MongodbDatabase) CreateReadOnlyUser( return "", "", errors.New("failed to generate unique username after 3 attempts") } +// BuildMongodumpURI builds a URI suitable for mongodump (without database in path) +func (m *MongodbDatabase) BuildMongodumpURI(password string) string { + authDB := m.AuthDatabase + if authDB == "" { + authDB = "admin" + } + + extraParams := "" + if m.IsHttps { + extraParams += "&tls=true&tlsInsecure=true" + } + if m.IsDirectConnection { + extraParams += "&directConnection=true" + } + + if m.IsSrv { + return fmt.Sprintf( + "mongodb+srv://%s:%s@%s/?authSource=%s&connectTimeoutMS=15000%s", + url.QueryEscape(m.Username), + url.QueryEscape(password), + m.Host, + authDB, + extraParams, + ) + } + + port := 27017 + if m.Port != nil { + port = *m.Port + } + + return fmt.Sprintf( + "mongodb://%s:%s@%s:%d/?authSource=%s&connectTimeoutMS=15000%s", + url.QueryEscape(m.Username), + url.QueryEscape(password), + m.Host, + port, + authDB, + extraParams, + ) +} + // buildConnectionURI builds a MongoDB connection URI func (m *MongodbDatabase) buildConnectionURI(password string) string { authDB := m.AuthDatabase @@ -495,48 +537,6 @@ func (m *MongodbDatabase) buildConnectionURI(password string) string { ) } -// BuildMongodumpURI builds a URI suitable for mongodump (without database in path) -func (m *MongodbDatabase) BuildMongodumpURI(password string) string { - authDB := m.AuthDatabase - if authDB == "" { - authDB = "admin" - } - - extraParams := "" - if m.IsHttps { - extraParams += "&tls=true&tlsInsecure=true" - } - if m.IsDirectConnection { - extraParams += "&directConnection=true" - } - - if m.IsSrv { - return fmt.Sprintf( - "mongodb+srv://%s:%s@%s/?authSource=%s&connectTimeoutMS=15000%s", - url.QueryEscape(m.Username), - url.QueryEscape(password), - m.Host, - authDB, - extraParams, - ) - } - - port := 27017 - if m.Port != nil { - port = *m.Port - } - - return fmt.Sprintf( - "mongodb://%s:%s@%s:%d/?authSource=%s&connectTimeoutMS=15000%s", - url.QueryEscape(m.Username), - url.QueryEscape(password), - m.Host, - port, - authDB, - extraParams, - ) -} - // detectMongodbVersion gets MongoDB server version from buildInfo command func detectMongodbVersion(ctx context.Context, client *mongo.Client) (tools.MongodbVersion, error) { adminDB := client.Database("admin") diff --git a/backend/internal/features/databases/databases/postgresql/model.go b/backend/internal/features/databases/databases/postgresql/model.go index 01414e2..bf83b4d 100644 --- a/backend/internal/features/databases/databases/postgresql/model.go +++ b/backend/internal/features/databases/databases/postgresql/model.go @@ -1153,8 +1153,8 @@ func isSupabaseConnection(host, username string) bool { } func extractSupabaseProjectID(username string) string { - if idx := strings.Index(username, "."); idx != -1 { - return username[idx+1:] + if _, after, found := strings.Cut(username, "."); found { + return after } return "" } diff --git a/backend/internal/features/email/email.go b/backend/internal/features/email/email.go index 8ce11b3..22d3f30 100644 --- a/backend/internal/features/email/email.go +++ b/backend/internal/features/email/email.go @@ -1,6 +1,7 @@ package email import ( + "context" "crypto/tls" "fmt" "log/slog" @@ -114,7 +115,7 @@ func (s *EmailSMTPSender) createImplicitTLSClient() (*smtp.Client, func(), error tlsConfig := &tls.Config{ServerName: s.smtpHost} dialer := &net.Dialer{Timeout: DefaultTimeout} - conn, err := tls.DialWithDialer(dialer, "tcp", addr, tlsConfig) + conn, err := (&tls.Dialer{NetDialer: dialer, Config: tlsConfig}).DialContext(context.Background(), "tcp", addr) if err != nil { return nil, nil, fmt.Errorf("failed to connect to SMTP server: %w", err) } diff --git a/backend/internal/features/notifiers/models/discord/model.go b/backend/internal/features/notifiers/models/discord/model.go index 55f8757..d864801 100644 --- a/backend/internal/features/notifiers/models/discord/model.go +++ b/backend/internal/features/notifiers/models/discord/model.go @@ -2,6 +2,7 @@ package discord_notifier import ( "bytes" + "context" "encoding/json" "errors" "fmt" @@ -47,7 +48,7 @@ func (d *DiscordNotifier) Send( fullMessage = fmt.Sprintf("%s\n\n%s", heading, message) } - payload := map[string]interface{}{ + payload := map[string]any{ "content": fullMessage, } @@ -56,7 +57,7 @@ func (d *DiscordNotifier) Send( return fmt.Errorf("failed to marshal Discord payload: %w", err) } - req, err := http.NewRequest("POST", webhookURL, bytes.NewReader(jsonPayload)) + req, err := http.NewRequestWithContext(context.Background(), "POST", webhookURL, bytes.NewReader(jsonPayload)) if err != nil { return fmt.Errorf("failed to create request: %w", err) } diff --git a/backend/internal/features/notifiers/models/email_notifier/model.go b/backend/internal/features/notifiers/models/email_notifier/model.go index a7f93a3..0e879ce 100644 --- a/backend/internal/features/notifiers/models/email_notifier/model.go +++ b/backend/internal/features/notifiers/models/email_notifier/model.go @@ -1,6 +1,7 @@ package email_notifier import ( + "context" "crypto/tls" "errors" "fmt" @@ -207,7 +208,7 @@ func (e *EmailNotifier) createImplicitTLSClient() (*smtp.Client, func(), error) } dialer := &net.Dialer{Timeout: DefaultTimeout} - conn, err := tls.DialWithDialer(dialer, "tcp", addr, tlsConfig) + conn, err := (&tls.Dialer{NetDialer: dialer, Config: tlsConfig}).DialContext(context.Background(), "tcp", addr) if err != nil { return nil, nil, fmt.Errorf("failed to connect to SMTP server: %w", err) } diff --git a/backend/internal/features/notifiers/models/slack/model.go b/backend/internal/features/notifiers/models/slack/model.go index 3b7d336..d42f3ea 100644 --- a/backend/internal/features/notifiers/models/slack/model.go +++ b/backend/internal/features/notifiers/models/slack/model.go @@ -2,6 +2,7 @@ package slack_notifier import ( "bytes" + "context" "encoding/json" "errors" "fmt" @@ -86,7 +87,8 @@ func (s *SlackNotifier) Send( for { attempts++ - req, err := http.NewRequest( + req, err := http.NewRequestWithContext( + context.Background(), "POST", "https://slack.com/api/chat.postMessage", bytes.NewReader(payload), @@ -136,7 +138,7 @@ func (s *SlackNotifier) Send( if err := json.NewDecoder(resp.Body).Decode(&respBody); err != nil { raw, _ := io.ReadAll(resp.Body) - return fmt.Errorf("decode response: %v – raw: %s", err, raw) + return fmt.Errorf("decode response: %w – raw: %s", err, raw) } if !respBody.OK { diff --git a/backend/internal/features/notifiers/models/teams/model.go b/backend/internal/features/notifiers/models/teams/model.go index 34fedf7..c75933e 100644 --- a/backend/internal/features/notifiers/models/teams/model.go +++ b/backend/internal/features/notifiers/models/teams/model.go @@ -2,6 +2,7 @@ package teams_notifier import ( "bytes" + "context" "encoding/json" "errors" "fmt" @@ -88,7 +89,7 @@ func (n *TeamsNotifier) Send( } body, _ := json.Marshal(p) - req, err := http.NewRequest(http.MethodPost, webhookURL, bytes.NewReader(body)) + req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, webhookURL, bytes.NewReader(body)) if err != nil { return err } diff --git a/backend/internal/features/notifiers/models/telegram/model.go b/backend/internal/features/notifiers/models/telegram/model.go index adf9f29..713ea81 100644 --- a/backend/internal/features/notifiers/models/telegram/model.go +++ b/backend/internal/features/notifiers/models/telegram/model.go @@ -1,6 +1,7 @@ package telegram_notifier import ( + "context" "errors" "fmt" "io" @@ -65,7 +66,7 @@ func (t *TelegramNotifier) Send( data.Set("message_thread_id", strconv.FormatInt(*t.ThreadID, 10)) } - req, err := http.NewRequest("POST", apiURL, strings.NewReader(data.Encode())) + req, err := http.NewRequestWithContext(context.Background(), "POST", apiURL, strings.NewReader(data.Encode())) if err != nil { return fmt.Errorf("failed to create request: %w", err) } diff --git a/backend/internal/features/notifiers/models/webhook/model.go b/backend/internal/features/notifiers/models/webhook/model.go index 6dbbe87..d82092b 100644 --- a/backend/internal/features/notifiers/models/webhook/model.go +++ b/backend/internal/features/notifiers/models/webhook/model.go @@ -2,6 +2,7 @@ package webhook_notifier import ( "bytes" + "context" "encoding/json" "errors" "fmt" @@ -146,7 +147,7 @@ func (t *WebhookNotifier) sendGET(webhookURL, heading, message string, logger *s url.QueryEscape(message), ) - req, err := http.NewRequest(http.MethodGet, reqURL, nil) + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, reqURL, nil) if err != nil { return fmt.Errorf("failed to create GET request: %w", err) } @@ -180,7 +181,7 @@ func (t *WebhookNotifier) sendGET(webhookURL, heading, message string, logger *s func (t *WebhookNotifier) sendPOST(webhookURL, heading, message string, logger *slog.Logger) error { body := t.buildRequestBody(heading, message) - req, err := http.NewRequest(http.MethodPost, webhookURL, bytes.NewReader(body)) + req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, webhookURL, bytes.NewReader(body)) if err != nil { return fmt.Errorf("failed to create POST request: %w", err) } diff --git a/backend/internal/features/notifiers/service.go b/backend/internal/features/notifiers/service.go index 027a51e..c01a50e 100644 --- a/backend/internal/features/notifiers/service.go +++ b/backend/internal/features/notifiers/service.go @@ -343,10 +343,8 @@ func (s *NotifierService) TransferNotifierToWorkspace( return ErrNotifierHasOtherAttachedDatabasesCannotTransfer } } - } else { - if len(attachedDatabasesIDs) > 0 { - return ErrNotifierHasAttachedDatabasesCannotTransfer - } + } else if len(attachedDatabasesIDs) > 0 { + return ErrNotifierHasAttachedDatabasesCannotTransfer } sourceWorkspaceID := existingNotifier.WorkspaceID diff --git a/backend/internal/features/restores/restoring/restorer_test.go b/backend/internal/features/restores/restoring/restorer_test.go index ce315c4..f7881fb 100644 --- a/backend/internal/features/restores/restoring/restorer_test.go +++ b/backend/internal/features/restores/restoring/restorer_test.go @@ -162,3 +162,7 @@ func Test_MakeRestore_WhenTaskStarts_CacheDeletedImmediately(t *testing.T) { cachedDBAfter := restoreDatabaseCache.Get(restore.ID.String()) assert.Nil(t, cachedDBAfter, "Cache should be deleted immediately when task starts") } + +func stringPtr(s string) *string { + return &s +} diff --git a/backend/internal/features/restores/restoring/scheduler.go b/backend/internal/features/restores/restoring/scheduler.go index 0f36766..80c55a8 100644 --- a/backend/internal/features/restores/restoring/scheduler.go +++ b/backend/internal/features/restores/restoring/scheduler.go @@ -101,27 +101,6 @@ func (s *RestoresScheduler) IsSchedulerRunning() bool { return s.lastCheckTime.After(time.Now().UTC().Add(-schedulerHealthcheckThreshold)) } -func (s *RestoresScheduler) failRestoresInProgress() error { - restoresInProgress, err := s.restoreRepository.FindByStatus( - restores_core.RestoreStatusInProgress, - ) - if err != nil { - return err - } - - for _, restore := range restoresInProgress { - failMessage := "Restore failed due to application restart" - restore.FailMessage = &failMessage - restore.Status = restores_core.RestoreStatusFailed - - if err := s.restoreRepository.Save(restore); err != nil { - return err - } - } - - return nil -} - func (s *RestoresScheduler) StartRestore(restoreID uuid.UUID, dbCache *RestoreDatabaseCache) error { // If dbCache not provided, try to fetch from DB (for backward compatibility/testing) if dbCache == nil { @@ -326,6 +305,27 @@ func (s *RestoresScheduler) onRestoreCompleted(nodeID, restoreID uuid.UUID) { } } +func (s *RestoresScheduler) failRestoresInProgress() error { + restoresInProgress, err := s.restoreRepository.FindByStatus( + restores_core.RestoreStatusInProgress, + ) + if err != nil { + return err + } + + for _, restore := range restoresInProgress { + failMessage := "Restore failed due to application restart" + restore.FailMessage = &failMessage + restore.Status = restores_core.RestoreStatusFailed + + if err := s.restoreRepository.Save(restore); err != nil { + return err + } + } + + return nil +} + func (s *RestoresScheduler) checkDeadNodesAndFailRestores() error { nodes, err := s.restoreNodesRegistry.GetAvailableNodes() if err != nil { diff --git a/backend/internal/features/restores/restoring/testing.go b/backend/internal/features/restores/restoring/testing.go index 2db3ce3..30002d7 100644 --- a/backend/internal/features/restores/restoring/testing.go +++ b/backend/internal/features/restores/restoring/testing.go @@ -324,7 +324,7 @@ func CreateTestRestore( Port: 5432, Username: "test", Password: "test", - Database: stringPtr("testdb"), + Database: func() *string { s := "testdb"; return &s }(), Version: "16", }, } @@ -336,7 +336,3 @@ func CreateTestRestore( return restore } - -func stringPtr(s string) *string { - return &s -} diff --git a/backend/internal/features/restores/service.go b/backend/internal/features/restores/service.go index 1807a5d..e9ef880 100644 --- a/backend/internal/features/restores/service.go +++ b/backend/internal/features/restores/service.go @@ -199,6 +199,54 @@ func (s *RestoreService) RestoreBackupWithAuth( return nil } +func (s *RestoreService) CancelRestore( + user *users_models.User, + restoreID uuid.UUID, +) error { + restore, err := s.restoreRepository.FindByID(restoreID) + if err != nil { + return err + } + + backup, err := s.backupService.GetBackup(restore.BackupID) + if err != nil { + return err + } + + database, err := s.databaseService.GetDatabaseByID(backup.DatabaseID) + if err != nil { + return err + } + + if database.WorkspaceID == nil { + return errors.New("cannot cancel restore for database without workspace") + } + + canManage, err := s.workspaceService.CanUserManageDBs(*database.WorkspaceID, user) + if err != nil { + return err + } + if !canManage { + return errors.New("insufficient permissions to cancel restore for this database") + } + + if restore.Status != restores_core.RestoreStatusInProgress { + return errors.New("restore is not in progress") + } + + if err := s.taskCancelManager.CancelTask(restoreID); err != nil { + return err + } + + s.auditLogService.WriteAuditLog( + fmt.Sprintf("Restore cancelled for database: %s", database.Name), + &user.ID, + database.WorkspaceID, + ) + + return nil +} + func (s *RestoreService) validateVersionCompatibility( backupDatabase *databases.Database, requestDTO restores_core.RestoreBackupRequest, @@ -295,6 +343,7 @@ func (s *RestoreService) validateVersionCompatibility( `For example, you can restore MongoDB 6.0 backup to MongoDB 6.0, 7.0 or higher. But cannot restore to 5.0`) } } + return nil } @@ -368,51 +417,3 @@ func (s *RestoreService) validateNoParallelRestores(databaseID uuid.UUID) error return nil } - -func (s *RestoreService) CancelRestore( - user *users_models.User, - restoreID uuid.UUID, -) error { - restore, err := s.restoreRepository.FindByID(restoreID) - if err != nil { - return err - } - - backup, err := s.backupService.GetBackup(restore.BackupID) - if err != nil { - return err - } - - database, err := s.databaseService.GetDatabaseByID(backup.DatabaseID) - if err != nil { - return err - } - - if database.WorkspaceID == nil { - return errors.New("cannot cancel restore for database without workspace") - } - - canManage, err := s.workspaceService.CanUserManageDBs(*database.WorkspaceID, user) - if err != nil { - return err - } - if !canManage { - return errors.New("insufficient permissions to cancel restore for this database") - } - - if restore.Status != restores_core.RestoreStatusInProgress { - return errors.New("restore is not in progress") - } - - if err := s.taskCancelManager.CancelTask(restoreID); err != nil { - return err - } - - s.auditLogService.WriteAuditLog( - fmt.Sprintf("Restore cancelled for database: %s", database.Name), - &user.ID, - database.WorkspaceID, - ) - - return nil -} diff --git a/backend/internal/features/restores/usecases/postgresql/restore_backup_uc.go b/backend/internal/features/restores/usecases/postgresql/restore_backup_uc.go index 6ce9172..b074d3e 100644 --- a/backend/internal/features/restores/usecases/postgresql/restore_backup_uc.go +++ b/backend/internal/features/restores/usecases/postgresql/restore_backup_uc.go @@ -304,11 +304,12 @@ func (uc *RestorePostgresqlBackupUsecase) restoreViaStdin( _, copyErr := io.Copy(stdinPipe, backupReader) // Close stdin pipe to signal EOF to pg_restore - critical for proper termination closeErr := stdinPipe.Close() - if copyErr != nil { + switch { + case copyErr != nil: copyErrCh <- fmt.Errorf("copy to stdin: %w", copyErr) - } else if closeErr != nil { + case closeErr != nil: copyErrCh <- fmt.Errorf("close stdin: %w", closeErr) - } else { + default: copyErrCh <- nil } }() @@ -764,10 +765,12 @@ func (uc *RestorePostgresqlBackupUsecase) handlePgRestoreError( ) // Check for specific PostgreSQL error patterns - if exitErr, ok := waitErr.(*exec.ExitError); ok { + var exitErr *exec.ExitError + if errors.As(waitErr, &exitErr) { exitCode := exitErr.ExitCode() - if exitCode == 1 && strings.TrimSpace(stderrStr) == "" { + switch { + case exitCode == 1 && strings.TrimSpace(stderrStr) == "": errorMsg = fmt.Sprintf( "%s failed with exit status 1 but provided no error details. "+ "This often indicates: "+ @@ -782,45 +785,46 @@ func (uc *RestorePostgresqlBackupUsecase) handlePgRestoreError( pgBin, strings.Join(args, " "), ) - } else if exitCode == -1073741819 { // 0xC0000005 in decimal + case exitCode == -1073741819: // 0xC0000005 in decimal errorMsg = fmt.Sprintf( "%s crashed with access violation (0xC0000005). This may indicate incompatible PostgreSQL version, corrupted installation, or connection issues. stderr: %s", filepath.Base(pgBin), stderrStr, ) - } else if exitCode == 1 || exitCode == 2 { + case exitCode == 1 || exitCode == 2: // Check for common connection and authentication issues - if containsIgnoreCase(stderrStr, "pg_hba.conf") { + switch { + case containsIgnoreCase(stderrStr, "pg_hba.conf"): errorMsg = fmt.Sprintf( "PostgreSQL connection rejected by server configuration (pg_hba.conf). stderr: %s", stderrStr, ) - } else if containsIgnoreCase(stderrStr, "no password supplied") || containsIgnoreCase(stderrStr, "fe_sendauth") { + case containsIgnoreCase(stderrStr, "no password supplied") || containsIgnoreCase(stderrStr, "fe_sendauth"): errorMsg = fmt.Sprintf( "PostgreSQL authentication failed - no password supplied. stderr: %s", stderrStr, ) - } else if containsIgnoreCase(stderrStr, "ssl") && containsIgnoreCase(stderrStr, "connection") { + case containsIgnoreCase(stderrStr, "ssl") && containsIgnoreCase(stderrStr, "connection"): errorMsg = fmt.Sprintf( "PostgreSQL SSL connection failed. stderr: %s", stderrStr, ) - } else if containsIgnoreCase(stderrStr, "connection") && containsIgnoreCase(stderrStr, "refused") { + case containsIgnoreCase(stderrStr, "connection") && containsIgnoreCase(stderrStr, "refused"): errorMsg = fmt.Sprintf( "PostgreSQL connection refused. Check if the server is running and accessible. stderr: %s", stderrStr, ) - } else if containsIgnoreCase(stderrStr, "authentication") || containsIgnoreCase(stderrStr, "password") { + case containsIgnoreCase(stderrStr, "authentication") || containsIgnoreCase(stderrStr, "password"): errorMsg = fmt.Sprintf( "PostgreSQL authentication failed. Check username and password. stderr: %s", stderrStr, ) - } else if containsIgnoreCase(stderrStr, "timeout") { + case containsIgnoreCase(stderrStr, "timeout"): errorMsg = fmt.Sprintf( "PostgreSQL connection timeout. stderr: %s", stderrStr, ) - } else if containsIgnoreCase(stderrStr, "database") && containsIgnoreCase(stderrStr, "does not exist") { + case containsIgnoreCase(stderrStr, "database") && containsIgnoreCase(stderrStr, "does not exist"): backupDbName := "unknown" if database.Postgresql != nil && database.Postgresql.Database != nil { backupDbName = *database.Postgresql.Database diff --git a/backend/internal/features/storages/models/azure_blob/model.go b/backend/internal/features/storages/models/azure_blob/model.go index 32f5d42..d938119 100644 --- a/backend/internal/features/storages/models/azure_blob/model.go +++ b/backend/internal/features/storages/models/azure_blob/model.go @@ -109,7 +109,7 @@ func (s *AzureBlobStorage) SaveFile( return fmt.Errorf("read error: %w", readErr) } - blockID := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%06d", blockNumber))) + blockID := base64.StdEncoding.EncodeToString(fmt.Appendf(nil, "%06d", blockNumber)) _, err := blockBlobClient.StageBlock( ctx, @@ -337,7 +337,7 @@ func (s *AzureBlobStorage) buildBlobName(fileName string) string { prefix = strings.TrimPrefix(prefix, "/") if !strings.HasSuffix(prefix, "/") { - prefix = prefix + "/" + prefix += "/" } return prefix + fileName diff --git a/backend/internal/features/storages/models/google_drive/model.go b/backend/internal/features/storages/models/google_drive/model.go index 74f2424..5e08776 100644 --- a/backend/internal/features/storages/models/google_drive/model.go +++ b/backend/internal/features/storages/models/google_drive/model.go @@ -167,7 +167,7 @@ func (s *GoogleDriveStorage) GetFile( return err } - resp, err := driveService.Files.Get(fileIDGoogle).Download() + resp, err := driveService.Files.Get(fileIDGoogle).Download() //nolint:bodyclose if err != nil { return fmt.Errorf("failed to download file from Google Drive: %w", err) } @@ -358,7 +358,7 @@ func (s *GoogleDriveStorage) withRetryOnAuth( if strings.Contains(refreshErr.Error(), "invalid_grant") || strings.Contains(refreshErr.Error(), "refresh token") { return fmt.Errorf( - "google drive refresh token has expired. Please re-authenticate and update your token configuration. Original error: %w. Refresh error: %v", + "google drive refresh token has expired. Please re-authenticate and update your token configuration. Original error: %w. Refresh error: %w", err, refreshErr, ) @@ -488,7 +488,7 @@ func (s *GoogleDriveStorage) refreshToken(encryptor encryption.FieldEncryptor) e // maskSensitiveData masks sensitive information in token JSON for logging func maskSensitiveData(tokenJSON string) string { // Replace sensitive values with masked versions - var data map[string]interface{} + var data map[string]any if err := json.Unmarshal([]byte(tokenJSON), &data); err != nil { return "invalid JSON" } diff --git a/backend/internal/features/storages/models/nas/model.go b/backend/internal/features/storages/models/nas/model.go index 2ccec4c..cafab8d 100644 --- a/backend/internal/features/storages/models/nas/model.go +++ b/backend/internal/features/storages/models/nas/model.go @@ -356,7 +356,7 @@ func (n *NASStorage) createConnectionWithContext(ctx context.Context) (net.Conn, ServerName: n.Host, InsecureSkipVerify: false, } - conn, err := tls.DialWithDialer(dialer, "tcp", address, tlsConfig) + conn, err := (&tls.Dialer{NetDialer: dialer, Config: tlsConfig}).DialContext(ctx, "tcp", address) if err != nil { return nil, fmt.Errorf("failed to create SSL connection to %s: %w", address, err) } diff --git a/backend/internal/features/storages/models/s3/model.go b/backend/internal/features/storages/models/s3/model.go index 5b1e9d5..ddbf911 100644 --- a/backend/internal/features/storages/models/s3/model.go +++ b/backend/internal/features/storages/models/s3/model.go @@ -207,7 +207,7 @@ func (s *S3Storage) GetFile( // Check if the file actually exists by reading the first byte buf := make([]byte, 1) _, readErr := object.Read(buf) - if readErr != nil && readErr != io.EOF { + if readErr != nil && !errors.Is(readErr, io.EOF) { _ = object.Close() return nil, fmt.Errorf("file does not exist in S3: %w", readErr) } @@ -372,7 +372,7 @@ func (s *S3Storage) buildObjectKey(fileName string) string { prefix = strings.TrimPrefix(prefix, "/") if !strings.HasSuffix(prefix, "/") { - prefix = prefix + "/" + prefix += "/" } return prefix + fileName @@ -428,11 +428,11 @@ func (s *S3Storage) getClientParams( endpoint = s.S3Endpoint useSSL = true - if strings.HasPrefix(endpoint, "http://") { + if after, ok := strings.CutPrefix(endpoint, "http://"); ok { useSSL = false - endpoint = strings.TrimPrefix(endpoint, "http://") - } else if strings.HasPrefix(endpoint, "https://") { - endpoint = strings.TrimPrefix(endpoint, "https://") + endpoint = after + } else if after, ok := strings.CutPrefix(endpoint, "https://"); ok { + endpoint = after } if endpoint == "" { diff --git a/backend/internal/features/storages/models/sftp/model.go b/backend/internal/features/storages/models/sftp/model.go index b3f56db..869ada9 100644 --- a/backend/internal/features/storages/models/sftp/model.go +++ b/backend/internal/features/storages/models/sftp/model.go @@ -298,12 +298,7 @@ func (s *SFTPStorage) connectWithContext( authMethods = append(authMethods, ssh.PublicKeys(signer)) } - var hostKeyCallback ssh.HostKeyCallback - if s.SkipHostKeyVerify { - hostKeyCallback = ssh.InsecureIgnoreHostKey() - } else { - hostKeyCallback = ssh.InsecureIgnoreHostKey() - } + hostKeyCallback := ssh.InsecureIgnoreHostKey() config := &ssh.ClientConfig{ User: s.Username, diff --git a/backend/internal/features/storages/service.go b/backend/internal/features/storages/service.go index 46fb3ab..eb567b8 100644 --- a/backend/internal/features/storages/service.go +++ b/backend/internal/features/storages/service.go @@ -364,10 +364,8 @@ func (s *StorageService) TransferStorageToWorkspace( return ErrStorageHasOtherAttachedDatabasesCannotTransfer } } - } else { - if len(attachedDatabasesIDs) > 0 { - return ErrStorageHasAttachedDatabasesCannotTransfer - } + } else if len(attachedDatabasesIDs) > 0 { + return ErrStorageHasAttachedDatabasesCannotTransfer } sourceWorkspaceID := existingStorage.WorkspaceID diff --git a/backend/internal/features/test_once_protection.go b/backend/internal/features/test_once_protection.go index d88a42e..c43864d 100644 --- a/backend/internal/features/test_once_protection.go +++ b/backend/internal/features/test_once_protection.go @@ -1,7 +1,6 @@ package features import ( - "context" "fmt" "sync" "testing" @@ -60,11 +59,9 @@ func Test_SetupDependencies_ConcurrentCalls_Safe(t *testing.T) { // Call SetupDependencies concurrently from 10 goroutines for range 10 { - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { audit_logs.SetupDependencies() - }() + }) } wg.Wait() @@ -73,8 +70,7 @@ func Test_SetupDependencies_ConcurrentCalls_Safe(t *testing.T) { // Test_BackgroundService_Run_CalledTwice_Panics verifies Run() panics on duplicate calls func Test_BackgroundService_Run_CalledTwice_Panics(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx := t.Context() // Create a test background service backgroundService := audit_logs.GetAuditLogBackgroundService() @@ -107,8 +103,7 @@ func Test_BackgroundService_Run_CalledTwice_Panics(t *testing.T) { // Test_BackupsScheduler_Run_CalledTwice_Panics verifies scheduler panics on duplicate calls func Test_BackupsScheduler_Run_CalledTwice_Panics(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx := t.Context() scheduler := backuping.GetBackupsScheduler() diff --git a/backend/internal/features/users/repositories/user_repository.go b/backend/internal/features/users/repositories/user_repository.go index 10830ca..f4a9faf 100644 --- a/backend/internal/features/users/repositories/user_repository.go +++ b/backend/internal/features/users/repositories/user_repository.go @@ -1,6 +1,7 @@ package users_repositories import ( + "errors" "fmt" "time" @@ -31,7 +32,7 @@ func (r *UserRepository) GetUserByEmail(email string) (*users_models.User, error var user users_models.User if err := storage.GetDb().Where("email = ?", email).First(&user).Error; err != nil { - if err == gorm.ErrRecordNotFound { + if errors.Is(err, gorm.ErrRecordNotFound) { return nil, nil } @@ -179,7 +180,7 @@ func (r *UserRepository) UpdateUserInfo(userID uuid.UUID, name, email *string) e func (r *UserRepository) GetUserByGitHubOAuthID(githubID string) (*users_models.User, error) { var user users_models.User err := storage.GetDb().Where("github_oauth_id = ?", githubID).First(&user).Error - if err == gorm.ErrRecordNotFound { + if errors.Is(err, gorm.ErrRecordNotFound) { return nil, nil } if err != nil { @@ -191,7 +192,7 @@ func (r *UserRepository) GetUserByGitHubOAuthID(githubID string) (*users_models. func (r *UserRepository) GetUserByGoogleOAuthID(googleID string) (*users_models.User, error) { var user users_models.User err := storage.GetDb().Where("google_oauth_id = ?", googleID).First(&user).Error - if err == gorm.ErrRecordNotFound { + if errors.Is(err, gorm.ErrRecordNotFound) { return nil, nil } if err != nil { diff --git a/backend/internal/features/users/repositories/users_settings_repository.go b/backend/internal/features/users/repositories/users_settings_repository.go index e15fc10..2df61fb 100644 --- a/backend/internal/features/users/repositories/users_settings_repository.go +++ b/backend/internal/features/users/repositories/users_settings_repository.go @@ -1,6 +1,8 @@ package users_repositories import ( + "errors" + "github.com/google/uuid" "gorm.io/gorm" @@ -14,7 +16,7 @@ func (r *UsersSettingsRepository) GetSettings() (*user_models.UsersSettings, err var settings user_models.UsersSettings if err := storage.GetDb().First(&settings).Error; err != nil { - if err == gorm.ErrRecordNotFound { + if errors.Is(err, gorm.ErrRecordNotFound) { // Create default settings if none exist defaultSettings := &user_models.UsersSettings{ ID: uuid.New(), diff --git a/backend/internal/features/users/services/user_services.go b/backend/internal/features/users/services/user_services.go index f283745..bd76f52 100644 --- a/backend/internal/features/users/services/user_services.go +++ b/backend/internal/features/users/services/user_services.go @@ -685,7 +685,11 @@ func (s *UserService) handleGitHubOAuthWithEndpoint( } client := oauthConfig.Client(context.Background(), token) - resp, err := client.Get(userAPIURL) + githubReq, err := http.NewRequestWithContext(context.Background(), "GET", userAPIURL, nil) + if err != nil { + return nil, fmt.Errorf("failed to create user info request: %w", err) + } + resp, err := client.Do(githubReq) if err != nil { return nil, fmt.Errorf("failed to get user info: %w", err) } @@ -754,7 +758,11 @@ func (s *UserService) handleGoogleOAuthWithEndpoint( } client := oauthConfig.Client(context.Background(), token) - resp, err := client.Get(userAPIURL) + googleReq, err := http.NewRequestWithContext(context.Background(), "GET", userAPIURL, nil) + if err != nil { + return nil, fmt.Errorf("failed to create user info request: %w", err) + } + resp, err := client.Do(googleReq) if err != nil { return nil, fmt.Errorf("failed to get user info: %w", err) } @@ -950,7 +958,11 @@ func (s *UserService) fetchGitHubPrimaryEmail( emailsURL = baseURL + "/user/emails" } - resp, err := client.Get(emailsURL) + emailsReq, err := http.NewRequestWithContext(context.Background(), "GET", emailsURL, nil) + if err != nil { + return "", fmt.Errorf("failed to create user emails request: %w", err) + } + resp, err := client.Do(emailsReq) if err != nil { return "", fmt.Errorf("failed to get user emails: %w", err) } diff --git a/backend/internal/features/users/testing/mocks.go b/backend/internal/features/users/testing/mocks.go index 57fc27d..93b85b3 100644 --- a/backend/internal/features/users/testing/mocks.go +++ b/backend/internal/features/users/testing/mocks.go @@ -13,6 +13,13 @@ type EmailCall struct { Body string } +func NewMockEmailSender() *MockEmailSender { + return &MockEmailSender{ + SentEmails: []EmailCall{}, + ShouldFail: false, + } +} + func (m *MockEmailSender) SendEmail(to, subject, body string) error { m.SentEmails = append(m.SentEmails, EmailCall{ To: to, @@ -24,10 +31,3 @@ func (m *MockEmailSender) SendEmail(to, subject, body string) error { } return nil } - -func NewMockEmailSender() *MockEmailSender { - return &MockEmailSender{ - SentEmails: []EmailCall{}, - ShouldFail: false, - } -} diff --git a/backend/internal/features/workspaces/testing/mocks.go b/backend/internal/features/workspaces/testing/mocks.go index b469bc1..3c9477f 100644 --- a/backend/internal/features/workspaces/testing/mocks.go +++ b/backend/internal/features/workspaces/testing/mocks.go @@ -13,6 +13,13 @@ type EmailCall struct { Body string } +func NewMockEmailSender() *MockEmailSender { + return &MockEmailSender{ + SendEmailCalls: []EmailCall{}, + ShouldFail: false, + } +} + func (m *MockEmailSender) SendEmail(to, subject, body string) error { m.SendEmailCalls = append(m.SendEmailCalls, EmailCall{ To: to, @@ -24,10 +31,3 @@ func (m *MockEmailSender) SendEmail(to, subject, body string) error { } return nil } - -func NewMockEmailSender() *MockEmailSender { - return &MockEmailSender{ - SendEmailCalls: []EmailCall{}, - ShouldFail: false, - } -} diff --git a/backend/internal/features/workspaces/testing/testing.go b/backend/internal/features/workspaces/testing/testing.go index cafaebd..169f669 100644 --- a/backend/internal/features/workspaces/testing/testing.go +++ b/backend/internal/features/workspaces/testing/testing.go @@ -2,6 +2,7 @@ package workspaces_testing import ( "bytes" + "context" "encoding/json" "fmt" "net/http" @@ -433,7 +434,7 @@ func MakeAPIRequest( requestBody = bytes.NewBuffer(nil) } - req, err := http.NewRequest(method, url, requestBody) + req, err := http.NewRequestWithContext(context.Background(), method, url, requestBody) if err != nil { panic(err) } diff --git a/backend/internal/util/cloudflare_turnstile/cloudflare_turnstile_service.go b/backend/internal/util/cloudflare_turnstile/cloudflare_turnstile_service.go index b95a474..0a5e46a 100644 --- a/backend/internal/util/cloudflare_turnstile/cloudflare_turnstile_service.go +++ b/backend/internal/util/cloudflare_turnstile/cloudflare_turnstile_service.go @@ -1,12 +1,14 @@ package cloudflare_turnstile import ( + "context" "encoding/json" "errors" "fmt" "io" "net/http" "net/url" + "strings" "time" ) @@ -42,7 +44,15 @@ func (s *CloudflareTurnstileService) VerifyToken(token, remoteIP string) (bool, formData.Set("response", token) formData.Set("remoteip", remoteIP) - resp, err := http.PostForm(cloudflareTurnstileVerifyURL, formData) + req, err := http.NewRequestWithContext( + context.Background(), "POST", cloudflareTurnstileVerifyURL, strings.NewReader(formData.Encode()), + ) + if err != nil { + return false, fmt.Errorf("failed to create Cloudflare Turnstile request: %w", err) + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + resp, err := http.DefaultClient.Do(req) if err != nil { return false, fmt.Errorf("failed to verify Cloudflare Turnstile: %w", err) } diff --git a/backend/internal/util/logger/multi_handler.go b/backend/internal/util/logger/multi_handler.go index 8d48a5f..3274c72 100644 --- a/backend/internal/util/logger/multi_handler.go +++ b/backend/internal/util/logger/multi_handler.go @@ -32,7 +32,7 @@ func (h *MultiHandler) Handle(ctx context.Context, record slog.Record) error { // Send to VictoriaLogs if configured if h.victoriaLogsWriter != nil { - attrs := make(map[string]interface{}) + attrs := make(map[string]any) record.Attrs(func(a slog.Attr) bool { attrs[a.Key] = a.Value.Any() return true diff --git a/backend/internal/util/logger/victorialogs_writer.go b/backend/internal/util/logger/victorialogs_writer.go index 48190a8..42beb18 100644 --- a/backend/internal/util/logger/victorialogs_writer.go +++ b/backend/internal/util/logger/victorialogs_writer.go @@ -59,7 +59,7 @@ func NewVictoriaLogsWriter(url, username, password string) *VictoriaLogsWriter { return writer } -func (w *VictoriaLogsWriter) Write(level, message string, attrs map[string]interface{}) { +func (w *VictoriaLogsWriter) Write(level, message string, attrs map[string]any) { entry := logEntry{ Time: time.Now().UTC().Format(time.RFC3339Nano), Message: message, @@ -76,6 +76,27 @@ func (w *VictoriaLogsWriter) Write(level, message string, attrs map[string]inter } } +func (w *VictoriaLogsWriter) Shutdown(timeout time.Duration) { + w.once.Do(func() { + // Stop accepting new logs + w.cancel() + + // Wait for workers to finish with timeout + done := make(chan struct{}) + go func() { + w.wg.Wait() + close(done) + }() + + select { + case <-done: + w.logger.Info("VictoriaLogs writer shutdown gracefully") + case <-time.After(timeout): + w.logger.Warn("VictoriaLogs writer shutdown timeout, some logs may be lost") + } + }) +} + func (w *VictoriaLogsWriter) worker() { defer w.wg.Done() @@ -180,24 +201,3 @@ func (w *VictoriaLogsWriter) flushBatch(batch []logEntry) { w.sendBatch(batch) } } - -func (w *VictoriaLogsWriter) Shutdown(timeout time.Duration) { - w.once.Do(func() { - // Stop accepting new logs - w.cancel() - - // Wait for workers to finish with timeout - done := make(chan struct{}) - go func() { - w.wg.Wait() - close(done) - }() - - select { - case <-done: - w.logger.Info("VictoriaLogs writer shutdown gracefully") - case <-time.After(timeout): - w.logger.Warn("VictoriaLogs writer shutdown timeout, some logs may be lost") - } - }) -} diff --git a/backend/internal/util/testing/requests.go b/backend/internal/util/testing/requests.go index c3d69d6..55c474f 100644 --- a/backend/internal/util/testing/requests.go +++ b/backend/internal/util/testing/requests.go @@ -2,6 +2,7 @@ package testing import ( "bytes" + "context" "encoding/json" "net/http" "net/http/httptest" @@ -14,7 +15,7 @@ import ( type RequestOptions struct { Method string URL string - Body interface{} + Body any Headers map[string]string AuthToken string ExpectedStatus int @@ -40,7 +41,7 @@ func MakeGetRequestAndUnmarshal( router *gin.Engine, url, authToken string, expectedStatus int, - responseStruct interface{}, + responseStruct any, ) *TestResponse { return makeAuthenticatedRequestAndUnmarshal( t, @@ -58,7 +59,7 @@ func MakePostRequest( t *testing.T, router *gin.Engine, url, authToken string, - body interface{}, + body any, expectedStatus int, ) *TestResponse { return makeAuthenticatedRequest(t, router, "POST", url, authToken, body, expectedStatus) @@ -68,9 +69,9 @@ func MakePostRequestAndUnmarshal( t *testing.T, router *gin.Engine, url, authToken string, - body interface{}, + body any, expectedStatus int, - responseStruct interface{}, + responseStruct any, ) *TestResponse { return makeAuthenticatedRequestAndUnmarshal( t, @@ -88,7 +89,7 @@ func MakePutRequest( t *testing.T, router *gin.Engine, url, authToken string, - body interface{}, + body any, expectedStatus int, ) *TestResponse { return makeAuthenticatedRequest(t, router, "PUT", url, authToken, body, expectedStatus) @@ -98,9 +99,9 @@ func MakePutRequestAndUnmarshal( t *testing.T, router *gin.Engine, url, authToken string, - body interface{}, + body any, expectedStatus int, - responseStruct interface{}, + responseStruct any, ) *TestResponse { return makeAuthenticatedRequestAndUnmarshal( t, @@ -134,7 +135,7 @@ func MakeRequest(t *testing.T, router *gin.Engine, options RequestOptions) *Test requestBody = bytes.NewBuffer(nil) } - req, err := http.NewRequest(options.Method, options.URL, requestBody) + req, err := http.NewRequestWithContext(context.Background(), options.Method, options.URL, requestBody) assert.NoError(t, err, "Failed to create HTTP request") if options.Body != nil { @@ -167,7 +168,7 @@ func makeRequestAndUnmarshal( t *testing.T, router *gin.Engine, options RequestOptions, - responseStruct interface{}, + responseStruct any, ) *TestResponse { response := MakeRequest(t, router, options) @@ -183,7 +184,7 @@ func makeAuthenticatedRequest( t *testing.T, router *gin.Engine, method, url, authToken string, - body interface{}, + body any, expectedStatus int, ) *TestResponse { return MakeRequest(t, router, RequestOptions{ @@ -199,9 +200,9 @@ func makeAuthenticatedRequestAndUnmarshal( t *testing.T, router *gin.Engine, method, url, authToken string, - body interface{}, + body any, expectedStatus int, - responseStruct interface{}, + responseStruct any, ) *TestResponse { return makeRequestAndUnmarshal(t, router, RequestOptions{ Method: method,