From fcec602a9f84286975e2026089f5680e199d40b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Thu, 13 Nov 2025 17:51:39 -0300 Subject: [PATCH 01/28] chore: Add CI and lint workflows, configure golangci-lint, and improve error handling in main application --- .github/workflows/ci.yml | 137 ++++++++++++++++++++++++++++++ .github/workflows/lint.yml | 72 ++++++++++++++++ .github/workflows/tests.yml | 76 +++++++++++++++++ .golangci.yml | 50 +++++++++++ Makefile | 5 +- extensions/gocqltesting/db.go | 2 +- extensions/gocqltesting/docker.go | 6 +- gateways/gocql/setup.go | 11 ++- main.go | 6 +- 9 files changed, 357 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/tests.yml create mode 100644 .golangci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..489020b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,137 @@ +name: CI + +on: + push: + branches: ["**"] + pull_request: + branches: ["**"] + +env: + GO_VERSION: "1.24" + +jobs: + lint: + name: Lint Code + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + cache-dependency-path: go.sum + + - name: Cache Go build cache + uses: actions/cache@v4 + with: + path: ~/.cache/go-build + key: ${{ runner.os }}-go${{ env.GO_VERSION }}-build-${{ hashFiles('go.sum') }} + restore-keys: | + ${{ runner.os }}-go${{ env.GO_VERSION }}-build- + ${{ runner.os }}-go-build- + + - name: Cache golangci-lint + uses: actions/cache@v4 + with: + path: ~/.cache/golangci-lint + key: ${{ runner.os }}-golangci-lint-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-golangci-lint- + + - name: Run golangci-lint + uses: golangci/golangci-lint-action@v4 + with: + version: latest + args: --timeout=5m + cache: true + + - name: Check formatting + run: | + if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then + echo "Code is not formatted. Run 'gofmt -s -w .'" + gofmt -s -d . + exit 1 + fi + + - name: Run go vet + run: go vet ./... + + - name: Verify dependencies + run: go mod verify + + - name: Check for unused dependencies + run: | + go mod tidy + if [ -n "$(git status --porcelain go.mod go.sum)" ]; then + echo "go.mod or go.sum is not up to date. Run 'go mod tidy'" + git diff go.mod go.sum + exit 1 + fi + + test: + name: Run Tests + runs-on: ubuntu-latest + # Run in parallel with lint for faster CI + + services: + docker: + image: docker:dind + options: >- + --privileged + --health-cmd "docker info" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + cache-dependency-path: go.sum + + - name: Cache Go build cache + uses: actions/cache@v4 + with: + path: ~/.cache/go-build + key: ${{ runner.os }}-go${{ env.GO_VERSION }}-build-${{ hashFiles('go.sum') }} + restore-keys: | + ${{ runner.os }}-go${{ env.GO_VERSION }}-build- + ${{ runner.os }}-go-build- + + - name: Download dependencies + run: go mod download + + - name: Verify dependencies + run: go mod verify + + - name: Wait for Docker to be ready + run: | + echo "Waiting for Docker daemon..." + until docker info > /dev/null 2>&1; do + echo "Docker daemon not ready yet..." + sleep 1 + done + echo "Docker daemon is ready" + + - name: Run tests + run: | + go test -timeout 120s -v -race -coverprofile=coverage.out ./... + + - name: Generate coverage report + run: make coverage + continue-on-error: true + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + if: always() + with: + file: ./coverage.out + flags: unittests + name: codecov-umbrella + fail_ci_if_error: false diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..0a89bea --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,72 @@ +name: Lint + +on: + push: + branches: ["**"] + pull_request: + branches: ["**"] + +env: + GO_VERSION: "1.24" + +jobs: + lint: + name: Lint Code + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + cache-dependency-path: go.sum + + - name: Cache Go build cache + uses: actions/cache@v4 + with: + path: ~/.cache/go-build + key: ${{ runner.os }}-go${{ env.GO_VERSION }}-build-${{ hashFiles('go.sum') }} + restore-keys: | + ${{ runner.os }}-go${{ env.GO_VERSION }}-build- + ${{ runner.os }}-go-build- + + - name: Cache golangci-lint + uses: actions/cache@v4 + with: + path: ~/.cache/golangci-lint + key: ${{ runner.os }}-golangci-lint-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-golangci-lint- + + - name: Run golangci-lint + uses: golangci/golangci-lint-action@v4 + with: + version: latest + args: --timeout=5m + cache: true + + - name: Check formatting + run: | + if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then + echo "Code is not formatted. Run 'gofmt -s -w .'" + gofmt -s -d . + exit 1 + fi + + - name: Run go vet + run: go vet ./... + + - name: Verify dependencies + run: go mod verify + + - name: Check for unused dependencies + run: | + go mod tidy + if [ -n "$(git status --porcelain go.mod go.sum)" ]; then + echo "go.mod or go.sum is not up to date. Run 'go mod tidy'" + git diff go.mod go.sum + exit 1 + fi diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..4deb9ac --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,76 @@ +name: Tests + +on: + push: + branches: ["**"] + pull_request: + branches: ["**"] + +env: + GO_VERSION: "1.24" + +jobs: + test: + name: Run Tests + runs-on: ubuntu-latest + + services: + docker: + image: docker:dind + options: >- + --privileged + --health-cmd "docker info" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + cache-dependency-path: go.sum + + - name: Cache Go build cache + uses: actions/cache@v4 + with: + path: ~/.cache/go-build + key: ${{ runner.os }}-go${{ env.GO_VERSION }}-build-${{ hashFiles('go.sum') }} + restore-keys: | + ${{ runner.os }}-go${{ env.GO_VERSION }}-build- + ${{ runner.os }}-go-build- + + - name: Download dependencies + run: go mod download + + - name: Verify dependencies + run: go mod verify + + - name: Wait for Docker to be ready + run: | + echo "Waiting for Docker daemon..." + until docker info > /dev/null 2>&1; do + echo "Docker daemon not ready yet..." + sleep 1 + done + echo "Docker daemon is ready" + + - name: Run tests + run: | + go test -timeout 120s -v -race -coverprofile=coverage.out ./... + + - name: Generate coverage report + run: make coverage + continue-on-error: true + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + if: always() + with: + file: ./coverage.out + flags: unittests + name: codecov-umbrella + fail_ci_if_error: false diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..335c63b --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,50 @@ +version: "2" +linters: + default: all + disable: + - depguard + - err113 + - exhaustive + - exhaustruct + - godot + - godox + - gomoddirectives + - ireturn + - lll + - musttag + - nlreturn + - nonamedreturns + - tagalign + - tagliatelle + - varnamelen + - wsl + - wsl_v5 + - embeddedstructfieldcheck + - noinlineerr + - revive + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - mock\.go$ + - mock_[^/]*\.go$ + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gci + - gofumpt + - goimports + exclusions: + generated: lax + paths: + - mock\.go$ + - mock_[^/]*\.go$ + - third_party$ + - builtin$ + - examples$ diff --git a/Makefile b/Makefile index 12ea1e6..4896ce9 100644 --- a/Makefile +++ b/Makefile @@ -19,4 +19,7 @@ test: @go test -timeout 30s -run ./... coverage: - @go test ./... -coverprofile=coverage.out \ No newline at end of file + @go test ./... -coverprofile=coverage.out + +lint: + @golangci-lint run ./... \ No newline at end of file diff --git a/extensions/gocqltesting/db.go b/extensions/gocqltesting/db.go index a69a6a6..80fe81d 100644 --- a/extensions/gocqltesting/db.go +++ b/extensions/gocqltesting/db.go @@ -51,7 +51,7 @@ func NewDB(t *testing.T, dbName string) (*cassandra.Session, error) { port := 9042 if dbPort != "" { - fmt.Sscanf(dbPort, "%d", &port) + _, _ = fmt.Sscanf(dbPort, "%d", &port) } cluster := cassandra.NewCluster("localhost") diff --git a/extensions/gocqltesting/docker.go b/extensions/gocqltesting/docker.go index 78be440..b996137 100644 --- a/extensions/gocqltesting/docker.go +++ b/extensions/gocqltesting/docker.go @@ -119,7 +119,7 @@ func StartDockerContainer(cfg DockerContainerConfig) (teardownFn func(), err err concurrentSession.Close() concurrentSession = nil } - dockerResource.Close() + _ = dockerResource.Close() containerInitialized = false templateReady = false } @@ -211,7 +211,7 @@ func pingCassandraFn(port string) func() error { return func() error { portInt := 9042 if port != "" { - fmt.Sscanf(port, "%d", &portInt) + _, _ = fmt.Sscanf(port, "%d", &portInt) } cluster := cassandra.NewCluster("localhost") @@ -246,7 +246,7 @@ func getCassandraConnString(port, keyspace string) string { func newDB(_ string) (*cassandra.Session, error) { port := 9042 if dbPort != "" { - fmt.Sscanf(dbPort, "%d", &port) + _, _ = fmt.Sscanf(dbPort, "%d", &port) } cluster := cassandra.NewCluster("localhost") diff --git a/gateways/gocql/setup.go b/gateways/gocql/setup.go index ea301d5..222a046 100644 --- a/gateways/gocql/setup.go +++ b/gateways/gocql/setup.go @@ -33,7 +33,9 @@ func SetupDatabase(config *Config, logger *zap.Logger) (*gocql.Session, error) { "CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}", config.Keyspace, ) - session.Query(createKeyspaceQuery).Exec() + if err := session.Query(createKeyspaceQuery).Exec(); err != nil { + logger.Warn("Failed to create keyspace (may already exist)", zap.Error(err)) + } cluster.Keyspace = config.Keyspace sessionWithKeyspace, err := cluster.CreateSession() @@ -90,7 +92,12 @@ func runMigrations(config *Config, logger *zap.Logger) error { } logger.Info("Running migrations") - defer m.Close() + defer func() { + _, err := m.Close() + if err != nil { + logger.Warn("Failed to close migration instance", zap.Error(err)) + } + }() if err := m.Up(); err != nil { if errors.Is(err, migrate.ErrNoChange) { diff --git a/main.go b/main.go index 4fa6578..fbb17b0 100644 --- a/main.go +++ b/main.go @@ -46,7 +46,11 @@ func main() { if err != nil { logger.Fatal("Failed to setup Redis", zap.Error(err)) } - defer redisClient.Close() + defer func() { + if err := redisClient.Close(); err != nil { + logger.Error("Failed to close Redis client", zap.Error(err)) + } + }() setInitialCounter, err := redis.SetInitialCounterValue(ctx, redisClient, &cfg.Redis, logger) if err != nil && !setInitialCounter { From 092b8f67c1dd68262a137df0294ecfa0a37f37cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Thu, 13 Nov 2025 18:52:31 -0300 Subject: [PATCH 02/28] style: apply lint --- domain/entities/helpers/decode_short_url.go | 2 +- domain/entities/helpers/encode_url.go | 18 +- domain/entities/helpers/encode_url_test.go | 6 +- domain/entities/usecases/create_url.go | 9 +- domain/entities/usecases/create_url_test.go | 12 +- domain/entities/usecases/get_long_test.go | 9 +- domain/entities/usecases/usecase.go | 5 - extensions/config/config.go | 14 +- extensions/gocqltesting/db.go | 215 +++++++++++------- extensions/gocqltesting/docker.go | 85 +++++-- extensions/redis/redis.go | 6 +- gateways/gocql/repositories/repository.go | 7 +- gateways/gocql/repositories/url_repository.go | 8 +- gateways/gocql/setup.go | 21 +- gateways/http/handlers/urls_handler.go | 3 +- gateways/http/middleware/middleware.go | 12 +- gateways/http/server.go | 10 +- main.go | 73 ++++-- 18 files changed, 327 insertions(+), 188 deletions(-) diff --git a/domain/entities/helpers/decode_short_url.go b/domain/entities/helpers/decode_short_url.go index f73ffb0..95ce063 100644 --- a/domain/entities/helpers/decode_short_url.go +++ b/domain/entities/helpers/decode_short_url.go @@ -13,7 +13,7 @@ func Base62Decode(shortURL string, salt string) int64 { if index == -1 { return 0 } - decoded = decoded*62 + int64(index) + decoded = decoded*base62 + int64(index) } return decoded } diff --git a/domain/entities/helpers/encode_url.go b/domain/entities/helpers/encode_url.go index d02f67f..b72c3fe 100644 --- a/domain/entities/helpers/encode_url.go +++ b/domain/entities/helpers/encode_url.go @@ -5,7 +5,11 @@ import ( "strings" ) -const base62Alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +const ( + base62Alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + base62 = 62 + minEncodedLen = 4 +) func Base62Encode(id int64, salt string) string { alphabet := getShuffledAlphabet(salt) @@ -16,14 +20,14 @@ func Base62Encode(id int64, salt string) string { encoded = string(alphabet[0]) } else { for num > 0 { - encoded = string(alphabet[num%62]) + encoded - num = num / 62 + encoded = string(alphabet[num%base62]) + encoded + num /= base62 } } - if len(encoded) < 4 { - encoded = strings.Repeat(string(alphabet[0]), 4-len(encoded)) + encoded - } else if len(encoded) > 4 { - encoded = encoded[:4] + if len(encoded) < minEncodedLen { + encoded = strings.Repeat(string(alphabet[0]), minEncodedLen-len(encoded)) + encoded + } else if len(encoded) > minEncodedLen { + encoded = encoded[:minEncodedLen] } return encoded } diff --git a/domain/entities/helpers/encode_url_test.go b/domain/entities/helpers/encode_url_test.go index 87edc57..14d7ac0 100644 --- a/domain/entities/helpers/encode_url_test.go +++ b/domain/entities/helpers/encode_url_test.go @@ -1,7 +1,9 @@ -package helpers +package helpers_test import ( "testing" + + "lnk/domain/entities/helpers" ) func Test_Helper_Base62Encode(t *testing.T) { @@ -24,7 +26,7 @@ func Test_Helper_Base62Encode(t *testing.T) { } for _, test := range tests { - got := Base62Encode(test.id, salt) + got := helpers.Base62Encode(test.id, salt) if got != test.want { t.Errorf("Base62Encode(%d, %s) = %s, want %s", test.id, salt, got, test.want) } diff --git a/domain/entities/usecases/create_url.go b/domain/entities/usecases/create_url.go index 6728a7a..61a3009 100644 --- a/domain/entities/usecases/create_url.go +++ b/domain/entities/usecases/create_url.go @@ -1,14 +1,15 @@ package usecases import ( + "context" "fmt" "lnk/domain/entities" "lnk/domain/entities/helpers" ) -func (uc *UseCase) CreateShortURL(longURL string) (string, error) { - id, err := uc.redis.Incr(uc.ctx, uc.counterKey) +func (uc *UseCase) CreateShortURL(ctx context.Context, longURL string) (string, error) { + id, err := uc.redis.Incr(ctx, uc.counterKey) if err != nil { return "", fmt.Errorf("failed to increment counter: %w", err) } @@ -20,9 +21,9 @@ func (uc *UseCase) CreateShortURL(longURL string) (string, error) { LongURL: longURL, } - err = uc.repository.CreateURL(url) + err = uc.repository.CreateURL(ctx, url) if err != nil { - return "", err + return "", fmt.Errorf("failed to create URL in repository: %w", err) } return shortCode, nil diff --git a/domain/entities/usecases/create_url_test.go b/domain/entities/usecases/create_url_test.go index 035cd79..0be3c52 100644 --- a/domain/entities/usecases/create_url_test.go +++ b/domain/entities/usecases/create_url_test.go @@ -1,8 +1,9 @@ -package usecases +package usecases_test import ( "context" + "lnk/domain/entities/usecases" "lnk/extensions/gocqltesting" "lnk/extensions/redis/mocks" "lnk/gateways/gocql/repositories" @@ -22,13 +23,12 @@ func Test_UseCase_CreateURL(t *testing.T) { ctx := context.Background() logger := zap.NewNop() - repository := repositories.NewRepository(ctx, logger, session) + repository := repositories.NewRepository(logger, session) mockRedis := mocks.NewMockRedis(t) mockRedis.On("Incr", mock.Anything, mock.Anything).Return(int64(1), nil) - params := NewUseCaseParams{ - Ctx: ctx, + params := usecases.NewUseCaseParams{ Logger: logger, Repository: repository, Redis: mockRedis, @@ -36,10 +36,10 @@ func Test_UseCase_CreateURL(t *testing.T) { CounterKey: "test", } - useCase := NewUseCase(params) + useCase := usecases.NewUseCase(params) longURL := "https://www.google.com" - shortCode, err := useCase.CreateShortURL(longURL) + shortCode, err := useCase.CreateShortURL(ctx, longURL) require.NoError(t, err) require.NotEmpty(t, shortCode) } diff --git a/domain/entities/usecases/get_long_test.go b/domain/entities/usecases/get_long_test.go index 73f922d..3d1f786 100644 --- a/domain/entities/usecases/get_long_test.go +++ b/domain/entities/usecases/get_long_test.go @@ -23,13 +23,12 @@ func Test_UseCase_GetLongURL(t *testing.T) { ctx := context.Background() logger := zap.NewNop() - repository := repositories.NewRepository(ctx, logger, session) + repository := repositories.NewRepository(logger, session) mockRedis := mocks.NewMockRedis(t) mockRedis.On("Incr", mock.Anything, mock.Anything).Return(int64(1), nil) params := usecases.NewUseCaseParams{ - Ctx: ctx, Logger: logger, Repository: repository, Redis: mockRedis, @@ -39,7 +38,7 @@ func Test_UseCase_GetLongURL(t *testing.T) { useCase := usecases.NewUseCase(params) - shortCode, err := useCase.CreateShortURL(url) + shortCode, err := useCase.CreateShortURL(ctx, url) require.NoError(t, err) require.NotEmpty(t, shortCode) @@ -55,13 +54,11 @@ func Test_UseCase_GetLongURL_NotFound(t *testing.T) { session, err := gocqltesting.NewDB(t, t.Name()) require.NoError(t, err) - ctx := context.Background() logger := zap.NewNop() - repository := repositories.NewRepository(ctx, logger, session) + repository := repositories.NewRepository(logger, session) params := usecases.NewUseCaseParams{ - Ctx: ctx, Logger: logger, Repository: repository, } diff --git a/domain/entities/usecases/usecase.go b/domain/entities/usecases/usecase.go index 06cd8ad..ede4226 100644 --- a/domain/entities/usecases/usecase.go +++ b/domain/entities/usecases/usecase.go @@ -1,7 +1,6 @@ package usecases import ( - "context" "errors" "lnk/extensions/redis" "lnk/gateways/gocql/repositories" @@ -9,11 +8,9 @@ import ( "go.uber.org/zap" ) -// ErrURLNotFound is returned when a URL is not found var ErrURLNotFound = errors.New("URL not found") type UseCase struct { - ctx context.Context logger *zap.Logger repository *repositories.Repository redis redis.Redis @@ -22,7 +19,6 @@ type UseCase struct { } type NewUseCaseParams struct { - Ctx context.Context Logger *zap.Logger Repository *repositories.Repository Redis redis.Redis @@ -32,7 +28,6 @@ type NewUseCaseParams struct { func NewUseCase(params NewUseCaseParams) *UseCase { return &UseCase{ - ctx: params.Ctx, logger: params.Logger, repository: params.Repository, redis: params.Redis, diff --git a/extensions/config/config.go b/extensions/config/config.go index 072acfd..e5894d6 100644 --- a/extensions/config/config.go +++ b/extensions/config/config.go @@ -1,12 +1,14 @@ package config import ( - "lnk/extensions/logger" - "lnk/extensions/redis" - "lnk/gateways/gocql" + "fmt" "github.com/joho/godotenv" "github.com/kelseyhightower/envconfig" + + "lnk/extensions/logger" + "lnk/extensions/redis" + "lnk/gateways/gocql" ) type Config struct { @@ -26,14 +28,14 @@ type App struct { func LoadConfig() (*Config, error) { err := godotenv.Load() if err != nil { - return nil, err + return nil, fmt.Errorf("failed to load .env file: %w", err) } config := &Config{} if err := envconfig.Process("", config); err != nil { - return nil, err + return nil, fmt.Errorf("failed to process app config: %w", err) } if err := envconfig.Process("", &config.Gocql); err != nil { - return nil, err + return nil, fmt.Errorf("failed to process gocql config: %w", err) } return config, nil } diff --git a/extensions/gocqltesting/db.go b/extensions/gocqltesting/db.go index 80fe81d..c87fbbf 100644 --- a/extensions/gocqltesting/db.go +++ b/extensions/gocqltesting/db.go @@ -12,6 +12,11 @@ import ( cassandra "github.com/apache/cassandra-gocql-driver/v2" ) +const ( + connectTimeout = 30 * time.Second + timeout = 10 * time.Second +) + var nonAlphaRegex = regexp.MustCompile(`[\W]`) // NewDB creates a new isolated test keyspace by copying the schema from the template keyspace. @@ -31,7 +36,7 @@ func NewDB(t *testing.T, dbName string) (*cassandra.Session, error) { } dbName = nonAlphaRegex.ReplaceAllString(strings.ToLower(dbName), "_") - dropQuery := fmt.Sprintf("DROP KEYSPACE IF EXISTS %s", dbName) + dropQuery := "DROP KEYSPACE IF EXISTS " + dbName if err := session.Query(dropQuery).Exec(); err != nil { return nil, fmt.Errorf("failed to drop existing keyspace: %w", err) } @@ -57,8 +62,8 @@ func NewDB(t *testing.T, dbName string) (*cassandra.Session, error) { cluster := cassandra.NewCluster("localhost") cluster.Port = port cluster.Keyspace = dbName - cluster.ConnectTimeout = 30 * time.Second - cluster.Timeout = 10 * time.Second + cluster.ConnectTimeout = connectTimeout + cluster.Timeout = timeout cluster.Consistency = cassandra.Quorum testSession, err := cluster.CreateSession() @@ -69,7 +74,7 @@ func NewDB(t *testing.T, dbName string) (*cassandra.Session, error) { t.Cleanup(func() { testSession.Close() - _ = session.Query(fmt.Sprintf("DROP KEYSPACE IF EXISTS %s", dbName)).Exec() + _ = session.Query("DROP KEYSPACE IF EXISTS " + dbName).Exec() }) return testSession, nil @@ -78,6 +83,21 @@ func NewDB(t *testing.T, dbName string) (*cassandra.Session, error) { // copySchemaFromTemplate copies all tables and indexes from the template keyspace to the target keyspace // by querying system_schema tables. This provides fast schema replication without running migrations. func copySchemaFromTemplate(session *cassandra.Session, targetKeyspace string) error { + tableNames, err := getTableNames(session) + if err != nil { + return err + } + + for _, tableName := range tableNames { + if err := copyTable(session, targetKeyspace, tableName); err != nil { + return err + } + } + + return nil +} + +func getTableNames(session *cassandra.Session) ([]string, error) { iter := session.Query(` SELECT table_name FROM system_schema.tables @@ -93,101 +113,130 @@ func copySchemaFromTemplate(session *cassandra.Session, targetKeyspace string) e tableNames = append(tableNames, tableName) } if err := iter.Close(); err != nil { - return fmt.Errorf("failed to get tables: %w", err) + return nil, fmt.Errorf("failed to get tables: %w", err) } - for _, tableName := range tableNames { - colIter := session.Query(` - SELECT column_name, type, kind, position - FROM system_schema.columns - WHERE keyspace_name = ? AND table_name = ? - `, _templateKeyspace, tableName).Iter() - - type columnInfo struct { - name string - typ string - kind string - position int - } + return tableNames, nil +} - var columnInfos []columnInfo - var columnName, columnType, columnKind string - var position int - - for colIter.Scan(&columnName, &columnType, &columnKind, &position) { - columnInfos = append(columnInfos, columnInfo{ - name: columnName, - typ: columnType, - kind: columnKind, - position: position, - }) - } - if err := colIter.Close(); err != nil { - return fmt.Errorf("failed to get columns for table %s: %w", tableName, err) - } +type columnInfo struct { + name string + typ string + kind string + position int +} + +func copyTable(session *cassandra.Session, targetKeyspace, tableName string) error { + columnInfos, err := getColumnInfos(session, tableName) + if err != nil { + return err + } + + if err := createTable(session, targetKeyspace, tableName, columnInfos); err != nil { + return err + } + + return copyIndexes(session, targetKeyspace, tableName) +} - sort.Slice(columnInfos, func(i, j int) bool { - return columnInfos[i].position < columnInfos[j].position +func getColumnInfos(session *cassandra.Session, tableName string) ([]columnInfo, error) { + colIter := session.Query(` + SELECT column_name, type, kind, position + FROM system_schema.columns + WHERE keyspace_name = ? AND table_name = ? + `, _templateKeyspace, tableName).Iter() + + var columnInfos []columnInfo + var columnName, columnType, columnKind string + var position int + + for colIter.Scan(&columnName, &columnType, &columnKind, &position) { + columnInfos = append(columnInfos, columnInfo{ + name: columnName, + typ: columnType, + kind: columnKind, + position: position, }) + } + if err := colIter.Close(); err != nil { + return nil, fmt.Errorf("failed to get columns for table %s: %w", tableName, err) + } - var columns []string - var partitionKeys []string - var clusteringKeys []string - - for _, col := range columnInfos { - columns = append(columns, fmt.Sprintf("%s %s", col.name, col.typ)) - switch col.kind { - case "partition_key": - partitionKeys = append(partitionKeys, col.name) - case "clustering": - clusteringKeys = append(clusteringKeys, col.name) - } - } + sort.Slice(columnInfos, func(i, j int) bool { + return columnInfos[i].position < columnInfos[j].position + }) - createStmt := fmt.Sprintf("CREATE TABLE %s.%s (", targetKeyspace, tableName) - createStmt += strings.Join(columns, ", ") - createStmt += ", PRIMARY KEY (" + return columnInfos, nil +} - if len(partitionKeys) > 0 { - if len(partitionKeys) == 1 { - createStmt += partitionKeys[0] - } else { - createStmt += "(" + strings.Join(partitionKeys, ", ") + ")" - } - if len(clusteringKeys) > 0 { - createStmt += ", " + strings.Join(clusteringKeys, ", ") - } +func createTable(session *cassandra.Session, targetKeyspace, tableName string, columnInfos []columnInfo) error { + var columns []string + var partitionKeys []string + var clusteringKeys []string + + for _, col := range columnInfos { + columns = append(columns, fmt.Sprintf("%s %s", col.name, col.typ)) + switch col.kind { + case "partition_key": + partitionKeys = append(partitionKeys, col.name) + case "clustering": + clusteringKeys = append(clusteringKeys, col.name) } - createStmt += "))" + } + + createStmt := buildCreateTableStatement(targetKeyspace, tableName, columns, partitionKeys, clusteringKeys) - if err := session.Query(createStmt).Exec(); err != nil { - return fmt.Errorf("failed to create table %s: %w", tableName, err) + if err := session.Query(createStmt).Exec(); err != nil { + return fmt.Errorf("failed to create table %s: %w", tableName, err) + } + + return nil +} + +func buildCreateTableStatement(targetKeyspace, tableName string, columns, partitionKeys, clusteringKeys []string) string { + createStmt := fmt.Sprintf("CREATE TABLE %s.%s (", targetKeyspace, tableName) + createStmt += strings.Join(columns, ", ") + createStmt += ", PRIMARY KEY (" + + if len(partitionKeys) > 0 { + if len(partitionKeys) == 1 { + createStmt += partitionKeys[0] + } else { + createStmt += "(" + strings.Join(partitionKeys, ", ") + ")" } + if len(clusteringKeys) > 0 { + createStmt += ", " + strings.Join(clusteringKeys, ", ") + } + } + createStmt += "))" + + return createStmt +} - idxIter := session.Query(` - SELECT index_name, kind, options - FROM system_schema.indexes - WHERE keyspace_name = ? AND table_name = ? - `, _templateKeyspace, tableName).Iter() +func copyIndexes(session *cassandra.Session, targetKeyspace, tableName string) error { + idxIter := session.Query(` + SELECT index_name, kind, options + FROM system_schema.indexes + WHERE keyspace_name = ? AND table_name = ? + `, _templateKeyspace, tableName).Iter() - var indexName, kind, options string - for idxIter.Scan(&indexName, &kind, &options) { - if kind == "KEYS" { - continue - } + var indexName, kind string + for idxIter.Scan(&indexName, &kind, new(string)) { + if kind == "KEYS" { + continue + } - columnName := extractColumnFromIndexName(indexName, tableName) - if columnName != "" { - createIndexStmt := fmt.Sprintf("CREATE INDEX IF NOT EXISTS %s ON %s.%s (%s)", - indexName, targetKeyspace, tableName, columnName) - if err := session.Query(createIndexStmt).Exec(); err != nil { - _ = err - } + columnName := extractColumnFromIndexName(indexName, tableName) + if columnName != "" { + createIndexStmt := fmt.Sprintf("CREATE INDEX IF NOT EXISTS %s ON %s.%s (%s)", + indexName, targetKeyspace, tableName, columnName) + if err := session.Query(createIndexStmt).Exec(); err != nil { + _ = err } } - if err := idxIter.Close(); err != nil { - _ = err - } + } + if err := idxIter.Close(); err != nil { + _ = err } return nil diff --git a/extensions/gocqltesting/docker.go b/extensions/gocqltesting/docker.go index b996137..83dd34a 100644 --- a/extensions/gocqltesting/docker.go +++ b/extensions/gocqltesting/docker.go @@ -22,9 +22,19 @@ const ( _defaultGocqlDockerVersion = "latest" _templateKeyspace = "template_keyspace" retryIntervalMs = 500 + maxWait = 180 * time.Second + initialBackoff = 2 * time.Second + maxBackoff = 10 * time.Second + postReadyDelay = 3 * time.Second + cassandraPort = 9042 + pingConnectTimeout = 5 * time.Second + pingTimeout = 3 * time.Second + dockerConnectTimeout = 30 * time.Second + dockerTimeout = 10 * time.Second ) var ( + //nolint:gochecknoglobals // These are intentionally global for test infrastructure coordination dbPort string setupMutex sync.Mutex containerInitialized bool @@ -53,15 +63,33 @@ func StartDockerContainer(cfg DockerContainerConfig) (teardownFn func(), err err return func() {}, nil } + _, dockerResource, err := initializeDockerPool(cfg) + if err != nil { + return nil, err + } + + if err := waitForCassandra(); err != nil { + return nil, err + } + + if err := initializeSession(cfg); err != nil { + return nil, err + } + + teardownFn = createTeardownFn(cfg, dockerResource) + return teardownFn, nil +} + +func initializeDockerPool(cfg DockerContainerConfig) (*dockertest.Pool, *dockertest.Resource, error) { dockerPool, err := dockertest.NewPool("") if err != nil { - return nil, fmt.Errorf(`could not connect to docker: %w`, err) + return nil, nil, fmt.Errorf(`could not connect to docker: %w`, err) } - dockerPool.MaxWait = 180 * time.Second + dockerPool.MaxWait = maxWait if err = dockerPool.Client.Ping(); err != nil { - return nil, fmt.Errorf(`could not connect to docker: %w`, err) + return nil, nil, fmt.Errorf(`could not connect to docker: %w`, err) } if cfg.Version == "" { @@ -70,37 +98,42 @@ func StartDockerContainer(cfg DockerContainerConfig) (teardownFn func(), err err dockerResource, err := getDockerGocqlResource(dockerPool, cfg) if err != nil { - return nil, fmt.Errorf(`failed to initialize gocql docker resource: %w`, err) + return nil, nil, fmt.Errorf(`failed to initialize gocql docker resource: %w`, err) } dbPort = dockerResource.GetPort("9042/tcp") + return dockerPool, dockerResource, nil +} +func waitForCassandra() error { startTime := time.Now() - maxWait := 180 * time.Second - backoff := 2 * time.Second + backoff := initialBackoff for { if time.Since(startTime) > maxWait { - return nil, fmt.Errorf("cassandra not ready after %v: timeout exceeded", maxWait) + return fmt.Errorf("cassandra not ready after %v: timeout exceeded", maxWait) } - err = pingCassandraFn(dbPort)() + err := pingCassandraFn(dbPort)() if err == nil { break } - fmt.Printf("Cassandra not ready yet (attempt after %v): %v\n", time.Since(startTime), err) time.Sleep(backoff) - if backoff < 10*time.Second { + if backoff < maxBackoff { backoff *= 2 } } - time.Sleep(3 * time.Second) + time.Sleep(postReadyDelay) + return nil +} +func initializeSession(cfg DockerContainerConfig) error { + var err error concurrentSession, err = newDB(getCassandraConnString(dbPort, "master")) if err != nil { - return nil, fmt.Errorf("failed to create session: %w", err) + return fmt.Errorf("failed to create session: %w", err) } containerInitialized = true @@ -108,12 +141,16 @@ func StartDockerContainer(cfg DockerContainerConfig) (teardownFn func(), err err if !templateReady { if err := setupTemplateDatabase(concurrentSession, cfg.Migrations.FS); err != nil { concurrentSession.Close() - return nil, err + return err } templateReady = true } - teardownFn = func() { + return nil +} + +func createTeardownFn(cfg DockerContainerConfig, dockerResource *dockertest.Resource) func() { + return func() { if !cfg.ReuseContainer { if concurrentSession != nil { concurrentSession.Close() @@ -124,8 +161,6 @@ func StartDockerContainer(cfg DockerContainerConfig) (teardownFn func(), err err templateReady = false } } - - return teardownFn, nil } // setupTemplateDatabase creates a template keyspace and runs migrations on it once. @@ -143,7 +178,7 @@ func setupTemplateDatabase(conn *cassandra.Session, migrationsFs fs.FS) error { return nil } - fmt.Println("Creating template keyspace and running migrations") + // Creating template keyspace and running migrations createQuery := fmt.Sprintf( "CREATE KEYSPACE %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}", @@ -209,15 +244,15 @@ func getDockerGocqlResource(dockerPool *dockertest.Pool, cfg DockerContainerConf // pingCassandraFn returns a function that tests if Cassandra is ready by attempting a connection and query. func pingCassandraFn(port string) func() error { return func() error { - portInt := 9042 + portInt := cassandraPort if port != "" { _, _ = fmt.Sscanf(port, "%d", &portInt) } cluster := cassandra.NewCluster("localhost") cluster.Port = portInt - cluster.ConnectTimeout = 5 * time.Second - cluster.Timeout = 3 * time.Second + cluster.ConnectTimeout = pingConnectTimeout + cluster.Timeout = pingTimeout cluster.Consistency = cassandra.One cluster.DisableInitialHostLookup = true cluster.NumConns = 1 @@ -244,15 +279,15 @@ func getCassandraConnString(port, keyspace string) string { // newDB creates a new Cassandra session using the dbPort package variable. func newDB(_ string) (*cassandra.Session, error) { - port := 9042 + port := cassandraPort if dbPort != "" { _, _ = fmt.Sscanf(dbPort, "%d", &port) } cluster := cassandra.NewCluster("localhost") cluster.Port = port - cluster.ConnectTimeout = 30 * time.Second - cluster.Timeout = 10 * time.Second + cluster.ConnectTimeout = dockerConnectTimeout + cluster.Timeout = dockerTimeout cluster.Consistency = cassandra.Quorum session, err := cluster.CreateSession() @@ -266,7 +301,7 @@ func newDB(_ string) (*cassandra.Session, error) { func runMigrations(connectionString string, migrationsFs fs.FS) error { sourceDriver, err := migrationHTTPS.New(http.FS(migrationsFs), ".") if err != nil { - return err + return fmt.Errorf("failed to create migration source driver: %w", err) } m, err := migrate.NewWithSourceInstance("httpfs", sourceDriver, connectionString) @@ -275,7 +310,7 @@ func runMigrations(connectionString string, migrationsFs fs.FS) error { } if err := m.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) { - return err + return fmt.Errorf("failed to run migrations: %w", err) } return nil diff --git a/extensions/redis/redis.go b/extensions/redis/redis.go index 603b21b..7afb399 100644 --- a/extensions/redis/redis.go +++ b/extensions/redis/redis.go @@ -24,7 +24,11 @@ func NewRedisAdapter(client *redis.Client) Redis { } func (r *redisAdapter) Incr(ctx context.Context, key string) (int64, error) { - return r.client.Incr(ctx, key).Result() + result, err := r.client.Incr(ctx, key).Result() + if err != nil { + return 0, fmt.Errorf("failed to increment Redis key %s: %w", key, err) + } + return result, nil } func SetupRedis(ctx context.Context, config *Config, logger *zap.Logger) (*redis.Client, error) { diff --git a/gateways/gocql/repositories/repository.go b/gateways/gocql/repositories/repository.go index f6bd5c5..ec18aa0 100644 --- a/gateways/gocql/repositories/repository.go +++ b/gateways/gocql/repositories/repository.go @@ -1,18 +1,15 @@ package repositories import ( - "context" - gocql "github.com/apache/cassandra-gocql-driver/v2" "go.uber.org/zap" ) type Repository struct { - ctx context.Context logger *zap.Logger session *gocql.Session } -func NewRepository(ctx context.Context, logger *zap.Logger, session *gocql.Session) *Repository { - return &Repository{ctx: ctx, logger: logger, session: session} +func NewRepository(logger *zap.Logger, session *gocql.Session) *Repository { + return &Repository{logger: logger, session: session} } diff --git a/gateways/gocql/repositories/url_repository.go b/gateways/gocql/repositories/url_repository.go index d3f2780..bad4a93 100644 --- a/gateways/gocql/repositories/url_repository.go +++ b/gateways/gocql/repositories/url_repository.go @@ -1,21 +1,23 @@ package repositories import ( + "context" "errors" "fmt" - "lnk/domain/entities" "time" "github.com/gocql/gocql" + + "lnk/domain/entities" ) -func (r *Repository) CreateURL(url *entities.URL) error { +func (r *Repository) CreateURL(ctx context.Context, url *entities.URL) error { url.CreatedAt = time.Now().UTC() err := r.session.Query( "INSERT INTO urls (short_code, long_url, created_at) VALUES (?, ?, ?)", url.ShortCode, url.LongURL, url.CreatedAt, - ).ExecContext(r.ctx) + ).ExecContext(ctx) if err != nil { return fmt.Errorf("failed to create URL: %w", err) } diff --git a/gateways/gocql/setup.go b/gateways/gocql/setup.go index 222a046..8420475 100644 --- a/gateways/gocql/setup.go +++ b/gateways/gocql/setup.go @@ -3,15 +3,22 @@ package gocql import ( "errors" "fmt" + "net" "time" - "lnk/gateways/gocql/migrations" - gocql "github.com/apache/cassandra-gocql-driver/v2" "github.com/golang-migrate/migrate/v4" _ "github.com/golang-migrate/migrate/v4/database/cassandra" "github.com/golang-migrate/migrate/v4/source/iofs" "go.uber.org/zap" + + "lnk/gateways/gocql/migrations" +) + +const ( + connectTimeout = 30 * time.Second + timeout = 10 * time.Second + shutdownTimeout = 5 * time.Second ) func SetupDatabase(config *Config, logger *zap.Logger) (*gocql.Session, error) { @@ -22,7 +29,7 @@ func SetupDatabase(config *Config, logger *zap.Logger) (*gocql.Session, error) { Password: config.Password, } cluster.Consistency = gocql.Quorum - cluster.ConnectTimeout = 30 * time.Second + cluster.ConnectTimeout = connectTimeout session, err := createSessionWithRetry(cluster) if err != nil { @@ -64,7 +71,8 @@ func createSessionWithRetry(cluster *gocql.ClusterConfig) (*gocql.Session, error ) var lastErr error - for attempt := 0; attempt < maxAttempts; attempt++ { + maxAttemptsVar := maxAttempts + for range maxAttemptsVar { session, err := cluster.CreateSession() if err == nil { return session, nil @@ -83,7 +91,8 @@ func runMigrations(config *Config, logger *zap.Logger) error { return fmt.Errorf("failed to load embedded migrations: %w", err) } - migrationURL := fmt.Sprintf("cassandra://%s:%d/%s?x-multi-statement=true", config.Host, config.Port, config.Keyspace) + hostPort := net.JoinHostPort(config.Host, fmt.Sprintf("%d", config.Port)) + migrationURL := fmt.Sprintf("cassandra://%s/%s?x-multi-statement=true", hostPort, config.Keyspace) m, err := migrate.NewWithSourceInstance("iofs", sourceDriver, migrationURL) if err != nil { @@ -104,7 +113,7 @@ func runMigrations(config *Config, logger *zap.Logger) error { logger.Info("No migration changes to apply") return nil } - return err + return fmt.Errorf("failed to run migrations: %w", err) } return nil } diff --git a/gateways/http/handlers/urls_handler.go b/gateways/http/handlers/urls_handler.go index 6a1da87..50b996c 100644 --- a/gateways/http/handlers/urls_handler.go +++ b/gateways/http/handlers/urls_handler.go @@ -57,7 +57,7 @@ func (h *URLsHandler) CreateURL(c *gin.Context) { return } - shortURL, err := h.useCase.CreateShortURL(req.URL) + shortURL, err := h.useCase.CreateShortURL(c.Request.Context(), req.URL) if err != nil { c.JSON(http.StatusInternalServerError, ErrorResponse{Error: err.Error()}) return @@ -91,7 +91,6 @@ func (h *URLsHandler) GetURL(c *gin.Context) { c.JSON(http.StatusInternalServerError, ErrorResponse{Error: err.Error()}) return - } c.JSON(http.StatusPermanentRedirect, gin.H{"url": longURL}) diff --git a/gateways/http/middleware/middleware.go b/gateways/http/middleware/middleware.go index dcda95b..68714a2 100644 --- a/gateways/http/middleware/middleware.go +++ b/gateways/http/middleware/middleware.go @@ -1,12 +1,18 @@ package middleware import ( + "net/http" "time" "github.com/gin-gonic/gin" "go.uber.org/zap" ) +const ( + httpStatusInternalServerError = 500 + httpStatusNoContent = 204 +) + func RequestLogger(logger *zap.Logger) gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() @@ -34,7 +40,7 @@ func Recovery(logger *zap.Logger) gin.HandlerFunc { zap.Any("error", recovered), zap.String("path", c.Request.URL.Path), ) - c.AbortWithStatus(500) + c.AbortWithStatus(httpStatusInternalServerError) }) } @@ -45,8 +51,8 @@ func CORS() gin.HandlerFunc { c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With") c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE, PATCH") - if c.Request.Method == "OPTIONS" { - c.AbortWithStatus(204) + if c.Request.Method == http.MethodOptions { + c.AbortWithStatus(httpStatusNoContent) return } diff --git a/gateways/http/server.go b/gateways/http/server.go index 849f000..8350a76 100644 --- a/gateways/http/server.go +++ b/gateways/http/server.go @@ -41,12 +41,13 @@ func NewServer(logger *zap.Logger, port string, router *gin.Engine) *Server { } func (s *Server) Start() error { - addr := fmt.Sprintf(":%s", s.port) + addr := ":" + s.port s.logger.Info("Starting HTTP server", zap.String("address", addr)) s.srv = &http.Server{ - Addr: addr, - Handler: s.router, + Addr: addr, + Handler: s.router, + ReadHeaderTimeout: 5 * time.Second, } go func() { @@ -65,7 +66,8 @@ func (s *Server) Shutdown(ctx context.Context) error { s.logger.Info("Shutting down HTTP server") - shutdownCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + const shutdownTimeout = 5 * time.Second + shutdownCtx, cancel := context.WithTimeout(ctx, shutdownTimeout) defer cancel() if err := s.srv.Shutdown(shutdownCtx); err != nil { diff --git a/main.go b/main.go index fbb17b0..c2b4395 100644 --- a/main.go +++ b/main.go @@ -11,16 +11,43 @@ import ( "lnk/domain/entities/usecases" "lnk/extensions/config" "lnk/extensions/logger" - "lnk/extensions/redis" - "lnk/gateways/gocql" + redisPackage "lnk/extensions/redis" + gocqlPackage "lnk/gateways/gocql" "lnk/gateways/gocql/repositories" httpServer "lnk/gateways/http" "lnk/gateways/http/handlers" + redis "github.com/redis/go-redis/v9" + + gocql "github.com/apache/cassandra-gocql-driver/v2" "go.uber.org/zap" ) func main() { + cfg, logger := setupConfigAndLogger() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + session := setupDatabase(cfg, logger) + defer session.Close() + + redisClient := setupRedis(ctx, cfg, logger) + defer func() { + if err := redisClient.Close(); err != nil { + logger.Error("Failed to close Redis client", zap.Error(err)) + } + }() + + initializeCounter(ctx, redisClient, cfg, logger) + + useCase := createUseCase(cfg, logger, session, redisClient) + server := createAndStartServer(cfg, logger, useCase) + + shutdownServer(ctx, logger, server) +} + +func setupConfigAndLogger() (*config.Config, *zap.Logger) { cfg, err := config.LoadConfig() if err != nil { log.Fatalf("Failed to load config: %v", err) @@ -32,43 +59,46 @@ func main() { } logger.Info("Starting application") + return cfg, logger +} - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - session, err := gocql.SetupDatabase(&cfg.Gocql, logger) +func setupDatabase(cfg *config.Config, logger *zap.Logger) *gocql.Session { + session, err := gocqlPackage.SetupDatabase(&cfg.Gocql, logger) if err != nil { logger.Fatal("Failed to setup database", zap.Error(err)) } - defer session.Close() + return session +} - redisClient, err := redis.SetupRedis(ctx, &cfg.Redis, logger) +func setupRedis(ctx context.Context, cfg *config.Config, logger *zap.Logger) *redis.Client { + redisClient, err := redisPackage.SetupRedis(ctx, &cfg.Redis, logger) if err != nil { logger.Fatal("Failed to setup Redis", zap.Error(err)) } - defer func() { - if err := redisClient.Close(); err != nil { - logger.Error("Failed to close Redis client", zap.Error(err)) - } - }() + return redisClient +} - setInitialCounter, err := redis.SetInitialCounterValue(ctx, redisClient, &cfg.Redis, logger) +func initializeCounter(ctx context.Context, redisClient *redis.Client, cfg *config.Config, logger *zap.Logger) { + setInitialCounter, err := redisPackage.SetInitialCounterValue(ctx, redisClient, &cfg.Redis, logger) if err != nil && !setInitialCounter { logger.Fatal("Failed to set initial counter", zap.Error(err)) } +} - repository := repositories.NewRepository(ctx, logger, session) - redisAdapter := redis.NewRedisAdapter(redisClient) +func createUseCase(cfg *config.Config, logger *zap.Logger, session *gocql.Session, redisClient *redis.Client) *usecases.UseCase { + repository := repositories.NewRepository(logger, session) + redisAdapter := redisPackage.NewRedisAdapter(redisClient) - useCase := usecases.NewUseCase(usecases.NewUseCaseParams{ - Ctx: ctx, + return usecases.NewUseCase(usecases.NewUseCaseParams{ Logger: logger, Repository: repository, Redis: redisAdapter, Salt: cfg.App.Base62Salt, CounterKey: cfg.Redis.CounterKey, }) +} +func createAndStartServer(cfg *config.Config, logger *zap.Logger, useCase *usecases.UseCase) *httpServer.Server { httpHandlers := handlers.NewHandlers(logger, useCase) router := httpServer.NewRouter(httpServer.RouterConfig{ @@ -83,13 +113,18 @@ func main() { logger.Fatal("Failed to start HTTP server", zap.Error(err)) } + return server +} + +func shutdownServer(ctx context.Context, logger *zap.Logger, server *httpServer.Server) { sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) <-sigChan logger.Info("Received shutdown signal") - shutdownCtx, shutdownCancel := context.WithTimeout(ctx, 10*time.Second) + const shutdownTimeout = 10 * time.Second + shutdownCtx, shutdownCancel := context.WithTimeout(ctx, shutdownTimeout) defer shutdownCancel() if err := server.Shutdown(shutdownCtx); err != nil { From b7c323e15d7ab6feb032032d96ff374497069a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Thu, 13 Nov 2025 18:55:26 -0300 Subject: [PATCH 03/28] chore: Remove pull request triggers from CI, lint, and tests workflows --- .github/workflows/ci.yml | 2 -- .github/workflows/lint.yml | 2 -- .github/workflows/tests.yml | 2 -- 3 files changed, 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 489020b..83bac3d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,8 +3,6 @@ name: CI on: push: branches: ["**"] - pull_request: - branches: ["**"] env: GO_VERSION: "1.24" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0a89bea..10d8137 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -3,8 +3,6 @@ name: Lint on: push: branches: ["**"] - pull_request: - branches: ["**"] env: GO_VERSION: "1.24" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4deb9ac..bab4e6b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -3,8 +3,6 @@ name: Tests on: push: branches: ["**"] - pull_request: - branches: ["**"] env: GO_VERSION: "1.24" From 6b6b91b08f7b57169900347dd8c99ee5fc12e8dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Thu, 13 Nov 2025 18:58:06 -0300 Subject: [PATCH 04/28] chore: Add pull request triggers to CI workflow and remove lint and tests workflows --- .github/workflows/ci.yml | 2 + .github/workflows/lint.yml | 70 ----------------------------------- .github/workflows/tests.yml | 74 ------------------------------------- 3 files changed, 2 insertions(+), 144 deletions(-) delete mode 100644 .github/workflows/lint.yml delete mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 83bac3d..489020b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,6 +3,8 @@ name: CI on: push: branches: ["**"] + pull_request: + branches: ["**"] env: GO_VERSION: "1.24" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 10d8137..0000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: Lint - -on: - push: - branches: ["**"] - -env: - GO_VERSION: "1.24" - -jobs: - lint: - name: Lint Code - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - cache-dependency-path: go.sum - - - name: Cache Go build cache - uses: actions/cache@v4 - with: - path: ~/.cache/go-build - key: ${{ runner.os }}-go${{ env.GO_VERSION }}-build-${{ hashFiles('go.sum') }} - restore-keys: | - ${{ runner.os }}-go${{ env.GO_VERSION }}-build- - ${{ runner.os }}-go-build- - - - name: Cache golangci-lint - uses: actions/cache@v4 - with: - path: ~/.cache/golangci-lint - key: ${{ runner.os }}-golangci-lint-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-golangci-lint- - - - name: Run golangci-lint - uses: golangci/golangci-lint-action@v4 - with: - version: latest - args: --timeout=5m - cache: true - - - name: Check formatting - run: | - if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then - echo "Code is not formatted. Run 'gofmt -s -w .'" - gofmt -s -d . - exit 1 - fi - - - name: Run go vet - run: go vet ./... - - - name: Verify dependencies - run: go mod verify - - - name: Check for unused dependencies - run: | - go mod tidy - if [ -n "$(git status --porcelain go.mod go.sum)" ]; then - echo "go.mod or go.sum is not up to date. Run 'go mod tidy'" - git diff go.mod go.sum - exit 1 - fi diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index bab4e6b..0000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,74 +0,0 @@ -name: Tests - -on: - push: - branches: ["**"] - -env: - GO_VERSION: "1.24" - -jobs: - test: - name: Run Tests - runs-on: ubuntu-latest - - services: - docker: - image: docker:dind - options: >- - --privileged - --health-cmd "docker info" - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - cache-dependency-path: go.sum - - - name: Cache Go build cache - uses: actions/cache@v4 - with: - path: ~/.cache/go-build - key: ${{ runner.os }}-go${{ env.GO_VERSION }}-build-${{ hashFiles('go.sum') }} - restore-keys: | - ${{ runner.os }}-go${{ env.GO_VERSION }}-build- - ${{ runner.os }}-go-build- - - - name: Download dependencies - run: go mod download - - - name: Verify dependencies - run: go mod verify - - - name: Wait for Docker to be ready - run: | - echo "Waiting for Docker daemon..." - until docker info > /dev/null 2>&1; do - echo "Docker daemon not ready yet..." - sleep 1 - done - echo "Docker daemon is ready" - - - name: Run tests - run: | - go test -timeout 120s -v -race -coverprofile=coverage.out ./... - - - name: Generate coverage report - run: make coverage - continue-on-error: true - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 - if: always() - with: - file: ./coverage.out - flags: unittests - name: codecov-umbrella - fail_ci_if_error: false From 108f3e5110e9634ed7a2500c8568aae4ce64c321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Thu, 13 Nov 2025 18:59:05 -0300 Subject: [PATCH 05/28] chore: Remove pull request triggers from CI workflow --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 489020b..83bac3d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,8 +3,6 @@ name: CI on: push: branches: ["**"] - pull_request: - branches: ["**"] env: GO_VERSION: "1.24" From cab66ce3f1a0eb4619e28f4a1ff3aed93b7194d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Thu, 13 Nov 2025 19:03:55 -0300 Subject: [PATCH 06/28] chore: Remove dependency download step from CI workflow --- .github/workflows/ci.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 83bac3d..d188695 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -102,9 +102,6 @@ jobs: ${{ runner.os }}-go${{ env.GO_VERSION }}-build- ${{ runner.os }}-go-build- - - name: Download dependencies - run: go mod download - - name: Verify dependencies run: go mod verify From edace5c89f3ed30c43ba60c6af3f5c3064fa5fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Thu, 13 Nov 2025 19:09:19 -0300 Subject: [PATCH 07/28] chore: Update golangci-lint configuration and CI workflow to include colored output --- .github/workflows/ci.yml | 2 +- .golangci.yml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d188695..8945289 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: uses: golangci/golangci-lint-action@v4 with: version: latest - args: --timeout=5m + args: --timeout=5m --out-format=colored-line-number cache: true - name: Check formatting diff --git a/.golangci.yml b/.golangci.yml index 335c63b..94b089c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,4 +1,3 @@ -version: "2" linters: default: all disable: From c0bb275a24e092ea4a0a43f6f23481807a5298fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Thu, 13 Nov 2025 19:15:50 -0300 Subject: [PATCH 08/28] chore: Remove unused linters from golangci-lint configuration --- .golangci.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 94b089c..e1dd786 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -17,9 +17,6 @@ linters: - tagliatelle - varnamelen - wsl - - wsl_v5 - - embeddedstructfieldcheck - - noinlineerr - revive exclusions: generated: lax From f8fd24b6a2b13d30c693294d7e84b2541dc70501 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Thu, 13 Nov 2025 19:41:21 -0300 Subject: [PATCH 09/28] chore: Update CI workflow to set working directory for all steps --- .github/workflows/ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8945289..b24c871 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,6 +46,7 @@ jobs: cache: true - name: Check formatting + working-directory: ${{ github.workspace }} run: | if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then echo "Code is not formatted. Run 'gofmt -s -w .'" @@ -54,12 +55,15 @@ jobs: fi - name: Run go vet + working-directory: ${{ github.workspace }} run: go vet ./... - name: Verify dependencies + working-directory: ${{ github.workspace }} run: go mod verify - name: Check for unused dependencies + working-directory: ${{ github.workspace }} run: | go mod tidy if [ -n "$(git status --porcelain go.mod go.sum)" ]; then @@ -103,9 +107,11 @@ jobs: ${{ runner.os }}-go-build- - name: Verify dependencies + working-directory: ${{ github.workspace }} run: go mod verify - name: Wait for Docker to be ready + working-directory: ${{ github.workspace }} run: | echo "Waiting for Docker daemon..." until docker info > /dev/null 2>&1; do @@ -115,10 +121,12 @@ jobs: echo "Docker daemon is ready" - name: Run tests + working-directory: ${{ github.workspace }} run: | go test -timeout 120s -v -race -coverprofile=coverage.out ./... - name: Generate coverage report + working-directory: ${{ github.workspace }} run: make coverage continue-on-error: true From 7661b722d335c8c07084909446c3edca6b30c6f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Thu, 13 Nov 2025 19:48:15 -0300 Subject: [PATCH 10/28] chore: Update golangci-lint configuration to version 2, enable tests, and refine linter settings --- .golangci.yml | 55 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index e1dd786..f3bb82d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,3 +1,12 @@ +version: "2" +run: + tests: true +output: + formats: + text: + path: stdout + print-linter-name: true + print-issued-lines: true linters: default: all disable: @@ -13,34 +22,60 @@ linters: - musttag - nlreturn - nonamedreturns + - revive - tagalign - tagliatelle - varnamelen - wsl - - revive + settings: + gocritic: + enabled-tags: + - diagnostic + - experimental + - opinionated + - performance + - style + govet: + enable-all: true + misspell: + locale: US + staticcheck: + checks: + - all exclusions: generated: lax - presets: - - comments - - common-false-positives - - legacy - - std-error-handling + rules: + - linters: + - errcheck + - gosec + - unparam + path: _test\.go + - linters: + - errcheck + path: _test\.go + text: Error return value of .((t|b|tb|f|g|gb)\.(Fatal|Fatalf|Skip|Skipf)|.*\.Errorf?|.*\.Fail(Now)?). is not checked + - linters: + - all + path: mocks/ + - linters: + - all + path: docs/ paths: - - mock\.go$ - - mock_[^/]*\.go$ - third_party$ - builtin$ - examples$ +issues: + max-issues-per-linter: 0 + max-same-issues: 0 formatters: enable: - gci + - gofmt - gofumpt - goimports exclusions: generated: lax paths: - - mock\.go$ - - mock_[^/]*\.go$ - third_party$ - builtin$ - examples$ From a296c409b1e853e9986dfc96d4cb4e675372d075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Thu, 13 Nov 2025 19:54:50 -0300 Subject: [PATCH 11/28] chore: Refactor CI workflow to streamline golangci-lint step and remove redundant checks --- .github/workflows/ci.yml | 71 ++++++---------------------------------- 1 file changed, 10 insertions(+), 61 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b24c871..669c0a0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,75 +8,24 @@ env: GO_VERSION: "1.24" jobs: - lint: - name: Lint Code + jobs: + golangci: + name: lint runs-on: ubuntu-latest steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 with: go-version: ${{ env.GO_VERSION }} - cache-dependency-path: go.sum - - - name: Cache Go build cache - uses: actions/cache@v4 - with: - path: ~/.cache/go-build - key: ${{ runner.os }}-go${{ env.GO_VERSION }}-build-${{ hashFiles('go.sum') }} - restore-keys: | - ${{ runner.os }}-go${{ env.GO_VERSION }}-build- - ${{ runner.os }}-go-build- - - - name: Cache golangci-lint - uses: actions/cache@v4 - with: - path: ~/.cache/golangci-lint - key: ${{ runner.os }}-golangci-lint-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-golangci-lint- - - - name: Run golangci-lint - uses: golangci/golangci-lint-action@v4 - with: - version: latest - args: --timeout=5m --out-format=colored-line-number cache: true - - - name: Check formatting - working-directory: ${{ github.workspace }} - run: | - if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then - echo "Code is not formatted. Run 'gofmt -s -w .'" - gofmt -s -d . - exit 1 - fi - - - name: Run go vet - working-directory: ${{ github.workspace }} - run: go vet ./... - - - name: Verify dependencies - working-directory: ${{ github.workspace }} - run: go mod verify - - - name: Check for unused dependencies - working-directory: ${{ github.workspace }} - run: | - go mod tidy - if [ -n "$(git status --porcelain go.mod go.sum)" ]; then - echo "go.mod or go.sum is not up to date. Run 'go mod tidy'" - git diff go.mod go.sum - exit 1 - fi - + - name: golangci-lint + uses: golangci/golangci-lint-action@v5 + with: + version: v1.60.0 + args: "--timeout=5m" test: name: Run Tests runs-on: ubuntu-latest - # Run in parallel with lint for faster CI - services: docker: image: docker:dind From a461e6166907a1228bcf06b50301b00e8595b38b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Thu, 13 Nov 2025 19:55:56 -0300 Subject: [PATCH 12/28] fix: ci file --- .github/workflows/ci.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 669c0a0..6fa2f00 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,6 @@ env: GO_VERSION: "1.24" jobs: - jobs: golangci: name: lint runs-on: ubuntu-latest @@ -16,16 +15,20 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: - go-version: ${{ env.GO_VERSION }} + go-version: "1.22" # Specify your desired Go version cache: true - name: golangci-lint - uses: golangci/golangci-lint-action@v5 + uses: golangci/golangci-lint-action@v5 # Use the official action for golangci-lint with: - version: v1.60.0 - args: "--timeout=5m" + version: v1.55.2 # Specify the desired golangci-lint version (e.g., v1.55.2 for a recent v2 release) + args: "--timeout=5m" # Optional: Add arguments to golangci-lint command + # Optional: Specify a custom configuration file if not using .golangci.yml in the root + # config: .golangci.custom.yml test: name: Run Tests runs-on: ubuntu-latest + # Run in parallel with lint for faster CI + services: docker: image: docker:dind From 59affbd1a4194ffa17b04094dc3d416696c0bb90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Thu, 13 Nov 2025 19:58:55 -0300 Subject: [PATCH 13/28] chore: Update CI workflow to use environment variable for Go version and upgrade golangci-lint to version 1.60.0 --- .github/workflows/ci.yml | 10 ++++------ .golangci.yml | 1 - 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6fa2f00..62c6f13 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,15 +15,13 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: - go-version: "1.22" # Specify your desired Go version + go-version: ${{ env.GO_VERSION }} cache: true - name: golangci-lint - uses: golangci/golangci-lint-action@v5 # Use the official action for golangci-lint + uses: golangci/golangci-lint-action@v5 with: - version: v1.55.2 # Specify the desired golangci-lint version (e.g., v1.55.2 for a recent v2 release) - args: "--timeout=5m" # Optional: Add arguments to golangci-lint command - # Optional: Specify a custom configuration file if not using .golangci.yml in the root - # config: .golangci.custom.yml + version: latest + args: "--timeout=5m" test: name: Run Tests runs-on: ubuntu-latest diff --git a/.golangci.yml b/.golangci.yml index f3bb82d..4053a1c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,4 +1,3 @@ -version: "2" run: tests: true output: From cc1f8776c8e0bd09aafb5eb2fb91dc5063e360ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Thu, 13 Nov 2025 20:01:40 -0300 Subject: [PATCH 14/28] chore: Simplify CI workflow by consolidating build and lint steps, updating Go version, and restricting branch triggers to main --- .github/workflows/ci.yml | 90 +++++++++++----------------------------- 1 file changed, 24 insertions(+), 66 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 62c6f13..0002d04 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,89 +2,47 @@ name: CI on: push: - branches: ["**"] + branches: [main] + pull_request: + +permissions: + contents: read env: - GO_VERSION: "1.24" + GO_VERSION: 1.24.10 jobs: - golangci: - name: lint + build-and-lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - cache: true - - name: golangci-lint - uses: golangci/golangci-lint-action@v5 - with: - version: latest - args: "--timeout=5m" - test: - name: Run Tests - runs-on: ubuntu-latest - # Run in parallel with lint for faster CI - - services: - docker: - image: docker:dind - options: >- - --privileged - --health-cmd "docker info" - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - steps: - - name: Checkout code + - name: Checkout uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v5 with: go-version: ${{ env.GO_VERSION }} - cache-dependency-path: go.sum + cache: true - - name: Cache Go build cache + - name: Cache golangci-lint + id: cache-golangci-lint uses: actions/cache@v4 with: - path: ~/.cache/go-build - key: ${{ runner.os }}-go${{ env.GO_VERSION }}-build-${{ hashFiles('go.sum') }} - restore-keys: | - ${{ runner.os }}-go${{ env.GO_VERSION }}-build- - ${{ runner.os }}-go-build- + path: ~/.cache/golangci-lint + key: golangci-lint-v2-${{ runner.os }}-cache - - name: Verify dependencies - working-directory: ${{ github.workspace }} - run: go mod verify - - - name: Wait for Docker to be ready - working-directory: ${{ github.workspace }} + - name: Install golangci-lint v2 run: | - echo "Waiting for Docker daemon..." - until docker info > /dev/null 2>&1; do - echo "Docker daemon not ready yet..." - sleep 1 - done - echo "Docker daemon is ready" + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh \ + | sh -s -- -b $(go env GOPATH)/bin v2.0.0 - - name: Run tests - working-directory: ${{ github.workspace }} - run: | - go test -timeout 120s -v -race -coverprofile=coverage.out ./... + - name: Go mod download + run: go mod download - - name: Generate coverage report - working-directory: ${{ github.workspace }} - run: make coverage - continue-on-error: true + - name: Lint + run: golangci-lint run + env: + GOLANGCI_LINT_CACHE: ~/.cache/golangci-lint - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 - if: always() - with: - file: ./coverage.out - flags: unittests - name: codecov-umbrella - fail_ci_if_error: false + - name: Test + run: go test ./... From e4504672c7fd61ce9dfd579a5549ccc56107dcba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Thu, 13 Nov 2025 20:03:30 -0300 Subject: [PATCH 15/28] chore: Update CI workflow to use '...' syntax for go mod download, enhancing dependency management --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0002d04..b6af4d2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,7 @@ jobs: | sh -s -- -b $(go env GOPATH)/bin v2.0.0 - name: Go mod download - run: go mod download + run: go mod download ... - name: Lint run: golangci-lint run From 1c4c9ccb5b907490618f29bd37d3013b4d90532b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Thu, 13 Nov 2025 20:10:55 -0300 Subject: [PATCH 16/28] chore: Set default working directory for CI steps to streamline execution --- .github/workflows/ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b6af4d2..995c17d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,10 @@ env: jobs: build-and-lint: runs-on: ubuntu-latest + defaults: + run: + working-directory: ./lnk + steps: - name: Checkout uses: actions/checkout@v4 @@ -37,7 +41,7 @@ jobs: | sh -s -- -b $(go env GOPATH)/bin v2.0.0 - name: Go mod download - run: go mod download ... + run: go mod download - name: Lint run: golangci-lint run From 79e1f0e555aa601e1d9cc6715c09afb8d7360dea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Thu, 13 Nov 2025 20:16:07 -0300 Subject: [PATCH 17/28] fix: ci file --- .github/workflows/ci.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 995c17d..0002d04 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,10 +14,6 @@ env: jobs: build-and-lint: runs-on: ubuntu-latest - defaults: - run: - working-directory: ./lnk - steps: - name: Checkout uses: actions/checkout@v4 From b433f60f2c6ccb44d726f31c08f5ddcb600e6764 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Thu, 13 Nov 2025 20:19:37 -0300 Subject: [PATCH 18/28] chore: Remove redundant 'go mod download' step from CI workflow to streamline execution --- .github/workflows/ci.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0002d04..c1e4708 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,9 +36,6 @@ jobs: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh \ | sh -s -- -b $(go env GOPATH)/bin v2.0.0 - - name: Go mod download - run: go mod download - - name: Lint run: golangci-lint run env: From 214382405cbcbf5b29f53d9b382ba71d73beaa10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Thu, 13 Nov 2025 20:26:13 -0300 Subject: [PATCH 19/28] chore: Update golangci-lint configuration to version 2 and enable test execution --- .golangci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.golangci.yml b/.golangci.yml index 4053a1c..ec07531 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,3 +1,5 @@ +version: 2 + run: tests: true output: From 8951edd73461f63cb8b9173db8df607efcc98210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Thu, 13 Nov 2025 20:31:41 -0300 Subject: [PATCH 20/28] fix: Update cache paths in CI workflow for golangci-lint to ensure compatibility with GitHub Actions environment --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c1e4708..fac7274 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: id: cache-golangci-lint uses: actions/cache@v4 with: - path: ~/.cache/golangci-lint + path: /home/runner/.cache/golangci-lint key: golangci-lint-v2-${{ runner.os }}-cache - name: Install golangci-lint v2 @@ -39,7 +39,7 @@ jobs: - name: Lint run: golangci-lint run env: - GOLANGCI_LINT_CACHE: ~/.cache/golangci-lint + GOLANGCI_LINT_CACHE: /home/runner/.cache/golangci-lint - name: Test run: go test ./... From 20e0a340ccc71feb3776e35a2b8b8f30bcce33a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Thu, 13 Nov 2025 20:36:08 -0300 Subject: [PATCH 21/28] chore: Add Go module verification step to CI workflow and update golangci-lint command to run on all packages --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fac7274..a9a1d46 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,8 +36,11 @@ jobs: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh \ | sh -s -- -b $(go env GOPATH)/bin v2.0.0 + - name: Verify Go module + run: go mod verify + - name: Lint - run: golangci-lint run + run: golangci-lint run ./... env: GOLANGCI_LINT_CACHE: /home/runner/.cache/golangci-lint From 8a0f7d3d66b313e81a79d5105cf60c4ef8961a59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Thu, 13 Nov 2025 20:38:12 -0300 Subject: [PATCH 22/28] chore: Refactor CI workflow to rename job, streamline steps, and ensure golangci-lint runs after tests --- .github/workflows/ci.yml | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a9a1d46..521a888 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,37 +12,27 @@ env: GO_VERSION: 1.24.10 jobs: - build-and-lint: + build-and-test: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v5 with: go-version: ${{ env.GO_VERSION }} - cache: true - - name: Cache golangci-lint - id: cache-golangci-lint - uses: actions/cache@v4 - with: - path: /home/runner/.cache/golangci-lint - key: golangci-lint-v2-${{ runner.os }}-cache + - name: Download Go modules + run: go mod tidy && go mod download - - name: Install golangci-lint v2 - run: | - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh \ - | sh -s -- -b $(go env GOPATH)/bin v2.0.0 + - name: Build + run: go build -v ./... - - name: Verify Go module - run: go mod verify + - name: Run tests + run: go test -v ./... - - name: Lint - run: golangci-lint run ./... - env: - GOLANGCI_LINT_CACHE: /home/runner/.cache/golangci-lint + - name: Install golangci-lint + run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest - - name: Test - run: go test ./... + - name: Run golangci-lint + run: golangci-lint run ./... From 02b744db0299607a7b5b78d3b25a6226816dbc58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Thu, 13 Nov 2025 20:40:41 -0300 Subject: [PATCH 23/28] chore: Initialize Go module and update .gitignore to exclude go.mod and go.sum files --- .gitignore | 3 - go.mod | 104 ++++++++++++++++++ go.sum | 310 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 414 insertions(+), 3 deletions(-) create mode 100644 go.mod create mode 100644 go.sum diff --git a/.gitignore b/.gitignore index c3eb9d5..fc5dc15 100644 --- a/.gitignore +++ b/.gitignore @@ -20,9 +20,6 @@ go.work go.work.sum # Dependency directories (remove the comment below if you use Go modules) -/go.sum -/go.mod - # IDE/editor specific files .vscode/ .idea/ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..dde35b9 --- /dev/null +++ b/go.mod @@ -0,0 +1,104 @@ +module lnk + +go 1.24.0 + +toolchain go1.24.10 + +require ( + github.com/apache/cassandra-gocql-driver/v2 v2.0.0 + github.com/gin-gonic/gin v1.11.0 + github.com/gocql/gocql v0.0.0-20210515062232-b7ef815b4556 + github.com/golang-migrate/migrate/v4 v4.19.0 + github.com/google/uuid v1.6.0 + github.com/joho/godotenv v1.5.1 + github.com/kelseyhightower/envconfig v1.4.0 + github.com/ory/dockertest/v3 v3.12.0 + github.com/redis/go-redis/v9 v9.16.0 + github.com/stretchr/testify v1.11.1 + github.com/swaggo/files v1.0.1 + github.com/swaggo/gin-swagger v1.6.1 + github.com/swaggo/swag v1.16.6 + go.uber.org/zap v1.27.0 +) + +require ( + dario.cat/mergo v1.0.2 // indirect + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/bytedance/sonic v1.14.0 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/containerd/continuity v0.4.5 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/docker/cli v27.4.1+incompatible // indirect + github.com/docker/docker v28.5.1+incompatible // indirect + github.com/docker/go-connections v0.6.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.6 // indirect + github.com/go-openapi/spec v0.20.4 // indirect + github.com/go-openapi/swag v0.19.15 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.27.0 // indirect + github.com/go-viper/mapstructure/v2 v2.1.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mailru/easyjson v0.7.6 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/sys/user v0.4.0 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect + github.com/opencontainers/runc v1.2.3 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/quic-go v0.54.0 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.0 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + go.uber.org/mock v0.5.0 // indirect + go.uber.org/multierr v1.10.0 // indirect + golang.org/x/arch v0.20.0 // indirect + golang.org/x/crypto v0.43.0 // indirect + golang.org/x/mod v0.28.0 // indirect + golang.org/x/net v0.45.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/text v0.30.0 // indirect + golang.org/x/tools v0.37.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gotest.tools/v3 v3.5.2 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..abd621c --- /dev/null +++ b/go.sum @@ -0,0 +1,310 @@ +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/apache/cassandra-gocql-driver/v2 v2.0.0 h1:Omnzb1Z/P90Dr2TbVNu54ICQL7TKVIIsJO231w484HU= +github.com/apache/cassandra-gocql-driver/v2 v2.0.0/go.mod h1:QH/asJjB3mHvY6Dot6ZKMMpTcOrWJ8i9GhsvG1g0PK4= +github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= +github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= +github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dhui/dktest v0.4.6 h1:+DPKyScKSEp3VLtbMDHcUq6V5Lm5zfZZVb0Sk7Ahom4= +github.com/dhui/dktest v0.4.6/go.mod h1:JHTSYDtKkvFNFHJKqCzVzqXecyv+tKt8EzceOmQOgbU= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/cli v27.4.1+incompatible h1:VzPiUlRJ/xh+otB75gva3r05isHMo5wXDfPRi5/b4hI= +github.com/docker/cli v27.4.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM= +github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= +github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= +github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= +github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= +github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= +github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-viper/mapstructure/v2 v2.1.0 h1:gHnMa2Y/pIxElCH2GlZZ1lZSsn6XMtufpGyP1XxdC/w= +github.com/go-viper/mapstructure/v2 v2.1.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/gocql/gocql v0.0.0-20210515062232-b7ef815b4556 h1:N/MD/sr6o61X+iZBAT2qEUF023s4KbA8RWfKzl0L6MQ= +github.com/gocql/gocql v0.0.0-20210515062232-b7ef815b4556/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY= +github.com/golang-migrate/migrate/v4 v4.19.0 h1:RcjOnCGz3Or6HQYEJ/EEVLfWnmw9KnoigPSjzhCuaSE= +github.com/golang-migrate/migrate/v4 v4.19.0/go.mod h1:9dyEcu+hO+G9hPSw8AIg50yg622pXJsoHItQnDGZkI0= +github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= +github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/opencontainers/runc v1.2.3 h1:fxE7amCzfZflJO2lHXf4y/y8M1BoAqp+FVmG19oYB80= +github.com/opencontainers/runc v1.2.3/go.mod h1:nSxcWUydXrsBZVYNSkTjoQ/N6rcyTtn+1SD5D4+kRIM= +github.com/ory/dockertest/v3 v3.12.0 h1:3oV9d0sDzlSQfHtIaB5k6ghUCVMVLpAY8hwrqoCyRCw= +github.com/ory/dockertest/v3 v3.12.0/go.mod h1:aKNDTva3cp8dwOWwb9cWuX84aH5akkxXRvO7KCwWVjE= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pierrec/lz4/v4 v4.1.16 h1:kQPfno+wyx6C5572ABwV+Uo3pDFzQ7yhyGchSyRda0c= +github.com/pierrec/lz4/v4 v4.1.16/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= +github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/redis/go-redis/v9 v9.16.0 h1:OotgqgLSRCmzfqChbQyG1PHC3tLNR89DG4jdOERSEP4= +github.com/redis/go-redis/v9 v9.16.0/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= +github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= +github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= +github.com/swaggo/gin-swagger v1.6.1 h1:Ri06G4gc9N4t4k8hekMigJ9zKTFSlqj/9paAQCQs7cY= +github.com/swaggo/gin-swagger v1.6.1/go.mod h1:LQ+hJStHakCWRiK/YNYtJOu4mR2FP+pxLnILT/qNiTw= +github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI= +github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= +github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= +golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM= +golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= From da1ea393ee513898e8f030314aeee6fef26c844e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Thu, 13 Nov 2025 20:46:10 -0300 Subject: [PATCH 24/28] chore: Simplify golangci-lint output configuration by consolidating format settings --- .golangci.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index ec07531..93e9ea9 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -3,11 +3,9 @@ version: 2 run: tests: true output: - formats: - text: - path: stdout - print-linter-name: true - print-issued-lines: true + format: text + print-linter-name: true + print-issued-lines: true linters: default: all disable: From 95b7cd46ec9fe4a95ec13cb14d53e5ff89cb2723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Thu, 13 Nov 2025 20:55:15 -0300 Subject: [PATCH 25/28] chore: Update golangci-lint output configuration to use structured formats for improved readability --- .golangci.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 93e9ea9..ec07531 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -3,9 +3,11 @@ version: 2 run: tests: true output: - format: text - print-linter-name: true - print-issued-lines: true + formats: + text: + path: stdout + print-linter-name: true + print-issued-lines: true linters: default: all disable: From 79baca0e7db2bfcc1a70c9f390ec8fb2990b1d98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Thu, 13 Nov 2025 21:00:16 -0300 Subject: [PATCH 26/28] chore: Add text format option to golangci-lint output configuration for enhanced logging --- .golangci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.golangci.yml b/.golangci.yml index ec07531..530d549 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -3,6 +3,7 @@ version: 2 run: tests: true output: + format: text formats: text: path: stdout From dc78e08e07780e3b28bfbe24c8489eaae2454038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Thu, 13 Nov 2025 21:00:40 -0300 Subject: [PATCH 27/28] chore: Remove test execution option from golangci-lint configuration to streamline output settings --- .golangci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 530d549..711c7a5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,7 +1,5 @@ version: 2 -run: - tests: true output: format: text formats: From 90163da014be7ca29967053174feef8731f38cef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Thu, 13 Nov 2025 21:05:40 -0300 Subject: [PATCH 28/28] chore: Update golangci-lint installation method in CI workflow to use script for version control --- .github/workflows/ci.yml | 3 ++- .golangci.yml | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 521a888..c874351 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,8 @@ jobs: run: go test -v ./... - name: Install golangci-lint - run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest + run: | + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.0.0 - name: Run golangci-lint run: golangci-lint run ./... diff --git a/.golangci.yml b/.golangci.yml index 711c7a5..867e052 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,7 +1,6 @@ version: 2 output: - format: text formats: text: path: stdout